You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

415 lines
15 KiB
PHTML

<?php
/**
* Class Assets
*
* @link https://github.com/googleforcreators/web-stories-wp
*
* @copyright 2020 Google LLC
* @license https://www.apache.org/licenses/LICENSE-2.0 Apache License 2.0
*/
/**
* Copyright 2020 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
declare(strict_types = 1);
namespace Google\Web_Stories;
/**
* Class Assets
*
* @phpstan-type AssetMetadata array{
* version: string,
* dependencies: string[],
* js: string[],
* css: string[],
* chunks: string[],
* }
*/
class Assets {
/**
* An array of registered styles.
*
* @var array<string, bool>
*/
protected array $register_styles = [];
/**
* An array of registered scripts.
*
* @var array<string, bool>
*/
protected array $register_scripts = [];
/**
* Get path to file and directory.
*
* @since 1.8.0
*
* @param string $path Path.
*/
public function get_base_path( string $path ): string {
return WEBSTORIES_PLUGIN_DIR_PATH . $path;
}
/**
* Get url of file and directory.
*
* @since 1.8.0
*
* @param string $path Path.
*/
public function get_base_url( string $path ): string {
return WEBSTORIES_PLUGIN_DIR_URL . $path;
}
/**
* Get asset metadata.
*
* @since 1.8.0
*
* @param string $handle Script handle.
* @return array Array containing combined contents of "<$handle>.asset.php" and "<$handle>.chunks.php".
*
* @phpstan-return AssetMetadata
*/
public function get_asset_metadata( string $handle ): array {
$base_path = $this->get_base_path( 'assets/js/' );
// *.asset.php is generated by DependencyExtractionWebpackPlugin.
// *.chunks.php is generated by HtmlWebpackPlugin with a custom template.
$asset_file = $base_path . $handle . '.asset.php';
$chunks_file = $base_path . $handle . '.chunks.php';
// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$asset = is_readable( $asset_file ) ? require $asset_file : [];
// phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.UsingVariable
$chunks = is_readable( $chunks_file ) ? require $chunks_file : [];
// A hash calculated based on the file content of the entry point bundle at <$handle>.js.
$asset['version'] ??= WEBSTORIES_VERSION;
$asset['dependencies'] ??= [];
$asset['js'] = $chunks['js'] ?? [];
$asset['css'] = $chunks['css'] ?? [];
$asset['chunks'] = $chunks['chunks'] ?? [];
return $asset;
}
/**
* Register script using handle.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $script_handle Handle of script.
* @param string[] $script_dependencies Array of extra dependencies.
* @param bool $with_i18n Optional. Whether to setup i18n for this asset. Default true.
*/
public function register_script_asset( string $script_handle, array $script_dependencies = [], bool $with_i18n = true ): void {
if ( isset( $this->register_scripts[ $script_handle ] ) ) {
return;
}
$base_script_path = $this->get_base_url( 'assets/js/' );
$in_footer = true;
$asset = $this->get_asset_metadata( $script_handle );
$entry_version = $asset['version'];
// Register any chunks of $script_handle first.
// `$asset['js']` are preloaded chunks, `$asset['chunks']` dynamically imported ones.
foreach ( $asset['js'] as $chunk ) {
$this->register_script(
$chunk,
$base_script_path . $chunk . '.js',
[],
$entry_version,
$in_footer,
$with_i18n
);
}
// Dynamically imported chunks MUST NOT be added as dependencies here.
$dependencies = [ ...$asset['dependencies'], ...$script_dependencies, ...$asset['js'] ];
$this->register_script(
$script_handle,
$base_script_path . $script_handle . '.js',
$dependencies,
$entry_version,
$in_footer,
$with_i18n
);
// "Save" all the script's chunks so we can later manually fetch them and their translations if needed.
wp_script_add_data( $script_handle, 'chunks', $asset['chunks'] );
// Register every dynamically imported chunk as a script, just so
// that we can print their translations whenever the main script is enqueued.
// The actual enqueueing of these chunks is done by the main script via dynamic imports.
foreach ( $asset['chunks'] as $dynamic_chunk ) {
$this->register_script(
$dynamic_chunk,
$base_script_path . $dynamic_chunk . '.js',
[],
$entry_version, // Not actually used / relevant, since enqueueing is done by webpack.
$in_footer, // Ditto.
$with_i18n
);
if ( $with_i18n ) {
wp_add_inline_script( $script_handle, (string) wp_scripts()->print_translations( $dynamic_chunk, false ) );
}
}
}
/**
* Enqueue script using handle.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $script_handle Handle of script.
* @param string[] $script_dependencies Array of extra dependencies.
* @param bool $with_i18n Optional. Whether to setup i18n for this asset. Default true.
*/
public function enqueue_script_asset( string $script_handle, array $script_dependencies = [], bool $with_i18n = true ): void {
$this->register_script_asset( $script_handle, $script_dependencies, $with_i18n );
$this->enqueue_script( $script_handle );
}
/**
* Register style using handle.
*
* @since 1.8.0
*
* @param string $style_handle Handle of style.
* @param string[] $style_dependencies Array of extra dependencies.
*/
public function register_style_asset( string $style_handle, array $style_dependencies = [] ): void {
if ( isset( $this->register_styles[ $style_handle ] ) ) {
return;
}
$base_style_url = $this->get_base_url( 'assets/css/' );
$base_style_path = $this->get_base_path( 'assets/css/' );
$ext = is_rtl() ? '-rtl.css' : '.css';
// Register any chunks of $style_handle first.
$asset = $this->get_asset_metadata( $style_handle );
// Webpack appends "-[contenthash]" to filenames of chunks, so omit the `?ver=` query param.
$chunk_version = null;
foreach ( $asset['css'] as $style_chunk ) {
$this->register_style(
$style_chunk,
$base_style_url . $style_chunk . '.css',
[],
$chunk_version
);
wp_style_add_data( $style_chunk, 'path', $base_style_path . $style_chunk . $ext );
}
$style_dependencies = [ ...$style_dependencies, ...$asset['css'] ];
$entry_version = $asset['version'];
$this->register_style(
$style_handle,
$base_style_url . $style_handle . '.css',
$style_dependencies,
$entry_version
);
wp_style_add_data( $style_handle, 'rtl', 'replace' );
wp_style_add_data( $style_handle, 'path', $base_style_path . $style_handle . $ext );
}
/**
* Enqueue style using handle.
*
* @since 1.8.0
*
* @param string $style_handle Handle of style.
* @param string[] $style_dependencies Array of extra dependencies.
*/
public function enqueue_style_asset( string $style_handle, array $style_dependencies = [] ): void {
$this->register_style_asset( $style_handle, $style_dependencies );
$this->enqueue_style( $style_handle );
}
/**
* Register a CSS stylesheet.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $style_handle Name of the stylesheet. Should be unique.
* @param string|false $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
* If source is set to false, stylesheet is an alias of other stylesheets it depends on.
* @param string[] $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param string $media Optional. The media for which this stylesheet has been defined.
* Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
* '(orientation: portrait)' and '(max-width: 640px)'.
* @return bool Whether the style has been registered. True on success, false on failure.
*/
public function register_style( string $style_handle, $src, array $deps = [], $ver = false, string $media = 'all' ): bool {
if ( ! isset( $this->register_styles[ $style_handle ] ) ) {
$this->register_styles[ $style_handle ] = wp_register_style( $style_handle, $src, $deps, $ver, $media );
}
return $this->register_styles[ $style_handle ];
}
/**
* Register a new script.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $script_handle Name of the script. Should be unique.
* @param string|false $src Full URL of the script, or path of the script relative to the WordPress root directory.
* If source is set to false, script is an alias of other scripts it depends on.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
* Default 'false'.
* @param bool $with_i18n Optional. Whether to setup i18n for this asset. Default true.
* @return bool Whether the script has been registered. True on success, false on failure.
*/
public function register_script( string $script_handle, $src, array $deps = [], $ver = false, bool $in_footer = false, bool $with_i18n = true ): bool {
if ( ! isset( $this->register_scripts[ $script_handle ] ) ) {
$this->register_scripts[ $script_handle ] = wp_register_script( $script_handle, $src, $deps, $ver, $in_footer );
if ( $src && $with_i18n ) {
wp_set_script_translations( $script_handle, 'web-stories' );
}
}
return $this->register_scripts[ $script_handle ];
}
/**
* Enqueue a style.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $style_handle Name of the stylesheet. Should be unique.
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
* Default empty.
* @param string[] $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param string $media Optional. The media for which this stylesheet has been defined.
* Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
* '(orientation: portrait)' and '(max-width: 640px)'.
*/
public function enqueue_style( string $style_handle, string $src = '', array $deps = [], $ver = false, string $media = 'all' ): void {
$this->register_style( $style_handle, $src, $deps, $ver, $media );
wp_enqueue_style( $style_handle, $src, $deps, $ver, $media );
}
/**
* Enqueue a script.
*
* @SuppressWarnings(PHPMD.BooleanArgumentFlag)
*
* @since 1.8.0
*
* @param string $script_handle Name of the script. Should be unique.
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
* Default empty.
* @param string[] $deps Optional. An array of registered script handles this script depends on. Default empty array.
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
* as a query string for cache busting purposes. If version is set to false, a version
* number is automatically added equal to current installed WordPress version.
* If set to null, no version is added.
* @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
* Default 'false'.
* @param bool $with_i18n Optional. Whether to setup i18n for this asset. Default true.
*/
public function enqueue_script( string $script_handle, string $src = '', array $deps = [], $ver = false, bool $in_footer = false, bool $with_i18n = false ): void {
$this->register_script( $script_handle, $src, $deps, $ver, $in_footer, $with_i18n );
wp_enqueue_script( $script_handle, $src, $deps, $ver, $in_footer );
}
/**
* Remove admin styles.
*
* @since 1.8.0
*
* @param string[] $styles Array of styles to be removed.
*/
public function remove_admin_style( array $styles ): void {
wp_styles()->registered['wp-admin']->deps = array_diff( wp_styles()->registered['wp-admin']->deps, $styles );
}
/**
* Returns the translations for a script and all of its chunks.
*
* @since 1.14.0
*
* @param string $script_handle Name of the script. Should be unique.
* @return array<int, mixed> Script translations.
*/
public function get_translations( string $script_handle ): array {
/**
* List of script chunks.
*
* @var false|string[]
*/
$chunks = wp_scripts()->get_data( $script_handle, 'chunks' );
if ( ! \is_array( $chunks ) ) {
return [];
}
$translations = [
(string) load_script_textdomain( $script_handle, 'web-stories' ),
];
/**
* Dynamic chunk name.
*
* @var string $dynamic_chunk
*/
foreach ( $chunks as $dynamic_chunk ) {
$translations[] = (string) load_script_textdomain( $dynamic_chunk, 'web-stories' );
}
return array_values(
array_map(
static fn( $translations ) => json_decode( $translations, true ),
array_filter( $translations )
)
);
}
}