PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3
Jetpack – WP Security, Backup, Speed, & Growth v16.3
16.3 16.3-beta 16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 All 508 releases
← All changes | jetpack_vendor/automattic/jetpack-assets/src/class-assets.php +96 -23 13.3.3 → 16.3 View file →
@@ -7,8 +7,9 @@
7 7
8 8 namespace Automattic\Jetpack;
9 9
10 10 use Automattic\Jetpack\Assets\Semver;
11 +use Automattic\Jetpack\Assets\Shared_Stores_Assets;
11 12 use Automattic\Jetpack\Constants as Jetpack_Constants;
12 13 use InvalidArgumentException;
13 14
14 15 /**
@@ -31,13 +32,36 @@
31 32
32 33 /**
33 34 * The registered textdomain mappings.
34 35 *
35 - * @var array `array( mapped_domain => array( string target_domain, string target_type, string semver, string path_prefix ) )`.
36 + * @var array `array( mapped_domain => array( string target_domain, string target_type, string semver ) )`.
36 37 */
37 38 private static $domain_map = array();
38 39
39 40 /**
41 + * The registered package paths, by textdomain.
42 + *
43 + * Separate from `$domain_map` because the two answer different questions:
44 + * the map says which domain a package's strings are translated under, while
45 + * this says where the package's files live — the prefix WordPress hashes to
46 + * name a JS translation file. A package whose textdomain is already its
47 + * plugin's has nothing to alias but still needs the path.
48 + *
49 + * Note the entries are keyed by domain, not by script: `downloadI18n()`
50 + * prepends a domain's prefix to every bundle path looked up under it. That
51 + * is only ever one package's path, so a plugin must not both bundle a
52 + * package whose textdomain is the plugin's own and load its own
53 + * `wp-jp-i18n-loader` bundles under that same domain — the package's prefix
54 + * would be applied to the plugin's catalogs too, and they would all 404. No
55 + * plugin does both today. One that needs to should give the package a
56 + * distinct textdomain, the way `jetpack-backup-pkg` and
57 + * `jetpack-videopress-pkg` do.
58 + *
59 + * @var array `array( domain => array( string semver, string path_prefix ) )`.
60 + */
61 + private static $domain_paths = array();
62 +
63 + /**
40 64 * Constructor.
41 65 *
42 66 * Static-only class, so nothing here.
43 67 */
@@ -108,11 +132,10 @@
108 132 * @param bool $in_footer Should the script be included in the footer.
109 133 */
110 134 public static function enqueue_async_script( $handle, $min_path, $non_min_path, $deps = array(), $ver = false, $in_footer = true ) {
111 135 _deprecated_function( __METHOD__, '2.1.0' );
112 - $assets_instance = self::instance();
113 - $assets_instance->add_async_script( $handle );
114 136 wp_enqueue_script( $handle, self::get_file_url_for_environment( $min_path, $non_min_path ), $deps, $ver, $in_footer );
137 + wp_script_add_data( $handle, 'strategy', 'defer' );
115 138 }
116 139
117 140 // endregion .
118 141
@@ -257,13 +280,13 @@
257 280
258 281 $ret = '';
259 282 $ret .= isset( $parts['scheme'] ) ? $parts['scheme'] . '://' : '';
260 283 if ( isset( $parts['user'] ) || isset( $parts['pass'] ) ) {
261 - $ret .= isset( $parts['user'] ) ? $parts['user'] : '';
284 + $ret .= $parts['user'] ?? '';
262 285 $ret .= isset( $parts['pass'] ) ? ':' . $parts['pass'] : '';
263 286 $ret .= '@';
264 287 }
265 - $ret .= isset( $parts['host'] ) ? $parts['host'] : '';
288 + $ret .= $parts['host'] ?? '';
266 289 $ret .= isset( $parts['port'] ) ? ':' . $parts['port'] : '';
267 290
268 291 $pp = explode( '/', $parts['path'] );
269 292 if ( '' === $pp[0] ) {
@@ -322,8 +345,9 @@
322 345 * - `nonmin_path`: (string) Non-minified script path.
323 346 * - `strategy`: (string) Specify a script strategy to use, eg. `defer` or `async`. Default is `""`.
324 347 * - `textdomain`: (string) Text domain for the script. Required if the script depends on wp-i18n.
325 348 * - `version`: (string) Override the version from the `asset_path` file.
349 + * @phan-param array{asset_path?:?string,async?:bool,css_dependencies?:string[],css_path?:?string,dependencies?:string[],enqueue?:bool,in_footer?:bool,media?:string,minify?:?bool,nonmin_path?:string,strategy?:string,textdomain?:string,version?:string} $options
326 350 * @throws \InvalidArgumentException If arguments are invalid.
327 351 */
328 352 public static function register_script( $handle, $path, $relative_to, array $options = array() ) {
329 353 if ( substr( $path, -3 ) !== '.js' ) {
@@ -348,10 +372,11 @@
348 372 'minify' => false,
349 373 'strategy' => '',
350 374 'textdomain' => null,
351 375 );
376 + '@phan-var array{asset_path:?string,async:bool,css_dependencies:string[],css_path:?string,dependencies:string[],enqueue:bool,in_footer:bool,media:string,minify:?bool,nonmin_path?:string,strategy:string,textdomain:string,version?:string} $options'; // Phan gets confused by the array addition.
352 377
353 - if ( $options['css_path'] && substr( $options['css_path'], -4 ) !== '.css' ) {
378 + if ( is_string( $options['css_path'] ) && $options['css_path'] !== '' && substr( $options['css_path'], -4 ) !== '.css' ) {
354 379 throw new \InvalidArgumentException( '$options[\'css_path\'] must end in ".css"' );
355 380 }
356 381
357 382 if ( isset( $options['nonmin_path'] ) ) {
@@ -375,11 +400,12 @@
375 400 }
376 401 ),
377 402 $options['css_dependencies']
378 403 );
379 - $ver = isset( $options['version'] ) ? $options['version'] : $asset['version'];
404 + $ver = $options['version'] ?? $asset['version'];
380 405 } else {
381 - $ver = isset( $options['version'] ) ? $options['version'] : filemtime( "$dir/$path" );
406 + // phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
407 + $ver = $options['version'] ?? @filemtime( "$dir/$path" );
382 408 }
383 409
384 410 if ( $options['async'] && '' === $options['strategy'] ) { // Handle the deprecated `async` option
385 411 $options['strategy'] = 'defer';
@@ -406,9 +432,9 @@
406 432 ''
407 433 );
408 434 }
409 435
410 - if ( $options['css_path'] && file_exists( "$dir/{$options['css_path']}" ) ) {
436 + if ( is_string( $options['css_path'] ) && $options['css_path'] !== '' && file_exists( "$dir/{$options['css_path']}" ) ) {
411 437 $csspath = $options['css_path'];
412 438 if ( is_rtl() ) {
413 439 $rtlcsspath = substr( $csspath, 0, -4 ) . '.rtl.css';
414 440 if ( file_exists( "$dir/$rtlcsspath" ) ) {
@@ -444,8 +470,21 @@
444 470 }
445 471 }
446 472
447 473 /**
474 + * Re-hook the bootstraps an older copy's `actions.php` did not know about. See JETPACK-2649.
475 + *
476 + * Static callables only: `add_action()` dedupes those, but closures and object callables would
477 + * register twice. Callers must run before `wp_loaded`.
478 + *
479 + * @access private
480 + * @since 5.0.5
481 + */
482 + public static function ensure_package_bootstrap() {
483 + Shared_Stores_Assets::configure();
484 + }
485 +
486 + /**
448 487 * 'wp_default_scripts' action handler.
449 488 *
450 489 * This registers the `wp-jp-i18n-loader` script for use by Webpack bundles built with
451 490 * `@automattic/i18n-loader-webpack-plugin`.
@@ -471,10 +510,12 @@
471 510 } elseif ( strpos( $lang_dir, $abspath ) === 0 ) {
472 511 $data['baseUrl'] = site_url( substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) ) );
473 512 }
474 513
475 - foreach ( self::$domain_map as $from => list( $to, $type, , $path ) ) {
514 + foreach ( self::$domain_map as $from => list( $to, $type ) ) {
476 515 $data['domainMap'][ $from ] = ( 'core' === $type ? '' : "{$type}/" ) . $to;
516 + }
517 + foreach ( self::$domain_paths as $from => list( , $path ) ) {
477 518 if ( '' !== $path ) {
478 519 $data['domainPaths'][ $from ] = trailingslashit( $path );
479 520 }
480 521 }
@@ -507,9 +548,16 @@
507 548 );
508 549 }
509 550 $url = self::normalize_path( plugins_url( $path, __FILE__ ) );
510 551 $url = add_query_arg( 'minify', 'true', $url );
511 - $wp_scripts->add( 'wp-jp-i18n-loader', $url, $asset['dependencies'], $asset['version'] );
552 +
553 + $handle = 'wp-jp-i18n-loader';
554 +
555 + $wp_scripts->add( $handle, $url, $asset['dependencies'], $asset['version'] );
556 +
557 + // Ensure the script is loaded in the footer and deferred.
558 + $wp_scripts->add_data( $handle, 'group', 1 );
559 +
512 560 if ( ! is_array( $data ) ||
513 561 ! isset( $data['baseUrl'] ) || ! ( is_string( $data['baseUrl'] ) || false === $data['baseUrl'] ) ||
514 562 ! isset( $data['locale'] ) || ! is_string( $data['locale'] ) ||
515 563 ! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] ) ||
@@ -514,19 +562,19 @@
514 562 ! isset( $data['locale'] ) || ! is_string( $data['locale'] ) ||
515 563 ! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] ) ||
516 564 ! isset( $data['domainPaths'] ) || ! is_array( $data['domainPaths'] )
517 565 ) {
518 - $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );' );
566 + $wp_scripts->add_inline_script( $handle, 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );' );
519 567 } elseif ( ! $data['baseUrl'] ) {
520 - $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );' );
568 + $wp_scripts->add_inline_script( $handle, 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );' );
521 569 } else {
522 570 $data['domainMap'] = (object) $data['domainMap']; // Ensure it becomes a json object.
523 571 $data['domainPaths'] = (object) $data['domainPaths']; // Ensure it becomes a json object.
524 - $wp_scripts->add_inline_script( 'wp-jp-i18n-loader', 'wp.jpI18nLoader.state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES ) . ';' );
572 + $wp_scripts->add_inline_script( $handle, 'wp.jpI18nLoader.state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';' );
525 573 }
526 574
527 575 // Deprecated state module: Depend on wp-i18n to ensure global `wp` exists and because anything needing this will need that too.
528 - $wp_scripts->add( 'wp-jp-i18n-state', false, array( 'wp-deprecated', 'wp-jp-i18n-loader' ) );
576 + $wp_scripts->add( 'wp-jp-i18n-state', false, array( 'wp-deprecated', $handle ) );
529 577 $wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.deprecated( "wp-jp-i18n-state", { alternative: "wp-jp-i18n-loader" } );' );
530 578 $wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.jpI18nState = wp.jpI18nLoader.state;' );
531 579 }
532 580
@@ -575,13 +623,30 @@
575 623 ''
576 624 );
577 625 }
578 626
627 + // Where the package lives is needed for JS translation files whether or
628 + // not its domain is aliased, so it is recorded before the self-alias
629 + // check below.
630 + if (
631 + empty( self::$domain_paths[ $from ] ) ||
632 + Semver::compare( $ver, self::$domain_paths[ $from ][0] ) > 0
633 + ) {
634 + self::$domain_paths[ $from ] = array( $ver, $path );
635 + }
636 +
637 + // A self-alias would make filter_gettext() re-translate into the same
638 + // domain, recursing infinitely on any untranslated string (a package
639 + // textdomain can collide with its containing plugin's slug).
640 + if ( $from === $to ) {
641 + return;
642 + }
643 +
579 644 if ( empty( self::$domain_map[ $from ] ) ) {
580 645 self::init_domain_map_hooks( $from, array() === self::$domain_map );
581 - self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
646 + self::$domain_map[ $from ] = array( $to, $totype, $ver );
582 647 } elseif ( Semver::compare( $ver, self::$domain_map[ $from ][2] ) > 0 ) {
583 - self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
648 + self::$domain_map[ $from ] = array( $to, $totype, $ver );
584 649 }
585 650 }
586 651
587 652 /**
@@ -591,8 +656,11 @@
591 656 * with the following properties:
592 657 * - 'domain': String, `$to`
593 658 * - 'type': String, `$totype`
594 659 * - 'packages': Array, mapping `$from` to `array( 'path' => $path, 'ver' => $ver )` (or to the string `$ver` for back compat).
660 + * - 'paths': Array, same shape, for packages whose textdomain is already
661 + * `$to`. Those must not be aliased — that would recurse — but their
662 + * paths are still needed to locate their JavaScript translations.
595 663 *
596 664 * @since 1.15.0
597 665 * @param string $file Mapping file.
598 666 */
@@ -606,8 +674,13 @@
606 674 );
607 675 }
608 676 self::alias_textdomain( $from, $data['domain'], $data['type'], $fromdata['ver'], $fromdata['path'] );
609 677 }
678 + // Aliasing a domain to itself is a no-op that `alias_textdomain()`
679 + // declines, leaving just the path registration these entries are for.
680 + foreach ( $data['paths'] ?? array() as $from => $fromdata ) {
681 + self::alias_textdomain( $from, $from, $data['type'], $fromdata['ver'], $fromdata['path'] );
682 + }
610 683 }
611 684
612 685 /**
613 686 * Register the hooks for textdomain aliasing.
@@ -650,9 +723,9 @@
650 723 * @return string Translated text.
651 724 */
652 725 public static function filter_gettext( $translation, $text, $domain ) {
653 726 if ( $translation === $text ) {
654 - // phpcs:ignore WordPress.WP.I18n
727 + // phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
655 728 $newtext = __( $text, self::$domain_map[ $domain ][0] );
656 729 if ( $newtext !== $text ) {
657 730 return $newtext;
658 731 }
@@ -666,15 +739,15 @@
666 739 * @since 1.15.0
667 740 * @param string $translation Translated text.
668 741 * @param string $single The text to be used if the number is singular.
669 742 * @param string $plural The text to be used if the number is plural.
670 - * @param string $number The number to compare against to use either the singular or plural form.
743 + * @param int $number The number to compare against to use either the singular or plural form.
671 744 * @param string $domain Text domain.
672 745 * @return string Translated text.
673 746 */
674 747 public static function filter_ngettext( $translation, $single, $plural, $number, $domain ) {
675 748 if ( $translation === $single || $translation === $plural ) {
676 - // phpcs:ignore WordPress.WP.I18n
749 + // phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
677 750 $translation = _n( $single, $plural, $number, self::$domain_map[ $domain ][0] );
678 751 }
679 752 return $translation;
680 753 }
@@ -690,9 +763,9 @@
690 763 * @return string Translated text.
691 764 */
692 765 public static function filter_gettext_with_context( $translation, $text, $context, $domain ) {
693 766 if ( $translation === $text ) {
694 - // phpcs:ignore WordPress.WP.I18n
767 + // phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
695 768 $translation = _x( $text, $context, self::$domain_map[ $domain ][0] );
696 769 }
697 770 return $translation;
698 771 }
@@ -703,9 +776,9 @@
703 776 * @since 1.15.0
704 777 * @param string $translation Translated text.
705 778 * @param string $single The text to be used if the number is singular.
706 779 * @param string $plural The text to be used if the number is plural.
707 - * @param string $number The number to compare against to use either the singular or plural form.
780 + * @param int $number The number to compare against to use either the singular or plural form.
708 781 * @param string $context Context information for the translators.
709 782 * @param string $domain Text domain.
710 783 * @return string Translated text.
711 784 */
@@ -710,9 +783,9 @@
710 783 * @return string Translated text.
711 784 */
712 785 public static function filter_ngettext_with_context( $translation, $single, $plural, $number, $context, $domain ) {
713 786 if ( $translation === $single || $translation === $plural ) {
714 - // phpcs:ignore WordPress.WP.I18n
787 + // phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
715 788 $translation = _nx( $single, $plural, $number, $context, self::$domain_map[ $domain ][0] );
716 789 }
717 790 return $translation;
718 791 }