[ Index ]

PHP Cross Reference of WordPress Trunk (Updated Daily)

Search

title

Body

[close]

/wp-includes/ -> script-loader.php (source)

   1  <?php
   2  /**
   3   * WordPress scripts and styles default loader.
   4   *
   5   * This constant is used to manage the loading of scripts and CSS:
   6   * define('SCRIPT_DEBUG', true); loads the development (non-minified) versions of all scripts and CSS.
   7   *
   8   * @package WordPress
   9   */
  10  
  11  /** WordPress Dependency Class */
  12  require  ABSPATH . WPINC . '/class-wp-dependency.php';
  13  
  14  /** WordPress Dependencies Class */
  15  require  ABSPATH . WPINC . '/class-wp-dependencies.php';
  16  
  17  /** WordPress Scripts Class */
  18  require  ABSPATH . WPINC . '/class-wp-scripts.php';
  19  
  20  /** WordPress Scripts Functions */
  21  require  ABSPATH . WPINC . '/functions.wp-scripts.php';
  22  
  23  /** WordPress Styles Class */
  24  require  ABSPATH . WPINC . '/class-wp-styles.php';
  25  
  26  /** WordPress Styles Functions */
  27  require  ABSPATH . WPINC . '/functions.wp-styles.php';
  28  
  29  /**
  30   * Registers TinyMCE scripts.
  31   *
  32   * TinyMCE core and the compat3x plugin are registered as separate files. TinyMCE loads its theme
  33   * and any other plugins itself when an editor is initialized. The `wp-tinymce.js` bundle of all of
  34   * them is no longer registered, but it can still be registered in their place under the
  35   * `wp-tinymce` handle.
  36   *
  37   * @since 5.0.0
  38   * @since 7.2.0 The `wp-tinymce.js` bundle is no longer registered, regardless of whether scripts
  39   *              are concatenated or compressed.
  40   * @since 7.2.0 The `$force_uncompressed` parameter was deprecated and renamed to `$deprecated`.
  41   *
  42   * @global string $tinymce_version
  43   *
  44   * @param WP_Scripts $scripts    WP_Scripts object.
  45   * @param bool       $deprecated Not used.
  46   *
  47   * @phpstan-param false $deprecated
  48   */
  49  function wp_register_tinymce_scripts( $scripts, $deprecated = false ): void {
  50      global $tinymce_version;
  51  
  52      if ( false !== $deprecated ) {
  53          _deprecated_argument( __FUNCTION__, '7.2.0' );
  54      }
  55  
  56      $suffix     = wp_scripts_get_suffix();
  57      $dev_suffix = wp_scripts_get_suffix( 'dev' );
  58  
  59      // Load tinymce.js when running from /src, otherwise tinymce.min.js.
  60      $scripts->add( 'wp-tinymce-root', includes_url( 'js/tinymce/' ) . "tinymce$dev_suffix.js", array(), $tinymce_version );
  61      $scripts->add( 'wp-tinymce', includes_url( 'js/tinymce/' ) . "plugins/compat3x/plugin$dev_suffix.js", array( 'wp-tinymce-root' ), $tinymce_version );
  62  
  63      $scripts->add( 'wp-tinymce-lists', includes_url( "js/tinymce/plugins/lists/plugin$suffix.js" ), array( 'wp-tinymce' ), $tinymce_version );
  64  }
  65  
  66  /**
  67   * Registers all the WordPress vendor scripts that are in the standardized
  68   * `js/dist/vendor/` location.
  69   *
  70   * For the order of `$scripts->add` see `wp_default_scripts`.
  71   *
  72   * @since 5.0.0
  73   *
  74   * @global WP_Locale $wp_locale WordPress date and time locale object.
  75   *
  76   * @param WP_Scripts $scripts WP_Scripts object.
  77   */
  78  function wp_default_packages_vendor( $scripts ) {
  79      global $wp_locale;
  80  
  81      $suffix = wp_scripts_get_suffix();
  82  
  83      $vendor_scripts = array(
  84          'react'                       => array(),
  85          'react-dom'                   => array( 'react' ),
  86          'react-jsx-runtime'           => array( 'react' ),
  87          'regenerator-runtime'         => array(),
  88          'moment'                      => array(),
  89          'lodash'                      => array(),
  90          'wp-polyfill-fetch'           => array(),
  91          'wp-polyfill-formdata'        => array(),
  92          'wp-polyfill-node-contains'   => array(),
  93          'wp-polyfill-url'             => array(),
  94          'wp-polyfill-dom-rect'        => array(),
  95          'wp-polyfill-element-closest' => array(),
  96          'wp-polyfill-object-fit'      => array(),
  97          'wp-polyfill-inert'           => array(),
  98          'wp-polyfill'                 => array(),
  99      );
 100  
 101      $vendor_scripts_versions = array(
 102          'react'                       => '18.3.1.1', // Final .1 due to switch to UMD build, can be removed in the next update.
 103          'react-dom'                   => '18.3.1.1', // Final .1 due to switch to UMD build, can be removed in the next update.
 104          'react-jsx-runtime'           => '18.3.1',
 105          'regenerator-runtime'         => '0.14.1',
 106          'moment'                      => '2.31.0',
 107          'lodash'                      => '4.18.1',
 108          'wp-polyfill-fetch'           => '3.6.20',
 109          'wp-polyfill-formdata'        => '4.0.10',
 110          'wp-polyfill-node-contains'   => '4.8.0',
 111          'wp-polyfill-url'             => '3.6.4',
 112          'wp-polyfill-dom-rect'        => '4.8.0',
 113          'wp-polyfill-element-closest' => '3.0.2',
 114          'wp-polyfill-object-fit'      => '2.3.5',
 115          'wp-polyfill-inert'           => '3.1.3',
 116          'wp-polyfill'                 => '3.15.0',
 117      );
 118  
 119      foreach ( $vendor_scripts as $handle => $dependencies ) {
 120          $scripts->add(
 121              $handle,
 122              "/wp-includes/js/dist/vendor/$handle$suffix.js",
 123              $dependencies,
 124              $vendor_scripts_versions[ $handle ],
 125              1
 126          );
 127      }
 128  
 129      did_action( 'init' ) && $scripts->add_inline_script( 'lodash', 'window.lodash = _.noConflict();' );
 130  
 131      did_action( 'init' ) && $scripts->add_inline_script(
 132          'moment',
 133          sprintf(
 134              "moment.updateLocale( '%s', %s );",
 135              esc_js( get_user_locale() ),
 136              wp_json_encode(
 137                  array(
 138                      'months'         => array_values( $wp_locale->month ),
 139                      'monthsShort'    => array_values( $wp_locale->month_abbrev ),
 140                      'weekdays'       => array_values( $wp_locale->weekday ),
 141                      'weekdaysShort'  => array_values( $wp_locale->weekday_abbrev ),
 142                      'week'           => array(
 143                          'dow' => (int) get_option( 'start_of_week', 0 ),
 144                      ),
 145                      'longDateFormat' => array(
 146                          'LT'   => get_option( 'time_format', __( 'g:i a' ) ),
 147                          'LTS'  => null,
 148                          'L'    => null,
 149                          'LL'   => get_option( 'date_format', __( 'F j, Y' ) ),
 150                          'LLL'  => __( 'F j, Y g:i a' ),
 151                          'LLLL' => null,
 152                      ),
 153                  ),
 154                  JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
 155              )
 156          ),
 157          'after'
 158      );
 159  }
 160  
 161  /**
 162   * Registers development scripts that integrate with `@wordpress/scripts`.
 163   *
 164   * These scripts enable hot module replacement (HMR) for block development
 165   * when using `wp-scripts start --hot`.
 166   *
 167   * @link https://github.com/WordPress/gutenberg/tree/trunk/packages/scripts#start
 168   *
 169   * @since 6.0.0
 170   *
 171   * @param WP_Scripts $scripts WP_Scripts object.
 172   */
 173  function wp_register_development_scripts( $scripts ) {
 174      if (
 175          ! defined( 'SCRIPT_DEBUG' ) || ! SCRIPT_DEBUG
 176          || empty( $scripts->registered['react'] )
 177          || defined( 'WP_RUN_CORE_TESTS' )
 178      ) {
 179          return;
 180      }
 181  
 182      // React Refresh runtime - exposes ReactRefreshRuntime global.
 183      // No dependencies.
 184      $scripts->add(
 185          'wp-react-refresh-runtime',
 186          '/wp-includes/js/dist/development/react-refresh-runtime.js',
 187          array(),
 188          '0.14.0'
 189      );
 190  
 191      // React Refresh entry - injects runtime into global hook.
 192      // Must load before React to set up hooks.
 193      $scripts->add(
 194          'wp-react-refresh-entry',
 195          '/wp-includes/js/dist/development/react-refresh-entry.js',
 196          array( 'wp-react-refresh-runtime' ),
 197          '0.14.0'
 198      );
 199  
 200      // Add entry as a dependency of React so it loads first.
 201      // See https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#externalising-react.
 202      $scripts->registered['react']->deps[] = 'wp-react-refresh-entry';
 203  }
 204  
 205  /**
 206   * Returns contents of an inline script used in appending polyfill scripts for
 207   * browsers which fail the provided tests. The provided array is a mapping from
 208   * a condition to verify feature support to its polyfill script handle.
 209   *
 210   * @since 5.0.0
 211   *
 212   * @param WP_Scripts $scripts WP_Scripts object.
 213   * @param string[]   $tests   Features to detect.
 214   * @return string Conditional polyfill inline script.
 215   */
 216  function wp_get_script_polyfill( $scripts, $tests ) {
 217      $polyfill = '';
 218      foreach ( $tests as $test => $handle ) {
 219          if ( ! array_key_exists( $handle, $scripts->registered ) ) {
 220              continue;
 221          }
 222  
 223          $src = $scripts->registered[ $handle ]->src;
 224          $ver = $scripts->registered[ $handle ]->ver;
 225  
 226          if ( ! preg_match( '|^(https?:)?//|', $src ) && ! ( $scripts->content_url && str_starts_with( $src, $scripts->content_url ) ) ) {
 227              $src = $scripts->base_url . $src;
 228          }
 229  
 230          if ( ! empty( $ver ) ) {
 231              $src = add_query_arg( 'ver', $ver, $src );
 232          }
 233  
 234          /** This filter is documented in wp-includes/class-wp-scripts.php */
 235          $src = esc_url( apply_filters( 'script_loader_src', $src, $handle ) );
 236  
 237          if ( ! $src ) {
 238              continue;
 239          }
 240  
 241          $polyfill .= (
 242              // Test presence of feature...
 243              '( ' . $test . ' ) || ' .
 244              /*
 245               * ...appending polyfill on any failures. Cautious viewers may balk
 246               * at the `document.write`. Its caveat of synchronous mid-stream
 247               * blocking write is exactly the behavior we need though.
 248               */
 249              'document.write( \'<script src="' .
 250              $src .
 251              '"></scr\' + \'ipt>\' );'
 252          );
 253      }
 254  
 255      return $polyfill;
 256  }
 257  
 258  /**
 259   * Registers all the WordPress packages scripts that are in the standardized
 260   * `js/dist/` location.
 261   *
 262   * For the order of `$scripts->add` see `wp_default_scripts`.
 263   *
 264   * @since 5.0.0
 265   *
 266   * @param WP_Scripts $scripts WP_Scripts object.
 267   */
 268  function wp_default_packages_scripts( $scripts ) {
 269      $suffix = defined( 'WP_RUN_CORE_TESTS' ) ? '.min' : wp_scripts_get_suffix();
 270      /*
 271       * Expects multidimensional array like:
 272       *
 273       *     'a11y.js' => array('dependencies' => array(...), 'version' => '...'),
 274       *     'annotations.js' => array('dependencies' => array(...), 'version' => '...'),
 275       *     'api-fetch.js' => array(...
 276       */
 277      $assets_file = ABSPATH . WPINC . '/assets/script-loader-packages.php';
 278      $assets      = file_exists( $assets_file ) ? include $assets_file : array();
 279  
 280      foreach ( $assets as $file_name => $package_data ) {
 281          $basename = str_replace( '.js', '', basename( $file_name ) );
 282          $handle   = 'wp-' . $basename;
 283          $path     = "/wp-includes/js/dist/{$basename}{$suffix}.js";
 284  
 285          if ( ! empty( $package_data['dependencies'] ) ) {
 286              $dependencies = $package_data['dependencies'];
 287          } else {
 288              $dependencies = array();
 289          }
 290  
 291          // Add dependencies that cannot be detected and generated by build tools.
 292          switch ( $handle ) {
 293              case 'wp-block-library':
 294                  array_push( $dependencies, 'editor' );
 295                  break;
 296              case 'wp-edit-post':
 297                  array_push( $dependencies, 'media-models', 'media-views', 'postbox', 'wp-dom-ready' );
 298                  break;
 299              case 'wp-preferences':
 300                  array_push( $dependencies, 'wp-preferences-persistence' );
 301                  break;
 302          }
 303  
 304          $scripts->add( $handle, $path, $dependencies, $package_data['version'], 1 );
 305  
 306          if ( ! empty( $package_data['module_dependencies'] ) ) {
 307              $scripts->add_data( $handle, 'module_dependencies', $package_data['module_dependencies'] );
 308          }
 309  
 310          if ( in_array( 'wp-i18n', $dependencies, true ) ) {
 311              $scripts->set_translations( $handle );
 312          }
 313  
 314          /*
 315           * Manually set the text direction localization after wp-i18n is printed.
 316           * This ensures that wp.i18n.isRTL() returns true in RTL languages.
 317           * We cannot use $scripts->set_translations( 'wp-i18n' ) to do this
 318           * because WordPress prints a script's translations *before* the script,
 319           * which means, in the case of wp-i18n, that wp.i18n.setLocaleData()
 320           * is called before wp.i18n is defined.
 321           */
 322          if ( 'wp-i18n' === $handle ) {
 323              $ltr    = _x( 'ltr', 'text direction' );
 324              $script = sprintf( "wp.i18n.setLocaleData( { 'text direction\u0004ltr': [ '%s' ] } );", $ltr );
 325              $scripts->add_inline_script( $handle, $script, 'after' );
 326          }
 327      }
 328  }
 329  
 330  /**
 331   * Adds inline scripts required for the WordPress JavaScript packages.
 332   *
 333   * @since 5.0.0
 334   * @since 6.4.0 Added relative time strings for the `wp-date` inline script output.
 335   *
 336   * @global WP_Locale $wp_locale WordPress date and time locale object.
 337   * @global wpdb      $wpdb      WordPress database abstraction object.
 338   *
 339   * @param WP_Scripts $scripts WP_Scripts object.
 340   */
 341  function wp_default_packages_inline_scripts( $scripts ) {
 342      global $wp_locale, $wpdb;
 343  
 344      if ( isset( $scripts->registered['wp-api-fetch'] ) ) {
 345          $scripts->registered['wp-api-fetch']->deps[] = 'wp-hooks';
 346      }
 347      $scripts->add_inline_script(
 348          'wp-api-fetch',
 349          sprintf(
 350              'wp.apiFetch.use( wp.apiFetch.createRootURLMiddleware( "%s" ) );',
 351              sanitize_url( get_rest_url() )
 352          ),
 353          'after'
 354      );
 355      $scripts->add_inline_script(
 356          'wp-api-fetch',
 357          implode(
 358              "\n",
 359              array(
 360                  sprintf(
 361                      'wp.apiFetch.nonceMiddleware = wp.apiFetch.createNonceMiddleware( "%s" );',
 362                      wp_installing() ? '' : wp_create_nonce( 'wp_rest' )
 363                  ),
 364                  'wp.apiFetch.use( wp.apiFetch.nonceMiddleware );',
 365                  'wp.apiFetch.use( wp.apiFetch.mediaUploadMiddleware );',
 366                  sprintf(
 367                      'wp.apiFetch.nonceEndpoint = "%s";',
 368                      admin_url( 'admin-ajax.php?action=rest-nonce' )
 369                  ),
 370              )
 371          ),
 372          'after'
 373      );
 374  
 375      $meta_key     = $wpdb->get_blog_prefix() . 'persisted_preferences';
 376      $user_id      = get_current_user_id();
 377      $preload_data = get_user_meta( $user_id, $meta_key, true );
 378      $scripts->add_inline_script(
 379          'wp-preferences',
 380          sprintf(
 381              '( function() {
 382                  var serverData = %s;
 383                  var userId = "%d";
 384                  var persistenceLayer = wp.preferencesPersistence.__unstableCreatePersistenceLayer( serverData, userId );
 385                  var preferencesStore = wp.preferences.store;
 386                  wp.data.dispatch( preferencesStore ).setPersistenceLayer( persistenceLayer );
 387              } ) ();',
 388              wp_json_encode( $preload_data, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
 389              $user_id
 390          )
 391      );
 392  
 393      // Backwards compatibility - configure the old wp-data persistence system.
 394      $scripts->add_inline_script(
 395          'wp-data',
 396          implode(
 397              "\n",
 398              array(
 399                  '( function() {',
 400                  '    var userId = ' . get_current_user_id() . ';',
 401                  '    var storageKey = "WP_DATA_USER_" + userId;',
 402                  '    wp.data',
 403                  '        .use( wp.data.plugins.persistence, { storageKey: storageKey } );',
 404                  '} )();',
 405              )
 406          )
 407      );
 408  
 409      // Calculate the timezone abbr (EDT, PST) if possible.
 410      $timezone_string = get_option( 'timezone_string', 'UTC' );
 411      $timezone_abbr   = '';
 412  
 413      if ( ! empty( $timezone_string ) ) {
 414          $timezone_date = new DateTime( 'now', new DateTimeZone( $timezone_string ) );
 415          $timezone_abbr = $timezone_date->format( 'T' );
 416      }
 417  
 418      $gmt_offset = get_option( 'gmt_offset', 0 );
 419  
 420      $scripts->add_inline_script(
 421          'wp-date',
 422          sprintf(
 423              'wp.date.setSettings( %s );',
 424              wp_json_encode(
 425                  array(
 426                      'l10n'     => array(
 427                          'locale'        => get_user_locale(),
 428                          'months'        => array_values( $wp_locale->month ),
 429                          'monthsShort'   => array_values( $wp_locale->month_abbrev ),
 430                          'weekdays'      => array_values( $wp_locale->weekday ),
 431                          'weekdaysShort' => array_values( $wp_locale->weekday_abbrev ),
 432                          'meridiem'      => (object) $wp_locale->meridiem,
 433                          'relative'      => array(
 434                              /* translators: %s: Duration. */
 435                              'future' => __( '%s from now' ),
 436                              /* translators: %s: Duration. */
 437                              'past'   => __( '%s ago' ),
 438                              /* translators: One second from or to a particular datetime, e.g., "a second ago" or "a second from now". */
 439                              's'      => __( 'a second' ),
 440                              /* translators: %d: Duration in seconds from or to a particular datetime, e.g., "4 seconds ago" or "4 seconds from now". */
 441                              'ss'     => __( '%d seconds' ),
 442                              /* translators: One minute from or to a particular datetime, e.g., "a minute ago" or "a minute from now". */
 443                              'm'      => __( 'a minute' ),
 444                              /* translators: %d: Duration in minutes from or to a particular datetime, e.g., "4 minutes ago" or "4 minutes from now". */
 445                              'mm'     => __( '%d minutes' ),
 446                              /* translators: One hour from or to a particular datetime, e.g., "an hour ago" or "an hour from now". */
 447                              'h'      => __( 'an hour' ),
 448                              /* translators: %d: Duration in hours from or to a particular datetime, e.g., "4 hours ago" or "4 hours from now". */
 449                              'hh'     => __( '%d hours' ),
 450                              /* translators: One day from or to a particular datetime, e.g., "a day ago" or "a day from now". */
 451                              'd'      => __( 'a day' ),
 452                              /* translators: %d: Duration in days from or to a particular datetime, e.g., "4 days ago" or "4 days from now". */
 453                              'dd'     => __( '%d days' ),
 454                              /* translators: One month from or to a particular datetime, e.g., "a month ago" or "a month from now". */
 455                              'M'      => __( 'a month' ),
 456                              /* translators: %d: Duration in months from or to a particular datetime, e.g., "4 months ago" or "4 months from now". */
 457                              'MM'     => __( '%d months' ),
 458                              /* translators: One year from or to a particular datetime, e.g., "a year ago" or "a year from now". */
 459                              'y'      => __( 'a year' ),
 460                              /* translators: %d: Duration in years from or to a particular datetime, e.g., "4 years ago" or "4 years from now". */
 461                              'yy'     => __( '%d years' ),
 462                          ),
 463                          'startOfWeek'   => (int) get_option( 'start_of_week', 0 ),
 464                      ),
 465                      'formats'  => array(
 466                          /* translators: Time format, see https://www.php.net/manual/datetime.format.php */
 467                          'time'                => get_option( 'time_format', __( 'g:i a' ) ),
 468                          /* translators: Date format, see https://www.php.net/manual/datetime.format.php */
 469                          'date'                => get_option( 'date_format', __( 'F j, Y' ) ),
 470                          /* translators: Date/Time format, see https://www.php.net/manual/datetime.format.php */
 471                          'datetime'            => __( 'F j, Y g:i a' ),
 472                          /* translators: Abbreviated date/time format, see https://www.php.net/manual/datetime.format.php */
 473                          'datetimeAbbreviated' => __( 'M j, Y g:i a' ),
 474                      ),
 475                      'timezone' => array(
 476                          'offset'          => (float) $gmt_offset,
 477                          'offsetFormatted' => str_replace( array( '.25', '.5', '.75' ), array( ':15', ':30', ':45' ), (string) $gmt_offset ),
 478                          'string'          => $timezone_string,
 479                          'abbr'            => $timezone_abbr,
 480                      ),
 481                  ),
 482                  JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
 483              )
 484          ),
 485          'after'
 486      );
 487  
 488      // Loading the old editor and its config to ensure the classic block works as expected.
 489      $scripts->add_inline_script(
 490          'editor',
 491          'window.wp.oldEditor = window.wp.editor;',
 492          'after'
 493      );
 494  
 495      /*
 496       * wp-editor module is exposed as window.wp.editor.
 497       * Problem: there is quite some code expecting window.wp.oldEditor object available under window.wp.editor.
 498       * Solution: fuse the two objects together to maintain backward compatibility.
 499       * For more context, see https://github.com/WordPress/gutenberg/issues/33203.
 500       */
 501      $scripts->add_inline_script(
 502          'wp-editor',
 503          'Object.assign( window.wp.editor, window.wp.oldEditor );',
 504          'after'
 505      );
 506  }
 507  
 508  /**
 509   * Adds inline scripts required for the TinyMCE in the block editor.
 510   *
 511   * These TinyMCE init settings are used to extend and override the default settings
 512   * from `_WP_Editors::default_settings()` for the Classic block.
 513   *
 514   * @since 5.0.0
 515   *
 516   * @global WP_Scripts $wp_scripts
 517   */
 518  function wp_tinymce_inline_scripts() {
 519      global $wp_scripts;
 520  
 521      /** This filter is documented in wp-includes/class-wp-editor.php */
 522      $editor_settings = apply_filters( 'wp_editor_settings', array( 'tinymce' => true ), 'classic-block' );
 523  
 524      $tinymce_plugins = array(
 525          'charmap',
 526          'colorpicker',
 527          'hr',
 528          'lists',
 529          'media',
 530          'paste',
 531          'tabfocus',
 532          'textcolor',
 533          'fullscreen',
 534          'wordpress',
 535          'wpautoresize',
 536          'wpeditimage',
 537          'wpemoji',
 538          'wpgallery',
 539          'wplink',
 540          'wpdialogs',
 541          'wptextpattern',
 542          'wpview',
 543      );
 544  
 545      /** This filter is documented in wp-includes/class-wp-editor.php */
 546      $tinymce_plugins = apply_filters( 'tiny_mce_plugins', $tinymce_plugins, 'classic-block' );
 547      $tinymce_plugins = array_unique( $tinymce_plugins );
 548  
 549      $disable_captions = false;
 550      // Runs after `tiny_mce_plugins` but before `mce_buttons`.
 551      /** This filter is documented in wp-admin/includes/media.php */
 552      if ( apply_filters( 'disable_captions', '' ) ) {
 553          $disable_captions = true;
 554      }
 555  
 556      $toolbar1 = array(
 557          'formatselect',
 558          'bold',
 559          'italic',
 560          'bullist',
 561          'numlist',
 562          'blockquote',
 563          'alignleft',
 564          'aligncenter',
 565          'alignright',
 566          'link',
 567          'unlink',
 568          'wp_more',
 569          'spellchecker',
 570          'wp_add_media',
 571          'wp_adv',
 572      );
 573  
 574      /** This filter is documented in wp-includes/class-wp-editor.php */
 575      $toolbar1 = apply_filters( 'mce_buttons', $toolbar1, 'classic-block' );
 576  
 577      $toolbar2 = array(
 578          'strikethrough',
 579          'hr',
 580          'forecolor',
 581          'pastetext',
 582          'removeformat',
 583          'charmap',
 584          'outdent',
 585          'indent',
 586          'undo',
 587          'redo',
 588          'wp_help',
 589      );
 590  
 591      /** This filter is documented in wp-includes/class-wp-editor.php */
 592      $toolbar2 = apply_filters( 'mce_buttons_2', $toolbar2, 'classic-block' );
 593      /** This filter is documented in wp-includes/class-wp-editor.php */
 594      $toolbar3 = apply_filters( 'mce_buttons_3', array(), 'classic-block' );
 595      /** This filter is documented in wp-includes/class-wp-editor.php */
 596      $toolbar4 = apply_filters( 'mce_buttons_4', array(), 'classic-block' );
 597      /** This filter is documented in wp-includes/class-wp-editor.php */
 598      $external_plugins = apply_filters( 'mce_external_plugins', array(), 'classic-block' );
 599  
 600      $tinymce_settings = array(
 601          'plugins'              => implode( ',', $tinymce_plugins ),
 602          'toolbar1'             => implode( ',', $toolbar1 ),
 603          'toolbar2'             => implode( ',', $toolbar2 ),
 604          'toolbar3'             => implode( ',', $toolbar3 ),
 605          'toolbar4'             => implode( ',', $toolbar4 ),
 606          'external_plugins'     => wp_json_encode( $external_plugins ),
 607          'classic_block_editor' => true,
 608      );
 609  
 610      if ( $disable_captions ) {
 611          $tinymce_settings['wpeditimage_disable_captions'] = true;
 612      }
 613  
 614      if ( ! empty( $editor_settings['tinymce'] ) && is_array( $editor_settings['tinymce'] ) ) {
 615          $tinymce_settings = array_merge( $tinymce_settings, $editor_settings['tinymce'] );
 616      }
 617  
 618      /** This filter is documented in wp-includes/class-wp-editor.php */
 619      $tinymce_settings = apply_filters( 'tiny_mce_before_init', $tinymce_settings, 'classic-block' );
 620  
 621      /*
 622       * Do "by hand" translation from PHP array to js object.
 623       * Prevents breakage in some custom settings.
 624       */
 625      $init_obj = '';
 626      foreach ( $tinymce_settings as $key => $value ) {
 627          if ( is_bool( $value ) ) {
 628              $val       = $value ? 'true' : 'false';
 629              $init_obj .= $key . ':' . $val . ',';
 630              continue;
 631          } elseif ( ! empty( $value ) && is_string( $value ) && (
 632              ( '{' === $value[0] && '}' === $value[ strlen( $value ) - 1 ] ) ||
 633              ( '[' === $value[0] && ']' === $value[ strlen( $value ) - 1 ] ) ||
 634              preg_match( '/^\(?function ?\(/', $value ) ) ) {
 635              $init_obj .= $key . ':' . $value . ',';
 636              continue;
 637          }
 638          $init_obj .= $key . ':"' . $value . '",';
 639      }
 640  
 641      $init_obj = '{' . trim( $init_obj, ' ,' ) . '}';
 642  
 643      $script = 'window.wpEditorL10n = {
 644          tinymce: {
 645              baseURL: ' . wp_json_encode( includes_url( 'js/tinymce' ), JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ',
 646              suffix: ' . ( SCRIPT_DEBUG ? '""' : '".min"' ) . ',
 647              settings: ' . $init_obj . ',
 648          }
 649      }';
 650  
 651      $wp_scripts->add_inline_script( 'wp-block-library', $script, 'before' );
 652  }
 653  
 654  /**
 655   * Registers all the WordPress packages scripts.
 656   *
 657   * @since 5.0.0
 658   *
 659   * @param WP_Scripts $scripts WP_Scripts object.
 660   */
 661  function wp_default_packages( $scripts ) {
 662      wp_default_packages_vendor( $scripts );
 663      wp_register_development_scripts( $scripts );
 664      wp_register_tinymce_scripts( $scripts );
 665      wp_default_packages_scripts( $scripts );
 666  
 667      if ( did_action( 'init' ) ) {
 668          wp_default_packages_inline_scripts( $scripts );
 669      }
 670  }
 671  
 672  /**
 673   * Returns the suffix that can be used for the scripts.
 674   *
 675   * There are two suffix types, the normal one and the dev suffix.
 676   *
 677   * @since 5.0.0
 678   *
 679   * @param string $type The type of suffix to retrieve.
 680   * @return string The script suffix.
 681   */
 682  function wp_scripts_get_suffix( $type = '' ) {
 683      static $suffixes;
 684  
 685      if ( null === $suffixes ) {
 686          $develop_src = str_contains( wp_get_wp_version(), '-src' );
 687  
 688          if ( ! defined( 'SCRIPT_DEBUG' ) ) {
 689              define( 'SCRIPT_DEBUG', $develop_src );
 690          }
 691          $suffix     = SCRIPT_DEBUG ? '' : '.min';
 692          $dev_suffix = $develop_src ? '' : '.min';
 693  
 694          $suffixes = array(
 695              'suffix'     => $suffix,
 696              'dev_suffix' => $dev_suffix,
 697          );
 698      }
 699  
 700      if ( 'dev' === $type ) {
 701          return $suffixes['dev_suffix'];
 702      }
 703  
 704      return $suffixes['suffix'];
 705  }
 706  
 707  /**
 708   * Registers all WordPress scripts.
 709   *
 710   * Localizes some of them.
 711   * args order: `$scripts->add( 'handle', 'url', 'dependencies', 'query-string', 1 );`
 712   * when last arg === 1 queues the script for the footer
 713   *
 714   * @since 2.6.0
 715   *
 716   * @param WP_Scripts $scripts WP_Scripts object.
 717   */
 718  function wp_default_scripts( $scripts ) {
 719      $suffix     = wp_scripts_get_suffix();
 720      $dev_suffix = wp_scripts_get_suffix( 'dev' );
 721      $guessurl   = site_url();
 722  
 723      if ( ! $guessurl ) {
 724          $guessed_url = true;
 725          $guessurl    = wp_guess_url();
 726      }
 727  
 728      $scripts->base_url        = $guessurl;
 729      $scripts->content_url     = defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL : '';
 730      $scripts->default_version = get_bloginfo( 'version' );
 731      $scripts->default_dirs    = array( '/wp-admin/js/', '/wp-includes/js/' ); // Deprecated, but still set for plugins that read it.
 732  
 733      $scripts->add( 'utils', "/wp-includes/js/utils$suffix.js" );
 734      did_action( 'init' ) && $scripts->localize(
 735          'utils',
 736          'userSettings',
 737          array(
 738              'url'    => (string) SITECOOKIEPATH,
 739              'uid'    => (string) get_current_user_id(),
 740              'time'   => (string) time(),
 741              'secure' => (string) ( 'https' === parse_url( site_url(), PHP_URL_SCHEME ) ),
 742          )
 743      );
 744  
 745      $scripts->add( 'common', "/wp-admin/js/common$suffix.js", array( 'jquery', 'hoverIntent', 'utils', 'wp-a11y' ), false, 1 );
 746      $scripts->set_translations( 'common' );
 747  
 748      $bulk_action_observer_ids = array(
 749          'bulk_action' => 'action',
 750          'changeit'    => 'new_role',
 751      );
 752      did_action( 'init' ) && $scripts->localize(
 753          'common',
 754          'bulkActionObserverIds',
 755          /**
 756           * Filters the array of field name attributes for bulk actions.
 757           *
 758           * @since 6.8.1
 759           *
 760           * @param array $bulk_action_observer_ids {
 761           *      An array of field name attributes for bulk actions.
 762           *
 763           *      @type string $bulk_action The bulk action field name. Default 'action'.
 764           *      @type string $changeit    The new role field name. Default 'new_role'.
 765           * }
 766           */
 767          apply_filters( 'bulk_action_observer_ids', $bulk_action_observer_ids )
 768      );
 769  
 770      $scripts->add( 'wp-sanitize', "/wp-includes/js/wp-sanitize$suffix.js", array(), false, 1 );
 771  
 772      $scripts->add( 'sack', "/wp-includes/js/tw-sack$suffix.js", array(), '1.6.1', 1 );
 773  
 774      $scripts->add( 'quicktags', "/wp-includes/js/quicktags$suffix.js", array(), false, 1 );
 775      did_action( 'init' ) && $scripts->localize(
 776          'quicktags',
 777          'quicktagsL10n',
 778          array(
 779              'closeAllOpenTags'      => __( 'Close all open tags' ),
 780              'closeTags'             => __( 'close tags' ),
 781              'enterURL'              => __( 'Enter the URL' ),
 782              'enterImageURL'         => __( 'Enter the URL of the image' ),
 783              'enterImageDescription' => __( 'Enter a description of the image' ),
 784              'textdirection'         => __( 'text direction' ),
 785              'toggleTextdirection'   => __( 'Switch Editor Text Direction' ),
 786              'dfw'                   => __( 'Distraction-free writing mode' ),
 787              'strong'                => __( 'Bold' ),
 788              'strongClose'           => __( 'Close bold tag' ),
 789              'em'                    => __( 'Italic' ),
 790              'emClose'               => __( 'Close italic tag' ),
 791              'link'                  => __( 'Insert link' ),
 792              'blockquote'            => __( 'Blockquote' ),
 793              'blockquoteClose'       => __( 'Close blockquote tag' ),
 794              'del'                   => __( 'Deleted text (strikethrough)' ),
 795              'delClose'              => __( 'Close deleted text tag' ),
 796              'ins'                   => __( 'Inserted text' ),
 797              'insClose'              => __( 'Close inserted text tag' ),
 798              'image'                 => __( 'Insert image' ),
 799              'ul'                    => __( 'Bulleted list' ),
 800              'ulClose'               => __( 'Close bulleted list tag' ),
 801              'ol'                    => __( 'Numbered list' ),
 802              'olClose'               => __( 'Close numbered list tag' ),
 803              'li'                    => __( 'List item' ),
 804              'liClose'               => __( 'Close list item tag' ),
 805              'code'                  => __( 'Code' ),
 806              'codeClose'             => __( 'Close code tag' ),
 807              'more'                  => __( 'Insert Read More tag' ),
 808          )
 809      );
 810  
 811      $scripts->add( 'colorpicker', "/wp-includes/js/colorpicker$suffix.js", array( 'prototype' ), '3517m' );
 812  
 813      $scripts->add( 'editor', "/wp-admin/js/editor$suffix.js", array( 'utils', 'jquery' ), false, 1 );
 814  
 815      $scripts->add( 'clipboard', "/wp-includes/js/clipboard$suffix.js", array(), '2.0.11', 1 );
 816  
 817      $scripts->add( 'wp-ajax-response', "/wp-includes/js/wp-ajax-response$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 );
 818      did_action( 'init' ) && $scripts->localize(
 819          'wp-ajax-response',
 820          'wpAjax',
 821          array(
 822              'noPerm' => __( 'Sorry, you are not allowed to do that.' ),
 823              'broken' => __( 'An error occurred while processing your request. Please try again later.' ),
 824          )
 825      );
 826  
 827      $scripts->add( 'wp-api-request', "/wp-includes/js/api-request$suffix.js", array( 'jquery' ), false, 1 );
 828      // `wpApiSettings` is also used by `wp-api`, which depends on this script.
 829      did_action( 'init' ) && $scripts->localize(
 830          'wp-api-request',
 831          'wpApiSettings',
 832          array(
 833              'root'          => sanitize_url( get_rest_url() ),
 834              'nonce'         => wp_installing() ? '' : wp_create_nonce( 'wp_rest' ),
 835              'versionString' => 'wp/v2/',
 836          )
 837      );
 838  
 839      $scripts->add( 'wp-pointer', "/wp-includes/js/wp-pointer$suffix.js", array( 'jquery-ui-core' ), false, 1 );
 840      $scripts->set_translations( 'wp-pointer' );
 841  
 842      $scripts->add( 'autosave', "/wp-includes/js/autosave$suffix.js", array( 'heartbeat' ), false, 1 );
 843  
 844      $scripts->add( 'heartbeat', "/wp-includes/js/heartbeat$suffix.js", array( 'jquery', 'wp-hooks' ), false, 1 );
 845      did_action( 'init' ) && $scripts->localize(
 846          'heartbeat',
 847          'heartbeatSettings',
 848          /**
 849           * Filters the Heartbeat settings.
 850           *
 851           * @since 3.6.0
 852           *
 853           * @param array $settings Heartbeat settings array.
 854           */
 855          apply_filters( 'heartbeat_settings', array() )
 856      );
 857  
 858      $scripts->add( 'wp-auth-check', "/wp-includes/js/wp-auth-check$suffix.js", array( 'heartbeat' ), false, 1 );
 859      $scripts->set_translations( 'wp-auth-check' );
 860  
 861      $scripts->add( 'wp-tooltip', '/wp-includes/js/wp-tooltip.js', array(), false, 1 );
 862  
 863      $scripts->add( 'wp-lists', "/wp-includes/js/wp-lists$suffix.js", array( 'wp-ajax-response', 'jquery-color' ), false, 1 );
 864  
 865      $scripts->add( 'site-icon', '/wp-admin/js/site-icon.js', array( 'jquery' ), false, 1 );
 866      $scripts->set_translations( 'site-icon' );
 867  
 868      // WordPress no longer uses or bundles Prototype or script.aculo.us. These are now pulled from an external source.
 869      $scripts->add( 'prototype', 'https://ajax.googleapis.com/ajax/libs/prototype/1.7.1.0/prototype.js', array(), '1.7.1' );
 870      $scripts->add( 'scriptaculous-root', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/scriptaculous.js', array( 'prototype' ), '1.9.0' );
 871      $scripts->add( 'scriptaculous-builder', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/builder.js', array( 'scriptaculous-root' ), '1.9.0' );
 872      $scripts->add( 'scriptaculous-dragdrop', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/dragdrop.js', array( 'scriptaculous-builder', 'scriptaculous-effects' ), '1.9.0' );
 873      $scripts->add( 'scriptaculous-effects', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/effects.js', array( 'scriptaculous-root' ), '1.9.0' );
 874      $scripts->add( 'scriptaculous-slider', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/slider.js', array( 'scriptaculous-effects' ), '1.9.0' );
 875      $scripts->add( 'scriptaculous-sound', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/sound.js', array( 'scriptaculous-root' ), '1.9.0' );
 876      $scripts->add( 'scriptaculous-controls', 'https://ajax.googleapis.com/ajax/libs/scriptaculous/1.9.0/controls.js', array( 'scriptaculous-root' ), '1.9.0' );
 877      $scripts->add( 'scriptaculous', false, array( 'scriptaculous-dragdrop', 'scriptaculous-slider', 'scriptaculous-controls' ) );
 878  
 879      // Not used in core, replaced by Jcrop.js.
 880      $scripts->add( 'cropper', '/wp-includes/js/crop/cropper.js', array( 'scriptaculous-dragdrop' ) );
 881  
 882      /*
 883       * jQuery.
 884       * The unminified jquery.js and jquery-migrate.js are included to facilitate debugging.
 885       */
 886      $scripts->add( 'jquery', false, array( 'jquery-core', 'jquery-migrate' ), '3.7.1' );
 887      $scripts->add( 'jquery-core', "/wp-includes/js/jquery/jquery$suffix.js", array(), '3.7.1' );
 888      $scripts->add( 'jquery-migrate', "/wp-includes/js/jquery/jquery-migrate$suffix.js", array(), '3.6.0' );
 889  
 890      /*
 891       * Full jQuery UI.
 892       * The build process in 1.12.1 has changed significantly.
 893       * In order to keep backwards compatibility, and to keep the optimized loading,
 894       * the source files were flattened and included with some modifications for AMD loading.
 895       * A notable change is that 'jquery-ui-core' now contains 'jquery-ui-position' and 'jquery-ui-widget'.
 896       */
 897      $scripts->add( 'jquery-ui-core', "/wp-includes/js/jquery/ui/core$suffix.js", array( 'jquery' ), '1.14.2', 1 );
 898      $scripts->add_inline_script( 'jquery-ui-core', 'jQuery.uiBackCompat = true;', 'before' );
 899      $scripts->add( 'jquery-effects-core', "/wp-includes/js/jquery/ui/effect$suffix.js", array( 'jquery' ), '1.14.2', 1 );
 900  
 901      $scripts->add( 'jquery-effects-blind', "/wp-includes/js/jquery/ui/effect-blind$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 902      $scripts->add( 'jquery-effects-bounce', "/wp-includes/js/jquery/ui/effect-bounce$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 903      $scripts->add( 'jquery-effects-clip', "/wp-includes/js/jquery/ui/effect-clip$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 904      $scripts->add( 'jquery-effects-drop', "/wp-includes/js/jquery/ui/effect-drop$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 905      $scripts->add( 'jquery-effects-explode', "/wp-includes/js/jquery/ui/effect-explode$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 906      $scripts->add( 'jquery-effects-fade', "/wp-includes/js/jquery/ui/effect-fade$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 907      $scripts->add( 'jquery-effects-fold', "/wp-includes/js/jquery/ui/effect-fold$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 908      $scripts->add( 'jquery-effects-highlight', "/wp-includes/js/jquery/ui/effect-highlight$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 909      $scripts->add( 'jquery-effects-puff', "/wp-includes/js/jquery/ui/effect-puff$suffix.js", array( 'jquery-effects-core', 'jquery-effects-scale' ), '1.14.2', 1 );
 910      $scripts->add( 'jquery-effects-pulsate', "/wp-includes/js/jquery/ui/effect-pulsate$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 911      $scripts->add( 'jquery-effects-scale', "/wp-includes/js/jquery/ui/effect-scale$suffix.js", array( 'jquery-effects-core', 'jquery-effects-size' ), '1.14.2', 1 );
 912      $scripts->add( 'jquery-effects-shake', "/wp-includes/js/jquery/ui/effect-shake$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 913      $scripts->add( 'jquery-effects-size', "/wp-includes/js/jquery/ui/effect-size$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 914      $scripts->add( 'jquery-effects-slide', "/wp-includes/js/jquery/ui/effect-slide$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 915      $scripts->add( 'jquery-effects-transfer', "/wp-includes/js/jquery/ui/effect-transfer$suffix.js", array( 'jquery-effects-core' ), '1.14.2', 1 );
 916  
 917      // Widgets
 918      $scripts->add( 'jquery-ui-accordion', "/wp-includes/js/jquery/ui/accordion$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 919      $scripts->add( 'jquery-ui-autocomplete', "/wp-includes/js/jquery/ui/autocomplete$suffix.js", array( 'jquery-ui-menu', 'wp-a11y' ), '1.14.2', 1 );
 920      $scripts->add( 'jquery-ui-button', "/wp-includes/js/jquery/ui/button$suffix.js", array( 'jquery-ui-core', 'jquery-ui-controlgroup', 'jquery-ui-checkboxradio' ), '1.14.2', 1 );
 921      $scripts->add( 'jquery-ui-datepicker', "/wp-includes/js/jquery/ui/datepicker$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 922      $scripts->add( 'jquery-ui-dialog', "/wp-includes/js/jquery/ui/dialog$suffix.js", array( 'jquery-ui-resizable', 'jquery-ui-draggable', 'jquery-ui-button' ), '1.14.2', 1 );
 923      $scripts->add( 'jquery-ui-menu', "/wp-includes/js/jquery/ui/menu$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 924      $scripts->add( 'jquery-ui-mouse', "/wp-includes/js/jquery/ui/mouse$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 925      $scripts->add( 'jquery-ui-progressbar', "/wp-includes/js/jquery/ui/progressbar$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 926      $scripts->add( 'jquery-ui-selectmenu', "/wp-includes/js/jquery/ui/selectmenu$suffix.js", array( 'jquery-ui-menu' ), '1.14.2', 1 );
 927      $scripts->add( 'jquery-ui-slider', "/wp-includes/js/jquery/ui/slider$suffix.js", array( 'jquery-ui-mouse' ), '1.14.2', 1 );
 928      $scripts->add( 'jquery-ui-spinner', "/wp-includes/js/jquery/ui/spinner$suffix.js", array( 'jquery-ui-button' ), '1.14.2', 1 );
 929      $scripts->add( 'jquery-ui-tabs', "/wp-includes/js/jquery/ui/tabs$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 930      $scripts->add( 'jquery-ui-tooltip', "/wp-includes/js/jquery/ui/tooltip$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 931  
 932      // Added in jQuery UI 1.12.1.
 933      $scripts->add( 'jquery-ui-checkboxradio', "/wp-includes/js/jquery/ui/checkboxradio$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 934      $scripts->add( 'jquery-ui-controlgroup', "/wp-includes/js/jquery/ui/controlgroup$suffix.js", array( 'jquery-ui-core' ), '1.14.2', 1 );
 935  
 936      // Interactions
 937      $scripts->add( 'jquery-ui-draggable', "/wp-includes/js/jquery/ui/draggable$suffix.js", array( 'jquery-ui-mouse' ), '1.14.2', 1 );
 938      $scripts->add( 'jquery-ui-droppable', "/wp-includes/js/jquery/ui/droppable$suffix.js", array( 'jquery-ui-draggable' ), '1.14.2', 1 );
 939      $scripts->add( 'jquery-ui-resizable', "/wp-includes/js/jquery/ui/resizable$suffix.js", array( 'jquery-ui-mouse' ), '1.14.2', 1 );
 940      $scripts->add( 'jquery-ui-selectable', "/wp-includes/js/jquery/ui/selectable$suffix.js", array( 'jquery-ui-mouse' ), '1.14.2', 1 );
 941      $scripts->add( 'jquery-ui-sortable', "/wp-includes/js/jquery/ui/sortable$suffix.js", array( 'jquery-ui-mouse' ), '1.14.2', 1 );
 942  
 943      /*
 944       * As of 1.12.1 `jquery-ui-position` and `jquery-ui-widget` are part of `jquery-ui-core`.
 945       * Listed here for back-compat.
 946       */
 947      $scripts->add( 'jquery-ui-position', false, array( 'jquery-ui-core' ), '1.14.2', 1 );
 948      $scripts->add( 'jquery-ui-widget', false, array( 'jquery-ui-core' ), '1.14.2', 1 );
 949  
 950      // Deprecated, not used in core, most functionality is included in jQuery 1.3.
 951      $scripts->add( 'jquery-form', "/wp-includes/js/jquery/jquery.form$suffix.js", array( 'jquery' ), '4.3.0', 1 );
 952  
 953      // jQuery plugins.
 954      $scripts->add( 'jquery-color', '/wp-includes/js/jquery/jquery.color.min.js', array( 'jquery' ), '3.0.0', 1 );
 955      $scripts->add( 'schedule', '/wp-includes/js/jquery/jquery.schedule.js', array( 'jquery' ), '20m', 1 );
 956      $scripts->add( 'jquery-query', '/wp-includes/js/jquery/jquery.query.js', array( 'jquery' ), '2.2.3', 1 );
 957      $scripts->add( 'jquery-serialize-object', '/wp-includes/js/jquery/jquery.serialize-object.js', array( 'jquery' ), '0.2-wp', 1 );
 958      $scripts->add( 'jquery-hotkeys', "/wp-includes/js/jquery/jquery.hotkeys$suffix.js", array( 'jquery' ), '0.0.2m', 1 );
 959      $scripts->add( 'jquery-table-hotkeys', "/wp-includes/js/jquery/jquery.table-hotkeys$suffix.js", array( 'jquery', 'jquery-hotkeys' ), false, 1 );
 960      $scripts->add( 'jquery-touch-punch', '/wp-includes/js/jquery/jquery.ui.touch-punch.js', array( 'jquery-ui-core', 'jquery-ui-mouse' ), '0.2.2', 1 );
 961  
 962      // Not used any more, registered for backward compatibility.
 963      $scripts->add( 'suggest', "/wp-includes/js/jquery/suggest$suffix.js", array( 'jquery' ), '1.1-20110113', 1 );
 964  
 965      /*
 966       * Masonry v2 depended on jQuery. v3 does not. The older jquery-masonry handle is a shiv.
 967       * It sets jQuery as a dependency, as the theme may have been implicitly loading it this way.
 968       */
 969      $scripts->add( 'imagesloaded', '/wp-includes/js/imagesloaded.min.js', array(), '5.0.0', 1 );
 970      $scripts->add( 'masonry', '/wp-includes/js/masonry.min.js', array( 'imagesloaded' ), '4.2.2', 1 );
 971      $scripts->add( 'jquery-masonry', '/wp-includes/js/jquery/jquery.masonry.min.js', array( 'jquery', 'masonry' ), '3.1.2b', 1 );
 972  
 973      $scripts->add( 'thickbox', '/wp-includes/js/thickbox/thickbox.js', array( 'jquery' ), '3.1-20121105', 1 );
 974      did_action( 'init' ) && $scripts->localize(
 975          'thickbox',
 976          'thickboxL10n',
 977          array(
 978              'next'             => __( 'Next &gt;' ),
 979              'prev'             => __( '&lt; Prev' ),
 980              'image'            => __( 'Image' ),
 981              'of'               => __( 'of' ),
 982              'close'            => __( 'Close' ),
 983              'noiframes'        => __( 'This feature requires inline frames. You have iframes disabled or your browser does not support them.' ),
 984              'loadingAnimation' => includes_url( 'js/thickbox/loadingAnimation.gif' ),
 985          )
 986      );
 987  
 988      // Not used in core, replaced by imgAreaSelect.
 989      $scripts->add( 'jcrop', '/wp-includes/js/jcrop/jquery.Jcrop.min.js', array( 'jquery' ), '0.9.15' );
 990  
 991      // Error messages for Plupload.
 992      $uploader_l10n = array(
 993          'queue_limit_exceeded'      => __( 'You have attempted to queue too many files.' ),
 994          /* translators: %s: File name. */
 995          'file_exceeds_size_limit'   => __( '%s exceeds the maximum upload size for this site.' ),
 996          'zero_byte_file'            => __( 'This file is empty. Please try another.' ),
 997          'invalid_filetype'          => __( 'This file cannot be processed by the web server.' ),
 998          'not_an_image'              => __( 'This file is not an image. Please try another.' ),
 999          'image_memory_exceeded'     => __( 'Memory exceeded. Please try another smaller file.' ),
1000          'image_dimensions_exceeded' => __( 'This is larger than the maximum size. Please try another.' ),
1001          'default_error'             => __( 'An error occurred in the upload. Please try again later.' ),
1002          'missing_upload_url'        => __( 'There was a configuration error. Please contact the server administrator.' ),
1003          'upload_limit_exceeded'     => __( 'You may only upload 1 file.' ),
1004          'http_error'                => __( 'Unexpected response from the server. The file may have been uploaded successfully. Check in the Media Library or reload the page.' ),
1005          'http_error_image'          => __( 'The server cannot process the image. This can happen if the server is busy or does not have enough resources to complete the task. Uploading a smaller image may help. Suggested maximum size is 2560 pixels.' ),
1006          'upload_failed'             => __( 'Upload failed.' ),
1007          /* translators: 1: Opening link tag, 2: Closing link tag. */
1008          'big_upload_failed'         => __( 'Please try uploading this file with the %1$sbrowser uploader%2$s.' ),
1009          /* translators: %s: File name. */
1010          'big_upload_queued'         => __( '%s exceeds the maximum upload size for the multi-file uploader when used in your browser.' ),
1011          'io_error'                  => __( 'IO error.' ),
1012          'security_error'            => __( 'Security error.' ),
1013          'file_cancelled'            => __( 'File canceled.' ),
1014          'upload_stopped'            => __( 'Upload stopped.' ),
1015          'dismiss'                   => __( 'Dismiss' ),
1016          'crunching'                 => __( 'Crunching&hellip;' ),
1017          'deleted'                   => __( 'moved to the Trash.' ),
1018          /* translators: %s: File name. */
1019          'error_uploading'           => __( '&#8220;%s&#8221; has failed to upload.' ),
1020          'unsupported_image'         => __( 'The server cannot process HEIC images. Convert it to JPEG before uploading.' ),
1021          'noneditable_image'         => __( 'The web server cannot generate responsive image sizes for this image. Convert it to JPEG or PNG before uploading.' ),
1022          'file_url_copied'           => __( 'The file URL has been copied to your clipboard' ),
1023      );
1024  
1025      $scripts->add( 'moxiejs', "/wp-includes/js/plupload/moxie$suffix.js", array(), '1.3.5.1' );
1026      $scripts->add( 'plupload', "/wp-includes/js/plupload/plupload$suffix.js", array( 'moxiejs' ), '2.1.9' );
1027      // Back compat handles:
1028      foreach ( array( 'all', 'html5', 'flash', 'silverlight', 'html4' ) as $handle ) {
1029          $scripts->add( "plupload-$handle", false, array( 'plupload' ), '2.1.1' );
1030      }
1031  
1032      $scripts->add( 'plupload-handlers', "/wp-includes/js/plupload/handlers$suffix.js", array( 'clipboard', 'jquery', 'plupload', 'underscore', 'wp-a11y', 'wp-i18n' ) );
1033      did_action( 'init' ) && $scripts->localize( 'plupload-handlers', 'pluploadL10n', $uploader_l10n );
1034  
1035      $scripts->add( 'wp-plupload', "/wp-includes/js/plupload/wp-plupload$suffix.js", array( 'plupload', 'jquery', 'media-models' ), false, 1 );
1036      did_action( 'init' ) && $scripts->localize( 'wp-plupload', 'pluploadL10n', $uploader_l10n );
1037  
1038      $scripts->add( 'comment-reply', "/wp-includes/js/comment-reply$suffix.js", array(), false, 1 );
1039      if ( did_action( 'init' ) ) {
1040          $scripts->add_data( 'comment-reply', 'strategy', 'async' );
1041          $scripts->add_data( 'comment-reply', 'fetchpriority', 'low' ); // In Chrome this is automatically low due to the async strategy, but in Firefox and Safari the priority is normal/medium.
1042      }
1043  
1044      // Not used in core, obsolete. Registered for backward compatibility.
1045      $scripts->add( 'json2', "/wp-includes/js/json2$suffix.js", array(), '2015-05-03' );
1046      did_action( 'init' ) && $scripts->add_data( 'json2', 'conditional', '_required-conditional-dependency_' );
1047  
1048      $scripts->add( 'underscore', "/wp-includes/js/underscore$dev_suffix.js", array(), '1.13.8', 1 );
1049      $scripts->add( 'backbone', "/wp-includes/js/backbone$dev_suffix.js", array( 'underscore', 'jquery' ), '1.6.1', 1 );
1050  
1051      $scripts->add( 'wp-util', "/wp-includes/js/wp-util$suffix.js", array( 'underscore', 'jquery' ), false, 1 );
1052      did_action( 'init' ) && $scripts->localize(
1053          'wp-util',
1054          '_wpUtilSettings',
1055          array(
1056              'ajax' => array(
1057                  'url' => admin_url( 'admin-ajax.php', 'relative' ),
1058              ),
1059          )
1060      );
1061  
1062      $scripts->add( 'wp-backbone', "/wp-includes/js/wp-backbone$suffix.js", array( 'backbone', 'wp-util' ), false, 1 );
1063  
1064      $scripts->add( 'revisions', "/wp-admin/js/revisions$suffix.js", array( 'wp-backbone', 'jquery-ui-slider', 'hoverIntent' ), false, 1 );
1065  
1066      $scripts->add( 'imgareaselect', "/wp-includes/js/imgareaselect/jquery.imgareaselect$suffix.js", array( 'jquery' ), false, 1 );
1067  
1068      $scripts->add( 'mediaelement', false, array( 'jquery', 'mediaelement-core', 'mediaelement-migrate' ), '4.2.17', 1 );
1069      $scripts->add( 'mediaelement-core', "/wp-includes/js/mediaelement/mediaelement-and-player$suffix.js", array(), '4.2.17', 1 );
1070      $scripts->add( 'mediaelement-migrate', "/wp-includes/js/mediaelement/mediaelement-migrate$suffix.js", array(), false, 1 );
1071  
1072      did_action( 'init' ) && $scripts->add_inline_script(
1073          'mediaelement-core',
1074          sprintf(
1075              'var mejsL10n = %s;',
1076              wp_json_encode(
1077                  array(
1078                      'language' => strtolower( strtok( determine_locale(), '_-' ) ),
1079                      'strings'  => array(
1080                          'mejs.download-file'       => __( 'Download File' ),
1081                          'mejs.install-flash'       => __( 'You are using a browser that does not have Flash player enabled or installed. Please turn on your Flash player plugin or download the latest version from https://get.adobe.com/flashplayer/' ),
1082                          'mejs.fullscreen'          => __( 'Fullscreen' ),
1083                          'mejs.play'                => __( 'Play' ),
1084                          'mejs.pause'               => __( 'Pause' ),
1085                          'mejs.time-slider'         => __( 'Time Slider' ),
1086                          'mejs.time-help-text'      => __( 'Use Left/Right Arrow keys to advance one second, Up/Down arrows to advance ten seconds.' ),
1087                          'mejs.live-broadcast'      => __( 'Live Broadcast' ),
1088                          'mejs.volume-help-text'    => __( 'Use Up/Down Arrow keys to increase or decrease volume.' ),
1089                          'mejs.unmute'              => __( 'Unmute' ),
1090                          'mejs.mute'                => __( 'Mute' ),
1091                          'mejs.volume-slider'       => __( 'Volume Slider' ),
1092                          'mejs.video-player'        => __( 'Video Player' ),
1093                          'mejs.audio-player'        => __( 'Audio Player' ),
1094                          'mejs.captions-subtitles'  => __( 'Captions/Subtitles' ),
1095                          'mejs.captions-chapters'   => __( 'Chapters' ),
1096                          'mejs.none'                => __( 'None' ),
1097                          'mejs.afrikaans'           => __( 'Afrikaans' ),
1098                          'mejs.albanian'            => __( 'Albanian' ),
1099                          'mejs.arabic'              => __( 'Arabic' ),
1100                          'mejs.belarusian'          => __( 'Belarusian' ),
1101                          'mejs.bulgarian'           => __( 'Bulgarian' ),
1102                          'mejs.catalan'             => __( 'Catalan' ),
1103                          'mejs.chinese'             => __( 'Chinese' ),
1104                          'mejs.chinese-simplified'  => __( 'Chinese (Simplified)' ),
1105                          'mejs.chinese-traditional' => __( 'Chinese (Traditional)' ),
1106                          'mejs.croatian'            => __( 'Croatian' ),
1107                          'mejs.czech'               => __( 'Czech' ),
1108                          'mejs.danish'              => __( 'Danish' ),
1109                          'mejs.dutch'               => __( 'Dutch' ),
1110                          'mejs.english'             => __( 'English' ),
1111                          'mejs.estonian'            => __( 'Estonian' ),
1112                          'mejs.filipino'            => __( 'Filipino' ),
1113                          'mejs.finnish'             => __( 'Finnish' ),
1114                          'mejs.french'              => __( 'French' ),
1115                          'mejs.galician'            => __( 'Galician' ),
1116                          'mejs.german'              => __( 'German' ),
1117                          'mejs.greek'               => __( 'Greek' ),
1118                          'mejs.haitian-creole'      => __( 'Haitian Creole' ),
1119                          'mejs.hebrew'              => __( 'Hebrew' ),
1120                          'mejs.hindi'               => __( 'Hindi' ),
1121                          'mejs.hungarian'           => __( 'Hungarian' ),
1122                          'mejs.icelandic'           => __( 'Icelandic' ),
1123                          'mejs.indonesian'          => __( 'Indonesian' ),
1124                          'mejs.irish'               => __( 'Irish' ),
1125                          'mejs.italian'             => __( 'Italian' ),
1126                          'mejs.japanese'            => __( 'Japanese' ),
1127                          'mejs.korean'              => __( 'Korean' ),
1128                          'mejs.latvian'             => __( 'Latvian' ),
1129                          'mejs.lithuanian'          => __( 'Lithuanian' ),
1130                          'mejs.macedonian'          => __( 'Macedonian' ),
1131                          'mejs.malay'               => __( 'Malay' ),
1132                          'mejs.maltese'             => __( 'Maltese' ),
1133                          'mejs.norwegian'           => __( 'Norwegian' ),
1134                          'mejs.persian'             => __( 'Persian' ),
1135                          'mejs.polish'              => __( 'Polish' ),
1136                          'mejs.portuguese'          => __( 'Portuguese' ),
1137                          'mejs.romanian'            => __( 'Romanian' ),
1138                          'mejs.russian'             => __( 'Russian' ),
1139                          'mejs.serbian'             => __( 'Serbian' ),
1140                          'mejs.slovak'              => __( 'Slovak' ),
1141                          'mejs.slovenian'           => __( 'Slovenian' ),
1142                          'mejs.spanish'             => __( 'Spanish' ),
1143                          'mejs.swahili'             => __( 'Swahili' ),
1144                          'mejs.swedish'             => __( 'Swedish' ),
1145                          'mejs.tagalog'             => __( 'Tagalog' ),
1146                          'mejs.thai'                => __( 'Thai' ),
1147                          'mejs.turkish'             => __( 'Turkish' ),
1148                          'mejs.ukrainian'           => __( 'Ukrainian' ),
1149                          'mejs.vietnamese'          => __( 'Vietnamese' ),
1150                          'mejs.welsh'               => __( 'Welsh' ),
1151                          'mejs.yiddish'             => __( 'Yiddish' ),
1152                      ),
1153                  ),
1154                  JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
1155              )
1156          ),
1157          'before'
1158      );
1159  
1160      $scripts->add( 'mediaelement-vimeo', '/wp-includes/js/mediaelement/renderers/vimeo.min.js', array( 'mediaelement' ), '4.2.17', 1 );
1161      $scripts->add( 'wp-mediaelement', "/wp-includes/js/mediaelement/wp-mediaelement$suffix.js", array( 'mediaelement' ), false, 1 );
1162      $mejs_settings = array(
1163          'pluginPath'            => includes_url( 'js/mediaelement/', 'relative' ),
1164          'classPrefix'           => 'mejs-',
1165          'stretching'            => 'responsive',
1166          /** This filter is documented in wp-includes/media.php */
1167          'audioShortcodeLibrary' => apply_filters( 'wp_audio_shortcode_library', 'mediaelement' ),
1168          /** This filter is documented in wp-includes/media.php */
1169          'videoShortcodeLibrary' => apply_filters( 'wp_video_shortcode_library', 'mediaelement' ),
1170      );
1171      did_action( 'init' ) && $scripts->localize(
1172          'mediaelement',
1173          '_wpmejsSettings',
1174          /**
1175           * Filters the MediaElement configuration settings.
1176           *
1177           * @since 4.4.0
1178           *
1179           * @param array $mejs_settings MediaElement settings array.
1180           */
1181          apply_filters( 'mejs_settings', $mejs_settings )
1182      );
1183  
1184      $scripts->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.js', array(), '5.65.20' );
1185      $scripts->add( 'csslint', '/wp-includes/js/codemirror/csslint.js', array(), '1.0.5' );
1186      $scripts->add( 'esprima', '/wp-includes/js/codemirror/esprima.js', array(), '4.0.1' ); // Deprecated.
1187      $scripts->add( 'jshint', '/wp-includes/js/codemirror/fakejshint.js', array( 'esprima' ), '2.9.5' ); // Deprecated.
1188      $scripts->add( 'jsonlint', '/wp-includes/js/codemirror/jsonlint.js', array(), '1.6.3' );
1189      $scripts->add( 'htmlhint', '/wp-includes/js/codemirror/htmlhint.js', array(), '1.9.2' );
1190      $scripts->add( 'htmlhint-kses', '/wp-includes/js/codemirror/htmlhint-kses.js', array( 'htmlhint' ) );
1191      $scripts->add( 'code-editor', "/wp-admin/js/code-editor$suffix.js", array( 'jquery', 'wp-codemirror', 'underscore' ) );
1192      $scripts->add( 'wp-theme-plugin-editor', "/wp-admin/js/theme-plugin-editor$suffix.js", array( 'common', 'wp-util', 'wp-sanitize', 'jquery', 'jquery-ui-core', 'wp-a11y', 'underscore' ), false, 1 );
1193      $scripts->set_translations( 'wp-theme-plugin-editor' );
1194  
1195      $scripts->add( 'wp-playlist', "/wp-includes/js/mediaelement/wp-playlist$suffix.js", array( 'wp-util', 'backbone', 'mediaelement' ), false, 1 );
1196  
1197      $scripts->add( 'zxcvbn-async', "/wp-includes/js/zxcvbn-async$suffix.js", array(), '1.0' );
1198      did_action( 'init' ) && $scripts->localize(
1199          'zxcvbn-async',
1200          '_zxcvbnSettings',
1201          array(
1202              'src' => empty( $guessed_url ) ? includes_url( '/js/zxcvbn.min.js' ) : $scripts->base_url . '/wp-includes/js/zxcvbn.min.js',
1203          )
1204      );
1205  
1206      $scripts->add( 'password-strength-meter', "/wp-admin/js/password-strength-meter$suffix.js", array( 'jquery', 'zxcvbn-async' ), false, 1 );
1207      did_action( 'init' ) && $scripts->localize(
1208          'password-strength-meter',
1209          'pwsL10n',
1210          array(
1211              'unknown'  => _x( 'Password strength unknown', 'password strength' ),
1212              'short'    => _x( 'Very weak', 'password strength' ),
1213              'bad'      => _x( 'Weak', 'password strength' ),
1214              'good'     => _x( 'Medium', 'password strength' ),
1215              'strong'   => _x( 'Strong', 'password strength' ),
1216              'mismatch' => _x( 'Mismatch', 'password mismatch' ),
1217          )
1218      );
1219      $scripts->set_translations( 'password-strength-meter' );
1220  
1221      $scripts->add( 'password-toggle', "/wp-admin/js/password-toggle$suffix.js", array(), false, 1 );
1222      $scripts->set_translations( 'password-toggle' );
1223  
1224      $scripts->add( 'application-passwords', "/wp-admin/js/application-passwords$suffix.js", array( 'jquery', 'wp-util', 'wp-api-request', 'wp-date', 'wp-i18n', 'wp-hooks' ), false, 1 );
1225      $scripts->set_translations( 'application-passwords' );
1226  
1227      $scripts->add( 'auth-app', "/wp-admin/js/auth-app$suffix.js", array( 'jquery', 'wp-api-request', 'wp-i18n', 'wp-hooks' ), false, 1 );
1228      $scripts->set_translations( 'auth-app' );
1229  
1230      $scripts->add( 'user-profile', "/wp-admin/js/user-profile$suffix.js", array( 'clipboard', 'jquery', 'password-strength-meter', 'wp-util', 'wp-a11y' ), false, 1 );
1231      $scripts->set_translations( 'user-profile' );
1232      $user_id = isset( $_GET['user_id'] ) ? (int) $_GET['user_id'] : 0;
1233      did_action( 'init' ) && $scripts->localize(
1234          'user-profile',
1235          'userProfileL10n',
1236          array(
1237              'user_id' => $user_id,
1238              'nonce'   => wp_installing() ? '' : wp_create_nonce( 'reset-password-for-' . $user_id ),
1239          )
1240      );
1241  
1242      $scripts->add( 'language-chooser', "/wp-admin/js/language-chooser$suffix.js", array( 'jquery' ), false, 1 );
1243  
1244      $scripts->add( 'user-suggest', "/wp-admin/js/user-suggest$suffix.js", array( 'jquery-ui-autocomplete' ), false, 1 );
1245  
1246      $scripts->add( 'admin-bar', "/wp-includes/js/admin-bar$suffix.js", array( 'hoverintent-js' ), false, 1 );
1247  
1248      $scripts->add( 'wplink', "/wp-includes/js/wplink$suffix.js", array( 'common', 'jquery', 'wp-a11y', 'wp-i18n' ), false, 1 );
1249      $scripts->set_translations( 'wplink' );
1250      did_action( 'init' ) && $scripts->localize(
1251          'wplink',
1252          'wpLinkL10n',
1253          array(
1254              'title'          => __( 'Insert/edit link' ),
1255              'update'         => __( 'Update' ),
1256              'save'           => __( 'Add Link' ),
1257              'noTitle'        => __( '(no title)' ),
1258              'noMatchesFound' => __( 'No results found.' ),
1259              'linkSelected'   => __( 'Link selected.' ),
1260              'linkInserted'   => __( 'Link inserted.' ),
1261              /* translators: Minimum input length in characters to start searching posts in the "Insert/edit link" modal. */
1262              'minInputLength' => (int) _x( '3', 'minimum input length for searching post links' ),
1263          )
1264      );
1265  
1266      $scripts->add( 'wpdialogs', "/wp-includes/js/wpdialog$suffix.js", array( 'jquery-ui-dialog' ), false, 1 );
1267  
1268      $scripts->add( 'word-count', "/wp-admin/js/word-count$suffix.js", array(), false, 1 );
1269  
1270      $scripts->add( 'media-upload', "/wp-admin/js/media-upload$suffix.js", array( 'thickbox', 'shortcode' ), false, 1 );
1271  
1272      $scripts->add( 'hoverIntent', "/wp-includes/js/hoverIntent$suffix.js", array( 'jquery' ), '1.10.2', 1 );
1273  
1274      // JS-only version of hoverintent (no dependencies).
1275      $scripts->add( 'hoverintent-js', '/wp-includes/js/hoverintent-js.min.js', array(), '2.2.1', 1 );
1276  
1277      $scripts->add( 'customize-base', "/wp-includes/js/customize-base$suffix.js", array( 'jquery', 'underscore' ), false, 1 );
1278      $scripts->add( 'customize-loader', "/wp-includes/js/customize-loader$suffix.js", array( 'customize-base' ), false, 1 );
1279      $scripts->add( 'customize-preview', "/wp-includes/js/customize-preview$suffix.js", array( 'wp-a11y', 'customize-base' ), false, 1 );
1280      $scripts->add( 'customize-models', '/wp-includes/js/customize-models.js', array( 'underscore', 'backbone' ), false, 1 );
1281      $scripts->add( 'customize-views', '/wp-includes/js/customize-views.js', array( 'jquery', 'underscore', 'imgareaselect', 'customize-models', 'media-editor', 'media-views' ), false, 1 );
1282      $scripts->add( 'customize-controls', "/wp-admin/js/customize-controls$suffix.js", array( 'customize-base', 'wp-a11y', 'wp-util', 'jquery-ui-core' ), false, 1 );
1283      did_action( 'init' ) && $scripts->localize(
1284          'customize-controls',
1285          '_wpCustomizeControlsL10n',
1286          array(
1287              'activate'                => __( 'Activate &amp; Publish' ),
1288              'save'                    => __( 'Save &amp; Publish' ), // @todo Remove as not required.
1289              'publish'                 => __( 'Publish' ),
1290              'published'               => __( 'Published' ),
1291              'saveDraft'               => __( 'Save Draft' ),
1292              'draftSaved'              => __( 'Draft Saved' ),
1293              'updating'                => __( 'Updating' ),
1294              'schedule'                => _x( 'Schedule', 'customizer changeset action/button label' ),
1295              'scheduled'               => _x( 'Scheduled', 'customizer changeset status' ),
1296              'invalid'                 => __( 'Invalid' ),
1297              'saveBeforeShare'         => __( 'Please save your changes in order to share the preview.' ),
1298              'futureDateError'         => __( 'You must supply a future date to schedule.' ),
1299              'saveAlert'               => __( 'The changes you made will be lost if you navigate away from this page.' ),
1300              'saved'                   => __( 'Saved' ),
1301              'cancel'                  => __( 'Cancel' ),
1302              'close'                   => __( 'Close' ),
1303              'action'                  => __( 'Action' ),
1304              'discardChanges'          => __( 'Discard changes' ),
1305              'cheatin'                 => __( 'An error occurred. Please try again later.' ),
1306              'notAllowedHeading'       => __( 'You need a higher level of permission.' ),
1307              'notAllowed'              => __( 'Sorry, you are not allowed to customize this site.' ),
1308              'previewIframeTitle'      => __( 'Site Preview' ),
1309              'loginIframeTitle'        => __( 'Session expired' ),
1310              'collapseSidebar'         => _x( 'Hide Controls', 'label for hide controls button without length constraints' ),
1311              'expandSidebar'           => _x( 'Show Controls', 'label for hide controls button without length constraints' ),
1312              'untitledBlogName'        => __( '(Untitled)' ),
1313              'unknownRequestFail'      => __( 'Looks like something&#8217;s gone wrong. Wait a couple seconds, and then try again.' ),
1314              'themeDownloading'        => __( 'Downloading your new theme&hellip;' ),
1315              'themePreviewWait'        => __( 'Setting up your live preview. This may take a bit.' ),
1316              'revertingChanges'        => __( 'Reverting unpublished changes&hellip;' ),
1317              'trashConfirm'            => __( 'Are you sure you want to discard your unpublished changes?' ),
1318              /* translators: %s: Display name of the user who has taken over the changeset in customizer. */
1319              'takenOverMessage'        => __( '%s has taken over and is currently customizing.' ),
1320              /* translators: %s: URL to the Customizer to load the autosaved version. */
1321              'autosaveNotice'          => __( 'There is a more recent autosave of your changes than the one you are previewing. <a href="%s">Restore the autosave</a>' ),
1322              'videoHeaderNotice'       => __( 'This theme does not support video headers on this page. Navigate to the front page or another page that supports video headers.' ),
1323              // Used for overriding the file types allowed in Plupload.
1324              'allowedFiles'            => __( 'Allowed Files' ),
1325              'customCssError'          => array(
1326                  /* translators: %d: Error count. */
1327                  'singular' => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 1 ),
1328                  /* translators: %d: Error count. */
1329                  'plural'   => _n( 'There is %d error which must be fixed before you can save.', 'There are %d errors which must be fixed before you can save.', 2 ),
1330                  // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491.
1331              ),
1332              'pageOnFrontError'        => __( 'Homepage and posts page must be different.' ),
1333              'saveBlockedError'        => array(
1334                  /* translators: %s: Number of invalid settings. */
1335                  'singular' => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 1 ),
1336                  /* translators: %s: Number of invalid settings. */
1337                  'plural'   => _n( 'Unable to save due to %s invalid setting.', 'Unable to save due to %s invalid settings.', 2 ),
1338                  // @todo This is lacking, as some languages have a dedicated dual form. For proper handling of plurals in JS, see #20491.
1339              ),
1340              'scheduleDescription'     => __( 'Schedule your customization changes to publish ("go live") at a future date.' ),
1341              'themePreviewUnavailable' => __( 'Sorry, you cannot preview new themes when you have changes scheduled or saved as a draft. Please publish your changes, or wait until they publish to preview new themes.' ),
1342              'themeInstallUnavailable' => sprintf(
1343                  /* translators: %s: URL to Add Themes admin screen. */
1344                  __( 'You will not be able to install new themes from here yet since your install requires SFTP credentials. For now, please <a href="%s">add themes in the admin</a>.' ),
1345                  esc_url( admin_url( 'theme-install.php' ) )
1346              ),
1347              'publishSettings'         => __( 'Publish Settings' ),
1348              'invalidDate'             => __( 'Invalid date.' ),
1349              'invalidValue'            => __( 'Invalid value.' ),
1350              'blockThemeNotification'  => sprintf(
1351                  /* translators: 1: Link to Site Editor documentation on HelpHub, 2: HTML button. */
1352                  __( 'Hurray! Your theme supports site editing with blocks. <a href="%1$s">Tell me more</a>. %2$s' ),
1353                  __( 'https://wordpress.org/documentation/article/site-editor/' ),
1354                  sprintf(
1355                      '<button type="button" data-action="%1$s" class="button switch-to-editor">%2$s</button>',
1356                      esc_url( admin_url( 'site-editor.php' ) ),
1357                      __( 'Use Site Editor' )
1358                  )
1359              ),
1360          )
1361      );
1362      $scripts->add( 'customize-selective-refresh', "/wp-includes/js/customize-selective-refresh$suffix.js", array( 'jquery', 'wp-util', 'customize-preview' ), false, 1 );
1363  
1364      $scripts->add( 'customize-widgets', "/wp-admin/js/customize-widgets$suffix.js", array( 'jquery', 'jquery-ui-sortable', 'jquery-ui-droppable', 'wp-backbone', 'customize-controls' ), false, 1 );
1365      $scripts->add( 'customize-preview-widgets', "/wp-includes/js/customize-preview-widgets$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
1366  
1367      $scripts->add( 'customize-nav-menus', "/wp-admin/js/customize-nav-menus$suffix.js", array( 'jquery', 'wp-backbone', 'customize-controls', 'accordion', 'nav-menu', 'wp-sanitize' ), false, 1 );
1368      $scripts->add( 'customize-preview-nav-menus', "/wp-includes/js/customize-preview-nav-menus$suffix.js", array( 'jquery', 'wp-util', 'customize-preview', 'customize-selective-refresh' ), false, 1 );
1369  
1370      $scripts->add( 'wp-custom-header', "/wp-includes/js/wp-custom-header$suffix.js", array( 'wp-a11y' ), false, 1 );
1371  
1372      $scripts->add( 'accordion', "/wp-admin/js/accordion$suffix.js", array( 'jquery' ), false, 1 );
1373  
1374      $scripts->add( 'shortcode', "/wp-includes/js/shortcode$suffix.js", array( 'underscore' ), false, 1 );
1375      $scripts->add( 'media-models', "/wp-includes/js/media-models$suffix.js", array( 'wp-backbone' ), false, 1 );
1376      did_action( 'init' ) && $scripts->localize(
1377          'media-models',
1378          '_wpMediaModelsL10n',
1379          array(
1380              'settings' => array(
1381                  'ajaxurl' => admin_url( 'admin-ajax.php', 'relative' ),
1382                  'post'    => array( 'id' => 0 ),
1383              ),
1384          )
1385      );
1386  
1387      $scripts->add( 'wp-embed', "/wp-includes/js/wp-embed$suffix.js" );
1388      did_action( 'init' ) && $scripts->add_data( 'wp-embed', 'strategy', 'defer' );
1389  
1390      /*
1391       * To enqueue media-views or media-editor, call wp_enqueue_media().
1392       * Both rely on numerous settings, styles, and templates to operate correctly.
1393       */
1394      $scripts->add( 'media-views', "/wp-includes/js/media-views$suffix.js", array( 'utils', 'media-models', 'wp-plupload', 'jquery-ui-sortable', 'wp-mediaelement', 'wp-api-request', 'wp-a11y', 'clipboard' ), false, 1 );
1395      $scripts->set_translations( 'media-views' );
1396  
1397      $scripts->add( 'media-editor', "/wp-includes/js/media-editor$suffix.js", array( 'shortcode', 'media-views' ), false, 1 );
1398      $scripts->set_translations( 'media-editor' );
1399      $scripts->add( 'media-audiovideo', "/wp-includes/js/media-audiovideo$suffix.js", array( 'media-editor' ), false, 1 );
1400      $scripts->add( 'mce-view', "/wp-includes/js/mce-view$suffix.js", array( 'shortcode', 'jquery', 'media-views', 'media-audiovideo' ), false, 1 );
1401  
1402      $scripts->add( 'wp-api', "/wp-includes/js/wp-api$suffix.js", array( 'jquery', 'backbone', 'underscore', 'wp-api-request' ), false, 1 );
1403  
1404      if ( is_admin() ) {
1405          $scripts->add( 'admin-tags', "/wp-admin/js/tags$suffix.js", array( 'jquery', 'wp-ajax-response' ), false, 1 );
1406          $scripts->set_translations( 'admin-tags' );
1407  
1408          $scripts->add( 'admin-comments', "/wp-admin/js/edit-comments$suffix.js", array( 'wp-lists', 'quicktags', 'jquery-query', 'wp-a11y' ), false, 1 );
1409          $scripts->set_translations( 'admin-comments' );
1410          did_action( 'init' ) && $scripts->localize(
1411              'admin-comments',
1412              'adminCommentsSettings',
1413              array(
1414                  'hotkeys_highlight_first' => isset( $_GET['hotkeys_highlight_first'] ),
1415                  'hotkeys_highlight_last'  => isset( $_GET['hotkeys_highlight_last'] ),
1416              )
1417          );
1418  
1419          $scripts->add( 'xfn', "/wp-admin/js/xfn$suffix.js", array( 'jquery' ), false, 1 );
1420  
1421          $scripts->add( 'postbox', "/wp-admin/js/postbox$suffix.js", array( 'jquery-ui-sortable', 'wp-a11y', 'wp-tooltip' ), false, 1 );
1422          $scripts->set_translations( 'postbox' );
1423  
1424          $scripts->add( 'tags-box', "/wp-admin/js/tags-box$suffix.js", array( 'jquery', 'tags-suggest' ), false, 1 );
1425          $scripts->set_translations( 'tags-box' );
1426  
1427          $scripts->add( 'tags-suggest', "/wp-admin/js/tags-suggest$suffix.js", array( 'common', 'jquery-ui-autocomplete', 'wp-a11y', 'wp-i18n' ), false, 1 );
1428          $scripts->set_translations( 'tags-suggest' );
1429  
1430          $scripts->add( 'post', "/wp-admin/js/post$suffix.js", array( 'suggest', 'wp-lists', 'postbox', 'tags-box', 'underscore', 'word-count', 'wp-a11y', 'wp-sanitize', 'clipboard' ), false, 1 );
1431          $scripts->set_translations( 'post' );
1432  
1433          $scripts->add( 'editor-expand', "/wp-admin/js/editor-expand$suffix.js", array( 'jquery', 'underscore' ), false, 1 );
1434  
1435          $scripts->add( 'link', "/wp-admin/js/link$suffix.js", array( 'wp-lists', 'postbox' ), false, 1 );
1436  
1437          $scripts->add( 'comment', "/wp-admin/js/comment$suffix.js", array( 'jquery', 'postbox' ), false, 1 );
1438          $scripts->set_translations( 'comment' );
1439  
1440          $scripts->add( 'admin-gallery', "/wp-admin/js/gallery$suffix.js", array( 'jquery-ui-sortable' ) );
1441  
1442          $scripts->add( 'admin-widgets', "/wp-admin/js/widgets$suffix.js", array( 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable', 'wp-a11y' ), false, 1 );
1443          $scripts->set_translations( 'admin-widgets' );
1444  
1445          $scripts->add( 'media-widgets', "/wp-admin/js/widgets/media-widgets$suffix.js", array( 'jquery', 'media-models', 'media-views', 'wp-api-request' ) );
1446          $scripts->add_inline_script( 'media-widgets', 'wp.mediaWidgets.init();', 'after' );
1447  
1448          $scripts->add( 'media-audio-widget', "/wp-admin/js/widgets/media-audio-widget$suffix.js", array( 'media-widgets', 'media-audiovideo' ) );
1449          $scripts->add( 'media-image-widget', "/wp-admin/js/widgets/media-image-widget$suffix.js", array( 'media-widgets' ) );
1450          $scripts->add( 'media-gallery-widget', "/wp-admin/js/widgets/media-gallery-widget$suffix.js", array( 'media-widgets' ) );
1451          $scripts->add( 'media-video-widget', "/wp-admin/js/widgets/media-video-widget$suffix.js", array( 'media-widgets', 'media-audiovideo', 'wp-api-request' ) );
1452          $scripts->add( 'text-widgets', "/wp-admin/js/widgets/text-widgets$suffix.js", array( 'jquery', 'backbone', 'editor', 'wp-util', 'wp-a11y' ) );
1453          $scripts->add( 'custom-html-widgets', "/wp-admin/js/widgets/custom-html-widgets$suffix.js", array( 'jquery', 'backbone', 'wp-util', 'jquery-ui-core', 'wp-a11y' ) );
1454  
1455          $scripts->add( 'theme', "/wp-admin/js/theme$suffix.js", array( 'wp-backbone', 'wp-a11y', 'customize-base' ), false, 1 );
1456  
1457          $scripts->add( 'inline-edit-post', "/wp-admin/js/inline-edit-post$suffix.js", array( 'jquery', 'tags-suggest', 'wp-a11y' ), false, 1 );
1458          $scripts->set_translations( 'inline-edit-post' );
1459  
1460          $scripts->add( 'inline-edit-tax', "/wp-admin/js/inline-edit-tax$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 );
1461          $scripts->set_translations( 'inline-edit-tax' );
1462  
1463          $scripts->add( 'plugin-install', "/wp-admin/js/plugin-install$suffix.js", array( 'jquery', 'jquery-ui-core', 'thickbox' ), false, 1 );
1464          $scripts->set_translations( 'plugin-install' );
1465  
1466          $scripts->add( 'site-health', "/wp-admin/js/site-health$suffix.js", array( 'clipboard', 'jquery', 'wp-util', 'wp-a11y', 'wp-api-request', 'wp-url', 'wp-i18n', 'wp-hooks' ), false, 1 );
1467          $scripts->set_translations( 'site-health' );
1468  
1469          $scripts->add( 'privacy-tools', "/wp-admin/js/privacy-tools$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 );
1470          $scripts->set_translations( 'privacy-tools' );
1471  
1472          $scripts->add( 'updates', "/wp-admin/js/updates$suffix.js", array( 'common', 'jquery', 'wp-util', 'wp-a11y', 'wp-sanitize', 'wp-i18n' ), false, 1 );
1473          $scripts->set_translations( 'updates' );
1474          did_action( 'init' ) && $scripts->localize(
1475              'updates',
1476              '_wpUpdatesSettings',
1477              array(
1478                  'ajax_nonce' => wp_installing() ? '' : wp_create_nonce( 'updates' ),
1479              )
1480          );
1481  
1482          $scripts->add( 'farbtastic', '/wp-admin/js/farbtastic.js', array( 'jquery' ), '1.2' );
1483  
1484          $scripts->add( 'iris', '/wp-admin/js/iris.min.js', array( 'jquery-ui-draggable', 'jquery-ui-slider', 'jquery-touch-punch' ), '1.1.1', 1 );
1485          $scripts->add( 'wp-color-picker', "/wp-admin/js/color-picker$suffix.js", array( 'iris' ), false, 1 );
1486          $scripts->set_translations( 'wp-color-picker' );
1487  
1488          $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'common', 'jquery', 'admin-comments', 'postbox', 'wp-util', 'wp-a11y', 'wp-date' ), false, 1 );
1489          $scripts->set_translations( 'dashboard' );
1490  
1491          // Deprecated, no longer used in core, registered for backward compatibility.
1492          $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" );
1493  
1494          $scripts->add( 'media-grid', "/wp-includes/js/media-grid$suffix.js", array( 'media-editor' ), false, 1 );
1495          $scripts->add( 'media', "/wp-admin/js/media$suffix.js", array( 'jquery', 'clipboard', 'wp-i18n', 'wp-a11y' ), false, 1 );
1496          $scripts->set_translations( 'media' );
1497  
1498          $scripts->add( 'media-upload-pipeline', "/wp-admin/js/media-upload-pipeline$suffix.js", array( 'plupload', 'wp-upload-media', 'wp-media-utils', 'wp-api-fetch', 'wp-data', 'wp-element', 'wp-i18n' ), false, 1 );
1499          $scripts->set_translations( 'media-upload-pipeline' );
1500  
1501          $scripts->add( 'media-frame-upload', "/wp-admin/js/media-frame-upload$suffix.js", array( 'media-views', 'media-upload-pipeline' ), false, 1 );
1502  
1503          $scripts->add( 'media-new-upload', "/wp-admin/js/media-new-upload$suffix.js", array( 'plupload-handlers', 'media-upload-pipeline', 'wp-a11y', 'wp-i18n' ), false, 1 );
1504          $scripts->set_translations( 'media-new-upload' );
1505  
1506          $scripts->add( 'image-edit', "/wp-admin/js/image-edit$suffix.js", array( 'jquery', 'jquery-ui-core', 'imgareaselect', 'wp-a11y' ), false, 1 );
1507          $scripts->set_translations( 'image-edit' );
1508  
1509          $scripts->add( 'set-post-thumbnail', "/wp-admin/js/set-post-thumbnail$suffix.js", array( 'jquery' ), false, 1 );
1510          $scripts->set_translations( 'set-post-thumbnail' );
1511  
1512          /*
1513           * Navigation Menus: Adding underscore as a dependency to utilize _.debounce
1514           * see https://core.trac.wordpress.org/ticket/42321
1515           */
1516          $scripts->add( 'nav-menu', "/wp-admin/js/nav-menu$suffix.js", array( 'jquery-ui-sortable', 'jquery-ui-draggable', 'jquery-ui-droppable', 'wp-lists', 'postbox', 'underscore' ) );
1517          $scripts->set_translations( 'nav-menu' );
1518  
1519          $scripts->add( 'custom-header', '/wp-admin/js/custom-header.js', array( 'jquery-masonry' ), false, 1 );
1520          $scripts->add( 'custom-background', "/wp-admin/js/custom-background$suffix.js", array( 'wp-color-picker', 'media-views' ), false, 1 );
1521          $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array( 'jquery' ), false, 1 );
1522  
1523          $scripts->add( 'svg-painter', '/wp-admin/js/svg-painter.js', array( 'jquery' ), false, 1 );
1524      }
1525  }
1526  
1527  /**
1528   * Assigns default styles to $styles object.
1529   *
1530   * Nothing is returned, because the $styles parameter is passed by reference.
1531   * Meaning that whatever object is passed will be updated without having to
1532   * reassign the variable that was passed back to the same value. This saves
1533   * memory.
1534   *
1535   * Adding default styles is not the only task, it also assigns the base_url
1536   * property, the default version, and text direction for the object.
1537   *
1538   * @since 2.6.0
1539   *
1540   * @global array $editor_styles
1541   *
1542   * @param WP_Styles $styles
1543   */
1544  function wp_default_styles( $styles ) {
1545      global $editor_styles;
1546  
1547      $guessurl = site_url();
1548  
1549      if ( ! $guessurl ) {
1550          $guessurl = wp_guess_url();
1551      }
1552  
1553      $styles->base_url        = $guessurl;
1554      $styles->content_url     = defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL : '';
1555      $styles->default_version = get_bloginfo( 'version' );
1556      $styles->text_direction  = is_rtl() ? 'rtl' : 'ltr';
1557      $styles->default_dirs    = array( '/wp-admin/', '/wp-includes/css/' ); // Deprecated, but still set for plugins that read it.
1558  
1559      // Open Sans is no longer used by core, but may be relied upon by themes and plugins.
1560      $open_sans_font_url = '';
1561  
1562      /*
1563       * translators: If there are characters in your language that are not supported
1564       * by Open Sans, translate this to 'off'. Do not translate into your own language.
1565       */
1566      if ( 'off' !== _x( 'on', 'Open Sans font: on or off' ) ) {
1567          $subsets = 'latin,latin-ext';
1568  
1569          /*
1570           * translators: To add an additional Open Sans character subset specific to your language,
1571           * translate this to 'greek', 'cyrillic' or 'vietnamese'. Do not translate into your own language.
1572           */
1573          $subset = _x( 'no-subset', 'Open Sans font: add new subset (greek, cyrillic, vietnamese)' );
1574  
1575          if ( 'cyrillic' === $subset ) {
1576              $subsets .= ',cyrillic,cyrillic-ext';
1577          } elseif ( 'greek' === $subset ) {
1578              $subsets .= ',greek,greek-ext';
1579          } elseif ( 'vietnamese' === $subset ) {
1580              $subsets .= ',vietnamese';
1581          }
1582  
1583          // Hotlink Open Sans, for now.
1584          $open_sans_font_url = "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=$subsets&display=fallback";
1585      }
1586  
1587      // Register a stylesheet for the selected admin color scheme.
1588      $styles->add( 'colors', true, array( 'wp-admin', 'buttons' ) );
1589  
1590      $suffix = wp_scripts_get_suffix();
1591  
1592      // Admin CSS.
1593      $styles->add( 'wp-tooltip', "/wp-admin/css/wp-tooltip$suffix.css", array( 'dashicons' ) );
1594      $styles->add( 'common', "/wp-admin/css/common$suffix.css" );
1595      $styles->add( 'forms', "/wp-admin/css/forms$suffix.css" );
1596      $styles->add( 'admin-menu', "/wp-admin/css/admin-menu$suffix.css" );
1597      $styles->add( 'dashboard', "/wp-admin/css/dashboard$suffix.css" );
1598      $styles->add( 'list-tables', "/wp-admin/css/list-tables$suffix.css" );
1599      $styles->add( 'edit', "/wp-admin/css/edit$suffix.css" );
1600      $styles->add( 'revisions', "/wp-admin/css/revisions$suffix.css" );
1601      $styles->add( 'media', "/wp-admin/css/media$suffix.css" );
1602      $styles->add( 'themes', "/wp-admin/css/themes$suffix.css" );
1603      $styles->add( 'about', "/wp-admin/css/about$suffix.css" );
1604      $styles->add( 'nav-menus', "/wp-admin/css/nav-menus$suffix.css" );
1605      $styles->add( 'widgets', "/wp-admin/css/widgets$suffix.css", array( 'wp-pointer' ) );
1606      $styles->add( 'site-icon', "/wp-admin/css/site-icon$suffix.css" );
1607      $styles->add( 'l10n', "/wp-admin/css/l10n$suffix.css" );
1608      $styles->add( 'code-editor', "/wp-admin/css/code-editor$suffix.css", array( 'wp-codemirror' ) );
1609      $styles->add( 'site-health', "/wp-admin/css/site-health$suffix.css" );
1610  
1611      $styles->add( 'wp-admin', false, array( 'dashicons', 'common', 'forms', 'admin-menu', 'dashboard', 'list-tables', 'edit', 'revisions', 'media', 'themes', 'about', 'nav-menus', 'widgets', 'site-icon', 'l10n', 'wp-base-styles', 'wp-tooltip' ) );
1612  
1613      $styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles', 'wp-tooltip' ) );
1614      $styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) );
1615      $styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" );
1616      $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'imgareaselect' ) );
1617      $styles->add( 'customize-widgets', "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) );
1618      $styles->add( 'customize-nav-menus', "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) );
1619  
1620      // Common dependencies.
1621      $styles->add( 'buttons', "/wp-includes/css/buttons$suffix.css" );
1622      $styles->add( 'dashicons', "/wp-includes/css/dashicons$suffix.css" );
1623  
1624      // Includes CSS.
1625      $styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'dashicons' ) );
1626      $styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) );
1627      $styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) );
1628      $styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) );
1629      $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) );
1630      $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) );
1631      $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" );
1632      $skip_link_style_path = WPINC . "/css/wp-block-template-skip-link$suffix.css";
1633      $styles->add( 'wp-block-template-skip-link', "/$skip_link_style_path" );
1634      $styles->add_data( 'wp-block-template-skip-link', 'path', ABSPATH . $skip_link_style_path );
1635  
1636      // External libraries and friends.
1637      $styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' );
1638      $styles->add( 'wp-jquery-ui-dialog', "/wp-includes/css/jquery-ui-dialog$suffix.css", array( 'dashicons' ) );
1639      $styles->add( 'mediaelement', '/wp-includes/js/mediaelement/mediaelementplayer-legacy.min.css', array(), '4.2.17' );
1640      $styles->add( 'wp-mediaelement', "/wp-includes/js/mediaelement/wp-mediaelement$suffix.css", array( 'mediaelement' ) );
1641      $styles->add( 'thickbox', '/wp-includes/js/thickbox/thickbox.css', array( 'dashicons' ) );
1642      $styles->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.css', array(), '5.65.20' );
1643  
1644      // Deprecated CSS.
1645      $styles->add( 'deprecated-media', "/wp-admin/css/deprecated-media$suffix.css" );
1646      $styles->add( 'farbtastic', "/wp-admin/css/farbtastic$suffix.css", array(), '1.3u1' );
1647      $styles->add( 'jcrop', '/wp-includes/js/jcrop/jquery.Jcrop.min.css', array(), '0.9.15' );
1648      $styles->add( 'colors-fresh', false, array( 'wp-admin', 'buttons' ) ); // Old handle.
1649      $styles->add( 'open-sans', $open_sans_font_url ); // No longer used in core as of 4.6.
1650      $styles->add( 'wp-embed-template-ie', false );
1651      $styles->add_data( 'wp-embed-template-ie', 'conditional', '_required-conditional-dependency_' );
1652  
1653      // Noto Serif is no longer used by core, but may be relied upon by themes and plugins.
1654      $fonts_url = '';
1655  
1656      /*
1657       * translators: Use this to specify the proper Google Font name and variants
1658       * to load that is supported by your language. Do not translate.
1659       * Set to 'off' to disable loading.
1660       */
1661      $font_family = _x( 'Noto Serif:400,400i,700,700i', 'Google Font Name and Variants' );
1662      if ( 'off' !== $font_family ) {
1663          $fonts_url = 'https://fonts.googleapis.com/css?family=' . urlencode( $font_family );
1664      }
1665      $styles->add( 'wp-editor-font', $fonts_url ); // No longer used in core as of 5.7.
1666      $block_library_theme_path = WPINC . "/css/dist/block-library/theme$suffix.css";
1667      $styles->add( 'wp-block-library-theme', "/$block_library_theme_path" );
1668      $styles->add_data( 'wp-block-library-theme', 'path', ABSPATH . $block_library_theme_path );
1669  
1670      $classic_theme_styles_path = WPINC . "/css/classic-themes$suffix.css";
1671      $styles->add( 'classic-theme-styles', "/$classic_theme_styles_path" );
1672      $styles->add_data( 'classic-theme-styles', 'path', ABSPATH . $classic_theme_styles_path );
1673  
1674      $styles->add(
1675          'wp-reset-editor-styles',
1676          "/wp-includes/css/dist/block-library/reset$suffix.css",
1677          array( 'common', 'forms' ) // Make sure the reset is loaded after the default WP Admin styles.
1678      );
1679  
1680      $styles->add(
1681          'wp-editor-classic-layout-styles',
1682          "/wp-includes/css/dist/edit-post/classic$suffix.css",
1683          array()
1684      );
1685  
1686      $styles->add(
1687          'wp-block-editor-content',
1688          "/wp-includes/css/dist/block-editor/content$suffix.css",
1689          array( 'wp-components' )
1690      );
1691  
1692      // Only add CONTENT styles here that should be enqueued in the iframe!
1693      $wp_edit_blocks_dependencies = array(
1694          'wp-theme',
1695          'wp-base-styles',
1696          'wp-components',
1697          /*
1698           * This needs to be added before the block library styles,
1699           * The block library styles override the "reset" styles.
1700           */
1701          'wp-reset-editor-styles',
1702          'wp-block-library',
1703          'wp-block-editor-content',
1704      );
1705  
1706      // Only load the default layout and margin styles for themes without theme.json file.
1707      if ( ! wp_theme_has_theme_json() ) {
1708          $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles';
1709      }
1710  
1711      if (
1712          current_theme_supports( 'wp-block-styles' ) &&
1713          ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 )
1714      ) {
1715          /*
1716           * Include opinionated block styles if the theme supports block styles and
1717           * no $editor_styles are declared, so the editor never appears broken.
1718           */
1719          $wp_edit_blocks_dependencies[] = 'wp-block-library-theme';
1720      }
1721  
1722      $styles->add(
1723          'wp-edit-blocks',
1724          "/wp-includes/css/dist/block-library/editor$suffix.css",
1725          $wp_edit_blocks_dependencies
1726      );
1727  
1728      $styles->add( 'wp-view-transitions-admin', false );
1729      did_action( 'init' ) && $styles->add_inline_style( 'wp-view-transitions-admin', wp_get_view_transitions_admin_css() );
1730  
1731      $package_styles = array(
1732          'block-editor'         => array( 'wp-components', 'wp-preferences' ),
1733          'block-library'        => array(),
1734          'block-directory'      => array(),
1735          'theme'                => array(),
1736          'base-styles'          => array(),
1737          'components'           => array( 'wp-theme' ),
1738          'commands'             => array( 'wp-components' ),
1739          'edit-post'            => array(
1740              'wp-components',
1741              'wp-block-editor',
1742              'wp-editor',
1743              'wp-edit-blocks',
1744              'wp-block-library',
1745              'wp-commands',
1746              'wp-preferences',
1747          ),
1748          'editor'               => array(
1749              'wp-components',
1750              'wp-block-editor',
1751              'wp-reusable-blocks',
1752              'wp-patterns',
1753              'wp-preferences',
1754              'wp-media-utils',
1755          ),
1756          'format-library'       => array(),
1757          'list-reusable-blocks' => array( 'wp-components' ),
1758          'media-utils'          => array( 'wp-components' ),
1759          'reusable-blocks'      => array( 'wp-components' ),
1760          'patterns'             => array( 'wp-components' ),
1761          'preferences'          => array( 'wp-components' ),
1762          'nux'                  => array( 'wp-components' ),
1763          'widgets'              => array(
1764              'wp-components',
1765          ),
1766          'edit-widgets'         => array(
1767              'wp-widgets',
1768              'wp-block-editor',
1769              'wp-editor',
1770              'wp-edit-blocks',
1771              'wp-block-library',
1772              'wp-patterns',
1773              'wp-preferences',
1774          ),
1775          'customize-widgets'    => array(
1776              'wp-widgets',
1777              'wp-block-editor',
1778              'wp-editor',
1779              'wp-edit-blocks',
1780              'wp-block-library',
1781              'wp-patterns',
1782              'wp-preferences',
1783          ),
1784          'edit-site'            => array(
1785              'wp-components',
1786              'wp-block-editor',
1787              'wp-editor',
1788              'wp-edit-blocks',
1789              'wp-commands',
1790              'wp-preferences',
1791          ),
1792      );
1793  
1794      foreach ( $package_styles as $package => $dependencies ) {
1795          $handle = 'wp-' . $package;
1796          $path   = "/wp-includes/css/dist/$package/style$suffix.css";
1797  
1798          if ( 'block-library' === $package && wp_should_load_separate_core_block_assets() ) {
1799              $path = "/wp-includes/css/dist/$package/common$suffix.css";
1800          }
1801  
1802          if ( 'base-styles' === $package ) {
1803              $path = "/wp-includes/css/dist/base-styles/admin-schemes$suffix.css";
1804          }
1805  
1806          if ( 'theme' === $package ) {
1807              $path = "/wp-includes/css/dist/theme/design-tokens$suffix.css";
1808          }
1809  
1810          $styles->add( $handle, $path, $dependencies );
1811          $styles->add_data( $handle, 'path', ABSPATH . $path );
1812      }
1813  
1814      // RTL CSS.
1815      $rtl_styles = array(
1816          // Admin CSS.
1817          'common',
1818          'forms',
1819          'admin-menu',
1820          'dashboard',
1821          'list-tables',
1822          'edit',
1823          'revisions',
1824          'media',
1825          'themes',
1826          'about',
1827          'nav-menus',
1828          'widgets',
1829          'site-icon',
1830          'l10n',
1831          'install',
1832          'wp-color-picker',
1833          'customize-controls',
1834          'customize-widgets',
1835          'customize-nav-menus',
1836          'customize-preview',
1837          'login',
1838          'site-health',
1839          'wp-empty-template-alert',
1840          // Includes CSS.
1841          'buttons',
1842          'admin-bar',
1843          'wp-auth-check',
1844          'editor-buttons',
1845          'media-views',
1846          'wp-pointer',
1847          'wp-jquery-ui-dialog',
1848          'wp-block-template-skip-link',
1849          // Package styles.
1850          'wp-reset-editor-styles',
1851          'wp-editor-classic-layout-styles',
1852          'wp-block-library-theme',
1853          'wp-theme',
1854          'wp-edit-blocks',
1855          'wp-block-editor',
1856          'wp-block-library',
1857          'wp-block-directory',
1858          'wp-commands',
1859          'wp-components',
1860          'wp-customize-widgets',
1861          'wp-edit-post',
1862          'wp-edit-site',
1863          'wp-edit-widgets',
1864          'wp-editor',
1865          'wp-format-library',
1866          'wp-list-reusable-blocks',
1867          'wp-media-utils',
1868          'wp-reusable-blocks',
1869          'wp-patterns',
1870          'wp-nux',
1871          'wp-widgets',
1872          // Deprecated CSS.
1873          'deprecated-media',
1874          'farbtastic',
1875      );
1876  
1877      foreach ( $rtl_styles as $rtl_style ) {
1878          $styles->add_data( $rtl_style, 'rtl', 'replace' );
1879          if ( $suffix ) {
1880              $styles->add_data( $rtl_style, 'suffix', $suffix );
1881          }
1882      }
1883  }
1884  
1885  /**
1886   * Reorders JavaScript scripts array to place prototype before jQuery.
1887   *
1888   * @since 2.3.1
1889   *
1890   * @param string[] $js_array JavaScript scripts array
1891   * @return string[] Reordered array, if needed.
1892   */
1893  function wp_prototype_before_jquery( $js_array ) {
1894      $prototype = array_search( 'prototype', $js_array, true );
1895  
1896      if ( false === $prototype ) {
1897          return $js_array;
1898      }
1899  
1900      $jquery = array_search( 'jquery', $js_array, true );
1901  
1902      if ( false === $jquery ) {
1903          return $js_array;
1904      }
1905  
1906      if ( $prototype < $jquery ) {
1907          return $js_array;
1908      }
1909  
1910      unset( $js_array[ $prototype ] );
1911  
1912      array_splice( $js_array, $jquery, 0, 'prototype' );
1913  
1914      return $js_array;
1915  }
1916  
1917  /**
1918   * Loads localized data on print rather than initialization.
1919   *
1920   * These localizations require information that may not be loaded even by init.
1921   *
1922   * @since 2.5.0
1923   *
1924   * @global array $shortcode_tags
1925   */
1926  function wp_just_in_time_script_localization() {
1927  
1928      wp_localize_script(
1929          'autosave',
1930          'autosaveL10n',
1931          array(
1932              'autosaveInterval' => AUTOSAVE_INTERVAL,
1933              'blog_id'          => get_current_blog_id(),
1934          )
1935      );
1936  
1937      wp_localize_script(
1938          'mce-view',
1939          'mceViewL10n',
1940          array(
1941              'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(),
1942          )
1943      );
1944  
1945      wp_localize_script(
1946          'word-count',
1947          'wordCountL10n',
1948          array(
1949              'type'       => wp_get_word_count_type(),
1950              'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(),
1951          )
1952      );
1953  }
1954  
1955  /**
1956   * Localizes the jQuery UI datepicker.
1957   *
1958   * @since 4.6.0
1959   *
1960   * @link https://api.jqueryui.com/datepicker/#options
1961   *
1962   * @global WP_Locale $wp_locale WordPress date and time locale object.
1963   */
1964  function wp_localize_jquery_ui_datepicker() {
1965      global $wp_locale;
1966  
1967      if ( ! wp_script_is( 'jquery-ui-datepicker', 'enqueued' ) ) {
1968          return;
1969      }
1970  
1971      // Convert the PHP date format into jQuery UI's format.
1972      $datepicker_date_format = str_replace(
1973          array(
1974              'd',
1975              'j',
1976              'l',
1977              'z', // Day.
1978              'F',
1979              'M',
1980              'n',
1981              'm', // Month.
1982              'Y',
1983              'y', // Year.
1984          ),
1985          array(
1986              'dd',
1987              'd',
1988              'DD',
1989              'o',
1990              'MM',
1991              'M',
1992              'm',
1993              'mm',
1994              'yy',
1995              'y',
1996          ),
1997          get_option( 'date_format' )
1998      );
1999  
2000      $datepicker_defaults = wp_json_encode(
2001          array(
2002              'closeText'       => __( 'Close' ),
2003              'currentText'     => __( 'Today' ),
2004              'monthNames'      => array_values( $wp_locale->month ),
2005              'monthNamesShort' => array_values( $wp_locale->month_abbrev ),
2006              'nextText'        => _x( 'Next', 'datepicker: navigate to next month' ),
2007              'prevText'        => _x( 'Previous', 'datepicker: navigate to previous month' ),
2008              'dayNames'        => array_values( $wp_locale->weekday ),
2009              'dayNamesShort'   => array_values( $wp_locale->weekday_abbrev ),
2010              'dayNamesMin'     => array_values( $wp_locale->weekday_initial ),
2011              'dateFormat'      => $datepicker_date_format,
2012              'firstDay'        => absint( get_option( 'start_of_week' ) ),
2013              'isRTL'           => $wp_locale->is_rtl(),
2014          ),
2015          JSON_HEX_TAG | JSON_UNESCAPED_SLASHES
2016      );
2017  
2018      wp_add_inline_script( 'jquery-ui-datepicker', "jQuery(function(jQuery){jQuery.datepicker.setDefaults({$datepicker_defaults});});" );
2019  }
2020  
2021  /**
2022   * Localizes community events data that needs to be passed to dashboard.js.
2023   *
2024   * @since 4.8.0
2025   */
2026  function wp_localize_community_events() {
2027      if ( ! wp_script_is( 'dashboard' ) ) {
2028          return;
2029      }
2030  
2031      require_once  ABSPATH . 'wp-admin/includes/class-wp-community-events.php';
2032  
2033      $user_id            = get_current_user_id();
2034      $saved_location     = get_user_option( 'community-events-location', $user_id );
2035      $saved_ip_address   = $saved_location['ip'] ?? false;
2036      $current_ip_address = WP_Community_Events::get_unsafe_client_ip();
2037  
2038      /*
2039       * If the user's location is based on their IP address, then update their
2040       * location when their IP address changes. This allows them to see events
2041       * in their current city when travelling. Otherwise, they would always be
2042       * shown events in the city where they were when they first loaded the
2043       * Dashboard, which could have been months or years ago.
2044       */
2045      if ( $saved_ip_address && $current_ip_address && $current_ip_address !== $saved_ip_address ) {
2046          $saved_location['ip'] = $current_ip_address;
2047          update_user_meta( $user_id, 'community-events-location', $saved_location );
2048      }
2049  
2050      $events_client = new WP_Community_Events( $user_id, $saved_location );
2051  
2052      wp_localize_script(
2053          'dashboard',
2054          'communityEventsData',
2055          array(
2056              'nonce'       => wp_create_nonce( 'community_events' ),
2057              'cache'       => $events_client->get_cached_events(),
2058              'time_format' => get_option( 'time_format' ),
2059          )
2060      );
2061  }
2062  
2063  /**
2064   * Administration Screen CSS for changing the styles.
2065   *
2066   * If installing the 'wp-admin/' directory will be replaced with './'.
2067   *
2068   * The $_wp_admin_css_colors global manages the Administration Screens CSS
2069   * stylesheet that is loaded. The option that is set is 'admin_color' and is the
2070   * color and key for the array. The value for the color key is an object with
2071   * a 'url' parameter that has the URL path to the CSS file.
2072   *
2073   * The query from $src parameter will be appended to the URL that is given from
2074   * the $_wp_admin_css_colors array value URL.
2075   *
2076   * @since 2.6.0
2077   *
2078   * @global array $_wp_admin_css_colors
2079   *
2080   * @param string $src    Source URL.
2081   * @param string $handle Either 'colors' or 'colors-rtl'.
2082   * @return string|false URL path to CSS stylesheet for Administration Screens.
2083   */
2084  function wp_style_loader_src( $src, $handle ) {
2085      global $_wp_admin_css_colors;
2086  
2087      if ( wp_installing() ) {
2088          return preg_replace( '#^wp-admin/#', './', $src );
2089      }
2090  
2091      if ( 'colors' === $handle ) {
2092          $color = get_user_option( 'admin_color' );
2093  
2094          if ( empty( $color ) || ! isset( $_wp_admin_css_colors[ $color ] ) ) {
2095              $color = 'modern';
2096          }
2097  
2098          $color = $_wp_admin_css_colors[ $color ] ?? null;
2099          $url   = $color->url ?? '';
2100  
2101          if ( ! $url ) {
2102              return false;
2103          }
2104  
2105          $parsed = parse_url( $src );
2106          if ( isset( $parsed['query'] ) && $parsed['query'] ) {
2107              wp_parse_str( $parsed['query'], $qv );
2108              $url = add_query_arg( $qv, $url );
2109          }
2110  
2111          return $url;
2112      }
2113  
2114      return $src;
2115  }
2116  
2117  /**
2118   * Prints the script queue in the HTML head on admin pages.
2119   *
2120   * Postpones the scripts that were queued for the footer.
2121   * print_footer_scripts() is called in the footer to print these scripts.
2122   *
2123   * @since 2.8.0
2124   *
2125   * @see wp_print_scripts()
2126   *
2127   * @return string[] Handles of the scripts that were printed.
2128   */
2129  function print_head_scripts() {
2130      if ( ! did_action( 'wp_print_scripts' ) ) {
2131          /** This action is documented in wp-includes/functions.wp-scripts.php */
2132          do_action( 'wp_print_scripts' );
2133      }
2134  
2135      $wp_scripts = wp_scripts();
2136      $wp_scripts->do_head_items();
2137  
2138      /**
2139       * Filters whether to print the head scripts.
2140       *
2141       * @since 2.8.0
2142       * @deprecated 7.2.0 Scripts are no longer concatenated, so there is nothing for this filter to control.
2143       *
2144       * @param bool $print Whether to print the head scripts. Default true.
2145       */
2146      apply_filters_deprecated( 'print_head_scripts', array( true ), '7.2.0' );
2147  
2148      $wp_scripts->reset();
2149      return $wp_scripts->done;
2150  }
2151  
2152  /**
2153   * Prints the scripts that were queued for the footer or too late for the HTML head.
2154   *
2155   * @since 2.8.0
2156   *
2157   * @global WP_Scripts $wp_scripts
2158   *
2159   * @return string[] Handles of the scripts that were printed.
2160   */
2161  function print_footer_scripts() {
2162      global $wp_scripts;
2163  
2164      if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
2165          return array(); // No need to run if not instantiated.
2166      }
2167      $wp_scripts->do_footer_items();
2168  
2169      /**
2170       * Filters whether to print the footer scripts.
2171       *
2172       * @since 2.8.0
2173       * @deprecated 7.2.0 Scripts are no longer concatenated, so there is nothing for this filter to control.
2174       *
2175       * @param bool $print Whether to print the footer scripts. Default true.
2176       */
2177      apply_filters_deprecated( 'print_footer_scripts', array( true ), '7.2.0' );
2178  
2179      $wp_scripts->reset();
2180      return $wp_scripts->done;
2181  }
2182  
2183  /**
2184   * Prints the script queue in the HTML head on the front end.
2185   *
2186   * Postpones the scripts that were queued for the footer.
2187   * wp_print_footer_scripts() is called in the footer to print these scripts.
2188   *
2189   * @since 2.8.0
2190   *
2191   * @global WP_Scripts $wp_scripts
2192   *
2193   * @return string[] Handles of the scripts that were printed.
2194   */
2195  function wp_print_head_scripts() {
2196      global $wp_scripts;
2197  
2198      if ( ! did_action( 'wp_print_scripts' ) ) {
2199          /** This action is documented in wp-includes/functions.wp-scripts.php */
2200          do_action( 'wp_print_scripts' );
2201      }
2202  
2203      if ( ! ( $wp_scripts instanceof WP_Scripts ) ) {
2204          return array(); // No need to run if nothing is queued.
2205      }
2206  
2207      return print_head_scripts();
2208  }
2209  
2210  /**
2211   * Private, for use in *_footer_scripts hooks
2212   *
2213   * In classic themes, when block styles are loaded on demand via wp_load_classic_theme_block_styles_on_demand(),
2214   * this function is replaced by a closure in wp_hoist_late_printed_styles() which will capture the printing of
2215   * two sets of "late" styles to be hoisted to the HEAD by means of the template enhancement output buffer:
2216   *
2217   * 1. Styles related to blocks are inserted right after the wp-block-library stylesheet.
2218   * 2. All other styles are appended to the end of the HEAD.
2219   *
2220   * The closure calls print_footer_scripts() to print scripts in the footer as usual.
2221   *
2222   * @since 3.3.0
2223   */
2224  function _wp_footer_scripts() {
2225      print_late_styles();
2226      print_footer_scripts();
2227  }
2228  
2229  /**
2230   * Hooks to print the scripts and styles in the footer.
2231   *
2232   * @since 2.8.0
2233   */
2234  function wp_print_footer_scripts() {
2235      /**
2236       * Fires when footer scripts are printed.
2237       *
2238       * @since 2.8.0
2239       */
2240      do_action( 'wp_print_footer_scripts' );
2241  }
2242  
2243  /**
2244   * Wrapper for do_action( 'wp_enqueue_scripts' ).
2245   *
2246   * Allows plugins to queue scripts for the front end using wp_enqueue_script().
2247   * Runs first in wp_head() where all is_home(), is_page(), etc. functions are available.
2248   *
2249   * @since 2.8.0
2250   */
2251  function wp_enqueue_scripts() {
2252      /**
2253       * Fires when scripts and styles are enqueued.
2254       *
2255       * @since 2.8.0
2256       */
2257      do_action( 'wp_enqueue_scripts' );
2258  }
2259  
2260  /**
2261   * Prints the styles queue in the HTML head on admin pages.
2262   *
2263   * @since 2.8.0
2264   *
2265   * @return string[] Handles of the styles that were printed.
2266   */
2267  function print_admin_styles() {
2268      $wp_styles = wp_styles();
2269      $wp_styles->do_items( false );
2270  
2271      /**
2272       * Filters whether to print the admin styles.
2273       *
2274       * @since 2.8.0
2275       * @deprecated 7.2.0 Styles are no longer concatenated, so there is nothing for this filter to control.
2276       *
2277       * @param bool $print Whether to print the admin styles. Default true.
2278       */
2279      apply_filters_deprecated( 'print_admin_styles', array( true ), '7.2.0' );
2280  
2281      $wp_styles->reset();
2282      return $wp_styles->done;
2283  }
2284  
2285  /**
2286   * Prints the styles that were queued too late for the HTML head.
2287   *
2288   * @since 3.3.0
2289   *
2290   * @global WP_Styles $wp_styles
2291   *
2292   * @return string[]|null
2293   */
2294  function print_late_styles() {
2295      global $wp_styles;
2296  
2297      if ( ! ( $wp_styles instanceof WP_Styles ) ) {
2298          return null;
2299      }
2300  
2301      $wp_styles->do_footer_items();
2302  
2303      /**
2304       * Filters whether to print the styles queued too late for the HTML head.
2305       *
2306       * @since 3.3.0
2307       * @deprecated 7.2.0 Styles are no longer concatenated, so there is nothing for this filter to control.
2308       *
2309       * @param bool $print Whether to print the 'late' styles. Default true.
2310       */
2311      apply_filters_deprecated( 'print_late_styles', array( true ), '7.2.0' );
2312  
2313      $wp_styles->reset();
2314      return $wp_styles->done;
2315  }
2316  
2317  /**
2318   * Prints prefetch links for the assets of the screen the user is most likely to open next.
2319   *
2320   * Runs wherever the next screen can be predicted with confidence, and prefetches only what that
2321   * screen is certain to need. Two cases qualify today:
2322   *
2323   * - The login screen, which is followed by an admin screen. That screen downloads each core script
2324   *   and stylesheet separately, which is what makes an uncached admin load slow. Requesting the
2325   *   ones that block its first paint while the login form is on screen puts them in the HTTP cache
2326   *   during the time the user spends typing credentials, so the redirect that follows finds them
2327   *   already there.
2328   * - The Dashboard and the post list tables, from which the editor is the usual next stop. Only the
2329   *   editor's stylesheets are prefetched, not its scripts: the scripts run to well over a megabyte
2330   *   compressed, which is far too much to spend on a screen the user may never open, whereas the
2331   *   stylesheets are render-blocking and in the same size class as the login screen's own prefetch.
2332   *
2333   * Handles the current screen has printed or queued are skipped, along with their dependencies, so
2334   * each context only fetches what it is actually adding. On the login screen this runs in the
2335   * footer, since some of what the login form loads, such as `user-profile` and the `jquery` it
2336   * depends on, is only enqueued after its header has printed. On admin screens it runs in the head,
2337   * where the screen's assets have been enqueued already.
2338   *
2339   * These are resources for the *next* navigation rather than for the screen printing them, which is
2340   * what `rel="prefetch"` describes. `rel="preload"` would fetch them at the current document's
2341   * priority, and browsers warn about preloaded resources the document never uses. A prefetch is
2342   * already dispatched at the browser's lowest priority, so it stays out of the way of that screen's
2343   * own render-blocking assets without needing `fetchpriority`.
2344   *
2345   * A prefetched response is reused only for as long as the HTTP cache considers it fresh, the same as
2346   * any other cached response. Core does not send caching headers for its static files, so how long
2347   * that is depends on the server: an explicit `max-age` or `Expires`, or else a heuristic lifetime
2348   * derived from `Last-Modified`. Once the response is stale the next screen still revalidates it,
2349   * which saves the download but not the round trip.
2350   *
2351   * The `as` attribute is still worth setting: it gives the request the same destination the admin
2352   * screen will later ask for, which is what lets the prefetched response be reused.
2353   *
2354   * The admin-wide handles cover every admin screen rather than only the Dashboard, so that part of
2355   * the list does not vary with where the login lands. They are printed on every screen of the login
2356   * page, such as the password reset form, since those mostly lead to the admin as well. Nothing is
2357   * printed at all when `redirect_to` points outside this site's admin.
2358   *
2359   * @since 7.2.0
2360   *
2361   * @see wp_preload_resources()
2362   *
2363   * @global WP_Screen|null $current_screen The current admin screen, if any.
2364   */
2365  function wp_prefetch_admin_assets(): void {
2366      global $current_screen;
2367  
2368      /*
2369       * The context is told apart by the request rather than by the hook this runs on, so it works
2370       * from whichever hook it is added to. The 'login_init' action is fired by wp-login.php however
2371       * it is reached. The is_login() function would not do: it compares the login URL with the
2372       * script handling the request, and a plugin serving the login screen at a URL of its own runs
2373       * wp-login.php from another script, such as index.php.
2374       */
2375      $on_login = (bool) did_action( 'login_init' );
2376  
2377      if ( ! $on_login ) {
2378          /*
2379           * From the Dashboard and the post list tables, the editor is the usual next stop. Anywhere
2380           * else in the admin there is no destination worth guessing at.
2381           *
2382           * The global is read rather than calling get_current_screen(), which only exists once the
2383           * admin includes are loaded. On a request with no screen, such as one for the front end,
2384           * this prints nothing.
2385           */
2386          if ( ! $current_screen instanceof WP_Screen || ! in_array( $current_screen->base, array( 'dashboard', 'edit' ), true ) ) {
2387              return;
2388          }
2389      }
2390  
2391      $script_roots = array();
2392      $style_roots  = array();
2393  
2394      if ( $on_login ) {
2395          /*
2396           * Resolve where the login is going to land, the same way wp-login.php will: `redirect_to`
2397           * when one was given, and the admin otherwise. wp_safe_redirect() sends the browser to what
2398           * wp_validate_redirect() returns, so that is used here as it is: it sanitizes the value,
2399           * resolves a relative path against the current request, as the browser would, and falls back
2400           * to the admin for a value pointing off-host. Passing the value through esc_url_raw() first
2401           * would not match it, since that takes a relative path such as `wp-admin/post.php` for a host.
2402           *
2403           * This runs on every screen wp-login.php prints, not only the login form, since the others
2404           * mostly lead to the admin as well, and each one gives the prefetching another chance to
2405           * finish before the user gets there. The password reset and registration flows end at the
2406           * login form, and the admin email confirmation follows a login that has already succeeded.
2407           * On the lost password and registration screens, `redirect_to` is where submitting the
2408           * form goes rather than where the login lands, but it is normally absent, and when it is
2409           * not, it typically points back into wp-login.php, so nothing is prefetched. Nor does an
2410           * interim login need to be excluded: it shows inside a modal on an admin screen that has
2411           * already loaded these assets, so they are served from the HTTP cache.
2412           *
2413           * An empty `redirect_to` counts as none, since wp-login.php falls back to the admin for it
2414           * too. The lost password form submits one, for instance, so it is present when the form is
2415           * shown again with an error.
2416           */
2417          $admin_url   = admin_url();
2418          $next_screen = $admin_url;
2419  
2420          if ( ! empty( $_REQUEST['redirect_to'] ) && is_string( $_REQUEST['redirect_to'] ) ) {
2421              $next_screen = wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ), $admin_url );
2422          }
2423  
2424          /*
2425           * When the login lands somewhere other than the admin, such as the front end or a plugin's
2426           * own screen, none of these assets are wanted. The admin's path ends in a slash, which a
2427           * `redirect_to` of the admin itself may leave off, as in `/wp-admin`.
2428           */
2429          $admin_path = (string) wp_parse_url( $admin_url, PHP_URL_PATH );
2430  
2431          if ( '' === $admin_path || ! str_starts_with( trailingslashit( (string) wp_parse_url( $next_screen, PHP_URL_PATH ) ), $admin_path ) ) {
2432              return;
2433          }
2434  
2435          /*
2436           * Nor are they when it lands in the admin of another host. wp_validate_redirect() accepts any
2437           * host in 'allowed_redirect_hosts', such as another site on a multisite network, and that
2438           * admin would request its assets from its own host rather than from this one. The same goes
2439           * for this host's admin under another scheme, such as an `https` destination from an `http`
2440           * login: the URLs prefetched here take the scheme of the current request, so that admin
2441           * would request different ones. A relative `redirect_to` stays on this host and scheme, while
2442           * wp_validate_redirect() gives a protocol-relative one the `http` scheme, which is where
2443           * wp_safe_redirect() then sends it. A port is compared with the scheme's default filled in
2444           * when none is given, since `https://example.com:443/` is the same admin as
2445           * `https://example.com/`.
2446           */
2447          $next_screen_host = wp_parse_url( $next_screen, PHP_URL_HOST );
2448  
2449          if ( is_string( $next_screen_host ) ) {
2450              $default_ports = array(
2451                  'http'  => 80,
2452                  'https' => 443,
2453              );
2454  
2455              $admin_scheme       = strtolower( (string) wp_parse_url( $admin_url, PHP_URL_SCHEME ) );
2456              $next_screen_scheme = strtolower( (string) wp_parse_url( $next_screen, PHP_URL_SCHEME ) );
2457              $admin_port         = wp_parse_url( $admin_url, PHP_URL_PORT ) ?? $default_ports[ $admin_scheme ] ?? null;
2458              $next_screen_port   = wp_parse_url( $next_screen, PHP_URL_PORT ) ?? $default_ports[ $next_screen_scheme ] ?? null;
2459  
2460              if (
2461                  strtolower( $next_screen_host ) !== strtolower( (string) wp_parse_url( $admin_url, PHP_URL_HOST ) ) ||
2462                  $next_screen_scheme !== $admin_scheme ||
2463                  $next_screen_port !== $admin_port
2464              ) {
2465                  return;
2466              }
2467          }
2468      } else {
2469          $post_type        = ( 'edit' === $current_screen->base && $current_screen->post_type ) ? $current_screen->post_type : 'post';
2470          $post_type_object = get_post_type_object( $post_type );
2471  
2472          if ( ! $post_type_object instanceof WP_Post_Type ) {
2473              return;
2474          }
2475  
2476          /*
2477           * A user who cannot edit posts of this type will never reach the editor from here. This is
2478           * `edit_posts` rather than `create_posts`, since the editor is reached by opening an existing
2479           * post as well as by adding a new one, and a user may be able to do the first but not the
2480           * second. Adding a new one requires `edit_posts` too.
2481           */
2482          if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) {
2483              return;
2484          }
2485  
2486          $next_screen = add_query_arg( 'post_type', $post_type, admin_url( 'post-new.php' ) );
2487      }
2488  
2489      if ( $on_login ) {
2490          /*
2491           * The handles that block rendering on every admin screen: the stylesheets, and the scripts
2492           * printed in the head. These are what stand between the redirect and the first paint, so
2493           * they are what is worth having in the cache already. Every handle these expand to loads on
2494           * all admin screens, not just the one the login happens to land on, so the list does not depend
2495           * on the destination. Screen-specific handles are deliberately left out: `site-health`
2496           * blocks rendering on the Dashboard but loads nowhere else.
2497           *
2498           * Scripts printed in the footer are left out even though they block DOMContentLoaded, since
2499           * they do not hold back the first paint. They are also where the bulk of the admin's bytes
2500           * are, largely the command palette's dependencies, and a prefetch of them still in flight
2501           * when the login form is submitted competes with the admin screen's own render-blocking
2502           * stylesheets and delays its first paint on a slow connection.
2503           *
2504           * These are roots rather than the full set: everything they depend on is pulled in with them
2505           * below, so the set follows the dependencies declared in wp_default_scripts() and
2506           * wp_default_styles(). `jquery` stands for `jquery-core` and `jquery-migrate`, and `wp-admin`
2507           * for the admin's own stylesheets, which is what the `colors` handle enqueued on every admin
2508           * screen depends on. Aliases like these have no source of their own, so only what they expand
2509           * to is prefetched. `colors` itself is left out, since the color scheme is a per-user setting
2510           * and the user is not known yet.
2511           *
2512           * Each root mirrors an enqueue elsewhere, which carries a note pointing back here: `common`
2513           * (for `jquery`) in wp-admin/admin.php, `colors` (for `wp-admin` and `buttons`) and `utils` in
2514           * wp-admin/admin-header.php, `admin-bar` in WP_Admin_Bar::initialize(), `wp-auth-check` in
2515           * wp_auth_check_load(), and `wp-commands` in wp_enqueue_command_palette_assets().
2516           */
2517          $script_roots = array(
2518              'jquery',
2519              'utils',
2520          );
2521  
2522          $style_roots = array(
2523              'wp-admin',
2524              'buttons',
2525              'admin-bar',
2526              'wp-auth-check',
2527              'wp-commands',
2528          );
2529      }
2530  
2531      /*
2532       * post-new.php always opens the editor, while post.php also handles trashing, restoring and
2533       * bulk edits, so it counts only when it is editing.
2534       */
2535      $next_screen_file  = basename( (string) wp_parse_url( $next_screen, PHP_URL_PATH ) );
2536      $next_screen_query = array();
2537      wp_parse_str( (string) wp_parse_url( $next_screen, PHP_URL_QUERY ), $next_screen_query );
2538  
2539      $next_screen_is_block_editor = 'post-new.php' === $next_screen_file
2540          || ( 'post.php' === $next_screen_file && 'edit' === ( $next_screen_query['action'] ?? '' ) );
2541  
2542      if ( $next_screen_is_block_editor ) {
2543          /*
2544           * The editor only loads these stylesheets when the post type uses the block editor, which
2545           * the classic editor, for instance, can turn off. The post type is the one in the query of
2546           * the next screen, as for post-new.php. An edit link to post.php from the login screen does
2547           * not carry one, so it is taken to be a post: looking the post up instead would let anyone
2548           * tell from the login screen whether a post with a given ID exists, drafts and private posts
2549           * included.
2550           */
2551          $next_screen_post_type       = $next_screen_query['post_type'] ?? 'post';
2552          $next_screen_is_block_editor = is_string( $next_screen_post_type ) && use_block_editor_for_post_type( $next_screen_post_type );
2553      }
2554  
2555      if ( $next_screen_is_block_editor ) {
2556          /*
2557           * Roots as well, expanded along with any from the login screen. `wp-edit-post` alone accounts
2558           * for most of the editor chrome, including the block editor's content and reset styles by way
2559           * of `wp-edit-blocks`; the rest cover the block directory, the format library, the classic
2560           * editor's buttons and the media modal.
2561           *
2562           * Each root mirrors an enqueue elsewhere, which carries a note pointing back here:
2563           * `wp-edit-post` in wp-admin/edit-form-blocks.php, `wp-block-directory` in
2564           * wp_enqueue_editor_block_directory_assets(), `wp-format-library` in
2565           * wp_enqueue_editor_format_library_assets(), `editor-buttons` in
2566           * _WP_Editors::enqueue_default_editor(), and `media-views` and `imgareaselect` in
2567           * wp_enqueue_media().
2568           */
2569          $style_roots = array_merge(
2570              $style_roots,
2571              array(
2572                  'wp-edit-post',
2573                  'wp-block-directory',
2574                  'wp-format-library',
2575                  'editor-buttons',
2576                  'media-views',
2577                  'imgareaselect',
2578              )
2579          );
2580      }
2581  
2582      // From an admin screen, nothing is left to prefetch when the editor turns out not to be the block editor.
2583      if ( ! $script_roots && ! $style_roots ) {
2584          return;
2585      }
2586  
2587      $resources = array();
2588  
2589      foreach (
2590          array(
2591              'script' => array( wp_scripts(), $script_roots ),
2592              'style'  => array( wp_styles(), $style_roots ),
2593          )
2594          as $as => list( $dependencies, $roots )
2595      ) {
2596          // Skip a type with nothing to prefetch, such as scripts for the editor, rather than expanding the current screen's queue of it.
2597          if ( ! $roots ) {
2598              continue;
2599          }
2600  
2601          /*
2602           * Expand the roots to include everything they depend on, roots first, and likewise what the
2603           * current screen has queued. A handle that is not registered is dropped along with its
2604           * dependencies. Unlike WP_Dependencies::all_deps(), this leaves the dependencies' state
2605           * untouched.
2606           */
2607          $expanded = array(
2608              'roots' => array(),
2609              'queue' => array(),
2610          );
2611  
2612          foreach (
2613              array(
2614                  'roots' => $roots,
2615                  'queue' => $dependencies->queue,
2616              )
2617              as $list => $handles
2618          ) {
2619              while ( $handles ) {
2620                  $handle = array_shift( $handles );
2621  
2622                  if ( isset( $expanded[ $list ][ $handle ] ) || ! isset( $dependencies->registered[ $handle ] ) ) {
2623                      continue;
2624                  }
2625  
2626                  $expanded[ $list ][ $handle ] = true;
2627  
2628                  foreach ( $dependencies->registered[ $handle ]->deps as $dependency ) {
2629                      if ( is_string( $dependency ) && '' !== $dependency && ! isset( $expanded[ $list ][ $dependency ] ) ) {
2630                          $handles[] = $dependency;
2631                      }
2632                  }
2633              }
2634          }
2635  
2636          /*
2637           * Whichever screen this is running on shares some of these handles, and the browser fetches
2638           * those for it anyway, so prefetching them as well would only compete with its own requests.
2639           * These are the ones it has printed, and the ones it has queued along with their
2640           * dependencies, which it has yet to print if this runs before its footer scripts. On the
2641           * login screen, for instance, `user-profile` brings in `jquery`.
2642           */
2643          $on_current_screen = $expanded['queue'] + array_fill_keys( $dependencies->done, true );
2644  
2645          foreach ( array_keys( $expanded['roots'] ) as $handle ) {
2646              if ( isset( $on_current_screen[ $handle ] ) ) {
2647                  continue;
2648              }
2649  
2650              // The next screen prints nothing for a handle with conditional data, as do_item() returns early for it.
2651              if ( $dependencies->registered[ $handle ]->extra['conditional'] ?? false ) {
2652                  continue;
2653              }
2654  
2655              /*
2656               * The URLs are built the same way WP_Scripts::do_item() and WP_Styles::do_item() build
2657               * those in the tags they print, so they match what the next screen will request. They are
2658               * not escaped yet, so the filter sees plain URLs; they are escaped when printed.
2659               *
2660               * A handle with no URL of its own, such as an alias or one whose URL was filtered away,
2661               * prints nothing either, and for a style that includes its right-to-left stylesheet,
2662               * since WP_Styles::do_item() returns before getting to it.
2663               */
2664              $src = $dependencies->get_src( $handle );
2665  
2666              if ( '' === $src ) {
2667                  continue;
2668              }
2669  
2670              $urls = array( $src );
2671  
2672              if ( $dependencies instanceof WP_Styles ) {
2673                  $rtl_src = $dependencies->get_rtl_src( $handle );
2674  
2675                  if ( null !== $rtl_src ) {
2676                      if ( 'replace' === $dependencies->get_data( $handle, 'rtl' ) ) {
2677                          $urls = array( $rtl_src );
2678                      } else {
2679                          $urls[] = $rtl_src;
2680                      }
2681                  }
2682              }
2683  
2684              foreach ( array_filter( $urls ) as $url ) {
2685                  $resources[] = array(
2686                      'href' => $url,
2687                      'as'   => $as,
2688                  );
2689              }
2690          }
2691      }
2692  
2693      /**
2694       * Filters the assets prefetched for the screen the user is expected to open next.
2695       *
2696       * Fires on any screen from which the next one can be predicted, so `$next_screen` is what
2697       * distinguishes the contexts: the login screen passes the URL it is about to redirect to, and
2698       * the Dashboard and post list tables pass the editor they expect the user to open.
2699       *
2700       * Only the `href` and `as` attributes below are printed; any other key is ignored. Resources
2701       * sharing an `href` are collapsed to the first of them, so a callback may append without
2702       * checking what is already there. Returning an empty array turns the prefetching off.
2703       *
2704       * On the login screen the URLs are built before the user is authenticated and outside the
2705       * admin, so there is no current user and `is_admin()` is false. A {@see 'script_loader_src'}
2706       * or {@see 'style_loader_src'} callback that depends on either can produce a URL the admin
2707       * screen will not request, which wastes the prefetch; a callback on this filter can correct it.
2708       *
2709       * @since 7.2.0
2710       *
2711       * @param array  $resources {
2712       *     Array of resources and their attributes to prefetch.
2713       *
2714       *     @type array ...$0 {
2715       *         Array of resource attributes.
2716       *
2717       *         @type string $href URL to prefetch. Required.
2718       *         @type string $as   How the browser should treat the resource
2719       *                            (`script`, `style`, `image`, `document`, etc). Required.
2720       *     }
2721       * }
2722       * @param string $next_screen URL of the screen the assets are being prefetched for. Always
2723       *                            points into the admin, since nothing is prefetched otherwise.
2724       *                            From the login screen this is the redirect target, already run
2725       *                            through wp_validate_redirect() with the admin as the fallback,
2726       *                            and it may be relative: it is the value as wp_safe_redirect()
2727       *                            will receive it, so a request-supplied path is passed through
2728       *                            unchanged and only the fallback is a full URL.
2729       */
2730      $resources = apply_filters( 'wp_prefetch_admin_assets', $resources, $next_screen );
2731  
2732      if ( ! is_array( $resources ) ) {
2733          return;
2734      }
2735  
2736      $unique_resources = array();
2737  
2738      // Parse the complete resource list and extract unique resources.
2739      foreach ( $resources as $resource ) {
2740          if ( ! is_array( $resource ) ) {
2741              continue;
2742          }
2743  
2744          $href = $resource['href'] ?? '';
2745          $as   = $resource['as'] ?? '';
2746  
2747          if ( ! is_string( $href ) || '' === $href || ! is_string( $as ) || '' === $as ) {
2748              continue;
2749          }
2750  
2751          /*
2752           * Check again once escaped, since esc_url() returns an empty string for a URL it rejects.
2753           * An empty `href` would resolve to the current page, which the script below would then fetch.
2754           * Only `http` and `https` URLs are allowed, as wp_preload_resources() does, since nothing else
2755           * can be prefetched, and the script would pass anything else to fetch().
2756           */
2757          $href = esc_url( $href, array( 'http', 'https' ) );
2758  
2759          if ( '' === $href || isset( $unique_resources[ $href ] ) ) {
2760              continue;
2761          }
2762  
2763          $unique_resources[ $href ] = $as;
2764      }
2765  
2766      if ( ! $unique_resources ) {
2767          return;
2768      }
2769  
2770      // Build and output the HTML for each unique resource. Each URL has already been escaped.
2771      foreach ( $unique_resources as $escaped_href => $as ) {
2772          printf(
2773              "<link rel='prefetch' href='%s' as='%s' />\n",
2774              $escaped_href,
2775              esc_attr( $as )
2776          );
2777      }
2778  
2779      /*
2780       * Safari does not support `rel="prefetch"`, so in browsers that lack it, fetch the URLs of the
2781       * page's prefetch links with fetch() instead, to put the responses in the HTTP cache all the
2782       * same. Reading the links from the page, rather than passing their URLs to the script, keeps a
2783       * single list of them, and also covers any prefetch links added by plugins. The fetches wait
2784       * until the current screen has loaded, and ask for low priority, so they stay out of the way
2785       * of its own assets. Unlike a prefetch link, a fetch still in flight is canceled when the user
2786       * navigates away, so it cannot compete with the next screen's render-blocking assets.
2787       *
2788       * The requests use `no-cors` mode and include credentials, as the stylesheet and script
2789       * requests of the next screen do, so the cached responses match what that screen will ask for.
2790       * The body is read to completion so that the full response is cached.
2791       */
2792      $js_function = <<<'JS'
2793          /**
2794           * Prefetches the page's rel=prefetch links with fetch() in browsers that do not support rel=prefetch.
2795           *
2796           * @link https://caniuse.com/link-rel-prefetch
2797           */
2798          () => {
2799              if ( document.createElement( 'link' ).relList?.supports?.( 'prefetch' ) ) {
2800                  return;
2801              }
2802  
2803              const prefetch = () => {
2804                  for ( const link of /** @type {NodeListOf<HTMLLinkElement>} */ ( document.querySelectorAll( 'link[rel~="prefetch"][href]' ) ) ) {
2805                      fetch( link.href, { mode: 'no-cors', credentials: 'include', priority: 'low' } )
2806                          .then( ( response ) => response.blob() )
2807                          .catch( () => {} );
2808                  }
2809              };
2810  
2811              const schedule = () => {
2812                  if ( 'requestIdleCallback' in window ) {
2813                      window.requestIdleCallback( prefetch );
2814                  } else {
2815                      setTimeout( prefetch, 0 );
2816                  }
2817              };
2818  
2819              if ( 'complete' === document.readyState ) {
2820                  schedule();
2821              } else {
2822                  window.addEventListener( 'load', schedule, { once: true } );
2823              }
2824          }
2825          JS;
2826  
2827      wp_print_inline_script_tag( "( $js_function )();\n//# sourceURL=" . rawurlencode( __FUNCTION__ ) );
2828  }
2829  
2830  /**
2831   * Handles the enqueueing of block scripts and styles that are common to both
2832   * the editor and the front-end.
2833   *
2834   * @since 5.0.0
2835   */
2836  function wp_common_block_scripts_and_styles() {
2837      if ( is_admin() && ! wp_should_load_block_editor_scripts_and_styles() ) {
2838          return;
2839      }
2840  
2841      wp_enqueue_style( 'wp-block-library' );
2842  
2843      if ( current_theme_supports( 'wp-block-styles' ) && ! wp_should_load_separate_core_block_assets() ) {
2844          wp_enqueue_style( 'wp-block-library-theme' );
2845      }
2846  
2847      /**
2848       * Fires after enqueuing block assets for both editor and front-end.
2849       *
2850       * Call `add_action` on any hook before 'wp_enqueue_scripts'.
2851       *
2852       * In the function call you supply, simply use `wp_enqueue_script` and
2853       * `wp_enqueue_style` to add your functionality to the Gutenberg editor.
2854       *
2855       * @since 5.0.0
2856       */
2857      do_action( 'enqueue_block_assets' );
2858  }
2859  
2860  /**
2861   * Applies a filter to the list of style nodes that comes from WP_Theme_JSON::get_style_nodes().
2862   *
2863   * This particular filter removes all of the blocks from the array.
2864   *
2865   * We want WP_Theme_JSON to be ignorant of the implementation details of how the CSS is being used.
2866   * This filter allows us to modify the output of WP_Theme_JSON depending on whether or not we are
2867   * loading separate assets, without making the class aware of that detail.
2868   *
2869   * @since 6.1.0
2870   *
2871   * @param array<array<string, mixed>> $nodes The nodes to filter.
2872   * @return array<array<string, mixed>> A filtered array of style nodes.
2873   */
2874  function wp_filter_out_block_nodes( $nodes ) {
2875      return array_filter(
2876          $nodes,
2877          static function ( $node ) {
2878              return ! in_array( 'blocks', $node['path'], true );
2879          },
2880          ARRAY_FILTER_USE_BOTH
2881      );
2882  }
2883  
2884  /**
2885   * Enqueues the global styles defined via theme.json.
2886   *
2887   * @since 5.8.0
2888   */
2889  function wp_enqueue_global_styles() {
2890      $assets_on_demand = wp_should_load_block_assets_on_demand();
2891      $is_block_theme   = wp_is_block_theme();
2892      $is_classic_theme = ! $is_block_theme;
2893  
2894      /**
2895       * Global styles should be printed in the HEAD for block themes, or for classic themes when loading assets on
2896       * demand is disabled (which is no longer the default since WordPress 6.9).
2897       *
2898       * @link https://core.trac.wordpress.org/ticket/53494
2899       * @link https://core.trac.wordpress.org/ticket/61965
2900       */
2901      if (
2902          doing_action( 'wp_footer' ) &&
2903          (
2904              $is_block_theme ||
2905              ( $is_classic_theme && ! $assets_on_demand )
2906          )
2907      ) {
2908          return;
2909      }
2910  
2911      /**
2912       * The footer should only be used for classic themes when loading assets on demand is enabled. In WP 6.9 this is the
2913       * default with the introduction of hoisting late-printed styles (via {@see wp_load_classic_theme_block_styles_on_demand()}).
2914       * So even though the main global styles are not printed here in the HEAD for classic themes with on-demand asset
2915       * loading, a placeholder for the global styles is still enqueued. Then when {@see wp_hoist_late_printed_styles()}
2916       * processes the output buffer, it can locate the placeholder and inject the global styles from the footer into the
2917       * HEAD, replacing the placeholder.
2918       *
2919       * @link https://core.trac.wordpress.org/ticket/64099
2920       */
2921      if ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) {
2922          if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) {
2923              wp_register_style( 'wp-global-styles-placeholder', false );
2924              wp_add_inline_style( 'wp-global-styles-placeholder', ':root { --wp-internal-comment: "Placeholder for wp_hoist_late_printed_styles() to replace with the global-styles printed at wp_footer." }' );
2925              wp_enqueue_style( 'wp-global-styles-placeholder' );
2926          }
2927          return;
2928      }
2929  
2930      /*
2931       * If loading the CSS for each block separately, then load the theme.json CSS conditionally.
2932       * This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block.
2933       * This filter must be registered before calling wp_get_global_stylesheet();
2934       */
2935      add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' );
2936  
2937      $stylesheet = wp_get_global_stylesheet();
2938  
2939      /*
2940       * For block themes, merge Customizer's custom CSS into the global styles stylesheet
2941       * before the global styles custom CSS, ensuring proper cascade order.
2942       * For classic themes, let the Customizer CSS print separately via wp_custom_css_cb()
2943       * at priority 101 in wp_head, preserving its position at the end of the <head>.
2944       */
2945      if ( $is_block_theme ) {
2946          /*
2947           * Dequeue the Customizer's custom CSS
2948           * and add it before the global styles custom CSS.
2949           */
2950          remove_action( 'wp_head', 'wp_custom_css_cb', 101 );
2951  
2952          /*
2953           * Get the custom CSS from the Customizer and add it to the global stylesheet.
2954           * Always do this in Customizer preview for the sake of live preview since it be empty.
2955           */
2956          $custom_css = trim( wp_get_custom_css() );
2957          if ( $custom_css || is_customize_preview() ) {
2958              if ( is_customize_preview() ) {
2959                  /*
2960                   * When in the Customizer preview, wrap the Custom CSS in milestone comments to allow customize-preview.js
2961                   * to locate the CSS to replace for live previewing. Make sure that the milestone comments are omitted from
2962                   * the stored Custom CSS if by chance someone tried to add them, which would be highly unlikely, but it
2963                   * would break live previewing.
2964                   */
2965                  $before_milestone = '/*BEGIN_CUSTOMIZER_CUSTOM_CSS*/';
2966                  $after_milestone  = '/*END_CUSTOMIZER_CUSTOM_CSS*/';
2967                  $custom_css       = str_replace( array( $before_milestone, $after_milestone ), '', $custom_css );
2968                  $custom_css       = $before_milestone . "\n" . $custom_css . "\n" . $after_milestone;
2969              }
2970              $custom_css = "\n" . $custom_css;
2971          }
2972          $stylesheet .= $custom_css;
2973  
2974          // Add the global styles custom CSS at the end.
2975          $stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) );
2976      }
2977  
2978      if ( empty( $stylesheet ) ) {
2979          return;
2980      }
2981  
2982      wp_register_style( 'global-styles', false );
2983      wp_add_inline_style( 'global-styles', $stylesheet );
2984      wp_enqueue_style( 'global-styles' );
2985  
2986      // Add each block as an inline css.
2987      wp_add_global_styles_for_blocks();
2988  }
2989  
2990  /**
2991   * Checks if the editor scripts and styles for all registered block types
2992   * should be enqueued on the current screen.
2993   *
2994   * @since 5.6.0
2995   *
2996   * @global WP_Screen $current_screen WordPress current screen object.
2997   *
2998   * @return bool Whether scripts and styles should be enqueued.
2999   */
3000  function wp_should_load_block_editor_scripts_and_styles() {
3001      global $current_screen;
3002  
3003      $is_block_editor_screen = ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor();
3004  
3005      /**
3006       * Filters the flag that decides whether or not block editor scripts and styles
3007       * are going to be enqueued on the current screen.
3008       *
3009       * @since 5.6.0
3010       *
3011       * @param bool $is_block_editor_screen Current value of the flag.
3012       */
3013      return apply_filters( 'should_load_block_editor_scripts_and_styles', $is_block_editor_screen );
3014  }
3015  
3016  /**
3017   * Checks whether separate styles should be loaded for core blocks.
3018   *
3019   * When this function returns true, other functions ensure that core blocks use their own separate stylesheets.
3020   * When this function returns false, all core blocks will use the single combined 'wp-block-library' stylesheet.
3021   *
3022   * As a side effect, the return value will by default result in block assets to be loaded on demand, via the
3023   * {@see wp_should_load_block_assets_on_demand()} function. This behavior can be separately altered via that function.
3024   *
3025   * This only affects front end and not the block editor screens.
3026   *
3027   * @since 5.8.0
3028   * @see wp_should_load_block_assets_on_demand()
3029   * @see wp_enqueue_registered_block_scripts_and_styles()
3030   * @see register_block_style_handle()
3031   *
3032   * @return bool Whether separate core block assets will be loaded.
3033   */
3034  function wp_should_load_separate_core_block_assets() {
3035      if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) {
3036          return false;
3037      }
3038  
3039      /**
3040       * Filters whether block styles should be loaded separately.
3041       *
3042       * Returning false loads all core block assets, regardless of whether they are rendered
3043       * in a page or not. Returning true loads core block assets only when they are rendered.
3044       *
3045       * @since 5.8.0
3046       *
3047       * @param bool $load_separate_assets Whether separate assets will be loaded.
3048       *                                   Default false (all block assets are loaded, even when not used).
3049       */
3050      return apply_filters( 'should_load_separate_core_block_assets', false );
3051  }
3052  
3053  /**
3054   * Checks whether block styles should be loaded only on-render.
3055   *
3056   * When this function returns true, other functions ensure that blocks only load their assets on-render.
3057   * When this function returns false, all block assets are loaded regardless of whether they are rendered in a page.
3058   *
3059   * The default return value depends on the result of {@see wp_should_load_separate_core_block_assets()}, which controls
3060   * whether Core block stylesheets should be loaded separately or via a combined 'wp-block-library' stylesheet.
3061   *
3062   * This only affects front end and not the block editor screens.
3063   *
3064   * @since 6.8.0
3065   * @see wp_should_load_separate_core_block_assets()
3066   *
3067   * @return bool Whether to load block assets only when they are rendered.
3068   */
3069  function wp_should_load_block_assets_on_demand() {
3070      if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) {
3071          return false;
3072      }
3073  
3074      /*
3075       * For backward compatibility, the default return value for this function is based on the return value of
3076       * `wp_should_load_separate_core_block_assets()`. Initially, this function used to control both of these concerns.
3077       */
3078      $load_assets_on_demand = wp_should_load_separate_core_block_assets();
3079  
3080      /**
3081       * Filters whether block styles should be loaded on demand.
3082       *
3083       * Returning false loads all block assets, regardless of whether they are rendered in a page or not.
3084       * Returning true loads block assets only when they are rendered.
3085       *
3086       * The default value of the filter depends on the result of {@see wp_should_load_separate_core_block_assets()},
3087       * which controls whether Core block stylesheets should be loaded separately or via a combined 'wp-block-library'
3088       * stylesheet.
3089       *
3090       * @since 6.8.0
3091       *
3092       * @param bool $load_assets_on_demand Whether to load block assets only when they are rendered.
3093       */
3094      return apply_filters( 'should_load_block_assets_on_demand', $load_assets_on_demand );
3095  }
3096  
3097  /**
3098   * Enqueues registered block scripts and styles, depending on current rendered
3099   * context (only enqueuing editor scripts while in context of the editor).
3100   *
3101   * @since 5.0.0
3102   */
3103  function wp_enqueue_registered_block_scripts_and_styles() {
3104      if ( wp_should_load_block_assets_on_demand() ) {
3105          /**
3106           * Add placeholder for where block styles would historically get enqueued in a classic theme when block assets
3107           * are not loaded on demand. This happens right after {@see wp_common_block_scripts_and_styles()} is called
3108           * at which time wp-block-library is enqueued.
3109           */
3110          if ( ! wp_is_block_theme() && has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) {
3111              wp_register_style( 'wp-block-styles-placeholder', false );
3112              wp_add_inline_style( 'wp-block-styles-placeholder', ':root { --wp-internal-comment: "Placeholder for wp_hoist_late_printed_styles() to replace with the block styles printed at wp_footer." }' );
3113              wp_enqueue_style( 'wp-block-styles-placeholder' );
3114          }
3115          return;
3116      }
3117  
3118      $load_editor_scripts_and_styles = is_admin() && wp_should_load_block_editor_scripts_and_styles();
3119  
3120      $block_registry = WP_Block_Type_Registry::get_instance();
3121  
3122      /*
3123       * Block styles are only enqueued if they're registered. For core blocks, this is only the case if
3124       * `wp_should_load_separate_core_block_assets()` returns true. Otherwise they use the single combined
3125       * 'wp-block-library` stylesheet. See also `register_core_block_style_handles()`.
3126       * Since `wp_enqueue_style()` does not trigger warnings if the style is not registered, it is okay to not cater for
3127       * this behavior here and simply call `wp_enqueue_style()` unconditionally.
3128       */
3129      foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) {
3130          // Front-end and editor styles.
3131          foreach ( $block_type->style_handles as $style_handle ) {
3132              wp_enqueue_style( $style_handle );
3133          }
3134  
3135          // Front-end and editor scripts.
3136          foreach ( $block_type->script_handles as $script_handle ) {
3137              wp_enqueue_script( $script_handle );
3138          }
3139  
3140          if ( $load_editor_scripts_and_styles ) {
3141              // Editor styles.
3142              foreach ( $block_type->editor_style_handles as $editor_style_handle ) {
3143                  wp_enqueue_style( $editor_style_handle );
3144              }
3145  
3146              // Editor scripts.
3147              foreach ( $block_type->editor_script_handles as $editor_script_handle ) {
3148                  wp_enqueue_script( $editor_script_handle );
3149              }
3150          }
3151      }
3152  }
3153  
3154  /**
3155   * Function responsible for enqueuing the styles required for block styles functionality on the editor and on the frontend.
3156   *
3157   * @since 5.3.0
3158   *
3159   * @global WP_Styles $wp_styles
3160   */
3161  function enqueue_block_styles_assets() {
3162      global $wp_styles;
3163  
3164      $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered();
3165  
3166      foreach ( $block_styles as $block_name => $styles ) {
3167          foreach ( $styles as $style_properties ) {
3168              if ( isset( $style_properties['style_handle'] ) ) {
3169  
3170                  // If the site loads block styles on demand, enqueue the stylesheet on render.
3171                  if ( wp_should_load_block_assets_on_demand() ) {
3172                      add_filter(
3173                          'render_block',
3174                          static function ( $html, $block ) use ( $block_name, $style_properties ) {
3175                              if ( $block['blockName'] === $block_name ) {
3176                                  wp_enqueue_style( $style_properties['style_handle'] );
3177                              }
3178                              return $html;
3179                          },
3180                          10,
3181                          2
3182                      );
3183                  } else {
3184                      wp_enqueue_style( $style_properties['style_handle'] );
3185                  }
3186              }
3187              if ( isset( $style_properties['inline_style'] ) ) {
3188  
3189                  // Default to "wp-block-library".
3190                  $handle = 'wp-block-library';
3191  
3192                  // If the site loads block styles on demand, check if the block has a stylesheet registered.
3193                  if ( wp_should_load_block_assets_on_demand() ) {
3194                      $block_stylesheet_handle = generate_block_asset_handle( $block_name, 'style' );
3195  
3196                      if ( isset( $wp_styles->registered[ $block_stylesheet_handle ] ) ) {
3197                          $handle = $block_stylesheet_handle;
3198                      }
3199                  }
3200  
3201                  // Add inline styles to the calculated handle.
3202                  wp_add_inline_style( $handle, $style_properties['inline_style'] );
3203              }
3204          }
3205      }
3206  }
3207  
3208  /**
3209   * Function responsible for enqueuing the assets required for block styles functionality on the editor.
3210   *
3211   * @since 5.3.0
3212   */
3213  function enqueue_editor_block_styles_assets() {
3214      $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered();
3215  
3216      $register_script_lines = array( '( function() {' );
3217      foreach ( $block_styles as $block_name => $styles ) {
3218          foreach ( $styles as $style_properties ) {
3219              $block_style = array(
3220                  'name'  => $style_properties['name'],
3221                  'label' => $style_properties['label'],
3222              );
3223              if ( isset( $style_properties['is_default'] ) ) {
3224                  $block_style['isDefault'] = $style_properties['is_default'];
3225              }
3226              $register_script_lines[] = sprintf(
3227                  '    wp.blocks.registerBlockStyle( \'%s\', %s );',
3228                  $block_name,
3229                  wp_json_encode( $block_style, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
3230              );
3231          }
3232      }
3233      $register_script_lines[] = '} )();';
3234      $inline_script           = implode( "\n", $register_script_lines );
3235  
3236      wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, array( 'in_footer' => true ) );
3237      wp_add_inline_script( 'wp-block-styles', $inline_script );
3238      wp_enqueue_script( 'wp-block-styles' );
3239  }
3240  
3241  /**
3242   * Enqueues the assets required for the block directory within the block editor.
3243   *
3244   * @since 5.5.0
3245   */
3246  function wp_enqueue_editor_block_directory_assets() {
3247      wp_enqueue_script( 'wp-block-directory' );
3248      // Prefetched for the block editor by wp_prefetch_admin_assets(), which needs updating if this changes.
3249      wp_enqueue_style( 'wp-block-directory' );
3250  }
3251  
3252  /**
3253   * Enqueues the assets required for the format library within the block editor.
3254   *
3255   * @since 5.8.0
3256   */
3257  function wp_enqueue_editor_format_library_assets() {
3258      wp_enqueue_script( 'wp-format-library' );
3259      // Prefetched for the block editor by wp_prefetch_admin_assets(), which needs updating if this changes.
3260      wp_enqueue_style( 'wp-format-library' );
3261  }
3262  
3263  /**
3264   * Formats `<script>` loader tags.
3265   *
3266   * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_script_attributes'} filter.
3267   * Automatically injects type attribute if needed.
3268   *
3269   * @since 5.7.0
3270   *
3271   * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes.
3272   * @return string String containing `<script>` opening and closing tags.
3273   *
3274   * @phpstan-return non-falsy-string
3275   */
3276  function wp_get_script_tag( $attributes ) {
3277      /**
3278       * Filters attributes to be added to a script tag.
3279       *
3280       * @since 5.7.0
3281       *
3282       * @param array $attributes Key-value pairs representing `<script>` tag attributes.
3283       *                          Only the attribute name is added to the `<script>` tag for
3284       *                          entries with a boolean value, and that are true.
3285       */
3286      $attributes = apply_filters( 'wp_script_attributes', $attributes );
3287  
3288      $processor = new WP_HTML_Tag_Processor( '<script></script>' );
3289      $processor->next_tag();
3290      foreach ( $attributes as $name => $value ) {
3291          /*
3292           * Lexical variations of an attribute name may represent the
3293           * same attribute in HTML, therefore it’s possible that the
3294           * input array might contain duplicate attributes even though
3295           * it’s keyed on their name. Calling code should rewrite an
3296           * attribute’s value rather than sending a duplicate attribute.
3297           *
3298           * Example:
3299           *
3300           *     array( 'id' => 'main', 'ID' => 'nav' )
3301           *
3302           * In this example, there are two keys both describing the `id`
3303           * attribute. PHP array iteration is in key-insertion order so
3304           * the 'id' value will be set in the SCRIPT tag.
3305           */
3306          if ( null !== $processor->get_attribute( $name ) ) {
3307              continue;
3308          }
3309  
3310          $processor->set_attribute( $name, $value ?? true );
3311      }
3312      return "{$processor->get_updated_html()}\n";
3313  }
3314  
3315  /**
3316   * Prints formatted `<script>` loader tag.
3317   *
3318   * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_script_attributes'} filter.
3319   * Automatically injects type attribute if needed.
3320   *
3321   * @since 5.7.0
3322   *
3323   * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes.
3324   */
3325  function wp_print_script_tag( $attributes ) {
3326      echo wp_get_script_tag( $attributes );
3327  }
3328  
3329  /**
3330   * Constructs an inline script tag.
3331   *
3332   * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_inline_script_attributes'} filter.
3333   *
3334   * If the `$data` is unsafe to embed in a `<script>` tag, an empty script tag with the provided
3335   * attributes will be returned. JavaScript and JSON contents can be escaped, so this is only likely
3336   * to be a problem with unusual content types.
3337   *
3338   * Example:
3339   *
3340   *     // The dangerous JavaScript in this example will be safely escaped.
3341   *     // A string with the script tag and the desired contents will be returned.
3342   *     wp_get_inline_script_tag( 'console.log( "</script>" );' );
3343   *
3344   *     // This data is unsafe and `text/plain` cannot be escaped.
3345   *     // The following will return `""` to indicate failure:
3346   *     wp_get_inline_script_tag( '</script>', array( 'type' => 'text/plain' ) );
3347   *
3348   * @since 5.7.0
3349   * @since 7.0.0 Returns an empty string if the data cannot be safely embedded in a script tag.
3350   *
3351   * @param string                     $data       Data for script tag: JavaScript, importmap, speculationrules, etc.
3352   * @param array<string, string|bool> $attributes Optional. Key-value pairs representing `<script>` tag attributes.
3353   * @return string HTML script tag containing the provided $data or the empty string `""` if the data cannot be safely embedded in a script tag.
3354   */
3355  function wp_get_inline_script_tag( $data, $attributes = array() ) {
3356      $data = "\n" . trim( $data, "\n\r " ) . "\n";
3357  
3358      /**
3359       * Filters attributes to be added to a script tag.
3360       *
3361       * Escaping and normalization can make the text a browser parses from the printed tag differ
3362       * from `$data`. A callback that needs that text, such as one computing a CSP hash, can set
3363       * `$data` as the modifiable text of a SCRIPT tag in a `WP_HTML_Tag_Processor` with the
3364       * filtered attributes applied and then read {@see WP_HTML_Tag_Processor::get_modifiable_text()},
3365       * at a late priority so that the attributes are final.
3366       *
3367       * @since 5.7.0
3368       *
3369       * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes.
3370       *                                               Only the attribute name is added to the `<script>` tag for
3371       *                                               entries with a boolean value, and that are true.
3372       * @param string                     $data       Inline data.
3373       */
3374      $attributes = apply_filters( 'wp_inline_script_attributes', $attributes, $data );
3375  
3376      $processor = new WP_HTML_Tag_Processor( '<script></script>' );
3377      $processor->next_tag();
3378      foreach ( $attributes as $name => $value ) {
3379          /*
3380           * Lexical variations of an attribute name may represent the
3381           * same attribute in HTML, therefore it’s possible that the
3382           * input array might contain duplicate attributes even though
3383           * it’s keyed on their name. Calling code should rewrite an
3384           * attribute’s value rather than sending a duplicate attribute.
3385           *
3386           * Example:
3387           *
3388           *     array( 'id' => 'main', 'ID' => 'nav' )
3389           *
3390           * In this example, there are two keys both describing the `id`
3391           * attribute. PHP array iteration is in key-insertion order so
3392           * the 'id' value will be set in the SCRIPT tag.
3393           */
3394          if ( null !== $processor->get_attribute( $name ) ) {
3395              continue;
3396          }
3397  
3398          $processor->set_attribute( $name, $value ?? true );
3399      }
3400  
3401      if ( ! $processor->set_modifiable_text( $data ) ) {
3402          return '';
3403      }
3404  
3405      return "{$processor->get_updated_html()}\n";
3406  }
3407  
3408  /**
3409   * Prints an inline script tag.
3410   *
3411   * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_inline_script_attributes'} filter.
3412   * Automatically injects type attribute if needed.
3413   *
3414   * @since 5.7.0
3415   *
3416   * @param string                     $data       Data for script tag: JavaScript, importmap, speculationrules, etc.
3417   * @param array<string, string|bool> $attributes Optional. Key-value pairs representing `<script>` tag attributes.
3418   */
3419  function wp_print_inline_script_tag( $data, $attributes = array() ) {
3420      echo wp_get_inline_script_tag( $data, $attributes );
3421  }
3422  
3423  /**
3424   * Allows small styles to be inlined.
3425   *
3426   * This improves performance and sustainability, and is opt-in. Stylesheets can opt in
3427   * by adding `path` data using `wp_style_add_data`, and defining the file's absolute path:
3428   *
3429   *     wp_style_add_data( $style_handle, 'path', $file_path );
3430   *
3431   * @since 5.8.0
3432   *
3433   * @global WP_Styles $wp_styles
3434   */
3435  function wp_maybe_inline_styles() {
3436      global $wp_styles;
3437  
3438      $total_inline_limit = 40000;
3439      /**
3440       * The maximum size of inlined styles in bytes.
3441       *
3442       * @since 5.8.0
3443       * @since 6.9.0 The default limit increased from 20K to 40K.
3444       *
3445       * @param int $total_inline_limit The file-size threshold, in bytes. Default 40000.
3446       */
3447      $total_inline_limit = apply_filters( 'styles_inline_size_limit', $total_inline_limit );
3448  
3449      $styles = array();
3450  
3451      // Build an array of styles that have a path defined.
3452      foreach ( $wp_styles->queue as $handle ) {
3453          if ( ! isset( $wp_styles->registered[ $handle ] ) ) {
3454              continue;
3455          }
3456          $src  = $wp_styles->registered[ $handle ]->src;
3457          $path = $wp_styles->get_data( $handle, 'path' );
3458          if ( $path && $src ) {
3459              $size = wp_filesize( $path );
3460              if ( 0 === $size && ! file_exists( $path ) ) {
3461                  _doing_it_wrong(
3462                      __FUNCTION__,
3463                      sprintf(
3464                          /* translators: 1: 'path', 2: filesystem path, 3: style handle */
3465                          __( 'Unable to read the "%1$s" key with value "%2$s" for stylesheet "%3$s".' ),
3466                          'path',
3467                          esc_html( $path ),
3468                          esc_html( $handle )
3469                      ),
3470                      '7.0.0'
3471                  );
3472                  continue;
3473              }
3474              $styles[] = array(
3475                  'handle' => $handle,
3476                  'src'    => $src,
3477                  'path'   => $path,
3478                  'size'   => $size,
3479              );
3480          }
3481      }
3482  
3483      if ( ! empty( $styles ) ) {
3484          // Reorder styles array based on size.
3485          usort(
3486              $styles,
3487              static function ( $a, $b ) {
3488                  return $a['size'] <=> $b['size'];
3489              }
3490          );
3491  
3492          /*
3493           * The total inlined size.
3494           *
3495           * On each iteration of the loop, if a style gets added inline the value of this var increases
3496           * to reflect the total size of inlined styles.
3497           */
3498          $total_inline_size = 0;
3499  
3500          // Loop styles.
3501          foreach ( $styles as $style ) {
3502  
3503              // Size check. Since styles are ordered by size, we can break the loop.
3504              if ( $total_inline_size + $style['size'] > $total_inline_limit ) {
3505                  break;
3506              }
3507  
3508              // Get the styles if we don't already have them.
3509              if ( ! is_readable( $style['path'] ) ) {
3510                  _doing_it_wrong(
3511                      __FUNCTION__,
3512                      sprintf(
3513                          /* translators: 1: 'path', 2: filesystem path, 3: style handle */
3514                          __( 'Unable to read the "%1$s" key with value "%2$s" for stylesheet "%3$s".' ),
3515                          'path',
3516                          esc_html( $style['path'] ),
3517                          esc_html( $style['handle'] )
3518                      ),
3519                      '7.0.0'
3520                  );
3521                  continue;
3522              }
3523              $style['css'] = file_get_contents( $style['path'] );
3524  
3525              /*
3526               * Check if the style contains relative URLs that need to be modified.
3527               * URLs relative to the stylesheet's path should be converted to relative to the site's root.
3528               */
3529              $style['css'] = _wp_normalize_relative_css_links( $style['css'], $style['src'] );
3530  
3531              // Keep track of the original `src` for the style that was inlined so that the `sourceURL` comment can be added.
3532              $wp_styles->add_data( $style['handle'], 'inlined_src', $style['src'] );
3533  
3534              // Set `src` to `false` and add styles inline.
3535              $wp_styles->registered[ $style['handle'] ]->src = false;
3536              if ( empty( $wp_styles->registered[ $style['handle'] ]->extra['after'] ) ) {
3537                  $wp_styles->registered[ $style['handle'] ]->extra['after'] = array();
3538              }
3539              array_unshift( $wp_styles->registered[ $style['handle'] ]->extra['after'], $style['css'] );
3540  
3541              // Add the styles size to the $total_inline_size var.
3542              $total_inline_size += (int) $style['size'];
3543          }
3544      }
3545  }
3546  
3547  /**
3548   * Makes URLs relative to the WordPress installation.
3549   *
3550   * @since 5.9.0
3551   * @access private
3552   *
3553   * @param string $css            The CSS to make URLs relative to the WordPress installation.
3554   * @param string $stylesheet_url The URL to the stylesheet.
3555   * @return string The CSS with URLs made relative to the WordPress installation.
3556   */
3557  function _wp_normalize_relative_css_links( $css, $stylesheet_url ) {
3558      return preg_replace_callback(
3559          '#(url\s*\(\s*[\'"]?\s*)([^\'"\)]+)#',
3560          static function ( $matches ) use ( $stylesheet_url ) {
3561              list( , $prefix, $url ) = $matches;
3562  
3563              // Short-circuit if the URL does not require normalization.
3564              if (
3565                  str_starts_with( $url, 'http:' ) ||
3566                  str_starts_with( $url, 'https:' ) ||
3567                  str_starts_with( $url, '/' ) ||
3568                  str_starts_with( $url, '#' ) ||
3569                  str_starts_with( $url, 'data:' )
3570              ) {
3571                  return $matches[0];
3572              }
3573  
3574              // Build the absolute URL.
3575              $absolute_url = dirname( $stylesheet_url ) . '/' . $url;
3576              $absolute_url = str_replace( '/./', '/', $absolute_url );
3577  
3578              // Convert to URL related to the site root.
3579              $url = wp_make_link_relative( $absolute_url );
3580  
3581              return $prefix . $url;
3582          },
3583          $css
3584      );
3585  }
3586  
3587  /**
3588   * Function that enqueues the CSS Custom Properties coming from theme.json.
3589   *
3590   * @since 5.9.0
3591   */
3592  function wp_enqueue_global_styles_css_custom_properties() {
3593      wp_register_style( 'global-styles-css-custom-properties', false );
3594      wp_add_inline_style( 'global-styles-css-custom-properties', wp_get_global_stylesheet( array( 'variables' ) ) );
3595      wp_enqueue_style( 'global-styles-css-custom-properties' );
3596  }
3597  
3598  /**
3599   * Hooks inline styles in the proper place, depending on the active theme.
3600   *
3601   * @since 5.9.1
3602   * @since 6.1.0 Added the `$priority` parameter.
3603   *
3604   * For block themes, styles are loaded in the head.
3605   * For classic ones, styles are loaded in the body because the wp_head action happens before render_block.
3606   *
3607   * @link https://core.trac.wordpress.org/ticket/53494.
3608   *
3609   * @param string $style    String containing the CSS styles to be added.
3610   * @param int    $priority To set the priority for the add_action.
3611   */
3612  function wp_enqueue_block_support_styles( $style, $priority = 10 ) {
3613      $action_hook_name = 'wp_footer';
3614      if ( wp_is_block_theme() ) {
3615          $action_hook_name = 'wp_head';
3616      }
3617      add_action(
3618          $action_hook_name,
3619          static function () use ( $style ) {
3620              $processor = new WP_HTML_Tag_Processor( '<style></style>' );
3621              $processor->next_tag();
3622              $processor->set_modifiable_text( $style );
3623              echo "{$processor->get_updated_html()}\n";
3624          },
3625          $priority
3626      );
3627  }
3628  
3629  /**
3630   * Fetches, processes and compiles stored core styles, then combines and renders them to the page.
3631   * Styles are stored via the style engine API.
3632   *
3633   * @link https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/
3634   *
3635   * @since 6.1.0
3636   *
3637   * @param array<string, bool> $options {
3638   *     Optional. An array of options to pass to wp_style_engine_get_stylesheet_from_context().
3639   *     Default empty array.
3640   *
3641   *     @type bool $optimize Whether to optimize the CSS output, e.g., combine rules.
3642   *                          Default false.
3643   *     @type bool $prettify Whether to add new lines and indents to output.
3644   *                          Default to whether the `SCRIPT_DEBUG` constant is defined.
3645   * }
3646   */
3647  function wp_enqueue_stored_styles( $options = array() ) {
3648      // Note: Styles printed at wp_footer for classic themes may still end up in the head due to wp_load_classic_theme_block_styles_on_demand().
3649      $is_block_theme   = wp_is_block_theme();
3650      $is_classic_theme = ! $is_block_theme;
3651  
3652      /*
3653       * For block themes, this function prints stored styles in the header.
3654       * For classic themes, in the footer.
3655       */
3656      if (
3657          ( $is_block_theme && doing_action( 'wp_footer' ) ) ||
3658          ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) )
3659      ) {
3660          return;
3661      }
3662  
3663      $core_styles_keys         = array( 'block-supports' );
3664      $compiled_core_stylesheet = '';
3665      $style_tag_id             = 'core';
3666      // Adds comment if code is prettified to identify core styles sections in debugging.
3667      $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG;
3668      foreach ( $core_styles_keys as $style_key ) {
3669          if ( $should_prettify ) {
3670              $compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n";
3671          }
3672          // Chains core store ids to signify what the styles contain.
3673          $style_tag_id             .= '-' . $style_key;
3674          $compiled_core_stylesheet .= wp_style_engine_get_stylesheet_from_context( $style_key, $options );
3675      }
3676  
3677      // Combines Core styles.
3678      if ( ! empty( $compiled_core_stylesheet ) ) {
3679          wp_register_style( $style_tag_id, false );
3680          wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet );
3681          wp_enqueue_style( $style_tag_id );
3682      }
3683  
3684      // Prints out any other stores registered by themes or otherwise.
3685      $additional_stores = WP_Style_Engine_CSS_Rules_Store::get_stores();
3686      foreach ( array_keys( $additional_stores ) as $store_name ) {
3687          if ( in_array( $store_name, $core_styles_keys, true ) ) {
3688              continue;
3689          }
3690          $styles = wp_style_engine_get_stylesheet_from_context( $store_name, $options );
3691          if ( ! empty( $styles ) ) {
3692              $key = "wp-style-engine-$store_name";
3693              wp_register_style( $key, false );
3694              wp_add_inline_style( $key, $styles );
3695              wp_enqueue_style( $key );
3696          }
3697      }
3698  }
3699  
3700  /**
3701   * Enqueues a stylesheet for a specific block.
3702   *
3703   * If the theme has opted-in to load block styles on demand,
3704   * then the stylesheet will be enqueued on-render,
3705   * otherwise when the block inits.
3706   *
3707   * @since 5.9.0
3708   *
3709   * @param string                                   $block_name The block-name, including namespace.
3710   * @param array<string, string|string[]|bool|null> $args       {
3711   *     An array of arguments. See wp_register_style() for full information about each argument.
3712   *
3713   *     @type string           $handle The handle for the stylesheet.
3714   *     @type string|false     $src    The source URL of the stylesheet.
3715   *     @type string[]         $deps   Array of registered stylesheet handles this stylesheet depends on.
3716   *     @type string|bool|null $ver    Stylesheet version number.
3717   *     @type string           $media  The media for which this stylesheet has been defined.
3718   *     @type string|null      $path   Absolute path to the stylesheet, so that it can potentially be inlined.
3719   * }
3720   */
3721  function wp_enqueue_block_style( $block_name, $args ) {
3722      $args = wp_parse_args(
3723          $args,
3724          array(
3725              'handle' => '',
3726              'src'    => '',
3727              'deps'   => array(),
3728              'ver'    => false,
3729              'media'  => 'all',
3730          )
3731      );
3732  
3733      /**
3734       * Callback function to register and enqueue styles.
3735       *
3736       * @param string $content When the callback is used for the render_block filter,
3737       *                        the content needs to be returned so the function parameter
3738       *                        is to ensure the content exists.
3739       * @return string Block content.
3740       */
3741      $callback = static function ( $content ) use ( $args ) {
3742          // Register the stylesheet.
3743          if ( ! empty( $args['src'] ) ) {
3744              wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] );
3745          }
3746  
3747          // Add `path` data if provided.
3748          if ( isset( $args['path'] ) ) {
3749              wp_style_add_data( $args['handle'], 'path', $args['path'] );
3750  
3751              // Get the RTL file path.
3752              $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] );
3753  
3754              // Add RTL stylesheet.
3755              if ( file_exists( $rtl_file_path ) ) {
3756                  wp_style_add_data( $args['handle'], 'rtl', 'replace' );
3757  
3758                  if ( is_rtl() ) {
3759                      wp_style_add_data( $args['handle'], 'path', $rtl_file_path );
3760                  }
3761              }
3762          }
3763  
3764          // Enqueue the stylesheet.
3765          wp_enqueue_style( $args['handle'] );
3766  
3767          return $content;
3768      };
3769  
3770      $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts';
3771      if ( wp_should_load_block_assets_on_demand() ) {
3772          /**
3773           * Callback function to register and enqueue styles.
3774           *
3775           * @param string $content The block content.
3776           * @param array  $block   The full block, including name and attributes.
3777           * @return string Block content.
3778           */
3779          $callback_separate = static function ( $content, $block ) use ( $block_name, $callback ) {
3780              if ( ! empty( $block['blockName'] ) && $block_name === $block['blockName'] ) {
3781                  return $callback( $content );
3782              }
3783              return $content;
3784          };
3785  
3786          /*
3787           * The filter's callback here is an anonymous function because
3788           * using a named function in this case is not possible.
3789           *
3790           * The function cannot be unhooked, however, users are still able
3791           * to dequeue the stylesheets registered/enqueued by the callback
3792           * which is why in this case, using an anonymous function
3793           * was deemed acceptable.
3794           */
3795          add_filter( 'render_block', $callback_separate, 10, 2 );
3796          return;
3797      }
3798  
3799      /*
3800       * The filter's callback here is an anonymous function because
3801       * using a named function in this case is not possible.
3802       *
3803       * The function cannot be unhooked, however, users are still able
3804       * to dequeue the stylesheets registered/enqueued by the callback
3805       * which is why in this case, using an anonymous function
3806       * was deemed acceptable.
3807       */
3808      add_filter( $hook, $callback );
3809  
3810      // Enqueue assets in the editor.
3811      add_action( 'enqueue_block_assets', $callback );
3812  }
3813  
3814  /**
3815   * Loads classic theme styles when the current theme lacks a theme.json file.
3816   *
3817   * This is used for backwards compatibility for Button and File blocks specifically.
3818   *
3819   * @since 6.1.0
3820   * @since 6.2.0 Added File block styles.
3821   * @since 6.8.0 Moved stylesheet registration outside of this function.
3822   */
3823  function wp_enqueue_classic_theme_styles() {
3824      if ( ! wp_theme_has_theme_json() ) {
3825          wp_enqueue_style( 'classic-theme-styles' );
3826      }
3827  }
3828  
3829  /**
3830   * Enqueues the assets required for the Command Palette.
3831   *
3832   * @since 6.9.0
3833   *
3834   * @global array  $menu
3835   * @global array  $submenu
3836   */
3837  function wp_enqueue_command_palette_assets() {
3838      global $menu, $submenu;
3839  
3840      $command_palette_settings = array(
3841          'is_network_admin' => is_network_admin(),
3842      );
3843  
3844      /**
3845       * Extracts root-level text nodes from HTML string.
3846       *
3847       * @ignore
3848       * @param string $label HTML string to extract text from.
3849       * @return string Extracted text content, trimmed.
3850       */
3851      $extract_root_text = static function ( string $label ): string {
3852          if ( '' === $label ) {
3853              return '';
3854          }
3855  
3856          $processor  = new WP_HTML_Tag_Processor( $label );
3857          $text_parts = array();
3858          $depth      = 0;
3859  
3860          while ( $processor->next_token() ) {
3861              $token_type = $processor->get_token_type();
3862  
3863              if ( '#text' === $token_type ) {
3864                  if ( 0 === $depth ) {
3865                      $text_parts[] = $processor->get_modifiable_text();
3866                  }
3867                  continue;
3868              }
3869  
3870              if ( '#tag' !== $token_type ) {
3871                  continue;
3872              }
3873  
3874              if ( $processor->is_tag_closer() ) {
3875                  if ( $depth > 0 ) {
3876                      --$depth;
3877                  }
3878                  continue;
3879              }
3880  
3881              $token_name = $processor->get_tag();
3882              if ( $token_name && ! WP_HTML_Processor::is_void( $token_name ) ) {
3883                  ++$depth;
3884              }
3885          }
3886  
3887          return trim( implode( '', $text_parts ) );
3888      };
3889  
3890      if ( $menu ) {
3891          $menu_commands = array();
3892          foreach ( $menu as $menu_item ) {
3893              if ( empty( $menu_item[0] ) || ! is_string( $menu_item[0] ) || ! empty( $menu_item[1] ) && ! current_user_can( $menu_item[1] ) ) {
3894                  continue;
3895              }
3896  
3897              $menu_label = $extract_root_text( $menu_item[0] );
3898              $menu_url   = '';
3899              $menu_slug  = $menu_item[2];
3900  
3901              if ( preg_match( '/\.php($|\?)/', $menu_slug ) || wp_http_validate_url( $menu_slug ) ) {
3902                  $menu_url = $menu_slug;
3903              } elseif ( ! empty( menu_page_url( $menu_slug, false ) ) ) {
3904                  $menu_url = WP_HTML_Decoder::decode_attribute( menu_page_url( $menu_slug, false ) );
3905              }
3906  
3907              if ( $menu_url ) {
3908                  $menu_commands[] = array(
3909                      'label' => $menu_label,
3910                      'url'   => $menu_url,
3911                      'name'  => $menu_slug,
3912                  );
3913              }
3914  
3915              if ( array_key_exists( $menu_slug, $submenu ) ) {
3916                  foreach ( $submenu[ $menu_slug ] as $submenu_item ) {
3917                      if ( empty( $submenu_item[0] ) || ! empty( $submenu_item[1] ) && ! current_user_can( $submenu_item[1] ) ) {
3918                          continue;
3919                      }
3920  
3921                      $submenu_label = $extract_root_text( $submenu_item[0] );
3922                      $submenu_url   = '';
3923                      $submenu_slug  = $submenu_item[2];
3924  
3925                      if ( preg_match( '/\.php($|\?)/', $submenu_slug ) || wp_http_validate_url( $submenu_slug ) ) {
3926                          $submenu_url = $submenu_slug;
3927                      } elseif ( ! empty( menu_page_url( $submenu_slug, false ) ) ) {
3928                          $submenu_url = WP_HTML_Decoder::decode_attribute( menu_page_url( $submenu_slug, false ) );
3929                      }
3930                      if ( $submenu_url ) {
3931                          $menu_commands[] = array(
3932                              'label' => sprintf(
3933                                  /* translators: 1: Menu label, 2: Submenu label. */
3934                                  __( '%1$s > %2$s' ),
3935                                  $menu_label,
3936                                  $submenu_label
3937                              ),
3938                              'url'   => $submenu_url,
3939                              'name'  => $menu_slug . '-' . $submenu_item[2],
3940                          );
3941                      }
3942                  }
3943              }
3944          }
3945          $command_palette_settings['menu_commands'] = $menu_commands;
3946      }
3947  
3948      wp_enqueue_script( 'wp-commands' );
3949      // Prefetched from the login screen by wp_prefetch_admin_assets(), which needs updating if this changes.
3950      wp_enqueue_style( 'wp-commands' );
3951      wp_enqueue_script( 'wp-core-commands' );
3952  
3953      wp_add_inline_script(
3954          'wp-core-commands',
3955          sprintf(
3956              'wp.coreCommands.initializeCommandPalette( %s );',
3957              wp_json_encode( $command_palette_settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
3958          )
3959      );
3960  }
3961  
3962  /**
3963   * Removes leading and trailing _empty_ script tags.
3964   *
3965   * This is a helper meant to be used for literal script tag construction
3966   * within `wp_get_inline_script_tag()` or `wp_print_inline_script_tag()`.
3967   * It removes the literal values of "<script>" and "</script>" from
3968   * around an inline script after trimming whitespace. Typically this
3969   * is used in conjunction with output buffering, where `ob_get_clean()`
3970   * is passed as the `$contents` argument.
3971   *
3972   * Example:
3973   *
3974   *     // Strips exact literal empty SCRIPT tags.
3975   *     $js = '<script>sayHello();</script>;
3976   *     'sayHello();' === wp_remove_surrounding_empty_script_tags( $js );
3977   *
3978   *     // Otherwise if anything is different it warns in the JS console.
3979   *     $js = '<script type="module">console.log( "hi" );</script>';
3980   *     'console.error( ... )' === wp_remove_surrounding_empty_script_tags( $js );
3981   *
3982   * @since 6.4.0
3983   * @access private
3984   *
3985   * @see wp_print_inline_script_tag()
3986   * @see wp_get_inline_script_tag()
3987   *
3988   * @param string $contents Script body with manually created SCRIPT tag literals.
3989   * @return string Script body without surrounding script tag literals, or
3990   *                original contents if both exact literals aren't present.
3991   */
3992  function wp_remove_surrounding_empty_script_tags( $contents ) {
3993      $contents = trim( $contents );
3994      $opener   = '<SCRIPT>';
3995      $closer   = '</SCRIPT>';
3996  
3997      if (
3998          strlen( $contents ) > strlen( $opener ) + strlen( $closer ) &&
3999          strtoupper( substr( $contents, 0, strlen( $opener ) ) ) === $opener &&
4000          strtoupper( substr( $contents, -strlen( $closer ) ) ) === $closer
4001      ) {
4002          return substr( $contents, strlen( $opener ), -strlen( $closer ) );
4003      } else {
4004          $error_message = __( 'Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.' );
4005          _doing_it_wrong( __FUNCTION__, $error_message, '6.4' );
4006          return sprintf(
4007              'console.error(%s)',
4008              wp_json_encode(
4009                  sprintf(
4010                      /* translators: %s: wp_remove_surrounding_empty_script_tags() */
4011                      __( 'Function %s used incorrectly in PHP.' ),
4012                      'wp_remove_surrounding_empty_script_tags()'
4013                  ) . ' ' . $error_message
4014              )
4015          );
4016      }
4017  }
4018  
4019  /**
4020   * Adds hooks to load block styles on demand in classic themes.
4021   *
4022   * This function must be called before {@see wp_default_styles()} and {@see register_core_block_style_handles()} so that
4023   * the filters are added to cause {@see wp_should_load_separate_core_block_assets()} to return true.
4024   *
4025   * @since 6.9.0
4026   * @since 7.0.0 This is now invoked at the `wp_default_styles` action with priority 0 instead of at `init` with priority 8.
4027   *
4028   * @see _add_default_theme_supports()
4029   */
4030  function wp_load_classic_theme_block_styles_on_demand(): void {
4031      // This is not relevant to block themes, as they are opted in to loading separate styles on demand via _add_default_theme_supports().
4032      if ( wp_is_block_theme() ) {
4033          return;
4034      }
4035  
4036      /*
4037       * Make sure that wp_should_output_buffer_template_for_enhancement() returns true even if there aren't any
4038       * `wp_template_enhancement_output_buffer` filters added, but do so at priority zero so that applications which
4039       * wish to stream responses can more easily turn this off.
4040       */
4041      add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 );
4042  
4043      // If a site has opted out of the template enhancement output buffer, then bail.
4044      if ( ! wp_should_output_buffer_template_for_enhancement() ) {
4045          return;
4046      }
4047  
4048      // The following two filters are added by default for block themes in _add_default_theme_supports().
4049  
4050      /*
4051       * Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally, and so
4052       * that block-specific styles will only be enqueued when they are used on the page. A priority of zero allows for
4053       * this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading
4054       * separate block styles, then abort.
4055       */
4056      add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 );
4057      if ( ! wp_should_load_separate_core_block_assets() ) {
4058          return;
4059      }
4060  
4061      /*
4062       * Also ensure that block assets are loaded on demand (although the default value is from should_load_separate_core_block_assets).
4063       * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site
4064       * has explicitly opted out of loading block styles on demand, then abort.
4065       */
4066      add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 );
4067      if ( ! wp_should_load_block_assets_on_demand() ) {
4068          return;
4069      }
4070  
4071      // Add hooks which require the presence of the output buffer. Ideally the above two filters could be added here, but they run too early.
4072      add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' );
4073  }
4074  
4075  /**
4076   * Adds the hooks needed for CSS output to be delayed until after the content of the page has been established.
4077   *
4078   * @since 6.9.0
4079   *
4080   * @see wp_load_classic_theme_block_styles_on_demand()
4081   * @see _wp_footer_scripts()
4082   */
4083  function wp_hoist_late_printed_styles(): void {
4084      // Skip the embed template on-demand styles aren't relevant, and there is no wp_head action.
4085      if ( is_embed() ) {
4086          return;
4087      }
4088  
4089      /*
4090       * Add a placeholder comment into the inline styles for wp-block-library, after which the late block styles
4091       * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement
4092       * output buffer.
4093       *
4094       * Note that wp_maybe_inline_styles() prepends the inlined style to the extra 'after' array, which happens after
4095       * this code runs. This ensures that the placeholder appears right after any inlined wp-block-library styles,
4096       * which would be common.css.
4097       */
4098      $placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) );
4099      $dependency  = wp_styles()->query( 'wp-block-library', 'registered' );
4100      if ( $dependency ) {
4101          if ( ! isset( $dependency->extra['after'] ) ) {
4102              wp_add_inline_style( 'wp-block-library', $placeholder );
4103          } else {
4104              array_unshift( $dependency->extra['after'], $placeholder );
4105          }
4106      }
4107  
4108      /*
4109       * Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print
4110       * the styles, but it captures what would be printed for block styles and non-block styles so that they can be
4111       * later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts`
4112       * before `print_footer_scripts()` is called.
4113       */
4114      $printed_core_block_styles  = '';
4115      $printed_other_block_styles = '';
4116      $printed_global_styles      = '';
4117      $printed_late_styles        = '';
4118  
4119      $capture_late_styles = static function () use ( &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) {
4120          // Gather the styles related to on-demand block enqueues.
4121          $all_core_block_style_handles  = array();
4122          $all_other_block_style_handles = array();
4123          foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) {
4124              if ( str_starts_with( $block_type->name, 'core/' ) ) {
4125                  foreach ( $block_type->style_handles as $style_handle ) {
4126                      $all_core_block_style_handles[] = $style_handle;
4127                  }
4128              } else {
4129                  foreach ( $block_type->style_handles as $style_handle ) {
4130                      $all_other_block_style_handles[] = $style_handle;
4131                  }
4132              }
4133          }
4134  
4135          /*
4136           * First print all styles related to core blocks which should be inserted right after the wp-block-library stylesheet
4137           * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`.
4138           */
4139          $enqueued_core_block_styles = array_values( array_intersect( $all_core_block_style_handles, wp_styles()->queue ) );
4140          if ( count( $enqueued_core_block_styles ) > 0 ) {
4141              ob_start();
4142              wp_styles()->do_items( $enqueued_core_block_styles );
4143              $printed_core_block_styles = (string) ob_get_clean();
4144          }
4145  
4146          // Capture non-core block styles so they can get printed at the point where wp_enqueue_registered_block_scripts_and_styles() runs.
4147          $enqueued_other_block_styles = array_values( array_intersect( $all_other_block_style_handles, wp_styles()->queue ) );
4148          if ( count( $enqueued_other_block_styles ) > 0 ) {
4149              ob_start();
4150              wp_styles()->do_items( $enqueued_other_block_styles );
4151              $printed_other_block_styles = (string) ob_get_clean();
4152          }
4153  
4154          // Capture the global-styles so that it can be printed at the point where wp_enqueue_global_styles() runs.
4155          if ( wp_style_is( 'global-styles' ) ) {
4156              ob_start();
4157              wp_styles()->do_items( array( 'global-styles' ) );
4158              $printed_global_styles = (string) ob_get_clean();
4159          }
4160  
4161          /*
4162           * Print all remaining styles not related to blocks. This contains a subset of the logic from
4163           * `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether
4164           * late styles are printed (since they are being hoisted anyway).
4165           */
4166          ob_start();
4167          wp_styles()->do_footer_items();
4168          $printed_late_styles = (string) ob_get_clean();
4169      };
4170  
4171      /*
4172       * If `_wp_footer_scripts()` was unhooked from the `wp_print_footer_scripts` action, or if `wp_print_footer_scripts()`
4173       * was unhooked from running at the `wp_footer` action, then only add a callback to `wp_footer` which will capture the
4174       * late-printed styles.
4175       *
4176       * Otherwise, in the normal case where `_wp_footer_scripts()` will run at the `wp_print_footer_scripts` action, then
4177       * swap out `_wp_footer_scripts()` with an alternative which captures the printed styles (for hoisting to HEAD) before
4178       * proceeding with printing the footer scripts.
4179       */
4180      $wp_print_footer_scripts_priority = has_action( 'wp_print_footer_scripts', '_wp_footer_scripts' );
4181      if ( false === $wp_print_footer_scripts_priority || false === has_action( 'wp_footer', 'wp_print_footer_scripts' ) ) {
4182          // The normal priority for wp_print_footer_scripts() is to run at 20.
4183          add_action( 'wp_footer', $capture_late_styles, 20 );
4184      } else {
4185          remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts', $wp_print_footer_scripts_priority );
4186          add_action(
4187              'wp_print_footer_scripts',
4188              static function () use ( $capture_late_styles ) {
4189                  $capture_late_styles();
4190                  print_footer_scripts();
4191              },
4192              $wp_print_footer_scripts_priority
4193          );
4194      }
4195  
4196      // Replace placeholder with the captured late styles.
4197      add_filter(
4198          'wp_template_enhancement_output_buffer',
4199          static function ( $buffer ) use ( $placeholder, &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) {
4200  
4201              // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans.
4202              $processor = new class( $buffer ) extends WP_HTML_Tag_Processor {
4203                  /**
4204                   * Gets the span for the current token.
4205                   *
4206                   * @return WP_HTML_Span Current token span.
4207                   */
4208  				private function get_span(): WP_HTML_Span {
4209                      // Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true.
4210                      $this->set_bookmark( 'here' );
4211                      return $this->bookmarks['here'];
4212                  }
4213  
4214                  /**
4215                   * Inserts text before the current token.
4216                   *
4217                   * @param string $text Text to insert.
4218                   */
4219  				public function insert_before( string $text ): void {
4220                      $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text );
4221                  }
4222  
4223                  /**
4224                   * Inserts text after the current token.
4225                   *
4226                   * @param string $text Text to insert.
4227                   */
4228  				public function insert_after( string $text ): void {
4229                      $span = $this->get_span();
4230  
4231                      $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text );
4232                  }
4233  
4234                  /**
4235                   * Removes the current token.
4236                   */
4237  				public function remove(): void {
4238                      $span = $this->get_span();
4239  
4240                      $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' );
4241                  }
4242  
4243                  /**
4244                   * Replaces the current token.
4245                   *
4246                   * @param string $text Text to replace with.
4247                   */
4248  				public function replace( string $text ): void {
4249                      $span = $this->get_span();
4250  
4251                      $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text );
4252                  }
4253              };
4254  
4255              // Locate the insertion points in the HEAD.
4256              while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
4257                  if (
4258                      'STYLE' === $processor->get_tag() &&
4259                      'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
4260                  ) {
4261                      /** This is added in {@see wp_enqueue_global_styles()} */
4262                      $processor->set_bookmark( 'wp_global_styles_placeholder' );
4263                  } elseif (
4264                      'STYLE' === $processor->get_tag() &&
4265                      'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' )
4266                  ) {
4267                      /** This is added in {@see wp_enqueue_registered_block_scripts_and_styles()} */
4268                      $processor->set_bookmark( 'wp_block_styles_placeholder' );
4269                  } elseif (
4270                      'STYLE' === $processor->get_tag() &&
4271                      'wp-block-library-inline-css' === $processor->get_attribute( 'id' )
4272                  ) {
4273                      /** This is added here in {@see wp_hoist_late_printed_styles()} */
4274                      $processor->set_bookmark( 'wp_block_library' );
4275                  } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) {
4276                      $processor->set_bookmark( 'head_end' );
4277                      break;
4278                  }
4279              }
4280  
4281              /**
4282               * Replace the placeholder for global styles enqueued during {@see wp_enqueue_global_styles()}. This is done
4283               * even if $printed_global_styles is empty.
4284               */
4285              if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) {
4286                  $processor->seek( 'wp_global_styles_placeholder' );
4287                  $processor->replace( $printed_global_styles );
4288                  $printed_global_styles = '';
4289              }
4290  
4291              /*
4292               * Insert block styles right after wp-block-library (if it is present). The placeholder CSS comment will
4293               * always be added to the wp-block-library inline style since it gets printed at `wp_head` before the blocks
4294               * are rendered. This means that there may not actually be any block styles to hoist from the footer to
4295               * insert after this inline style. The placeholder CSS comment needs to be added so that the inline style
4296               * gets printed, but if the resulting inline style is empty after the placeholder is removed, then the
4297               * inline style is removed.
4298               */
4299              if ( $processor->has_bookmark( 'wp_block_library' ) ) {
4300                  $processor->seek( 'wp_block_library' );
4301  
4302                  $css_text = $processor->get_modifiable_text();
4303  
4304                  /*
4305                   * Split the block library inline style by the placeholder to identify the original inlined CSS, which
4306                   * likely would be common.css, followed by any inline styles which had been added by the theme or
4307                   * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on
4308                   * demand will get inserted after the inlined common.css and before the extra inline styles added by the
4309                   * user.
4310                   */
4311                  $css_text_around_placeholder = explode( $placeholder, $css_text, 2 );
4312                  $extra_inline_styles         = '';
4313                  if ( count( $css_text_around_placeholder ) === 2 ) {
4314                      $css_text = $css_text_around_placeholder[0];
4315                      if ( '' !== trim( $css_text ) ) {
4316                          $inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' );
4317                          if ( $inlined_src ) {
4318                              $css_text .= sprintf(
4319                                  "\n/*# sourceURL=%s */\n",
4320                                  esc_url_raw( $inlined_src )
4321                              );
4322                          }
4323                      }
4324                      $extra_inline_styles = $css_text_around_placeholder[1];
4325                  }
4326  
4327                  /*
4328                   * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to
4329                   * be printed. Now that the inline style has been located and the placeholder comment has been removed, if
4330                   * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely.
4331                   */
4332                  if ( '' === trim( $css_text ) ) {
4333                      $processor->remove();
4334                  } else {
4335                      $processor->set_modifiable_text( $css_text );
4336                  }
4337  
4338                  $inserted_after            = $printed_core_block_styles;
4339                  $printed_core_block_styles = '';
4340  
4341                  /*
4342                   * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ).
4343                   * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when
4344                   * the combined block library stylesheet was used. The pattern here is checking to see if it is not just
4345                   * a sourceURL comment after the placeholder above is removed.
4346                   */
4347                  if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) {
4348                      $style_processor = new WP_HTML_Tag_Processor( '<style></style>' );
4349                      $style_processor->next_tag();
4350                      $style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' );
4351                      $style_processor->set_modifiable_text( $extra_inline_styles );
4352                      $inserted_after .= "{$style_processor->get_updated_html()}\n";
4353                  }
4354  
4355                  if ( '' !== $inserted_after ) {
4356                      $processor->insert_after( "\n" . $inserted_after );
4357                  }
4358              }
4359  
4360              // Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles.
4361              if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) {
4362                  $processor->seek( 'wp_block_styles_placeholder' );
4363                  if ( '' !== $printed_other_block_styles ) {
4364                      $processor->replace( "\n" . $printed_other_block_styles );
4365                  } else {
4366                      $processor->remove();
4367                  }
4368                  $printed_other_block_styles = '';
4369              }
4370  
4371              // Print all remaining styles.
4372              $remaining_styles = $printed_core_block_styles . $printed_other_block_styles . $printed_global_styles . $printed_late_styles;
4373              if ( $remaining_styles && $processor->has_bookmark( 'head_end' ) ) {
4374                  $processor->seek( 'head_end' );
4375                  $processor->insert_before( $remaining_styles . "\n" );
4376              }
4377              return $processor->get_updated_html();
4378          }
4379      );
4380  }
4381  
4382  /**
4383   * Return the corresponding JavaScript `dataset` name for an attribute
4384   * if it represents a custom data attribute, or `null` if not.
4385   *
4386   * Custom data attributes appear in an element's `dataset` property in a
4387   * browser, but there's a specific way the names are translated from HTML
4388   * into JavaScript. This function indicates how the name would appear in
4389   * JavaScript if a browser would recognize it as a custom data attribute.
4390   *
4391   * Example:
4392   *
4393   *     // Dash-letter pairs turn into capital letters.
4394   *     'postId'       === wp_js_dataset_name( 'data-post-id' );
4395   *     'Before'       === wp_js_dataset_name( 'data--before' );
4396   *     '-One--Two---' === wp_js_dataset_name( 'data---one---two---' );
4397   *
4398   *     // Not every attribute name will be interpreted as a custom data attribute.
4399   *     null === wp_js_dataset_name( 'post-id' );
4400   *     null === wp_js_dataset_name( 'data' );
4401   *
4402   *     // Some very surprising names will; for example, a property whose name is the empty string.
4403   *     '' === wp_js_dataset_name( 'data-' );
4404   *     0  === strlen( wp_js_dataset_name( 'data-' ) );
4405   *
4406   * @since 6.9.0
4407   *
4408   * @see \wp_html_custom_data_attribute_name()
4409   * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs
4410   *
4411   * @param string $html_attribute_name Raw attribute name as found in the source HTML.
4412   * @return string|null Transformed `dataset` name, if interpretable as a custom data attribute, else `null`.
4413   */
4414  function wp_js_dataset_name( string $html_attribute_name ): ?string {
4415      if ( 0 !== substr_compare( $html_attribute_name, 'data-', 0, 5, true ) ) {
4416          return null;
4417      }
4418  
4419      $end = strlen( $html_attribute_name );
4420  
4421      /*
4422       * If it contains characters which would end the attribute name parsing then
4423       * something else is wrong and this contains more than just an attribute name.
4424       */
4425      if ( ( $end - 5 ) !== strcspn( $html_attribute_name, "=/> \t\f\r\n", 5 ) ) {
4426          return null;
4427      }
4428  
4429      /**
4430       * > For each name in list, for each U+002D HYPHEN-MINUS character (-)
4431       * > in the name that is followed by an ASCII lower alpha, remove the
4432       * > U+002D HYPHEN-MINUS character (-) and replace the character that
4433       * > followed it by the same character converted to ASCII uppercase.
4434       *
4435       * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs
4436       */
4437      $custom_name = '';
4438      $at          = 5;
4439      $was_at      = $at;
4440  
4441      while ( $at < $end ) {
4442          $next_dash_at = strpos( $html_attribute_name, '-', $at );
4443          if ( false === $next_dash_at || $next_dash_at === $end - 1 ) {
4444              break;
4445          }
4446  
4447          // Transform `-a` to `A`, for example.
4448          $c = $html_attribute_name[ $next_dash_at + 1 ];
4449          if ( ( $c >= 'A' && $c <= 'Z' ) || ( $c >= 'a' && $c <= 'z' ) ) {
4450              $prefix       = substr( $html_attribute_name, $was_at, $next_dash_at - $was_at );
4451              $custom_name .= strtolower( $prefix );
4452              $custom_name .= strtoupper( $c );
4453              $at           = $next_dash_at + 2;
4454              $was_at       = $at;
4455              continue;
4456          }
4457  
4458          $at = $next_dash_at + 1;
4459      }
4460  
4461      // If nothing has been added it means there are no dash-letter pairs; return the name as-is.
4462      return '' === $custom_name
4463          ? strtolower( substr( $html_attribute_name, 5 ) )
4464          : ( $custom_name . strtolower( substr( $html_attribute_name, $was_at ) ) );
4465  }
4466  
4467  /**
4468   * Returns a corresponding HTML attribute name for the given name,
4469   * if that name were found in a JS element’s `dataset` property.
4470   *
4471   * Example:
4472   *
4473   *     'data-post-id'        === wp_html_custom_data_attribute_name( 'postId' );
4474   *     'data--before'        === wp_html_custom_data_attribute_name( 'Before' );
4475   *     'data---one---two---' === wp_html_custom_data_attribute_name( '-One--Two---' );
4476   *
4477   *     // Not every attribute name will be interpreted as a custom data attribute.
4478   *     null === wp_html_custom_data_attribute_name( '/not-an-attribute/' );
4479   *     null === wp_html_custom_data_attribute_name( 'no spaces' );
4480   *
4481   *     // Some very surprising names will; for example, a property whose name is the empty string.
4482   *     'data-' === wp_html_custom_data_attribute_name( '' );
4483   *
4484   * @since 6.9.0
4485   *
4486   * @see \wp_js_dataset_name()
4487   * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs
4488   *
4489   * @param string $js_dataset_name Name of JS `dataset` property to transform.
4490   * @return string|null Corresponding name of an HTML custom data attribute for the given dataset name,
4491   *                     if possible to represent in HTML, otherwise `null`.
4492   */
4493  function wp_html_custom_data_attribute_name( string $js_dataset_name ): ?string {
4494      $end = strlen( $js_dataset_name );
4495      if ( 0 === $end ) {
4496          return 'data-';
4497      }
4498  
4499      /*
4500       * If it contains characters which would end the attribute name parsing then
4501       * something it’s not possible to represent this in HTML.
4502       */
4503      if ( strcspn( $js_dataset_name, "=/> \t\f\r\n" ) !== $end ) {
4504          return null;
4505      }
4506  
4507      $html_name = 'data-';
4508      $at        = 0;
4509      $was_at    = $at;
4510  
4511      while ( $at < $end ) {
4512          $next_upper_after = strcspn( $js_dataset_name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', $at );
4513          $next_upper_at    = $at + $next_upper_after;
4514          if ( $next_upper_at >= $end ) {
4515              break;
4516          }
4517  
4518          $prefix     = substr( $js_dataset_name, $was_at, $next_upper_at - $was_at );
4519          $html_name .= strtolower( $prefix );
4520          $html_name .= '-' . strtolower( $js_dataset_name[ $next_upper_at ] );
4521          $at         = $next_upper_at + 1;
4522          $was_at     = $at;
4523      }
4524  
4525      if ( $was_at < $end ) {
4526          $html_name .= strtolower( substr( $js_dataset_name, $was_at ) );
4527      }
4528  
4529      return $html_name;
4530  }


Generated : Thu Oct 22 08:20:34 2026 Cross-referenced by PHPXref