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