| [ Index ] |
PHP Cross Reference of WordPress Trunk (Updated Daily) |
[Summary view] [Print] [Text view]
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 >' ), 979 'prev' => __( '< 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…' ), 1017 'deleted' => __( 'moved to the Trash.' ), 1018 /* translators: %s: File name. */ 1019 'error_uploading' => __( '“%s” 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 & Publish' ), 1288 'save' => __( 'Save & 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’s gone wrong. Wait a couple seconds, and then try again.' ), 1314 'themeDownloading' => __( 'Downloading your new theme…' ), 1315 'themePreviewWait' => __( 'Setting up your live preview. This may take a bit.' ), 1316 'revertingChanges' => __( 'Reverting unpublished changes…' ), 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 $scripts->set_translations( 'theme' ); 1457 1458 $scripts->add( 'inline-edit-post', "/wp-admin/js/inline-edit-post$suffix.js", array( 'jquery', 'tags-suggest', 'wp-a11y' ), false, 1 ); 1459 $scripts->set_translations( 'inline-edit-post' ); 1460 1461 $scripts->add( 'inline-edit-tax', "/wp-admin/js/inline-edit-tax$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 ); 1462 $scripts->set_translations( 'inline-edit-tax' ); 1463 1464 $scripts->add( 'plugin-install', "/wp-admin/js/plugin-install$suffix.js", array( 'jquery', 'jquery-ui-core', 'thickbox' ), false, 1 ); 1465 $scripts->set_translations( 'plugin-install' ); 1466 1467 $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 ); 1468 $scripts->set_translations( 'site-health' ); 1469 1470 $scripts->add( 'privacy-tools', "/wp-admin/js/privacy-tools$suffix.js", array( 'jquery', 'wp-a11y' ), false, 1 ); 1471 $scripts->set_translations( 'privacy-tools' ); 1472 1473 $scripts->add( 'updates', "/wp-admin/js/updates$suffix.js", array( 'common', 'jquery', 'wp-util', 'wp-a11y', 'wp-sanitize', 'wp-i18n' ), false, 1 ); 1474 $scripts->set_translations( 'updates' ); 1475 did_action( 'init' ) && $scripts->localize( 1476 'updates', 1477 '_wpUpdatesSettings', 1478 array( 1479 'ajax_nonce' => wp_installing() ? '' : wp_create_nonce( 'updates' ), 1480 ) 1481 ); 1482 1483 $scripts->add( 'farbtastic', '/wp-admin/js/farbtastic.js', array( 'jquery' ), '1.2' ); 1484 1485 $scripts->add( 'iris', '/wp-admin/js/iris.min.js', array( 'jquery-ui-draggable', 'jquery-ui-slider', 'jquery-touch-punch' ), '1.1.1', 1 ); 1486 $scripts->add( 'wp-color-picker', "/wp-admin/js/color-picker$suffix.js", array( 'iris' ), false, 1 ); 1487 $scripts->set_translations( 'wp-color-picker' ); 1488 1489 $scripts->add( 'dashboard', "/wp-admin/js/dashboard$suffix.js", array( 'common', 'jquery', 'admin-comments', 'postbox', 'wp-util', 'wp-a11y', 'wp-date' ), false, 1 ); 1490 $scripts->set_translations( 'dashboard' ); 1491 1492 // Deprecated, no longer used in core, registered for backward compatibility. 1493 $scripts->add( 'list-revisions', "/wp-includes/js/wp-list-revisions$suffix.js" ); 1494 1495 $scripts->add( 'media-grid', "/wp-includes/js/media-grid$suffix.js", array( 'media-editor' ), false, 1 ); 1496 $scripts->add( 'media', "/wp-admin/js/media$suffix.js", array( 'jquery', 'clipboard', 'wp-i18n', 'wp-a11y' ), false, 1 ); 1497 $scripts->set_translations( 'media' ); 1498 1499 $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 ); 1500 $scripts->set_translations( 'media-upload-pipeline' ); 1501 1502 $scripts->add( 'media-frame-upload', "/wp-admin/js/media-frame-upload$suffix.js", array( 'media-views', 'media-upload-pipeline' ), false, 1 ); 1503 1504 $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 ); 1505 $scripts->set_translations( 'media-new-upload' ); 1506 1507 $scripts->add( 'image-edit', "/wp-admin/js/image-edit$suffix.js", array( 'jquery', 'jquery-ui-core', 'imgareaselect', 'wp-a11y' ), false, 1 ); 1508 $scripts->set_translations( 'image-edit' ); 1509 1510 $scripts->add( 'set-post-thumbnail', "/wp-admin/js/set-post-thumbnail$suffix.js", array( 'jquery' ), false, 1 ); 1511 $scripts->set_translations( 'set-post-thumbnail' ); 1512 1513 /* 1514 * Navigation Menus: Adding underscore as a dependency to utilize _.debounce 1515 * see https://core.trac.wordpress.org/ticket/42321 1516 */ 1517 $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' ) ); 1518 $scripts->set_translations( 'nav-menu' ); 1519 1520 $scripts->add( 'custom-header', '/wp-admin/js/custom-header.js', array( 'jquery-masonry' ), false, 1 ); 1521 $scripts->add( 'custom-background', "/wp-admin/js/custom-background$suffix.js", array( 'wp-color-picker', 'media-views' ), false, 1 ); 1522 $scripts->add( 'media-gallery', "/wp-admin/js/media-gallery$suffix.js", array( 'jquery' ), false, 1 ); 1523 1524 $scripts->add( 'svg-painter', '/wp-admin/js/svg-painter.js', array( 'jquery' ), false, 1 ); 1525 } 1526 } 1527 1528 /** 1529 * Assigns default styles to $styles object. 1530 * 1531 * Nothing is returned, because the $styles parameter is passed by reference. 1532 * Meaning that whatever object is passed will be updated without having to 1533 * reassign the variable that was passed back to the same value. This saves 1534 * memory. 1535 * 1536 * Adding default styles is not the only task, it also assigns the base_url 1537 * property, the default version, and text direction for the object. 1538 * 1539 * @since 2.6.0 1540 * 1541 * @global array $editor_styles 1542 * 1543 * @param WP_Styles $styles 1544 */ 1545 function wp_default_styles( $styles ) { 1546 global $editor_styles; 1547 1548 $guessurl = site_url(); 1549 1550 if ( ! $guessurl ) { 1551 $guessurl = wp_guess_url(); 1552 } 1553 1554 $styles->base_url = $guessurl; 1555 $styles->content_url = defined( 'WP_CONTENT_URL' ) ? WP_CONTENT_URL : ''; 1556 $styles->default_version = get_bloginfo( 'version' ); 1557 $styles->text_direction = is_rtl() ? 'rtl' : 'ltr'; 1558 $styles->default_dirs = array( '/wp-admin/', '/wp-includes/css/' ); // Deprecated, but still set for plugins that read it. 1559 1560 // Open Sans is no longer used by core, but may be relied upon by themes and plugins. 1561 $open_sans_font_url = ''; 1562 1563 /* 1564 * translators: If there are characters in your language that are not supported 1565 * by Open Sans, translate this to 'off'. Do not translate into your own language. 1566 */ 1567 if ( 'off' !== _x( 'on', 'Open Sans font: on or off' ) ) { 1568 $subsets = 'latin,latin-ext'; 1569 1570 /* 1571 * translators: To add an additional Open Sans character subset specific to your language, 1572 * translate this to 'greek', 'cyrillic' or 'vietnamese'. Do not translate into your own language. 1573 */ 1574 $subset = _x( 'no-subset', 'Open Sans font: add new subset (greek, cyrillic, vietnamese)' ); 1575 1576 if ( 'cyrillic' === $subset ) { 1577 $subsets .= ',cyrillic,cyrillic-ext'; 1578 } elseif ( 'greek' === $subset ) { 1579 $subsets .= ',greek,greek-ext'; 1580 } elseif ( 'vietnamese' === $subset ) { 1581 $subsets .= ',vietnamese'; 1582 } 1583 1584 // Hotlink Open Sans, for now. 1585 $open_sans_font_url = "https://fonts.googleapis.com/css?family=Open+Sans:300italic,400italic,600italic,300,400,600&subset=$subsets&display=fallback"; 1586 } 1587 1588 // Register a stylesheet for the selected admin color scheme. 1589 $styles->add( 'colors', true, array( 'wp-admin', 'buttons' ) ); 1590 1591 $suffix = wp_scripts_get_suffix(); 1592 1593 // Admin CSS. 1594 $styles->add( 'wp-tooltip', "/wp-admin/css/wp-tooltip$suffix.css", array( 'dashicons' ) ); 1595 $styles->add( 'common', "/wp-admin/css/common$suffix.css" ); 1596 $styles->add( 'forms', "/wp-admin/css/forms$suffix.css" ); 1597 $styles->add( 'admin-menu', "/wp-admin/css/admin-menu$suffix.css" ); 1598 $styles->add( 'dashboard', "/wp-admin/css/dashboard$suffix.css" ); 1599 $styles->add( 'list-tables', "/wp-admin/css/list-tables$suffix.css" ); 1600 $styles->add( 'edit', "/wp-admin/css/edit$suffix.css" ); 1601 $styles->add( 'revisions', "/wp-admin/css/revisions$suffix.css" ); 1602 $styles->add( 'media', "/wp-admin/css/media$suffix.css" ); 1603 $styles->add( 'themes', "/wp-admin/css/themes$suffix.css" ); 1604 $styles->add( 'about', "/wp-admin/css/about$suffix.css" ); 1605 $styles->add( 'nav-menus', "/wp-admin/css/nav-menus$suffix.css" ); 1606 $styles->add( 'widgets', "/wp-admin/css/widgets$suffix.css", array( 'wp-pointer' ) ); 1607 $styles->add( 'site-icon', "/wp-admin/css/site-icon$suffix.css" ); 1608 $styles->add( 'l10n', "/wp-admin/css/l10n$suffix.css" ); 1609 $styles->add( 'code-editor', "/wp-admin/css/code-editor$suffix.css", array( 'wp-codemirror' ) ); 1610 $styles->add( 'site-health', "/wp-admin/css/site-health$suffix.css" ); 1611 1612 $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' ) ); 1613 1614 $styles->add( 'login', "/wp-admin/css/login$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles', 'wp-tooltip' ) ); 1615 $styles->add( 'install', "/wp-admin/css/install$suffix.css", array( 'dashicons', 'buttons', 'forms', 'l10n', 'wp-base-styles' ) ); 1616 $styles->add( 'wp-color-picker', "/wp-admin/css/color-picker$suffix.css" ); 1617 $styles->add( 'customize-controls', "/wp-admin/css/customize-controls$suffix.css", array( 'wp-admin', 'colors', 'imgareaselect' ) ); 1618 $styles->add( 'customize-widgets', "/wp-admin/css/customize-widgets$suffix.css", array( 'wp-admin', 'colors' ) ); 1619 $styles->add( 'customize-nav-menus', "/wp-admin/css/customize-nav-menus$suffix.css", array( 'wp-admin', 'colors' ) ); 1620 1621 // Common dependencies. 1622 $styles->add( 'buttons', "/wp-includes/css/buttons$suffix.css" ); 1623 $styles->add( 'dashicons', "/wp-includes/css/dashicons$suffix.css" ); 1624 1625 // Includes CSS. 1626 $styles->add( 'admin-bar', "/wp-includes/css/admin-bar$suffix.css", array( 'dashicons' ) ); 1627 $styles->add( 'wp-auth-check', "/wp-includes/css/wp-auth-check$suffix.css", array( 'dashicons' ) ); 1628 $styles->add( 'editor-buttons', "/wp-includes/css/editor$suffix.css", array( 'dashicons' ) ); 1629 $styles->add( 'media-views', "/wp-includes/css/media-views$suffix.css", array( 'buttons', 'dashicons', 'wp-mediaelement' ) ); 1630 $styles->add( 'wp-pointer', "/wp-includes/css/wp-pointer$suffix.css", array( 'dashicons' ) ); 1631 $styles->add( 'customize-preview', "/wp-includes/css/customize-preview$suffix.css", array( 'dashicons' ) ); 1632 $styles->add( 'wp-empty-template-alert', "/wp-includes/css/wp-empty-template-alert$suffix.css" ); 1633 $skip_link_style_path = WPINC . "/css/wp-block-template-skip-link$suffix.css"; 1634 $styles->add( 'wp-block-template-skip-link', "/$skip_link_style_path" ); 1635 $styles->add_data( 'wp-block-template-skip-link', 'path', ABSPATH . $skip_link_style_path ); 1636 1637 // External libraries and friends. 1638 $styles->add( 'imgareaselect', '/wp-includes/js/imgareaselect/imgareaselect.css', array(), '0.9.8' ); 1639 $styles->add( 'wp-jquery-ui-dialog', "/wp-includes/css/jquery-ui-dialog$suffix.css", array( 'dashicons' ) ); 1640 $styles->add( 'mediaelement', '/wp-includes/js/mediaelement/mediaelementplayer-legacy.min.css', array(), '4.2.17' ); 1641 $styles->add( 'wp-mediaelement', "/wp-includes/js/mediaelement/wp-mediaelement$suffix.css", array( 'mediaelement' ) ); 1642 $styles->add( 'thickbox', '/wp-includes/js/thickbox/thickbox.css', array( 'dashicons' ) ); 1643 $styles->add( 'wp-codemirror', '/wp-includes/js/codemirror/codemirror.min.css', array(), '5.65.20' ); 1644 1645 // Deprecated CSS. 1646 $styles->add( 'deprecated-media', "/wp-admin/css/deprecated-media$suffix.css" ); 1647 $styles->add( 'farbtastic', "/wp-admin/css/farbtastic$suffix.css", array(), '1.3u1' ); 1648 $styles->add( 'jcrop', '/wp-includes/js/jcrop/jquery.Jcrop.min.css', array(), '0.9.15' ); 1649 $styles->add( 'colors-fresh', false, array( 'wp-admin', 'buttons' ) ); // Old handle. 1650 $styles->add( 'open-sans', $open_sans_font_url ); // No longer used in core as of 4.6. 1651 $styles->add( 'wp-embed-template-ie', false ); 1652 $styles->add_data( 'wp-embed-template-ie', 'conditional', '_required-conditional-dependency_' ); 1653 1654 // Noto Serif is no longer used by core, but may be relied upon by themes and plugins. 1655 $fonts_url = ''; 1656 1657 /* 1658 * translators: Use this to specify the proper Google Font name and variants 1659 * to load that is supported by your language. Do not translate. 1660 * Set to 'off' to disable loading. 1661 */ 1662 $font_family = _x( 'Noto Serif:400,400i,700,700i', 'Google Font Name and Variants' ); 1663 if ( 'off' !== $font_family ) { 1664 $fonts_url = 'https://fonts.googleapis.com/css?family=' . urlencode( $font_family ); 1665 } 1666 $styles->add( 'wp-editor-font', $fonts_url ); // No longer used in core as of 5.7. 1667 $block_library_theme_path = WPINC . "/css/dist/block-library/theme$suffix.css"; 1668 $styles->add( 'wp-block-library-theme', "/$block_library_theme_path" ); 1669 $styles->add_data( 'wp-block-library-theme', 'path', ABSPATH . $block_library_theme_path ); 1670 1671 $classic_theme_styles_path = WPINC . "/css/classic-themes$suffix.css"; 1672 $styles->add( 'classic-theme-styles', "/$classic_theme_styles_path" ); 1673 $styles->add_data( 'classic-theme-styles', 'path', ABSPATH . $classic_theme_styles_path ); 1674 1675 $styles->add( 1676 'wp-reset-editor-styles', 1677 "/wp-includes/css/dist/block-library/reset$suffix.css", 1678 array( 'common', 'forms' ) // Make sure the reset is loaded after the default WP Admin styles. 1679 ); 1680 1681 $styles->add( 1682 'wp-editor-classic-layout-styles', 1683 "/wp-includes/css/dist/edit-post/classic$suffix.css", 1684 array() 1685 ); 1686 1687 $styles->add( 1688 'wp-block-editor-content', 1689 "/wp-includes/css/dist/block-editor/content$suffix.css", 1690 array( 'wp-components' ) 1691 ); 1692 1693 // Only add CONTENT styles here that should be enqueued in the iframe! 1694 $wp_edit_blocks_dependencies = array( 1695 'wp-theme', 1696 'wp-base-styles', 1697 'wp-components', 1698 /* 1699 * This needs to be added before the block library styles, 1700 * The block library styles override the "reset" styles. 1701 */ 1702 'wp-reset-editor-styles', 1703 'wp-block-library', 1704 'wp-block-editor-content', 1705 ); 1706 1707 // Only load the default layout and margin styles for themes without theme.json file. 1708 if ( ! wp_theme_has_theme_json() ) { 1709 $wp_edit_blocks_dependencies[] = 'wp-editor-classic-layout-styles'; 1710 } 1711 1712 if ( 1713 current_theme_supports( 'wp-block-styles' ) && 1714 ( ! is_array( $editor_styles ) || count( $editor_styles ) === 0 ) 1715 ) { 1716 /* 1717 * Include opinionated block styles if the theme supports block styles and 1718 * no $editor_styles are declared, so the editor never appears broken. 1719 */ 1720 $wp_edit_blocks_dependencies[] = 'wp-block-library-theme'; 1721 } 1722 1723 $styles->add( 1724 'wp-edit-blocks', 1725 "/wp-includes/css/dist/block-library/editor$suffix.css", 1726 $wp_edit_blocks_dependencies 1727 ); 1728 1729 $styles->add( 'wp-view-transitions-admin', false ); 1730 did_action( 'init' ) && $styles->add_inline_style( 'wp-view-transitions-admin', wp_get_view_transitions_admin_css() ); 1731 1732 $package_styles = array( 1733 'block-editor' => array( 'wp-components', 'wp-preferences' ), 1734 'block-library' => array(), 1735 'block-directory' => array(), 1736 'theme' => array(), 1737 'base-styles' => array(), 1738 'components' => array( 'wp-theme' ), 1739 'commands' => array( 'wp-components' ), 1740 'edit-post' => array( 1741 'wp-components', 1742 'wp-block-editor', 1743 'wp-editor', 1744 'wp-edit-blocks', 1745 'wp-block-library', 1746 'wp-commands', 1747 'wp-preferences', 1748 ), 1749 'editor' => array( 1750 'wp-components', 1751 'wp-block-editor', 1752 'wp-reusable-blocks', 1753 'wp-patterns', 1754 'wp-preferences', 1755 'wp-media-utils', 1756 ), 1757 'format-library' => array(), 1758 'list-reusable-blocks' => array( 'wp-components' ), 1759 'media-utils' => array( 'wp-components' ), 1760 'reusable-blocks' => array( 'wp-components' ), 1761 'patterns' => array( 'wp-components' ), 1762 'preferences' => array( 'wp-components' ), 1763 'nux' => array( 'wp-components' ), 1764 'widgets' => array( 1765 'wp-components', 1766 ), 1767 'edit-widgets' => array( 1768 'wp-widgets', 1769 'wp-block-editor', 1770 'wp-editor', 1771 'wp-edit-blocks', 1772 'wp-block-library', 1773 'wp-patterns', 1774 'wp-preferences', 1775 ), 1776 'customize-widgets' => array( 1777 'wp-widgets', 1778 'wp-block-editor', 1779 'wp-editor', 1780 'wp-edit-blocks', 1781 'wp-block-library', 1782 'wp-patterns', 1783 'wp-preferences', 1784 ), 1785 'edit-site' => array( 1786 'wp-components', 1787 'wp-block-editor', 1788 'wp-editor', 1789 'wp-edit-blocks', 1790 'wp-commands', 1791 'wp-preferences', 1792 ), 1793 ); 1794 1795 foreach ( $package_styles as $package => $dependencies ) { 1796 $handle = 'wp-' . $package; 1797 $path = "/wp-includes/css/dist/$package/style$suffix.css"; 1798 1799 if ( 'block-library' === $package && wp_should_load_separate_core_block_assets() ) { 1800 $path = "/wp-includes/css/dist/$package/common$suffix.css"; 1801 } 1802 1803 if ( 'base-styles' === $package ) { 1804 $path = "/wp-includes/css/dist/base-styles/admin-schemes$suffix.css"; 1805 } 1806 1807 if ( 'theme' === $package ) { 1808 $path = "/wp-includes/css/dist/theme/design-tokens$suffix.css"; 1809 } 1810 1811 $styles->add( $handle, $path, $dependencies ); 1812 $styles->add_data( $handle, 'path', ABSPATH . $path ); 1813 } 1814 1815 // RTL CSS. 1816 $rtl_styles = array( 1817 // Admin CSS. 1818 'common', 1819 'forms', 1820 'admin-menu', 1821 'dashboard', 1822 'list-tables', 1823 'edit', 1824 'revisions', 1825 'media', 1826 'themes', 1827 'about', 1828 'nav-menus', 1829 'widgets', 1830 'site-icon', 1831 'l10n', 1832 'install', 1833 'wp-color-picker', 1834 'customize-controls', 1835 'customize-widgets', 1836 'customize-nav-menus', 1837 'customize-preview', 1838 'login', 1839 'site-health', 1840 'wp-empty-template-alert', 1841 // Includes CSS. 1842 'buttons', 1843 'admin-bar', 1844 'wp-auth-check', 1845 'editor-buttons', 1846 'media-views', 1847 'wp-pointer', 1848 'wp-jquery-ui-dialog', 1849 'wp-block-template-skip-link', 1850 // Package styles. 1851 'wp-reset-editor-styles', 1852 'wp-editor-classic-layout-styles', 1853 'wp-block-library-theme', 1854 'wp-theme', 1855 'wp-edit-blocks', 1856 'wp-block-editor', 1857 'wp-block-library', 1858 'wp-block-directory', 1859 'wp-commands', 1860 'wp-components', 1861 'wp-customize-widgets', 1862 'wp-edit-post', 1863 'wp-edit-site', 1864 'wp-edit-widgets', 1865 'wp-editor', 1866 'wp-format-library', 1867 'wp-list-reusable-blocks', 1868 'wp-media-utils', 1869 'wp-reusable-blocks', 1870 'wp-patterns', 1871 'wp-nux', 1872 'wp-widgets', 1873 // Deprecated CSS. 1874 'deprecated-media', 1875 'farbtastic', 1876 ); 1877 1878 foreach ( $rtl_styles as $rtl_style ) { 1879 $styles->add_data( $rtl_style, 'rtl', 'replace' ); 1880 if ( $suffix ) { 1881 $styles->add_data( $rtl_style, 'suffix', $suffix ); 1882 } 1883 } 1884 } 1885 1886 /** 1887 * Reorders JavaScript scripts array to place prototype before jQuery. 1888 * 1889 * @since 2.3.1 1890 * 1891 * @param string[] $js_array JavaScript scripts array 1892 * @return string[] Reordered array, if needed. 1893 */ 1894 function wp_prototype_before_jquery( $js_array ) { 1895 $prototype = array_search( 'prototype', $js_array, true ); 1896 1897 if ( false === $prototype ) { 1898 return $js_array; 1899 } 1900 1901 $jquery = array_search( 'jquery', $js_array, true ); 1902 1903 if ( false === $jquery ) { 1904 return $js_array; 1905 } 1906 1907 if ( $prototype < $jquery ) { 1908 return $js_array; 1909 } 1910 1911 unset( $js_array[ $prototype ] ); 1912 1913 array_splice( $js_array, $jquery, 0, 'prototype' ); 1914 1915 return $js_array; 1916 } 1917 1918 /** 1919 * Loads localized data on print rather than initialization. 1920 * 1921 * These localizations require information that may not be loaded even by init. 1922 * 1923 * @since 2.5.0 1924 * 1925 * @global array $shortcode_tags 1926 */ 1927 function wp_just_in_time_script_localization() { 1928 1929 wp_localize_script( 1930 'autosave', 1931 'autosaveL10n', 1932 array( 1933 'autosaveInterval' => AUTOSAVE_INTERVAL, 1934 'blog_id' => get_current_blog_id(), 1935 ) 1936 ); 1937 1938 wp_localize_script( 1939 'mce-view', 1940 'mceViewL10n', 1941 array( 1942 'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(), 1943 ) 1944 ); 1945 1946 wp_localize_script( 1947 'word-count', 1948 'wordCountL10n', 1949 array( 1950 'type' => wp_get_word_count_type(), 1951 'shortcodes' => ! empty( $GLOBALS['shortcode_tags'] ) ? array_keys( $GLOBALS['shortcode_tags'] ) : array(), 1952 ) 1953 ); 1954 } 1955 1956 /** 1957 * Localizes the jQuery UI datepicker. 1958 * 1959 * @since 4.6.0 1960 * 1961 * @link https://api.jqueryui.com/datepicker/#options 1962 * 1963 * @global WP_Locale $wp_locale WordPress date and time locale object. 1964 */ 1965 function wp_localize_jquery_ui_datepicker() { 1966 global $wp_locale; 1967 1968 if ( ! wp_script_is( 'jquery-ui-datepicker', 'enqueued' ) ) { 1969 return; 1970 } 1971 1972 // Convert the PHP date format into jQuery UI's format. 1973 $datepicker_date_format = str_replace( 1974 array( 1975 'd', 1976 'j', 1977 'l', 1978 'z', // Day. 1979 'F', 1980 'M', 1981 'n', 1982 'm', // Month. 1983 'Y', 1984 'y', // Year. 1985 ), 1986 array( 1987 'dd', 1988 'd', 1989 'DD', 1990 'o', 1991 'MM', 1992 'M', 1993 'm', 1994 'mm', 1995 'yy', 1996 'y', 1997 ), 1998 get_option( 'date_format' ) 1999 ); 2000 2001 $datepicker_defaults = wp_json_encode( 2002 array( 2003 'closeText' => __( 'Close' ), 2004 'currentText' => __( 'Today' ), 2005 'monthNames' => array_values( $wp_locale->month ), 2006 'monthNamesShort' => array_values( $wp_locale->month_abbrev ), 2007 'nextText' => _x( 'Next', 'datepicker: navigate to next month' ), 2008 'prevText' => _x( 'Previous', 'datepicker: navigate to previous month' ), 2009 'dayNames' => array_values( $wp_locale->weekday ), 2010 'dayNamesShort' => array_values( $wp_locale->weekday_abbrev ), 2011 'dayNamesMin' => array_values( $wp_locale->weekday_initial ), 2012 'dateFormat' => $datepicker_date_format, 2013 'firstDay' => absint( get_option( 'start_of_week' ) ), 2014 'isRTL' => $wp_locale->is_rtl(), 2015 ), 2016 JSON_HEX_TAG | JSON_UNESCAPED_SLASHES 2017 ); 2018 2019 wp_add_inline_script( 'jquery-ui-datepicker', "jQuery(function(jQuery){jQuery.datepicker.setDefaults({$datepicker_defaults});});" ); 2020 } 2021 2022 /** 2023 * Localizes community events data that needs to be passed to dashboard.js. 2024 * 2025 * @since 4.8.0 2026 */ 2027 function wp_localize_community_events() { 2028 if ( ! wp_script_is( 'dashboard' ) ) { 2029 return; 2030 } 2031 2032 require_once ABSPATH . 'wp-admin/includes/class-wp-community-events.php'; 2033 2034 $user_id = get_current_user_id(); 2035 $saved_location = get_user_option( 'community-events-location', $user_id ); 2036 $saved_ip_address = $saved_location['ip'] ?? false; 2037 $current_ip_address = WP_Community_Events::get_unsafe_client_ip(); 2038 2039 /* 2040 * If the user's location is based on their IP address, then update their 2041 * location when their IP address changes. This allows them to see events 2042 * in their current city when travelling. Otherwise, they would always be 2043 * shown events in the city where they were when they first loaded the 2044 * Dashboard, which could have been months or years ago. 2045 */ 2046 if ( $saved_ip_address && $current_ip_address && $current_ip_address !== $saved_ip_address ) { 2047 $saved_location['ip'] = $current_ip_address; 2048 update_user_meta( $user_id, 'community-events-location', $saved_location ); 2049 } 2050 2051 $events_client = new WP_Community_Events( $user_id, $saved_location ); 2052 2053 wp_localize_script( 2054 'dashboard', 2055 'communityEventsData', 2056 array( 2057 'nonce' => wp_create_nonce( 'community_events' ), 2058 'cache' => $events_client->get_cached_events(), 2059 'time_format' => get_option( 'time_format' ), 2060 ) 2061 ); 2062 } 2063 2064 /** 2065 * Administration Screen CSS for changing the styles. 2066 * 2067 * If installing the 'wp-admin/' directory will be replaced with './'. 2068 * 2069 * The $_wp_admin_css_colors global manages the Administration Screens CSS 2070 * stylesheet that is loaded. The option that is set is 'admin_color' and is the 2071 * color and key for the array. The value for the color key is an object with 2072 * a 'url' parameter that has the URL path to the CSS file. 2073 * 2074 * The query from $src parameter will be appended to the URL that is given from 2075 * the $_wp_admin_css_colors array value URL. 2076 * 2077 * @since 2.6.0 2078 * 2079 * @global array $_wp_admin_css_colors 2080 * 2081 * @param string $src Source URL. 2082 * @param string $handle Either 'colors' or 'colors-rtl'. 2083 * @return string|false URL path to CSS stylesheet for Administration Screens. 2084 */ 2085 function wp_style_loader_src( $src, $handle ) { 2086 global $_wp_admin_css_colors; 2087 2088 if ( wp_installing() ) { 2089 return preg_replace( '#^wp-admin/#', './', $src ); 2090 } 2091 2092 if ( 'colors' === $handle ) { 2093 $color = get_user_option( 'admin_color' ); 2094 2095 if ( empty( $color ) || ! isset( $_wp_admin_css_colors[ $color ] ) ) { 2096 $color = 'modern'; 2097 } 2098 2099 $color = $_wp_admin_css_colors[ $color ] ?? null; 2100 $url = $color->url ?? ''; 2101 2102 if ( ! $url ) { 2103 return false; 2104 } 2105 2106 $parsed = parse_url( $src ); 2107 if ( isset( $parsed['query'] ) && $parsed['query'] ) { 2108 wp_parse_str( $parsed['query'], $qv ); 2109 $url = add_query_arg( $qv, $url ); 2110 } 2111 2112 return $url; 2113 } 2114 2115 return $src; 2116 } 2117 2118 /** 2119 * Prints the script queue in the HTML head on admin pages. 2120 * 2121 * Postpones the scripts that were queued for the footer. 2122 * print_footer_scripts() is called in the footer to print these scripts. 2123 * 2124 * @since 2.8.0 2125 * 2126 * @see wp_print_scripts() 2127 * 2128 * @return string[] Handles of the scripts that were printed. 2129 */ 2130 function print_head_scripts() { 2131 if ( ! did_action( 'wp_print_scripts' ) ) { 2132 /** This action is documented in wp-includes/functions.wp-scripts.php */ 2133 do_action( 'wp_print_scripts' ); 2134 } 2135 2136 $wp_scripts = wp_scripts(); 2137 $wp_scripts->do_head_items(); 2138 2139 /** 2140 * Filters whether to print the head scripts. 2141 * 2142 * @since 2.8.0 2143 * @deprecated 7.2.0 Scripts are no longer concatenated, so there is nothing for this filter to control. 2144 * 2145 * @param bool $print Whether to print the head scripts. Default true. 2146 */ 2147 apply_filters_deprecated( 'print_head_scripts', array( true ), '7.2.0' ); 2148 2149 $wp_scripts->reset(); 2150 return $wp_scripts->done; 2151 } 2152 2153 /** 2154 * Prints the scripts that were queued for the footer or too late for the HTML head. 2155 * 2156 * @since 2.8.0 2157 * 2158 * @global WP_Scripts $wp_scripts 2159 * 2160 * @return string[] Handles of the scripts that were printed. 2161 */ 2162 function print_footer_scripts() { 2163 global $wp_scripts; 2164 2165 if ( ! ( $wp_scripts instanceof WP_Scripts ) ) { 2166 return array(); // No need to run if not instantiated. 2167 } 2168 $wp_scripts->do_footer_items(); 2169 2170 /** 2171 * Filters whether to print the footer scripts. 2172 * 2173 * @since 2.8.0 2174 * @deprecated 7.2.0 Scripts are no longer concatenated, so there is nothing for this filter to control. 2175 * 2176 * @param bool $print Whether to print the footer scripts. Default true. 2177 */ 2178 apply_filters_deprecated( 'print_footer_scripts', array( true ), '7.2.0' ); 2179 2180 $wp_scripts->reset(); 2181 return $wp_scripts->done; 2182 } 2183 2184 /** 2185 * Prints the script queue in the HTML head on the front end. 2186 * 2187 * Postpones the scripts that were queued for the footer. 2188 * wp_print_footer_scripts() is called in the footer to print these scripts. 2189 * 2190 * @since 2.8.0 2191 * 2192 * @global WP_Scripts $wp_scripts 2193 * 2194 * @return string[] Handles of the scripts that were printed. 2195 */ 2196 function wp_print_head_scripts() { 2197 global $wp_scripts; 2198 2199 if ( ! did_action( 'wp_print_scripts' ) ) { 2200 /** This action is documented in wp-includes/functions.wp-scripts.php */ 2201 do_action( 'wp_print_scripts' ); 2202 } 2203 2204 if ( ! ( $wp_scripts instanceof WP_Scripts ) ) { 2205 return array(); // No need to run if nothing is queued. 2206 } 2207 2208 return print_head_scripts(); 2209 } 2210 2211 /** 2212 * Private, for use in *_footer_scripts hooks 2213 * 2214 * In classic themes, when block styles are loaded on demand via wp_load_classic_theme_block_styles_on_demand(), 2215 * this function is replaced by a closure in wp_hoist_late_printed_styles() which will capture the printing of 2216 * two sets of "late" styles to be hoisted to the HEAD by means of the template enhancement output buffer: 2217 * 2218 * 1. Styles related to blocks are inserted right after the wp-block-library stylesheet. 2219 * 2. All other styles are appended to the end of the HEAD. 2220 * 2221 * The closure calls print_footer_scripts() to print scripts in the footer as usual. 2222 * 2223 * @since 3.3.0 2224 */ 2225 function _wp_footer_scripts() { 2226 print_late_styles(); 2227 print_footer_scripts(); 2228 } 2229 2230 /** 2231 * Hooks to print the scripts and styles in the footer. 2232 * 2233 * @since 2.8.0 2234 */ 2235 function wp_print_footer_scripts() { 2236 /** 2237 * Fires when footer scripts are printed. 2238 * 2239 * @since 2.8.0 2240 */ 2241 do_action( 'wp_print_footer_scripts' ); 2242 } 2243 2244 /** 2245 * Wrapper for do_action( 'wp_enqueue_scripts' ). 2246 * 2247 * Allows plugins to queue scripts for the front end using wp_enqueue_script(). 2248 * Runs first in wp_head() where all is_home(), is_page(), etc. functions are available. 2249 * 2250 * @since 2.8.0 2251 */ 2252 function wp_enqueue_scripts() { 2253 /** 2254 * Fires when scripts and styles are enqueued. 2255 * 2256 * @since 2.8.0 2257 */ 2258 do_action( 'wp_enqueue_scripts' ); 2259 } 2260 2261 /** 2262 * Prints the styles queue in the HTML head on admin pages. 2263 * 2264 * @since 2.8.0 2265 * 2266 * @return string[] Handles of the styles that were printed. 2267 */ 2268 function print_admin_styles() { 2269 $wp_styles = wp_styles(); 2270 $wp_styles->do_items( false ); 2271 2272 /** 2273 * Filters whether to print the admin styles. 2274 * 2275 * @since 2.8.0 2276 * @deprecated 7.2.0 Styles are no longer concatenated, so there is nothing for this filter to control. 2277 * 2278 * @param bool $print Whether to print the admin styles. Default true. 2279 */ 2280 apply_filters_deprecated( 'print_admin_styles', array( true ), '7.2.0' ); 2281 2282 $wp_styles->reset(); 2283 return $wp_styles->done; 2284 } 2285 2286 /** 2287 * Prints the styles that were queued too late for the HTML head. 2288 * 2289 * @since 3.3.0 2290 * 2291 * @global WP_Styles $wp_styles 2292 * 2293 * @return string[]|null 2294 */ 2295 function print_late_styles() { 2296 global $wp_styles; 2297 2298 if ( ! ( $wp_styles instanceof WP_Styles ) ) { 2299 return null; 2300 } 2301 2302 $wp_styles->do_footer_items(); 2303 2304 /** 2305 * Filters whether to print the styles queued too late for the HTML head. 2306 * 2307 * @since 3.3.0 2308 * @deprecated 7.2.0 Styles are no longer concatenated, so there is nothing for this filter to control. 2309 * 2310 * @param bool $print Whether to print the 'late' styles. Default true. 2311 */ 2312 apply_filters_deprecated( 'print_late_styles', array( true ), '7.2.0' ); 2313 2314 $wp_styles->reset(); 2315 return $wp_styles->done; 2316 } 2317 2318 /** 2319 * Prints prefetch links for the assets of the screen the user is most likely to open next. 2320 * 2321 * Runs wherever the next screen can be predicted with confidence, and prefetches only what that 2322 * screen is certain to need. Two cases qualify today: 2323 * 2324 * - The login screen, which is followed by an admin screen. That screen downloads each core script 2325 * and stylesheet separately, which is what makes an uncached admin load slow. Requesting the 2326 * ones that block its first paint while the login form is on screen puts them in the HTTP cache 2327 * during the time the user spends typing credentials, so the redirect that follows finds them 2328 * already there. 2329 * - The Dashboard and the post list tables, from which the editor is the usual next stop. Only the 2330 * editor's stylesheets are prefetched, not its scripts: the scripts run to well over a megabyte 2331 * compressed, which is far too much to spend on a screen the user may never open, whereas the 2332 * stylesheets are render-blocking and in the same size class as the login screen's own prefetch. 2333 * 2334 * Handles the current screen has printed or queued are skipped, along with their dependencies, so 2335 * each context only fetches what it is actually adding. On the login screen this runs in the 2336 * footer, since some of what the login form loads, such as `user-profile` and the `jquery` it 2337 * depends on, is only enqueued after its header has printed. On admin screens it runs in the head, 2338 * where the screen's assets have been enqueued already. 2339 * 2340 * These are resources for the *next* navigation rather than for the screen printing them, which is 2341 * what `rel="prefetch"` describes. `rel="preload"` would fetch them at the current document's 2342 * priority, and browsers warn about preloaded resources the document never uses. A prefetch is 2343 * already dispatched at the browser's lowest priority, so it stays out of the way of that screen's 2344 * own render-blocking assets without needing `fetchpriority`. 2345 * 2346 * A prefetched response is reused only for as long as the HTTP cache considers it fresh, the same as 2347 * any other cached response. Core does not send caching headers for its static files, so how long 2348 * that is depends on the server: an explicit `max-age` or `Expires`, or else a heuristic lifetime 2349 * derived from `Last-Modified`. Once the response is stale the next screen still revalidates it, 2350 * which saves the download but not the round trip. 2351 * 2352 * The `as` attribute is still worth setting: it gives the request the same destination the admin 2353 * screen will later ask for, which is what lets the prefetched response be reused. 2354 * 2355 * The admin-wide handles cover every admin screen rather than only the Dashboard, so that part of 2356 * the list does not vary with where the login lands. They are printed on every screen of the login 2357 * page, such as the password reset form, since those mostly lead to the admin as well. Nothing is 2358 * printed at all when `redirect_to` points outside this site's admin. 2359 * 2360 * @since 7.2.0 2361 * 2362 * @see wp_preload_resources() 2363 * 2364 * @global WP_Screen|null $current_screen The current admin screen, if any. 2365 */ 2366 function wp_prefetch_admin_assets(): void { 2367 global $current_screen; 2368 2369 /* 2370 * The context is told apart by the request rather than by the hook this runs on, so it works 2371 * from whichever hook it is added to. The 'login_init' action is fired by wp-login.php however 2372 * it is reached. The is_login() function would not do: it compares the login URL with the 2373 * script handling the request, and a plugin serving the login screen at a URL of its own runs 2374 * wp-login.php from another script, such as index.php. 2375 */ 2376 $on_login = (bool) did_action( 'login_init' ); 2377 2378 if ( ! $on_login ) { 2379 /* 2380 * From the Dashboard and the post list tables, the editor is the usual next stop. Anywhere 2381 * else in the admin there is no destination worth guessing at. 2382 * 2383 * The global is read rather than calling get_current_screen(), which only exists once the 2384 * admin includes are loaded. On a request with no screen, such as one for the front end, 2385 * this prints nothing. 2386 */ 2387 if ( ! $current_screen instanceof WP_Screen || ! in_array( $current_screen->base, array( 'dashboard', 'edit' ), true ) ) { 2388 return; 2389 } 2390 } 2391 2392 $script_roots = array(); 2393 $style_roots = array(); 2394 2395 if ( $on_login ) { 2396 /* 2397 * Resolve where the login is going to land, the same way wp-login.php will: `redirect_to` 2398 * when one was given, and the admin otherwise. wp_safe_redirect() sends the browser to what 2399 * wp_validate_redirect() returns, so that is used here as it is: it sanitizes the value, 2400 * resolves a relative path against the current request, as the browser would, and falls back 2401 * to the admin for a value pointing off-host. Passing the value through esc_url_raw() first 2402 * would not match it, since that takes a relative path such as `wp-admin/post.php` for a host. 2403 * 2404 * This runs on every screen wp-login.php prints, not only the login form, since the others 2405 * mostly lead to the admin as well, and each one gives the prefetching another chance to 2406 * finish before the user gets there. The password reset and registration flows end at the 2407 * login form, and the admin email confirmation follows a login that has already succeeded. 2408 * On the lost password and registration screens, `redirect_to` is where submitting the 2409 * form goes rather than where the login lands, but it is normally absent, and when it is 2410 * not, it typically points back into wp-login.php, so nothing is prefetched. Nor does an 2411 * interim login need to be excluded: it shows inside a modal on an admin screen that has 2412 * already loaded these assets, so they are served from the HTTP cache. 2413 * 2414 * An empty `redirect_to` counts as none, since wp-login.php falls back to the admin for it 2415 * too. The lost password form submits one, for instance, so it is present when the form is 2416 * shown again with an error. 2417 */ 2418 $admin_url = admin_url(); 2419 $next_screen = $admin_url; 2420 2421 if ( ! empty( $_REQUEST['redirect_to'] ) && is_string( $_REQUEST['redirect_to'] ) ) { 2422 $next_screen = wp_validate_redirect( wp_unslash( $_REQUEST['redirect_to'] ), $admin_url ); 2423 } 2424 2425 /* 2426 * When the login lands somewhere other than the admin, such as the front end or a plugin's 2427 * own screen, none of these assets are wanted. The admin's path ends in a slash, which a 2428 * `redirect_to` of the admin itself may leave off, as in `/wp-admin`. 2429 */ 2430 $admin_path = (string) wp_parse_url( $admin_url, PHP_URL_PATH ); 2431 2432 if ( '' === $admin_path || ! str_starts_with( trailingslashit( (string) wp_parse_url( $next_screen, PHP_URL_PATH ) ), $admin_path ) ) { 2433 return; 2434 } 2435 2436 /* 2437 * Nor are they when it lands in the admin of another host. wp_validate_redirect() accepts any 2438 * host in 'allowed_redirect_hosts', such as another site on a multisite network, and that 2439 * admin would request its assets from its own host rather than from this one. The same goes 2440 * for this host's admin under another scheme, such as an `https` destination from an `http` 2441 * login: the URLs prefetched here take the scheme of the current request, so that admin 2442 * would request different ones. A relative `redirect_to` stays on this host and scheme, while 2443 * wp_validate_redirect() gives a protocol-relative one the `http` scheme, which is where 2444 * wp_safe_redirect() then sends it. A port is compared with the scheme's default filled in 2445 * when none is given, since `https://example.com:443/` is the same admin as 2446 * `https://example.com/`. 2447 */ 2448 $next_screen_host = wp_parse_url( $next_screen, PHP_URL_HOST ); 2449 2450 if ( is_string( $next_screen_host ) ) { 2451 $default_ports = array( 2452 'http' => 80, 2453 'https' => 443, 2454 ); 2455 2456 $admin_scheme = strtolower( (string) wp_parse_url( $admin_url, PHP_URL_SCHEME ) ); 2457 $next_screen_scheme = strtolower( (string) wp_parse_url( $next_screen, PHP_URL_SCHEME ) ); 2458 $admin_port = wp_parse_url( $admin_url, PHP_URL_PORT ) ?? $default_ports[ $admin_scheme ] ?? null; 2459 $next_screen_port = wp_parse_url( $next_screen, PHP_URL_PORT ) ?? $default_ports[ $next_screen_scheme ] ?? null; 2460 2461 if ( 2462 strtolower( $next_screen_host ) !== strtolower( (string) wp_parse_url( $admin_url, PHP_URL_HOST ) ) || 2463 $next_screen_scheme !== $admin_scheme || 2464 $next_screen_port !== $admin_port 2465 ) { 2466 return; 2467 } 2468 } 2469 } else { 2470 $post_type = ( 'edit' === $current_screen->base && $current_screen->post_type ) ? $current_screen->post_type : 'post'; 2471 $post_type_object = get_post_type_object( $post_type ); 2472 2473 if ( ! $post_type_object instanceof WP_Post_Type ) { 2474 return; 2475 } 2476 2477 /* 2478 * A user who cannot edit posts of this type will never reach the editor from here. This is 2479 * `edit_posts` rather than `create_posts`, since the editor is reached by opening an existing 2480 * post as well as by adding a new one, and a user may be able to do the first but not the 2481 * second. Adding a new one requires `edit_posts` too. 2482 */ 2483 if ( ! current_user_can( $post_type_object->cap->edit_posts ) ) { 2484 return; 2485 } 2486 2487 $next_screen = add_query_arg( 'post_type', $post_type, admin_url( 'post-new.php' ) ); 2488 } 2489 2490 if ( $on_login ) { 2491 /* 2492 * The handles that block rendering on every admin screen: the stylesheets, and the scripts 2493 * printed in the head. These are what stand between the redirect and the first paint, so 2494 * they are what is worth having in the cache already. Every handle these expand to loads on 2495 * all admin screens, not just the one the login happens to land on, so the list does not depend 2496 * on the destination. Screen-specific handles are deliberately left out: `site-health` 2497 * blocks rendering on the Dashboard but loads nowhere else. 2498 * 2499 * Scripts printed in the footer are left out even though they block DOMContentLoaded, since 2500 * they do not hold back the first paint. They are also where the bulk of the admin's bytes 2501 * are, largely the command palette's dependencies, and a prefetch of them still in flight 2502 * when the login form is submitted competes with the admin screen's own render-blocking 2503 * stylesheets and delays its first paint on a slow connection. 2504 * 2505 * These are roots rather than the full set: everything they depend on is pulled in with them 2506 * below, so the set follows the dependencies declared in wp_default_scripts() and 2507 * wp_default_styles(). `jquery` stands for `jquery-core` and `jquery-migrate`, and `wp-admin` 2508 * for the admin's own stylesheets, which is what the `colors` handle enqueued on every admin 2509 * screen depends on. Aliases like these have no source of their own, so only what they expand 2510 * to is prefetched. `colors` itself is left out, since the color scheme is a per-user setting 2511 * and the user is not known yet. 2512 * 2513 * Each root mirrors an enqueue elsewhere, which carries a note pointing back here: `common` 2514 * (for `jquery`) in wp-admin/admin.php, `colors` (for `wp-admin` and `buttons`) and `utils` in 2515 * wp-admin/admin-header.php, `admin-bar` in WP_Admin_Bar::initialize(), `wp-auth-check` in 2516 * wp_auth_check_load(), and `wp-commands` in wp_enqueue_command_palette_assets(). 2517 */ 2518 $script_roots = array( 2519 'jquery', 2520 'utils', 2521 ); 2522 2523 $style_roots = array( 2524 'wp-admin', 2525 'buttons', 2526 'admin-bar', 2527 'wp-auth-check', 2528 'wp-commands', 2529 ); 2530 } 2531 2532 /* 2533 * post-new.php always opens the editor, while post.php also handles trashing, restoring and 2534 * bulk edits, so it counts only when it is editing. 2535 */ 2536 $next_screen_file = basename( (string) wp_parse_url( $next_screen, PHP_URL_PATH ) ); 2537 $next_screen_query = array(); 2538 wp_parse_str( (string) wp_parse_url( $next_screen, PHP_URL_QUERY ), $next_screen_query ); 2539 2540 $next_screen_is_block_editor = 'post-new.php' === $next_screen_file 2541 || ( 'post.php' === $next_screen_file && 'edit' === ( $next_screen_query['action'] ?? '' ) ); 2542 2543 if ( $next_screen_is_block_editor ) { 2544 /* 2545 * The editor only loads these stylesheets when the post type uses the block editor, which 2546 * the classic editor, for instance, can turn off. The post type is the one in the query of 2547 * the next screen, as for post-new.php. An edit link to post.php from the login screen does 2548 * not carry one, so it is taken to be a post: looking the post up instead would let anyone 2549 * tell from the login screen whether a post with a given ID exists, drafts and private posts 2550 * included. 2551 */ 2552 $next_screen_post_type = $next_screen_query['post_type'] ?? 'post'; 2553 $next_screen_is_block_editor = is_string( $next_screen_post_type ) && use_block_editor_for_post_type( $next_screen_post_type ); 2554 } 2555 2556 if ( $next_screen_is_block_editor ) { 2557 /* 2558 * Roots as well, expanded along with any from the login screen. `wp-edit-post` alone accounts 2559 * for most of the editor chrome, including the block editor's content and reset styles by way 2560 * of `wp-edit-blocks`; the rest cover the block directory, the format library, the classic 2561 * editor's buttons and the media modal. 2562 * 2563 * Each root mirrors an enqueue elsewhere, which carries a note pointing back here: 2564 * `wp-edit-post` in wp-admin/edit-form-blocks.php, `wp-block-directory` in 2565 * wp_enqueue_editor_block_directory_assets(), `wp-format-library` in 2566 * wp_enqueue_editor_format_library_assets(), `editor-buttons` in 2567 * _WP_Editors::enqueue_default_editor(), and `media-views` and `imgareaselect` in 2568 * wp_enqueue_media(). 2569 */ 2570 $style_roots = array_merge( 2571 $style_roots, 2572 array( 2573 'wp-edit-post', 2574 'wp-block-directory', 2575 'wp-format-library', 2576 'editor-buttons', 2577 'media-views', 2578 'imgareaselect', 2579 ) 2580 ); 2581 } 2582 2583 // From an admin screen, nothing is left to prefetch when the editor turns out not to be the block editor. 2584 if ( ! $script_roots && ! $style_roots ) { 2585 return; 2586 } 2587 2588 $resources = array(); 2589 2590 foreach ( 2591 array( 2592 'script' => array( wp_scripts(), $script_roots ), 2593 'style' => array( wp_styles(), $style_roots ), 2594 ) 2595 as $as => list( $dependencies, $roots ) 2596 ) { 2597 // Skip a type with nothing to prefetch, such as scripts for the editor, rather than expanding the current screen's queue of it. 2598 if ( ! $roots ) { 2599 continue; 2600 } 2601 2602 /* 2603 * Expand the roots to include everything they depend on, roots first, and likewise what the 2604 * current screen has queued. A handle that is not registered is dropped along with its 2605 * dependencies. Unlike WP_Dependencies::all_deps(), this leaves the dependencies' state 2606 * untouched. 2607 */ 2608 $expanded = array( 2609 'roots' => array(), 2610 'queue' => array(), 2611 ); 2612 2613 foreach ( 2614 array( 2615 'roots' => $roots, 2616 'queue' => $dependencies->queue, 2617 ) 2618 as $list => $handles 2619 ) { 2620 while ( $handles ) { 2621 $handle = array_shift( $handles ); 2622 2623 if ( isset( $expanded[ $list ][ $handle ] ) || ! isset( $dependencies->registered[ $handle ] ) ) { 2624 continue; 2625 } 2626 2627 $expanded[ $list ][ $handle ] = true; 2628 2629 foreach ( $dependencies->registered[ $handle ]->deps as $dependency ) { 2630 if ( is_string( $dependency ) && '' !== $dependency && ! isset( $expanded[ $list ][ $dependency ] ) ) { 2631 $handles[] = $dependency; 2632 } 2633 } 2634 } 2635 } 2636 2637 /* 2638 * Whichever screen this is running on shares some of these handles, and the browser fetches 2639 * those for it anyway, so prefetching them as well would only compete with its own requests. 2640 * These are the ones it has printed, and the ones it has queued along with their 2641 * dependencies, which it has yet to print if this runs before its footer scripts. On the 2642 * login screen, for instance, `user-profile` brings in `jquery`. 2643 */ 2644 $on_current_screen = $expanded['queue'] + array_fill_keys( $dependencies->done, true ); 2645 2646 foreach ( array_keys( $expanded['roots'] ) as $handle ) { 2647 if ( isset( $on_current_screen[ $handle ] ) ) { 2648 continue; 2649 } 2650 2651 // The next screen prints nothing for a handle with conditional data, as do_item() returns early for it. 2652 if ( $dependencies->registered[ $handle ]->extra['conditional'] ?? false ) { 2653 continue; 2654 } 2655 2656 /* 2657 * The URLs are built the same way WP_Scripts::do_item() and WP_Styles::do_item() build 2658 * those in the tags they print, so they match what the next screen will request. They are 2659 * not escaped yet, so the filter sees plain URLs; they are escaped when printed. 2660 * 2661 * A handle with no URL of its own, such as an alias or one whose URL was filtered away, 2662 * prints nothing either, and for a style that includes its right-to-left stylesheet, 2663 * since WP_Styles::do_item() returns before getting to it. 2664 */ 2665 $src = $dependencies->get_src( $handle ); 2666 2667 if ( '' === $src ) { 2668 continue; 2669 } 2670 2671 $urls = array( $src ); 2672 2673 if ( $dependencies instanceof WP_Styles ) { 2674 $rtl_src = $dependencies->get_rtl_src( $handle ); 2675 2676 if ( null !== $rtl_src ) { 2677 if ( 'replace' === $dependencies->get_data( $handle, 'rtl' ) ) { 2678 $urls = array( $rtl_src ); 2679 } else { 2680 $urls[] = $rtl_src; 2681 } 2682 } 2683 } 2684 2685 foreach ( array_filter( $urls ) as $url ) { 2686 $resources[] = array( 2687 'href' => $url, 2688 'as' => $as, 2689 ); 2690 } 2691 } 2692 } 2693 2694 /** 2695 * Filters the assets prefetched for the screen the user is expected to open next. 2696 * 2697 * Fires on any screen from which the next one can be predicted, so `$next_screen` is what 2698 * distinguishes the contexts: the login screen passes the URL it is about to redirect to, and 2699 * the Dashboard and post list tables pass the editor they expect the user to open. 2700 * 2701 * Only the `href` and `as` attributes below are printed; any other key is ignored. Resources 2702 * sharing an `href` are collapsed to the first of them, so a callback may append without 2703 * checking what is already there. Returning an empty array turns the prefetching off. 2704 * 2705 * On the login screen the URLs are built before the user is authenticated and outside the 2706 * admin, so there is no current user and `is_admin()` is false. A {@see 'script_loader_src'} 2707 * or {@see 'style_loader_src'} callback that depends on either can produce a URL the admin 2708 * screen will not request, which wastes the prefetch; a callback on this filter can correct it. 2709 * 2710 * @since 7.2.0 2711 * 2712 * @param array $resources { 2713 * Array of resources and their attributes to prefetch. 2714 * 2715 * @type array ...$0 { 2716 * Array of resource attributes. 2717 * 2718 * @type string $href URL to prefetch. Required. 2719 * @type string $as How the browser should treat the resource 2720 * (`script`, `style`, `image`, `document`, etc). Required. 2721 * } 2722 * } 2723 * @param string $next_screen URL of the screen the assets are being prefetched for. Always 2724 * points into the admin, since nothing is prefetched otherwise. 2725 * From the login screen this is the redirect target, already run 2726 * through wp_validate_redirect() with the admin as the fallback, 2727 * and it may be relative: it is the value as wp_safe_redirect() 2728 * will receive it, so a request-supplied path is passed through 2729 * unchanged and only the fallback is a full URL. 2730 */ 2731 $resources = apply_filters( 'wp_prefetch_admin_assets', $resources, $next_screen ); 2732 2733 if ( ! is_array( $resources ) ) { 2734 return; 2735 } 2736 2737 $unique_resources = array(); 2738 2739 // Parse the complete resource list and extract unique resources. 2740 foreach ( $resources as $resource ) { 2741 if ( ! is_array( $resource ) ) { 2742 continue; 2743 } 2744 2745 $href = $resource['href'] ?? ''; 2746 $as = $resource['as'] ?? ''; 2747 2748 if ( ! is_string( $href ) || '' === $href || ! is_string( $as ) || '' === $as ) { 2749 continue; 2750 } 2751 2752 /* 2753 * Check again once escaped, since esc_url() returns an empty string for a URL it rejects. 2754 * An empty `href` would resolve to the current page, which the script below would then fetch. 2755 * Only `http` and `https` URLs are allowed, as wp_preload_resources() does, since nothing else 2756 * can be prefetched, and the script would pass anything else to fetch(). 2757 */ 2758 $href = esc_url( $href, array( 'http', 'https' ) ); 2759 2760 if ( '' === $href || isset( $unique_resources[ $href ] ) ) { 2761 continue; 2762 } 2763 2764 $unique_resources[ $href ] = $as; 2765 } 2766 2767 if ( ! $unique_resources ) { 2768 return; 2769 } 2770 2771 // Build and output the HTML for each unique resource. Each URL has already been escaped. 2772 foreach ( $unique_resources as $escaped_href => $as ) { 2773 printf( 2774 "<link rel='prefetch' href='%s' as='%s' />\n", 2775 $escaped_href, 2776 esc_attr( $as ) 2777 ); 2778 } 2779 2780 /* 2781 * Safari does not support `rel="prefetch"`, so in browsers that lack it, fetch the URLs of the 2782 * page's prefetch links with fetch() instead, to put the responses in the HTTP cache all the 2783 * same. Reading the links from the page, rather than passing their URLs to the script, keeps a 2784 * single list of them, and also covers any prefetch links added by plugins. The fetches wait 2785 * until the current screen has loaded, and ask for low priority, so they stay out of the way 2786 * of its own assets. Unlike a prefetch link, a fetch still in flight is canceled when the user 2787 * navigates away, so it cannot compete with the next screen's render-blocking assets. 2788 * 2789 * The requests use `no-cors` mode and include credentials, as the stylesheet and script 2790 * requests of the next screen do, so the cached responses match what that screen will ask for. 2791 * The body is read to completion so that the full response is cached. 2792 */ 2793 $js_function = <<<'JS' 2794 /** 2795 * Prefetches the page's rel=prefetch links with fetch() in browsers that do not support rel=prefetch. 2796 * 2797 * @link https://caniuse.com/link-rel-prefetch 2798 */ 2799 () => { 2800 if ( document.createElement( 'link' ).relList?.supports?.( 'prefetch' ) ) { 2801 return; 2802 } 2803 2804 const prefetch = () => { 2805 for ( const link of /** @type {NodeListOf<HTMLLinkElement>} */ ( document.querySelectorAll( 'link[rel~="prefetch"][href]' ) ) ) { 2806 fetch( link.href, { mode: 'no-cors', credentials: 'include', priority: 'low' } ) 2807 .then( ( response ) => response.blob() ) 2808 .catch( () => {} ); 2809 } 2810 }; 2811 2812 const schedule = () => { 2813 if ( 'requestIdleCallback' in window ) { 2814 window.requestIdleCallback( prefetch ); 2815 } else { 2816 setTimeout( prefetch, 0 ); 2817 } 2818 }; 2819 2820 if ( 'complete' === document.readyState ) { 2821 schedule(); 2822 } else { 2823 window.addEventListener( 'load', schedule, { once: true } ); 2824 } 2825 } 2826 JS; 2827 2828 wp_print_inline_script_tag( "( $js_function )();\n//# sourceURL=" . rawurlencode( __FUNCTION__ ) ); 2829 } 2830 2831 /** 2832 * Handles the enqueueing of block scripts and styles that are common to both 2833 * the editor and the front-end. 2834 * 2835 * @since 5.0.0 2836 */ 2837 function wp_common_block_scripts_and_styles() { 2838 if ( is_admin() && ! wp_should_load_block_editor_scripts_and_styles() ) { 2839 return; 2840 } 2841 2842 wp_enqueue_style( 'wp-block-library' ); 2843 2844 if ( current_theme_supports( 'wp-block-styles' ) && ! wp_should_load_separate_core_block_assets() ) { 2845 wp_enqueue_style( 'wp-block-library-theme' ); 2846 } 2847 2848 /** 2849 * Fires after enqueuing block assets for both editor and front-end. 2850 * 2851 * Call `add_action` on any hook before 'wp_enqueue_scripts'. 2852 * 2853 * In the function call you supply, simply use `wp_enqueue_script` and 2854 * `wp_enqueue_style` to add your functionality to the Gutenberg editor. 2855 * 2856 * @since 5.0.0 2857 */ 2858 do_action( 'enqueue_block_assets' ); 2859 } 2860 2861 /** 2862 * Applies a filter to the list of style nodes that comes from WP_Theme_JSON::get_style_nodes(). 2863 * 2864 * This particular filter removes all of the blocks from the array. 2865 * 2866 * We want WP_Theme_JSON to be ignorant of the implementation details of how the CSS is being used. 2867 * This filter allows us to modify the output of WP_Theme_JSON depending on whether or not we are 2868 * loading separate assets, without making the class aware of that detail. 2869 * 2870 * @since 6.1.0 2871 * 2872 * @param array<array<string, mixed>> $nodes The nodes to filter. 2873 * @return array<array<string, mixed>> A filtered array of style nodes. 2874 */ 2875 function wp_filter_out_block_nodes( $nodes ) { 2876 return array_filter( 2877 $nodes, 2878 static function ( $node ) { 2879 return ! in_array( 'blocks', $node['path'], true ); 2880 }, 2881 ARRAY_FILTER_USE_BOTH 2882 ); 2883 } 2884 2885 /** 2886 * Enqueues the global styles defined via theme.json. 2887 * 2888 * @since 5.8.0 2889 */ 2890 function wp_enqueue_global_styles() { 2891 $assets_on_demand = wp_should_load_block_assets_on_demand(); 2892 $is_block_theme = wp_is_block_theme(); 2893 $is_classic_theme = ! $is_block_theme; 2894 2895 /** 2896 * Global styles should be printed in the HEAD for block themes, or for classic themes when loading assets on 2897 * demand is disabled (which is no longer the default since WordPress 6.9). 2898 * 2899 * @link https://core.trac.wordpress.org/ticket/53494 2900 * @link https://core.trac.wordpress.org/ticket/61965 2901 */ 2902 if ( 2903 doing_action( 'wp_footer' ) && 2904 ( 2905 $is_block_theme || 2906 ( $is_classic_theme && ! $assets_on_demand ) 2907 ) 2908 ) { 2909 return; 2910 } 2911 2912 /** 2913 * The footer should only be used for classic themes when loading assets on demand is enabled. In WP 6.9 this is the 2914 * default with the introduction of hoisting late-printed styles (via {@see wp_load_classic_theme_block_styles_on_demand()}). 2915 * So even though the main global styles are not printed here in the HEAD for classic themes with on-demand asset 2916 * loading, a placeholder for the global styles is still enqueued. Then when {@see wp_hoist_late_printed_styles()} 2917 * processes the output buffer, it can locate the placeholder and inject the global styles from the footer into the 2918 * HEAD, replacing the placeholder. 2919 * 2920 * @link https://core.trac.wordpress.org/ticket/64099 2921 */ 2922 if ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) && $assets_on_demand ) { 2923 if ( has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { 2924 wp_register_style( 'wp-global-styles-placeholder', false ); 2925 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." }' ); 2926 wp_enqueue_style( 'wp-global-styles-placeholder' ); 2927 } 2928 return; 2929 } 2930 2931 /* 2932 * If loading the CSS for each block separately, then load the theme.json CSS conditionally. 2933 * This removes the CSS from the global-styles stylesheet and adds it to the inline CSS for each block. 2934 * This filter must be registered before calling wp_get_global_stylesheet(); 2935 */ 2936 add_filter( 'wp_theme_json_get_style_nodes', 'wp_filter_out_block_nodes' ); 2937 2938 $stylesheet = wp_get_global_stylesheet(); 2939 2940 /* 2941 * For block themes, merge Customizer's custom CSS into the global styles stylesheet 2942 * before the global styles custom CSS, ensuring proper cascade order. 2943 * For classic themes, let the Customizer CSS print separately via wp_custom_css_cb() 2944 * at priority 101 in wp_head, preserving its position at the end of the <head>. 2945 */ 2946 if ( $is_block_theme ) { 2947 /* 2948 * Dequeue the Customizer's custom CSS 2949 * and add it before the global styles custom CSS. 2950 */ 2951 remove_action( 'wp_head', 'wp_custom_css_cb', 101 ); 2952 2953 /* 2954 * Get the custom CSS from the Customizer and add it to the global stylesheet. 2955 * Always do this in Customizer preview for the sake of live preview since it be empty. 2956 */ 2957 $custom_css = trim( wp_get_custom_css() ); 2958 if ( $custom_css || is_customize_preview() ) { 2959 if ( is_customize_preview() ) { 2960 /* 2961 * When in the Customizer preview, wrap the Custom CSS in milestone comments to allow customize-preview.js 2962 * to locate the CSS to replace for live previewing. Make sure that the milestone comments are omitted from 2963 * the stored Custom CSS if by chance someone tried to add them, which would be highly unlikely, but it 2964 * would break live previewing. 2965 */ 2966 $before_milestone = '/*BEGIN_CUSTOMIZER_CUSTOM_CSS*/'; 2967 $after_milestone = '/*END_CUSTOMIZER_CUSTOM_CSS*/'; 2968 $custom_css = str_replace( array( $before_milestone, $after_milestone ), '', $custom_css ); 2969 $custom_css = $before_milestone . "\n" . $custom_css . "\n" . $after_milestone; 2970 } 2971 $custom_css = "\n" . $custom_css; 2972 } 2973 $stylesheet .= $custom_css; 2974 2975 // Add the global styles custom CSS at the end. 2976 $stylesheet .= wp_get_global_stylesheet( array( 'custom-css' ) ); 2977 } 2978 2979 if ( empty( $stylesheet ) ) { 2980 return; 2981 } 2982 2983 wp_register_style( 'global-styles', false ); 2984 wp_add_inline_style( 'global-styles', $stylesheet ); 2985 wp_enqueue_style( 'global-styles' ); 2986 2987 // Add each block as an inline css. 2988 wp_add_global_styles_for_blocks(); 2989 } 2990 2991 /** 2992 * Checks if the editor scripts and styles for all registered block types 2993 * should be enqueued on the current screen. 2994 * 2995 * @since 5.6.0 2996 * 2997 * @global WP_Screen $current_screen WordPress current screen object. 2998 * 2999 * @return bool Whether scripts and styles should be enqueued. 3000 */ 3001 function wp_should_load_block_editor_scripts_and_styles() { 3002 global $current_screen; 3003 3004 $is_block_editor_screen = ( $current_screen instanceof WP_Screen ) && $current_screen->is_block_editor(); 3005 3006 /** 3007 * Filters the flag that decides whether or not block editor scripts and styles 3008 * are going to be enqueued on the current screen. 3009 * 3010 * @since 5.6.0 3011 * 3012 * @param bool $is_block_editor_screen Current value of the flag. 3013 */ 3014 return apply_filters( 'should_load_block_editor_scripts_and_styles', $is_block_editor_screen ); 3015 } 3016 3017 /** 3018 * Checks whether separate styles should be loaded for core blocks. 3019 * 3020 * When this function returns true, other functions ensure that core blocks use their own separate stylesheets. 3021 * When this function returns false, all core blocks will use the single combined 'wp-block-library' stylesheet. 3022 * 3023 * As a side effect, the return value will by default result in block assets to be loaded on demand, via the 3024 * {@see wp_should_load_block_assets_on_demand()} function. This behavior can be separately altered via that function. 3025 * 3026 * This only affects front end and not the block editor screens. 3027 * 3028 * @since 5.8.0 3029 * @see wp_should_load_block_assets_on_demand() 3030 * @see wp_enqueue_registered_block_scripts_and_styles() 3031 * @see register_block_style_handle() 3032 * 3033 * @return bool Whether separate core block assets will be loaded. 3034 */ 3035 function wp_should_load_separate_core_block_assets() { 3036 if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) { 3037 return false; 3038 } 3039 3040 /** 3041 * Filters whether block styles should be loaded separately. 3042 * 3043 * Returning false loads all core block assets, regardless of whether they are rendered 3044 * in a page or not. Returning true loads core block assets only when they are rendered. 3045 * 3046 * @since 5.8.0 3047 * 3048 * @param bool $load_separate_assets Whether separate assets will be loaded. 3049 * Default false (all block assets are loaded, even when not used). 3050 */ 3051 return apply_filters( 'should_load_separate_core_block_assets', false ); 3052 } 3053 3054 /** 3055 * Checks whether block styles should be loaded only on-render. 3056 * 3057 * When this function returns true, other functions ensure that blocks only load their assets on-render. 3058 * When this function returns false, all block assets are loaded regardless of whether they are rendered in a page. 3059 * 3060 * The default return value depends on the result of {@see wp_should_load_separate_core_block_assets()}, which controls 3061 * whether Core block stylesheets should be loaded separately or via a combined 'wp-block-library' stylesheet. 3062 * 3063 * This only affects front end and not the block editor screens. 3064 * 3065 * @since 6.8.0 3066 * @see wp_should_load_separate_core_block_assets() 3067 * 3068 * @return bool Whether to load block assets only when they are rendered. 3069 */ 3070 function wp_should_load_block_assets_on_demand() { 3071 if ( is_admin() || is_feed() || wp_is_rest_endpoint() ) { 3072 return false; 3073 } 3074 3075 /* 3076 * For backward compatibility, the default return value for this function is based on the return value of 3077 * `wp_should_load_separate_core_block_assets()`. Initially, this function used to control both of these concerns. 3078 */ 3079 $load_assets_on_demand = wp_should_load_separate_core_block_assets(); 3080 3081 /** 3082 * Filters whether block styles should be loaded on demand. 3083 * 3084 * Returning false loads all block assets, regardless of whether they are rendered in a page or not. 3085 * Returning true loads block assets only when they are rendered. 3086 * 3087 * The default value of the filter depends on the result of {@see wp_should_load_separate_core_block_assets()}, 3088 * which controls whether Core block stylesheets should be loaded separately or via a combined 'wp-block-library' 3089 * stylesheet. 3090 * 3091 * @since 6.8.0 3092 * 3093 * @param bool $load_assets_on_demand Whether to load block assets only when they are rendered. 3094 */ 3095 return apply_filters( 'should_load_block_assets_on_demand', $load_assets_on_demand ); 3096 } 3097 3098 /** 3099 * Enqueues registered block scripts and styles, depending on current rendered 3100 * context (only enqueuing editor scripts while in context of the editor). 3101 * 3102 * @since 5.0.0 3103 */ 3104 function wp_enqueue_registered_block_scripts_and_styles() { 3105 if ( wp_should_load_block_assets_on_demand() ) { 3106 /** 3107 * Add placeholder for where block styles would historically get enqueued in a classic theme when block assets 3108 * are not loaded on demand. This happens right after {@see wp_common_block_scripts_and_styles()} is called 3109 * at which time wp-block-library is enqueued. 3110 */ 3111 if ( ! wp_is_block_theme() && has_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ) ) { 3112 wp_register_style( 'wp-block-styles-placeholder', false ); 3113 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." }' ); 3114 wp_enqueue_style( 'wp-block-styles-placeholder' ); 3115 } 3116 return; 3117 } 3118 3119 $load_editor_scripts_and_styles = is_admin() && wp_should_load_block_editor_scripts_and_styles(); 3120 3121 $block_registry = WP_Block_Type_Registry::get_instance(); 3122 3123 /* 3124 * Block styles are only enqueued if they're registered. For core blocks, this is only the case if 3125 * `wp_should_load_separate_core_block_assets()` returns true. Otherwise they use the single combined 3126 * 'wp-block-library` stylesheet. See also `register_core_block_style_handles()`. 3127 * Since `wp_enqueue_style()` does not trigger warnings if the style is not registered, it is okay to not cater for 3128 * this behavior here and simply call `wp_enqueue_style()` unconditionally. 3129 */ 3130 foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) { 3131 // Front-end and editor styles. 3132 foreach ( $block_type->style_handles as $style_handle ) { 3133 wp_enqueue_style( $style_handle ); 3134 } 3135 3136 // Front-end and editor scripts. 3137 foreach ( $block_type->script_handles as $script_handle ) { 3138 wp_enqueue_script( $script_handle ); 3139 } 3140 3141 if ( $load_editor_scripts_and_styles ) { 3142 // Editor styles. 3143 foreach ( $block_type->editor_style_handles as $editor_style_handle ) { 3144 wp_enqueue_style( $editor_style_handle ); 3145 } 3146 3147 // Editor scripts. 3148 foreach ( $block_type->editor_script_handles as $editor_script_handle ) { 3149 wp_enqueue_script( $editor_script_handle ); 3150 } 3151 } 3152 } 3153 } 3154 3155 /** 3156 * Function responsible for enqueuing the styles required for block styles functionality on the editor and on the frontend. 3157 * 3158 * @since 5.3.0 3159 * 3160 * @global WP_Styles $wp_styles 3161 */ 3162 function enqueue_block_styles_assets() { 3163 global $wp_styles; 3164 3165 $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); 3166 3167 foreach ( $block_styles as $block_name => $styles ) { 3168 foreach ( $styles as $style_properties ) { 3169 if ( isset( $style_properties['style_handle'] ) ) { 3170 3171 // If the site loads block styles on demand, enqueue the stylesheet on render. 3172 if ( wp_should_load_block_assets_on_demand() ) { 3173 add_filter( 3174 'render_block', 3175 static function ( $html, $block ) use ( $block_name, $style_properties ) { 3176 if ( $block['blockName'] === $block_name ) { 3177 wp_enqueue_style( $style_properties['style_handle'] ); 3178 } 3179 return $html; 3180 }, 3181 10, 3182 2 3183 ); 3184 } else { 3185 wp_enqueue_style( $style_properties['style_handle'] ); 3186 } 3187 } 3188 if ( isset( $style_properties['inline_style'] ) ) { 3189 3190 // Default to "wp-block-library". 3191 $handle = 'wp-block-library'; 3192 3193 // If the site loads block styles on demand, check if the block has a stylesheet registered. 3194 if ( wp_should_load_block_assets_on_demand() ) { 3195 $block_stylesheet_handle = generate_block_asset_handle( $block_name, 'style' ); 3196 3197 if ( isset( $wp_styles->registered[ $block_stylesheet_handle ] ) ) { 3198 $handle = $block_stylesheet_handle; 3199 } 3200 } 3201 3202 // Add inline styles to the calculated handle. 3203 wp_add_inline_style( $handle, $style_properties['inline_style'] ); 3204 } 3205 } 3206 } 3207 } 3208 3209 /** 3210 * Function responsible for enqueuing the assets required for block styles functionality on the editor. 3211 * 3212 * @since 5.3.0 3213 */ 3214 function enqueue_editor_block_styles_assets() { 3215 $block_styles = WP_Block_Styles_Registry::get_instance()->get_all_registered(); 3216 3217 $register_script_lines = array( '( function() {' ); 3218 foreach ( $block_styles as $block_name => $styles ) { 3219 foreach ( $styles as $style_properties ) { 3220 $block_style = array( 3221 'name' => $style_properties['name'], 3222 'label' => $style_properties['label'], 3223 ); 3224 if ( isset( $style_properties['is_default'] ) ) { 3225 $block_style['isDefault'] = $style_properties['is_default']; 3226 } 3227 $register_script_lines[] = sprintf( 3228 ' wp.blocks.registerBlockStyle( \'%s\', %s );', 3229 $block_name, 3230 wp_json_encode( $block_style, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) 3231 ); 3232 } 3233 } 3234 $register_script_lines[] = '} )();'; 3235 $inline_script = implode( "\n", $register_script_lines ); 3236 3237 wp_register_script( 'wp-block-styles', false, array( 'wp-blocks' ), true, array( 'in_footer' => true ) ); 3238 wp_add_inline_script( 'wp-block-styles', $inline_script ); 3239 wp_enqueue_script( 'wp-block-styles' ); 3240 } 3241 3242 /** 3243 * Enqueues the assets required for the block directory within the block editor. 3244 * 3245 * @since 5.5.0 3246 */ 3247 function wp_enqueue_editor_block_directory_assets() { 3248 wp_enqueue_script( 'wp-block-directory' ); 3249 // Prefetched for the block editor by wp_prefetch_admin_assets(), which needs updating if this changes. 3250 wp_enqueue_style( 'wp-block-directory' ); 3251 } 3252 3253 /** 3254 * Enqueues the assets required for the format library within the block editor. 3255 * 3256 * @since 5.8.0 3257 */ 3258 function wp_enqueue_editor_format_library_assets() { 3259 wp_enqueue_script( 'wp-format-library' ); 3260 // Prefetched for the block editor by wp_prefetch_admin_assets(), which needs updating if this changes. 3261 wp_enqueue_style( 'wp-format-library' ); 3262 } 3263 3264 /** 3265 * Formats `<script>` loader tags. 3266 * 3267 * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_script_attributes'} filter. 3268 * Automatically injects type attribute if needed. 3269 * 3270 * @since 5.7.0 3271 * 3272 * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes. 3273 * @return string String containing `<script>` opening and closing tags. 3274 * 3275 * @phpstan-return non-falsy-string 3276 */ 3277 function wp_get_script_tag( $attributes ) { 3278 /** 3279 * Filters attributes to be added to a script tag. 3280 * 3281 * @since 5.7.0 3282 * 3283 * @param array $attributes Key-value pairs representing `<script>` tag attributes. 3284 * Only the attribute name is added to the `<script>` tag for 3285 * entries with a boolean value, and that are true. 3286 */ 3287 $attributes = apply_filters( 'wp_script_attributes', $attributes ); 3288 3289 $processor = new WP_HTML_Tag_Processor( '<script></script>' ); 3290 $processor->next_tag(); 3291 foreach ( $attributes as $name => $value ) { 3292 /* 3293 * Lexical variations of an attribute name may represent the 3294 * same attribute in HTML, therefore it’s possible that the 3295 * input array might contain duplicate attributes even though 3296 * it’s keyed on their name. Calling code should rewrite an 3297 * attribute’s value rather than sending a duplicate attribute. 3298 * 3299 * Example: 3300 * 3301 * array( 'id' => 'main', 'ID' => 'nav' ) 3302 * 3303 * In this example, there are two keys both describing the `id` 3304 * attribute. PHP array iteration is in key-insertion order so 3305 * the 'id' value will be set in the SCRIPT tag. 3306 */ 3307 if ( null !== $processor->get_attribute( $name ) ) { 3308 continue; 3309 } 3310 3311 $processor->set_attribute( $name, $value ?? true ); 3312 } 3313 return "{$processor->get_updated_html()}\n"; 3314 } 3315 3316 /** 3317 * Prints formatted `<script>` loader tag. 3318 * 3319 * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_script_attributes'} filter. 3320 * Automatically injects type attribute if needed. 3321 * 3322 * @since 5.7.0 3323 * 3324 * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes. 3325 */ 3326 function wp_print_script_tag( $attributes ) { 3327 echo wp_get_script_tag( $attributes ); 3328 } 3329 3330 /** 3331 * Constructs an inline script tag. 3332 * 3333 * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_inline_script_attributes'} filter. 3334 * 3335 * If the `$data` is unsafe to embed in a `<script>` tag, an empty script tag with the provided 3336 * attributes will be returned. JavaScript and JSON contents can be escaped, so this is only likely 3337 * to be a problem with unusual content types. 3338 * 3339 * Example: 3340 * 3341 * // The dangerous JavaScript in this example will be safely escaped. 3342 * // A string with the script tag and the desired contents will be returned. 3343 * wp_get_inline_script_tag( 'console.log( "</script>" );' ); 3344 * 3345 * // This data is unsafe and `text/plain` cannot be escaped. 3346 * // The following will return `""` to indicate failure: 3347 * wp_get_inline_script_tag( '</script>', array( 'type' => 'text/plain' ) ); 3348 * 3349 * @since 5.7.0 3350 * @since 7.0.0 Returns an empty string if the data cannot be safely embedded in a script tag. 3351 * 3352 * @param string $data Data for script tag: JavaScript, importmap, speculationrules, etc. 3353 * @param array<string, string|bool> $attributes Optional. Key-value pairs representing `<script>` tag attributes. 3354 * @return string HTML script tag containing the provided $data or the empty string `""` if the data cannot be safely embedded in a script tag. 3355 */ 3356 function wp_get_inline_script_tag( $data, $attributes = array() ) { 3357 $data = "\n" . trim( $data, "\n\r " ) . "\n"; 3358 3359 /** 3360 * Filters attributes to be added to a script tag. 3361 * 3362 * Escaping and normalization can make the text a browser parses from the printed tag differ 3363 * from `$data`. A callback that needs that text, such as one computing a CSP hash, can set 3364 * `$data` as the modifiable text of a SCRIPT tag in a `WP_HTML_Tag_Processor` with the 3365 * filtered attributes applied and then read {@see WP_HTML_Tag_Processor::get_modifiable_text()}, 3366 * at a late priority so that the attributes are final. 3367 * 3368 * @since 5.7.0 3369 * 3370 * @param array<string, string|bool> $attributes Key-value pairs representing `<script>` tag attributes. 3371 * Only the attribute name is added to the `<script>` tag for 3372 * entries with a boolean value, and that are true. 3373 * @param string $data Inline data. 3374 */ 3375 $attributes = apply_filters( 'wp_inline_script_attributes', $attributes, $data ); 3376 3377 $processor = new WP_HTML_Tag_Processor( '<script></script>' ); 3378 $processor->next_tag(); 3379 foreach ( $attributes as $name => $value ) { 3380 /* 3381 * Lexical variations of an attribute name may represent the 3382 * same attribute in HTML, therefore it’s possible that the 3383 * input array might contain duplicate attributes even though 3384 * it’s keyed on their name. Calling code should rewrite an 3385 * attribute’s value rather than sending a duplicate attribute. 3386 * 3387 * Example: 3388 * 3389 * array( 'id' => 'main', 'ID' => 'nav' ) 3390 * 3391 * In this example, there are two keys both describing the `id` 3392 * attribute. PHP array iteration is in key-insertion order so 3393 * the 'id' value will be set in the SCRIPT tag. 3394 */ 3395 if ( null !== $processor->get_attribute( $name ) ) { 3396 continue; 3397 } 3398 3399 $processor->set_attribute( $name, $value ?? true ); 3400 } 3401 3402 if ( ! $processor->set_modifiable_text( $data ) ) { 3403 return ''; 3404 } 3405 3406 return "{$processor->get_updated_html()}\n"; 3407 } 3408 3409 /** 3410 * Prints an inline script tag. 3411 * 3412 * It is possible to inject attributes in the `<script>` tag via the {@see 'wp_inline_script_attributes'} filter. 3413 * Automatically injects type attribute if needed. 3414 * 3415 * @since 5.7.0 3416 * 3417 * @param string $data Data for script tag: JavaScript, importmap, speculationrules, etc. 3418 * @param array<string, string|bool> $attributes Optional. Key-value pairs representing `<script>` tag attributes. 3419 */ 3420 function wp_print_inline_script_tag( $data, $attributes = array() ) { 3421 echo wp_get_inline_script_tag( $data, $attributes ); 3422 } 3423 3424 /** 3425 * Allows small styles to be inlined. 3426 * 3427 * This improves performance and sustainability, and is opt-in. Stylesheets can opt in 3428 * by adding `path` data using `wp_style_add_data`, and defining the file's absolute path: 3429 * 3430 * wp_style_add_data( $style_handle, 'path', $file_path ); 3431 * 3432 * @since 5.8.0 3433 * 3434 * @global WP_Styles $wp_styles 3435 */ 3436 function wp_maybe_inline_styles() { 3437 global $wp_styles; 3438 3439 $total_inline_limit = 40000; 3440 /** 3441 * The maximum size of inlined styles in bytes. 3442 * 3443 * @since 5.8.0 3444 * @since 6.9.0 The default limit increased from 20K to 40K. 3445 * 3446 * @param int $total_inline_limit The file-size threshold, in bytes. Default 40000. 3447 */ 3448 $total_inline_limit = apply_filters( 'styles_inline_size_limit', $total_inline_limit ); 3449 3450 $styles = array(); 3451 3452 // Build an array of styles that have a path defined. 3453 foreach ( $wp_styles->queue as $handle ) { 3454 if ( ! isset( $wp_styles->registered[ $handle ] ) ) { 3455 continue; 3456 } 3457 $src = $wp_styles->registered[ $handle ]->src; 3458 $path = $wp_styles->get_data( $handle, 'path' ); 3459 if ( $path && $src ) { 3460 $size = wp_filesize( $path ); 3461 if ( 0 === $size && ! file_exists( $path ) ) { 3462 _doing_it_wrong( 3463 __FUNCTION__, 3464 sprintf( 3465 /* translators: 1: 'path', 2: filesystem path, 3: style handle */ 3466 __( 'Unable to read the "%1$s" key with value "%2$s" for stylesheet "%3$s".' ), 3467 'path', 3468 esc_html( $path ), 3469 esc_html( $handle ) 3470 ), 3471 '7.0.0' 3472 ); 3473 continue; 3474 } 3475 $styles[] = array( 3476 'handle' => $handle, 3477 'src' => $src, 3478 'path' => $path, 3479 'size' => $size, 3480 ); 3481 } 3482 } 3483 3484 if ( ! empty( $styles ) ) { 3485 // Reorder styles array based on size. 3486 usort( 3487 $styles, 3488 static function ( $a, $b ) { 3489 return $a['size'] <=> $b['size']; 3490 } 3491 ); 3492 3493 /* 3494 * The total inlined size. 3495 * 3496 * On each iteration of the loop, if a style gets added inline the value of this var increases 3497 * to reflect the total size of inlined styles. 3498 */ 3499 $total_inline_size = 0; 3500 3501 // Loop styles. 3502 foreach ( $styles as $style ) { 3503 3504 // Size check. Since styles are ordered by size, we can break the loop. 3505 if ( $total_inline_size + $style['size'] > $total_inline_limit ) { 3506 break; 3507 } 3508 3509 // Get the styles if we don't already have them. 3510 if ( ! is_readable( $style['path'] ) ) { 3511 _doing_it_wrong( 3512 __FUNCTION__, 3513 sprintf( 3514 /* translators: 1: 'path', 2: filesystem path, 3: style handle */ 3515 __( 'Unable to read the "%1$s" key with value "%2$s" for stylesheet "%3$s".' ), 3516 'path', 3517 esc_html( $style['path'] ), 3518 esc_html( $style['handle'] ) 3519 ), 3520 '7.0.0' 3521 ); 3522 continue; 3523 } 3524 $style['css'] = file_get_contents( $style['path'] ); 3525 3526 /* 3527 * Check if the style contains relative URLs that need to be modified. 3528 * URLs relative to the stylesheet's path should be converted to relative to the site's root. 3529 */ 3530 $style['css'] = _wp_normalize_relative_css_links( $style['css'], $style['src'] ); 3531 3532 // Keep track of the original `src` for the style that was inlined so that the `sourceURL` comment can be added. 3533 $wp_styles->add_data( $style['handle'], 'inlined_src', $style['src'] ); 3534 3535 // Set `src` to `false` and add styles inline. 3536 $wp_styles->registered[ $style['handle'] ]->src = false; 3537 if ( empty( $wp_styles->registered[ $style['handle'] ]->extra['after'] ) ) { 3538 $wp_styles->registered[ $style['handle'] ]->extra['after'] = array(); 3539 } 3540 array_unshift( $wp_styles->registered[ $style['handle'] ]->extra['after'], $style['css'] ); 3541 3542 // Add the styles size to the $total_inline_size var. 3543 $total_inline_size += (int) $style['size']; 3544 } 3545 } 3546 } 3547 3548 /** 3549 * Makes URLs relative to the WordPress installation. 3550 * 3551 * @since 5.9.0 3552 * @access private 3553 * 3554 * @param string $css The CSS to make URLs relative to the WordPress installation. 3555 * @param string $stylesheet_url The URL to the stylesheet. 3556 * @return string The CSS with URLs made relative to the WordPress installation. 3557 */ 3558 function _wp_normalize_relative_css_links( $css, $stylesheet_url ) { 3559 return preg_replace_callback( 3560 '#(url\s*\(\s*[\'"]?\s*)([^\'"\)]+)#', 3561 static function ( $matches ) use ( $stylesheet_url ) { 3562 list( , $prefix, $url ) = $matches; 3563 3564 // Short-circuit if the URL does not require normalization. 3565 if ( 3566 str_starts_with( $url, 'http:' ) || 3567 str_starts_with( $url, 'https:' ) || 3568 str_starts_with( $url, '/' ) || 3569 str_starts_with( $url, '#' ) || 3570 str_starts_with( $url, 'data:' ) 3571 ) { 3572 return $matches[0]; 3573 } 3574 3575 // Build the absolute URL. 3576 $absolute_url = dirname( $stylesheet_url ) . '/' . $url; 3577 $absolute_url = str_replace( '/./', '/', $absolute_url ); 3578 3579 // Convert to URL related to the site root. 3580 $url = wp_make_link_relative( $absolute_url ); 3581 3582 return $prefix . $url; 3583 }, 3584 $css 3585 ); 3586 } 3587 3588 /** 3589 * Function that enqueues the CSS Custom Properties coming from theme.json. 3590 * 3591 * @since 5.9.0 3592 */ 3593 function wp_enqueue_global_styles_css_custom_properties() { 3594 wp_register_style( 'global-styles-css-custom-properties', false ); 3595 wp_add_inline_style( 'global-styles-css-custom-properties', wp_get_global_stylesheet( array( 'variables' ) ) ); 3596 wp_enqueue_style( 'global-styles-css-custom-properties' ); 3597 } 3598 3599 /** 3600 * Hooks inline styles in the proper place, depending on the active theme. 3601 * 3602 * @since 5.9.1 3603 * @since 6.1.0 Added the `$priority` parameter. 3604 * 3605 * For block themes, styles are loaded in the head. 3606 * For classic ones, styles are loaded in the body because the wp_head action happens before render_block. 3607 * 3608 * @link https://core.trac.wordpress.org/ticket/53494. 3609 * 3610 * @param string $style String containing the CSS styles to be added. 3611 * @param int $priority To set the priority for the add_action. 3612 */ 3613 function wp_enqueue_block_support_styles( $style, $priority = 10 ) { 3614 $action_hook_name = 'wp_footer'; 3615 if ( wp_is_block_theme() ) { 3616 $action_hook_name = 'wp_head'; 3617 } 3618 add_action( 3619 $action_hook_name, 3620 static function () use ( $style ) { 3621 $processor = new WP_HTML_Tag_Processor( '<style></style>' ); 3622 $processor->next_tag(); 3623 $processor->set_modifiable_text( $style ); 3624 echo "{$processor->get_updated_html()}\n"; 3625 }, 3626 $priority 3627 ); 3628 } 3629 3630 /** 3631 * Fetches, processes and compiles stored core styles, then combines and renders them to the page. 3632 * Styles are stored via the style engine API. 3633 * 3634 * @link https://developer.wordpress.org/block-editor/reference-guides/packages/packages-style-engine/ 3635 * 3636 * @since 6.1.0 3637 * 3638 * @param array<string, bool> $options { 3639 * Optional. An array of options to pass to wp_style_engine_get_stylesheet_from_context(). 3640 * Default empty array. 3641 * 3642 * @type bool $optimize Whether to optimize the CSS output, e.g., combine rules. 3643 * Default false. 3644 * @type bool $prettify Whether to add new lines and indents to output. 3645 * Default to whether the `SCRIPT_DEBUG` constant is defined. 3646 * } 3647 */ 3648 function wp_enqueue_stored_styles( $options = array() ) { 3649 // 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(). 3650 $is_block_theme = wp_is_block_theme(); 3651 $is_classic_theme = ! $is_block_theme; 3652 3653 /* 3654 * For block themes, this function prints stored styles in the header. 3655 * For classic themes, in the footer. 3656 */ 3657 if ( 3658 ( $is_block_theme && doing_action( 'wp_footer' ) ) || 3659 ( $is_classic_theme && doing_action( 'wp_enqueue_scripts' ) ) 3660 ) { 3661 return; 3662 } 3663 3664 $core_styles_keys = array( 'block-supports' ); 3665 $compiled_core_stylesheet = ''; 3666 $style_tag_id = 'core'; 3667 // Adds comment if code is prettified to identify core styles sections in debugging. 3668 $should_prettify = isset( $options['prettify'] ) ? true === $options['prettify'] : defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG; 3669 foreach ( $core_styles_keys as $style_key ) { 3670 if ( $should_prettify ) { 3671 $compiled_core_stylesheet .= "/**\n * Core styles: $style_key\n */\n"; 3672 } 3673 // Chains core store ids to signify what the styles contain. 3674 $style_tag_id .= '-' . $style_key; 3675 $compiled_core_stylesheet .= wp_style_engine_get_stylesheet_from_context( $style_key, $options ); 3676 } 3677 3678 // Combines Core styles. 3679 if ( ! empty( $compiled_core_stylesheet ) ) { 3680 wp_register_style( $style_tag_id, false ); 3681 wp_add_inline_style( $style_tag_id, $compiled_core_stylesheet ); 3682 wp_enqueue_style( $style_tag_id ); 3683 } 3684 3685 // Prints out any other stores registered by themes or otherwise. 3686 $additional_stores = WP_Style_Engine_CSS_Rules_Store::get_stores(); 3687 foreach ( array_keys( $additional_stores ) as $store_name ) { 3688 if ( in_array( $store_name, $core_styles_keys, true ) ) { 3689 continue; 3690 } 3691 $styles = wp_style_engine_get_stylesheet_from_context( $store_name, $options ); 3692 if ( ! empty( $styles ) ) { 3693 $key = "wp-style-engine-$store_name"; 3694 wp_register_style( $key, false ); 3695 wp_add_inline_style( $key, $styles ); 3696 wp_enqueue_style( $key ); 3697 } 3698 } 3699 } 3700 3701 /** 3702 * Enqueues a stylesheet for a specific block. 3703 * 3704 * If the theme has opted-in to load block styles on demand, 3705 * then the stylesheet will be enqueued on-render, 3706 * otherwise when the block inits. 3707 * 3708 * @since 5.9.0 3709 * 3710 * @param string $block_name The block-name, including namespace. 3711 * @param array<string, string|string[]|bool|null> $args { 3712 * An array of arguments. See wp_register_style() for full information about each argument. 3713 * 3714 * @type string $handle The handle for the stylesheet. 3715 * @type string|false $src The source URL of the stylesheet. 3716 * @type string[] $deps Array of registered stylesheet handles this stylesheet depends on. 3717 * @type string|bool|null $ver Stylesheet version number. 3718 * @type string $media The media for which this stylesheet has been defined. 3719 * @type string|null $path Absolute path to the stylesheet, so that it can potentially be inlined. 3720 * } 3721 */ 3722 function wp_enqueue_block_style( $block_name, $args ) { 3723 $args = wp_parse_args( 3724 $args, 3725 array( 3726 'handle' => '', 3727 'src' => '', 3728 'deps' => array(), 3729 'ver' => false, 3730 'media' => 'all', 3731 ) 3732 ); 3733 3734 /** 3735 * Callback function to register and enqueue styles. 3736 * 3737 * @param string $content When the callback is used for the render_block filter, 3738 * the content needs to be returned so the function parameter 3739 * is to ensure the content exists. 3740 * @return string Block content. 3741 */ 3742 $callback = static function ( $content ) use ( $args ) { 3743 // Register the stylesheet. 3744 if ( ! empty( $args['src'] ) ) { 3745 wp_register_style( $args['handle'], $args['src'], $args['deps'], $args['ver'], $args['media'] ); 3746 } 3747 3748 // Add `path` data if provided. 3749 if ( isset( $args['path'] ) ) { 3750 wp_style_add_data( $args['handle'], 'path', $args['path'] ); 3751 3752 // Get the RTL file path. 3753 $rtl_file_path = str_replace( '.css', '-rtl.css', $args['path'] ); 3754 3755 // Add RTL stylesheet. 3756 if ( file_exists( $rtl_file_path ) ) { 3757 wp_style_add_data( $args['handle'], 'rtl', 'replace' ); 3758 3759 if ( is_rtl() ) { 3760 wp_style_add_data( $args['handle'], 'path', $rtl_file_path ); 3761 } 3762 } 3763 } 3764 3765 // Enqueue the stylesheet. 3766 wp_enqueue_style( $args['handle'] ); 3767 3768 return $content; 3769 }; 3770 3771 $hook = did_action( 'wp_enqueue_scripts' ) ? 'wp_footer' : 'wp_enqueue_scripts'; 3772 if ( wp_should_load_block_assets_on_demand() ) { 3773 /** 3774 * Callback function to register and enqueue styles. 3775 * 3776 * @param string $content The block content. 3777 * @param array $block The full block, including name and attributes. 3778 * @return string Block content. 3779 */ 3780 $callback_separate = static function ( $content, $block ) use ( $block_name, $callback ) { 3781 if ( ! empty( $block['blockName'] ) && $block_name === $block['blockName'] ) { 3782 return $callback( $content ); 3783 } 3784 return $content; 3785 }; 3786 3787 /* 3788 * The filter's callback here is an anonymous function because 3789 * using a named function in this case is not possible. 3790 * 3791 * The function cannot be unhooked, however, users are still able 3792 * to dequeue the stylesheets registered/enqueued by the callback 3793 * which is why in this case, using an anonymous function 3794 * was deemed acceptable. 3795 */ 3796 add_filter( 'render_block', $callback_separate, 10, 2 ); 3797 return; 3798 } 3799 3800 /* 3801 * The filter's callback here is an anonymous function because 3802 * using a named function in this case is not possible. 3803 * 3804 * The function cannot be unhooked, however, users are still able 3805 * to dequeue the stylesheets registered/enqueued by the callback 3806 * which is why in this case, using an anonymous function 3807 * was deemed acceptable. 3808 */ 3809 add_filter( $hook, $callback ); 3810 3811 // Enqueue assets in the editor. 3812 add_action( 'enqueue_block_assets', $callback ); 3813 } 3814 3815 /** 3816 * Loads classic theme styles when the current theme lacks a theme.json file. 3817 * 3818 * This is used for backwards compatibility for Button and File blocks specifically. 3819 * 3820 * @since 6.1.0 3821 * @since 6.2.0 Added File block styles. 3822 * @since 6.8.0 Moved stylesheet registration outside of this function. 3823 */ 3824 function wp_enqueue_classic_theme_styles() { 3825 if ( ! wp_theme_has_theme_json() ) { 3826 wp_enqueue_style( 'classic-theme-styles' ); 3827 } 3828 } 3829 3830 /** 3831 * Enqueues the assets required for the Command Palette. 3832 * 3833 * @since 6.9.0 3834 * 3835 * @global array $menu 3836 * @global array $submenu 3837 */ 3838 function wp_enqueue_command_palette_assets() { 3839 global $menu, $submenu; 3840 3841 $command_palette_settings = array( 3842 'is_network_admin' => is_network_admin(), 3843 ); 3844 3845 /** 3846 * Extracts root-level text nodes from HTML string. 3847 * 3848 * @ignore 3849 * @param string $label HTML string to extract text from. 3850 * @return string Extracted text content, trimmed. 3851 */ 3852 $extract_root_text = static function ( string $label ): string { 3853 if ( '' === $label ) { 3854 return ''; 3855 } 3856 3857 $processor = new WP_HTML_Tag_Processor( $label ); 3858 $text_parts = array(); 3859 $depth = 0; 3860 3861 while ( $processor->next_token() ) { 3862 $token_type = $processor->get_token_type(); 3863 3864 if ( '#text' === $token_type ) { 3865 if ( 0 === $depth ) { 3866 $text_parts[] = $processor->get_modifiable_text(); 3867 } 3868 continue; 3869 } 3870 3871 if ( '#tag' !== $token_type ) { 3872 continue; 3873 } 3874 3875 if ( $processor->is_tag_closer() ) { 3876 if ( $depth > 0 ) { 3877 --$depth; 3878 } 3879 continue; 3880 } 3881 3882 $token_name = $processor->get_tag(); 3883 if ( $token_name && ! WP_HTML_Processor::is_void( $token_name ) ) { 3884 ++$depth; 3885 } 3886 } 3887 3888 return trim( implode( '', $text_parts ) ); 3889 }; 3890 3891 if ( $menu ) { 3892 $menu_commands = array(); 3893 foreach ( $menu as $menu_item ) { 3894 if ( empty( $menu_item[0] ) || ! is_string( $menu_item[0] ) || ! empty( $menu_item[1] ) && ! current_user_can( $menu_item[1] ) ) { 3895 continue; 3896 } 3897 3898 $menu_label = $extract_root_text( $menu_item[0] ); 3899 $menu_url = ''; 3900 $menu_slug = $menu_item[2]; 3901 3902 if ( preg_match( '/\.php($|\?)/', $menu_slug ) || wp_http_validate_url( $menu_slug ) ) { 3903 $menu_url = $menu_slug; 3904 } elseif ( ! empty( menu_page_url( $menu_slug, false ) ) ) { 3905 $menu_url = WP_HTML_Decoder::decode_attribute( menu_page_url( $menu_slug, false ) ); 3906 } 3907 3908 if ( $menu_url ) { 3909 $menu_commands[] = array( 3910 'label' => $menu_label, 3911 'url' => $menu_url, 3912 'name' => $menu_slug, 3913 ); 3914 } 3915 3916 if ( array_key_exists( $menu_slug, $submenu ) ) { 3917 foreach ( $submenu[ $menu_slug ] as $submenu_item ) { 3918 if ( empty( $submenu_item[0] ) || ! empty( $submenu_item[1] ) && ! current_user_can( $submenu_item[1] ) ) { 3919 continue; 3920 } 3921 3922 $submenu_label = $extract_root_text( $submenu_item[0] ); 3923 $submenu_url = ''; 3924 $submenu_slug = $submenu_item[2]; 3925 3926 if ( preg_match( '/\.php($|\?)/', $submenu_slug ) || wp_http_validate_url( $submenu_slug ) ) { 3927 $submenu_url = $submenu_slug; 3928 } elseif ( ! empty( menu_page_url( $submenu_slug, false ) ) ) { 3929 $submenu_url = WP_HTML_Decoder::decode_attribute( menu_page_url( $submenu_slug, false ) ); 3930 } 3931 if ( $submenu_url ) { 3932 $menu_commands[] = array( 3933 'label' => sprintf( 3934 /* translators: 1: Menu label, 2: Submenu label. */ 3935 __( '%1$s > %2$s' ), 3936 $menu_label, 3937 $submenu_label 3938 ), 3939 'url' => $submenu_url, 3940 'name' => $menu_slug . '-' . $submenu_item[2], 3941 ); 3942 } 3943 } 3944 } 3945 } 3946 $command_palette_settings['menu_commands'] = $menu_commands; 3947 } 3948 3949 wp_enqueue_script( 'wp-commands' ); 3950 // Prefetched from the login screen by wp_prefetch_admin_assets(), which needs updating if this changes. 3951 wp_enqueue_style( 'wp-commands' ); 3952 wp_enqueue_script( 'wp-core-commands' ); 3953 3954 wp_add_inline_script( 3955 'wp-core-commands', 3956 sprintf( 3957 'wp.coreCommands.initializeCommandPalette( %s );', 3958 wp_json_encode( $command_palette_settings, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) 3959 ) 3960 ); 3961 } 3962 3963 /** 3964 * Removes leading and trailing _empty_ script tags. 3965 * 3966 * This is a helper meant to be used for literal script tag construction 3967 * within `wp_get_inline_script_tag()` or `wp_print_inline_script_tag()`. 3968 * It removes the literal values of "<script>" and "</script>" from 3969 * around an inline script after trimming whitespace. Typically this 3970 * is used in conjunction with output buffering, where `ob_get_clean()` 3971 * is passed as the `$contents` argument. 3972 * 3973 * Example: 3974 * 3975 * // Strips exact literal empty SCRIPT tags. 3976 * $js = '<script>sayHello();</script>; 3977 * 'sayHello();' === wp_remove_surrounding_empty_script_tags( $js ); 3978 * 3979 * // Otherwise if anything is different it warns in the JS console. 3980 * $js = '<script type="module">console.log( "hi" );</script>'; 3981 * 'console.error( ... )' === wp_remove_surrounding_empty_script_tags( $js ); 3982 * 3983 * @since 6.4.0 3984 * @access private 3985 * 3986 * @see wp_print_inline_script_tag() 3987 * @see wp_get_inline_script_tag() 3988 * 3989 * @param string $contents Script body with manually created SCRIPT tag literals. 3990 * @return string Script body without surrounding script tag literals, or 3991 * original contents if both exact literals aren't present. 3992 */ 3993 function wp_remove_surrounding_empty_script_tags( $contents ) { 3994 $contents = trim( $contents ); 3995 $opener = '<SCRIPT>'; 3996 $closer = '</SCRIPT>'; 3997 3998 if ( 3999 strlen( $contents ) > strlen( $opener ) + strlen( $closer ) && 4000 strtoupper( substr( $contents, 0, strlen( $opener ) ) ) === $opener && 4001 strtoupper( substr( $contents, -strlen( $closer ) ) ) === $closer 4002 ) { 4003 return substr( $contents, strlen( $opener ), -strlen( $closer ) ); 4004 } else { 4005 $error_message = __( 'Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.' ); 4006 _doing_it_wrong( __FUNCTION__, $error_message, '6.4' ); 4007 return sprintf( 4008 'console.error(%s)', 4009 wp_json_encode( 4010 sprintf( 4011 /* translators: %s: wp_remove_surrounding_empty_script_tags() */ 4012 __( 'Function %s used incorrectly in PHP.' ), 4013 'wp_remove_surrounding_empty_script_tags()' 4014 ) . ' ' . $error_message 4015 ) 4016 ); 4017 } 4018 } 4019 4020 /** 4021 * Adds hooks to load block styles on demand in classic themes. 4022 * 4023 * This function must be called before {@see wp_default_styles()} and {@see register_core_block_style_handles()} so that 4024 * the filters are added to cause {@see wp_should_load_separate_core_block_assets()} to return true. 4025 * 4026 * @since 6.9.0 4027 * @since 7.0.0 This is now invoked at the `wp_default_styles` action with priority 0 instead of at `init` with priority 8. 4028 * 4029 * @see _add_default_theme_supports() 4030 */ 4031 function wp_load_classic_theme_block_styles_on_demand(): void { 4032 // This is not relevant to block themes, as they are opted in to loading separate styles on demand via _add_default_theme_supports(). 4033 if ( wp_is_block_theme() ) { 4034 return; 4035 } 4036 4037 /* 4038 * Make sure that wp_should_output_buffer_template_for_enhancement() returns true even if there aren't any 4039 * `wp_template_enhancement_output_buffer` filters added, but do so at priority zero so that applications which 4040 * wish to stream responses can more easily turn this off. 4041 */ 4042 add_filter( 'wp_should_output_buffer_template_for_enhancement', '__return_true', 0 ); 4043 4044 // If a site has opted out of the template enhancement output buffer, then bail. 4045 if ( ! wp_should_output_buffer_template_for_enhancement() ) { 4046 return; 4047 } 4048 4049 // The following two filters are added by default for block themes in _add_default_theme_supports(). 4050 4051 /* 4052 * Load separate block styles so that the large block-library stylesheet is not enqueued unconditionally, and so 4053 * that block-specific styles will only be enqueued when they are used on the page. A priority of zero allows for 4054 * this to be easily overridden by themes which wish to opt out. If a site has explicitly opted out of loading 4055 * separate block styles, then abort. 4056 */ 4057 add_filter( 'should_load_separate_core_block_assets', '__return_true', 0 ); 4058 if ( ! wp_should_load_separate_core_block_assets() ) { 4059 return; 4060 } 4061 4062 /* 4063 * Also ensure that block assets are loaded on demand (although the default value is from should_load_separate_core_block_assets). 4064 * As above, a priority of zero allows for this to be easily overridden by themes which wish to opt out. If a site 4065 * has explicitly opted out of loading block styles on demand, then abort. 4066 */ 4067 add_filter( 'should_load_block_assets_on_demand', '__return_true', 0 ); 4068 if ( ! wp_should_load_block_assets_on_demand() ) { 4069 return; 4070 } 4071 4072 // Add hooks which require the presence of the output buffer. Ideally the above two filters could be added here, but they run too early. 4073 add_action( 'wp_template_enhancement_output_buffer_started', 'wp_hoist_late_printed_styles' ); 4074 } 4075 4076 /** 4077 * Adds the hooks needed for CSS output to be delayed until after the content of the page has been established. 4078 * 4079 * @since 6.9.0 4080 * 4081 * @see wp_load_classic_theme_block_styles_on_demand() 4082 * @see _wp_footer_scripts() 4083 */ 4084 function wp_hoist_late_printed_styles(): void { 4085 // Skip the embed template on-demand styles aren't relevant, and there is no wp_head action. 4086 if ( is_embed() ) { 4087 return; 4088 } 4089 4090 /* 4091 * Add a placeholder comment into the inline styles for wp-block-library, after which the late block styles 4092 * can be hoisted from the footer to be printed in the header by means of a filter below on the template enhancement 4093 * output buffer. 4094 * 4095 * Note that wp_maybe_inline_styles() prepends the inlined style to the extra 'after' array, which happens after 4096 * this code runs. This ensures that the placeholder appears right after any inlined wp-block-library styles, 4097 * which would be common.css. 4098 */ 4099 $placeholder = sprintf( '/*%s*/', uniqid( 'wp_block_styles_on_demand_placeholder:' ) ); 4100 $dependency = wp_styles()->query( 'wp-block-library', 'registered' ); 4101 if ( $dependency ) { 4102 if ( ! isset( $dependency->extra['after'] ) ) { 4103 wp_add_inline_style( 'wp-block-library', $placeholder ); 4104 } else { 4105 array_unshift( $dependency->extra['after'], $placeholder ); 4106 } 4107 } 4108 4109 /* 4110 * Create a substitute for `print_late_styles()` which is aware of block styles. This substitute does not print 4111 * the styles, but it captures what would be printed for block styles and non-block styles so that they can be 4112 * later hoisted to the HEAD in the template enhancement output buffer. This will run at `wp_print_footer_scripts` 4113 * before `print_footer_scripts()` is called. 4114 */ 4115 $printed_core_block_styles = ''; 4116 $printed_other_block_styles = ''; 4117 $printed_global_styles = ''; 4118 $printed_late_styles = ''; 4119 4120 $capture_late_styles = static function () use ( &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) { 4121 // Gather the styles related to on-demand block enqueues. 4122 $all_core_block_style_handles = array(); 4123 $all_other_block_style_handles = array(); 4124 foreach ( WP_Block_Type_Registry::get_instance()->get_all_registered() as $block_type ) { 4125 if ( str_starts_with( $block_type->name, 'core/' ) ) { 4126 foreach ( $block_type->style_handles as $style_handle ) { 4127 $all_core_block_style_handles[] = $style_handle; 4128 } 4129 } else { 4130 foreach ( $block_type->style_handles as $style_handle ) { 4131 $all_other_block_style_handles[] = $style_handle; 4132 } 4133 } 4134 } 4135 4136 /* 4137 * First print all styles related to core blocks which should be inserted right after the wp-block-library stylesheet 4138 * to preserve the CSS cascade. The logic in this `if` statement is derived from `wp_print_styles()`. 4139 */ 4140 $enqueued_core_block_styles = array_values( array_intersect( $all_core_block_style_handles, wp_styles()->queue ) ); 4141 if ( count( $enqueued_core_block_styles ) > 0 ) { 4142 ob_start(); 4143 wp_styles()->do_items( $enqueued_core_block_styles ); 4144 $printed_core_block_styles = (string) ob_get_clean(); 4145 } 4146 4147 // Capture non-core block styles so they can get printed at the point where wp_enqueue_registered_block_scripts_and_styles() runs. 4148 $enqueued_other_block_styles = array_values( array_intersect( $all_other_block_style_handles, wp_styles()->queue ) ); 4149 if ( count( $enqueued_other_block_styles ) > 0 ) { 4150 ob_start(); 4151 wp_styles()->do_items( $enqueued_other_block_styles ); 4152 $printed_other_block_styles = (string) ob_get_clean(); 4153 } 4154 4155 // Capture the global-styles so that it can be printed at the point where wp_enqueue_global_styles() runs. 4156 if ( wp_style_is( 'global-styles' ) ) { 4157 ob_start(); 4158 wp_styles()->do_items( array( 'global-styles' ) ); 4159 $printed_global_styles = (string) ob_get_clean(); 4160 } 4161 4162 /* 4163 * Print all remaining styles not related to blocks. This contains a subset of the logic from 4164 * `print_late_styles()`, without admin-specific logic and the `print_late_styles` filter to control whether 4165 * late styles are printed (since they are being hoisted anyway). 4166 */ 4167 ob_start(); 4168 wp_styles()->do_footer_items(); 4169 $printed_late_styles = (string) ob_get_clean(); 4170 }; 4171 4172 /* 4173 * If `_wp_footer_scripts()` was unhooked from the `wp_print_footer_scripts` action, or if `wp_print_footer_scripts()` 4174 * was unhooked from running at the `wp_footer` action, then only add a callback to `wp_footer` which will capture the 4175 * late-printed styles. 4176 * 4177 * Otherwise, in the normal case where `_wp_footer_scripts()` will run at the `wp_print_footer_scripts` action, then 4178 * swap out `_wp_footer_scripts()` with an alternative which captures the printed styles (for hoisting to HEAD) before 4179 * proceeding with printing the footer scripts. 4180 */ 4181 $wp_print_footer_scripts_priority = has_action( 'wp_print_footer_scripts', '_wp_footer_scripts' ); 4182 if ( false === $wp_print_footer_scripts_priority || false === has_action( 'wp_footer', 'wp_print_footer_scripts' ) ) { 4183 // The normal priority for wp_print_footer_scripts() is to run at 20. 4184 add_action( 'wp_footer', $capture_late_styles, 20 ); 4185 } else { 4186 remove_action( 'wp_print_footer_scripts', '_wp_footer_scripts', $wp_print_footer_scripts_priority ); 4187 add_action( 4188 'wp_print_footer_scripts', 4189 static function () use ( $capture_late_styles ) { 4190 $capture_late_styles(); 4191 print_footer_scripts(); 4192 }, 4193 $wp_print_footer_scripts_priority 4194 ); 4195 } 4196 4197 // Replace placeholder with the captured late styles. 4198 add_filter( 4199 'wp_template_enhancement_output_buffer', 4200 static function ( $buffer ) use ( $placeholder, &$printed_core_block_styles, &$printed_other_block_styles, &$printed_global_styles, &$printed_late_styles ) { 4201 4202 // Anonymous subclass of WP_HTML_Tag_Processor which exposes underlying bookmark spans. 4203 $processor = new class( $buffer ) extends WP_HTML_Tag_Processor { 4204 /** 4205 * Gets the span for the current token. 4206 * 4207 * @return WP_HTML_Span Current token span. 4208 */ 4209 private function get_span(): WP_HTML_Span { 4210 // Note: This call will never fail according to the usage of this class, given it is always called after ::next_tag() is true. 4211 $this->set_bookmark( 'here' ); 4212 return $this->bookmarks['here']; 4213 } 4214 4215 /** 4216 * Inserts text before the current token. 4217 * 4218 * @param string $text Text to insert. 4219 */ 4220 public function insert_before( string $text ): void { 4221 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $this->get_span()->start, 0, $text ); 4222 } 4223 4224 /** 4225 * Inserts text after the current token. 4226 * 4227 * @param string $text Text to insert. 4228 */ 4229 public function insert_after( string $text ): void { 4230 $span = $this->get_span(); 4231 4232 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start + $span->length, 0, $text ); 4233 } 4234 4235 /** 4236 * Removes the current token. 4237 */ 4238 public function remove(): void { 4239 $span = $this->get_span(); 4240 4241 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, '' ); 4242 } 4243 4244 /** 4245 * Replaces the current token. 4246 * 4247 * @param string $text Text to replace with. 4248 */ 4249 public function replace( string $text ): void { 4250 $span = $this->get_span(); 4251 4252 $this->lexical_updates[] = new WP_HTML_Text_Replacement( $span->start, $span->length, $text ); 4253 } 4254 }; 4255 4256 // Locate the insertion points in the HEAD. 4257 while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) { 4258 if ( 4259 'STYLE' === $processor->get_tag() && 4260 'wp-global-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) 4261 ) { 4262 /** This is added in {@see wp_enqueue_global_styles()} */ 4263 $processor->set_bookmark( 'wp_global_styles_placeholder' ); 4264 } elseif ( 4265 'STYLE' === $processor->get_tag() && 4266 'wp-block-styles-placeholder-inline-css' === $processor->get_attribute( 'id' ) 4267 ) { 4268 /** This is added in {@see wp_enqueue_registered_block_scripts_and_styles()} */ 4269 $processor->set_bookmark( 'wp_block_styles_placeholder' ); 4270 } elseif ( 4271 'STYLE' === $processor->get_tag() && 4272 'wp-block-library-inline-css' === $processor->get_attribute( 'id' ) 4273 ) { 4274 /** This is added here in {@see wp_hoist_late_printed_styles()} */ 4275 $processor->set_bookmark( 'wp_block_library' ); 4276 } elseif ( 'HEAD' === $processor->get_tag() && $processor->is_tag_closer() ) { 4277 $processor->set_bookmark( 'head_end' ); 4278 break; 4279 } 4280 } 4281 4282 /** 4283 * Replace the placeholder for global styles enqueued during {@see wp_enqueue_global_styles()}. This is done 4284 * even if $printed_global_styles is empty. 4285 */ 4286 if ( $processor->has_bookmark( 'wp_global_styles_placeholder' ) ) { 4287 $processor->seek( 'wp_global_styles_placeholder' ); 4288 $processor->replace( $printed_global_styles ); 4289 $printed_global_styles = ''; 4290 } 4291 4292 /* 4293 * Insert block styles right after wp-block-library (if it is present). The placeholder CSS comment will 4294 * always be added to the wp-block-library inline style since it gets printed at `wp_head` before the blocks 4295 * are rendered. This means that there may not actually be any block styles to hoist from the footer to 4296 * insert after this inline style. The placeholder CSS comment needs to be added so that the inline style 4297 * gets printed, but if the resulting inline style is empty after the placeholder is removed, then the 4298 * inline style is removed. 4299 */ 4300 if ( $processor->has_bookmark( 'wp_block_library' ) ) { 4301 $processor->seek( 'wp_block_library' ); 4302 4303 $css_text = $processor->get_modifiable_text(); 4304 4305 /* 4306 * Split the block library inline style by the placeholder to identify the original inlined CSS, which 4307 * likely would be common.css, followed by any inline styles which had been added by the theme or 4308 * plugins via `wp_add_inline_style( 'wp-block-library', '...' )`. The separate block styles loaded on 4309 * demand will get inserted after the inlined common.css and before the extra inline styles added by the 4310 * user. 4311 */ 4312 $css_text_around_placeholder = explode( $placeholder, $css_text, 2 ); 4313 $extra_inline_styles = ''; 4314 if ( count( $css_text_around_placeholder ) === 2 ) { 4315 $css_text = $css_text_around_placeholder[0]; 4316 if ( '' !== trim( $css_text ) ) { 4317 $inlined_src = wp_styles()->get_data( 'wp-block-library', 'inlined_src' ); 4318 if ( $inlined_src ) { 4319 $css_text .= sprintf( 4320 "\n/*# sourceURL=%s */\n", 4321 esc_url_raw( $inlined_src ) 4322 ); 4323 } 4324 } 4325 $extra_inline_styles = $css_text_around_placeholder[1]; 4326 } 4327 4328 /* 4329 * The placeholder CSS comment was added to the inline style in order to force an inline STYLE tag to 4330 * be printed. Now that the inline style has been located and the placeholder comment has been removed, if 4331 * there is no CSS left in the STYLE tag after removal, then remove the STYLE tag entirely. 4332 */ 4333 if ( '' === trim( $css_text ) ) { 4334 $processor->remove(); 4335 } else { 4336 $processor->set_modifiable_text( $css_text ); 4337 } 4338 4339 $inserted_after = $printed_core_block_styles; 4340 $printed_core_block_styles = ''; 4341 4342 /* 4343 * Add a new inline style for any user styles added via wp_add_inline_style( 'wp-block-library', '...' ). 4344 * This must be added here after $printed_core_block_styles to preserve the original CSS cascade when 4345 * the combined block library stylesheet was used. The pattern here is checking to see if it is not just 4346 * a sourceURL comment after the placeholder above is removed. 4347 */ 4348 if ( ! preg_match( ':^\s*(/\*# sourceURL=\S+? \*/\s*)?$:s', $extra_inline_styles ) ) { 4349 $style_processor = new WP_HTML_Tag_Processor( '<style></style>' ); 4350 $style_processor->next_tag(); 4351 $style_processor->set_attribute( 'id', 'wp-block-library-inline-css-extra' ); 4352 $style_processor->set_modifiable_text( $extra_inline_styles ); 4353 $inserted_after .= "{$style_processor->get_updated_html()}\n"; 4354 } 4355 4356 if ( '' !== $inserted_after ) { 4357 $processor->insert_after( "\n" . $inserted_after ); 4358 } 4359 } 4360 4361 // Insert block styles at the point where wp_enqueue_registered_block_scripts_and_styles() normally enqueues styles. 4362 if ( $processor->has_bookmark( 'wp_block_styles_placeholder' ) ) { 4363 $processor->seek( 'wp_block_styles_placeholder' ); 4364 if ( '' !== $printed_other_block_styles ) { 4365 $processor->replace( "\n" . $printed_other_block_styles ); 4366 } else { 4367 $processor->remove(); 4368 } 4369 $printed_other_block_styles = ''; 4370 } 4371 4372 // Print all remaining styles. 4373 $remaining_styles = $printed_core_block_styles . $printed_other_block_styles . $printed_global_styles . $printed_late_styles; 4374 if ( $remaining_styles && $processor->has_bookmark( 'head_end' ) ) { 4375 $processor->seek( 'head_end' ); 4376 $processor->insert_before( $remaining_styles . "\n" ); 4377 } 4378 return $processor->get_updated_html(); 4379 } 4380 ); 4381 } 4382 4383 /** 4384 * Return the corresponding JavaScript `dataset` name for an attribute 4385 * if it represents a custom data attribute, or `null` if not. 4386 * 4387 * Custom data attributes appear in an element's `dataset` property in a 4388 * browser, but there's a specific way the names are translated from HTML 4389 * into JavaScript. This function indicates how the name would appear in 4390 * JavaScript if a browser would recognize it as a custom data attribute. 4391 * 4392 * Example: 4393 * 4394 * // Dash-letter pairs turn into capital letters. 4395 * 'postId' === wp_js_dataset_name( 'data-post-id' ); 4396 * 'Before' === wp_js_dataset_name( 'data--before' ); 4397 * '-One--Two---' === wp_js_dataset_name( 'data---one---two---' ); 4398 * 4399 * // Not every attribute name will be interpreted as a custom data attribute. 4400 * null === wp_js_dataset_name( 'post-id' ); 4401 * null === wp_js_dataset_name( 'data' ); 4402 * 4403 * // Some very surprising names will; for example, a property whose name is the empty string. 4404 * '' === wp_js_dataset_name( 'data-' ); 4405 * 0 === strlen( wp_js_dataset_name( 'data-' ) ); 4406 * 4407 * @since 6.9.0 4408 * 4409 * @see \wp_html_custom_data_attribute_name() 4410 * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs 4411 * 4412 * @param string $html_attribute_name Raw attribute name as found in the source HTML. 4413 * @return string|null Transformed `dataset` name, if interpretable as a custom data attribute, else `null`. 4414 */ 4415 function wp_js_dataset_name( string $html_attribute_name ): ?string { 4416 if ( 0 !== substr_compare( $html_attribute_name, 'data-', 0, 5, true ) ) { 4417 return null; 4418 } 4419 4420 $end = strlen( $html_attribute_name ); 4421 4422 /* 4423 * If it contains characters which would end the attribute name parsing then 4424 * something else is wrong and this contains more than just an attribute name. 4425 */ 4426 if ( ( $end - 5 ) !== strcspn( $html_attribute_name, "=/> \t\f\r\n", 5 ) ) { 4427 return null; 4428 } 4429 4430 /** 4431 * > For each name in list, for each U+002D HYPHEN-MINUS character (-) 4432 * > in the name that is followed by an ASCII lower alpha, remove the 4433 * > U+002D HYPHEN-MINUS character (-) and replace the character that 4434 * > followed it by the same character converted to ASCII uppercase. 4435 * 4436 * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs 4437 */ 4438 $custom_name = ''; 4439 $at = 5; 4440 $was_at = $at; 4441 4442 while ( $at < $end ) { 4443 $next_dash_at = strpos( $html_attribute_name, '-', $at ); 4444 if ( false === $next_dash_at || $next_dash_at === $end - 1 ) { 4445 break; 4446 } 4447 4448 // Transform `-a` to `A`, for example. 4449 $c = $html_attribute_name[ $next_dash_at + 1 ]; 4450 if ( ( $c >= 'A' && $c <= 'Z' ) || ( $c >= 'a' && $c <= 'z' ) ) { 4451 $prefix = substr( $html_attribute_name, $was_at, $next_dash_at - $was_at ); 4452 $custom_name .= strtolower( $prefix ); 4453 $custom_name .= strtoupper( $c ); 4454 $at = $next_dash_at + 2; 4455 $was_at = $at; 4456 continue; 4457 } 4458 4459 $at = $next_dash_at + 1; 4460 } 4461 4462 // If nothing has been added it means there are no dash-letter pairs; return the name as-is. 4463 return '' === $custom_name 4464 ? strtolower( substr( $html_attribute_name, 5 ) ) 4465 : ( $custom_name . strtolower( substr( $html_attribute_name, $was_at ) ) ); 4466 } 4467 4468 /** 4469 * Returns a corresponding HTML attribute name for the given name, 4470 * if that name were found in a JS element’s `dataset` property. 4471 * 4472 * Example: 4473 * 4474 * 'data-post-id' === wp_html_custom_data_attribute_name( 'postId' ); 4475 * 'data--before' === wp_html_custom_data_attribute_name( 'Before' ); 4476 * 'data---one---two---' === wp_html_custom_data_attribute_name( '-One--Two---' ); 4477 * 4478 * // Not every attribute name will be interpreted as a custom data attribute. 4479 * null === wp_html_custom_data_attribute_name( '/not-an-attribute/' ); 4480 * null === wp_html_custom_data_attribute_name( 'no spaces' ); 4481 * 4482 * // Some very surprising names will; for example, a property whose name is the empty string. 4483 * 'data-' === wp_html_custom_data_attribute_name( '' ); 4484 * 4485 * @since 6.9.0 4486 * 4487 * @see \wp_js_dataset_name() 4488 * @link https://html.spec.whatwg.org/#concept-domstringmap-pairs 4489 * 4490 * @param string $js_dataset_name Name of JS `dataset` property to transform. 4491 * @return string|null Corresponding name of an HTML custom data attribute for the given dataset name, 4492 * if possible to represent in HTML, otherwise `null`. 4493 */ 4494 function wp_html_custom_data_attribute_name( string $js_dataset_name ): ?string { 4495 $end = strlen( $js_dataset_name ); 4496 if ( 0 === $end ) { 4497 return 'data-'; 4498 } 4499 4500 /* 4501 * If it contains characters which would end the attribute name parsing then 4502 * something it’s not possible to represent this in HTML. 4503 */ 4504 if ( strcspn( $js_dataset_name, "=/> \t\f\r\n" ) !== $end ) { 4505 return null; 4506 } 4507 4508 $html_name = 'data-'; 4509 $at = 0; 4510 $was_at = $at; 4511 4512 while ( $at < $end ) { 4513 $next_upper_after = strcspn( $js_dataset_name, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', $at ); 4514 $next_upper_at = $at + $next_upper_after; 4515 if ( $next_upper_at >= $end ) { 4516 break; 4517 } 4518 4519 $prefix = substr( $js_dataset_name, $was_at, $next_upper_at - $was_at ); 4520 $html_name .= strtolower( $prefix ); 4521 $html_name .= '-' . strtolower( $js_dataset_name[ $next_upper_at ] ); 4522 $at = $next_upper_at + 1; 4523 $was_at = $at; 4524 } 4525 4526 if ( $was_at < $end ) { 4527 $html_name .= strtolower( substr( $js_dataset_name, $was_at ) ); 4528 } 4529 4530 return $html_name; 4531 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated : Sun Nov 1 08:20:35 2026 | Cross-referenced by PHPXref |