s($useHandle, $rootSlug . '-' . $handle, $pluginPath . $packageDir . 'languages/frontend/json'); } else { \wp_enqueue_style($useHandle, $url, $deps, $cachebuster, $media); } return $useHandle; } return \false; } /** * Enqueue a composer package script from our multi-package repository. * * @param string $handle Name of the package. * @param string[] $deps An array of registered scripts handles this script depends on. * @param string $src The file to use in dist or dev folder. * @param boolean $in_footer Whether to enqueue the script before instead of in the . * @return string The used handle */ public function enqueueComposerScript($handle, $deps = [], $src = 'index.js', $in_footer = \true) { return $this->enqueueComposer($handle, $src, $deps, 'script', $in_footer); } /** * Enqueue a composer package style from our multi-package repository. * * @param string $handle Name of the package. * @param string[] $deps An array of registered scripts handles this script depends on. * @param string $src The file to use in dist or dev folder. * @param string $media The media for which this stylesheet has been defined. Accepts media types like 'all', 'print' and 'screen', or media queries like '(orientation: portrait)' and '(max-width: 640px)'. * @return string The used handle */ public function enqueueComposerStyle($handle, $deps = [], $src = 'index.css', $media = 'all') { return $this->enqueueComposer($handle, $src, $deps, 'style', null, $media); } /** * Enqueue scripts and styles for admin pages. * * @param string $hook_suffix The current admin page */ public function admin_enqueue_scripts($hook_suffix) { $this->enqueue_scripts_and_styles(Constants::ASSETS_TYPE_ADMIN, $hook_suffix); } /** * Enqueue scripts and styles for frontend pages. */ public function wp_enqueue_scripts() { $this->enqueue_scripts_and_styles(Constants::ASSETS_TYPE_FRONTEND); } /* * Enqueue blocker and banner in Login screen too, so reCaptcha forms or * similar scripts can be blocked. */ public function login_enqueue_scripts() { $this->enqueue_scripts_and_styles(Constants::ASSETS_TYPE_LOGIN); } /** * Enqueue scripts in customize (not preview!) */ public function customize_controls_print_scripts() { $this->enqueue_scripts_and_styles(Constants::ASSETS_TYPE_CUSTOMIZE); } /** * The function and mechanism of wp_set_script_translations() is great of course. Unfortunately * popular plugins like WP Rocket and Divi are not compatible with it (especially page builders * and caching plugins). Why? Shortly explained, the injected inline scripts relies on `wp.i18n` * which can be deferred or async loaded (the script itself) -> wp is not defined. * * In factory i18n.tsx the `window.wpi18nLazy` is automatically detected and the plugin gets localized. * * @param string $handle * @param string $domain * @param string $path * @see https://developer.wordpress.org/reference/classes/wp_scripts/print_translations/ * @see https://developer.wordpress.org/reference/functions/wp_set_script_translations/ * @see https://app.clickup.com/t/3mjh0e */ public function setLazyScriptTranslations($handle, $domain, $path) { \add_filter('load_script_textdomain_relative_path', [$this, 'load_script_textdomain_relative_path']); \add_filter('load_script_translation_file', [$this, 'load_script_translation_file']); $json_translations = \load_script_textdomain($handle, $domain, PackageLocalization::getParentLanguageFolder($path)); \remove_filter('load_script_textdomain_relative_path', [$this, 'load_script_textdomain_relative_path']); \remove_filter('load_script_translation_file', [$this, 'load_script_translation_file']); if (!empty($json_translations)) { $output = <<getPluginConstant(Constants::PLUGIN_CONST_ROOT_SLUG) . '-', '/', $file); } /** * Force the basename for the md5 of a loaded JSON translation file. * * @param string $src * @see https://github.com/wp-cli/i18n-command/issues/177#issuecomment-523759266 */ public function load_script_textdomain_relative_path($src) { return \basename($src); } /** * Get the cachebuster entry for a given file. If the $src begins with public/lib/ it * will use the inc/base/others/cachebuster-lib.php cachebuster instead of inc/base/others/cachebuster.php. * * @param string $src The src relative to public/ folder * @param boolean $isLib If true the cachebuster-lib.php cachebuster is used * @param string $default * @return string _VERSION or cachebuster timestamp */ public function getCachebusterVersion($src, $isLib = \false, $default = null) { $default = $default ?? $this->getPluginConstant(Constants::PLUGIN_CONST_VERSION); $path = $this->getPluginConstant(Constants::PLUGIN_CONST_INC) . '/base/others/'; $path_lib = $path . 'cachebuster-lib.php'; $path = $path . 'cachebuster.php'; if ($isLib) { // Library cachebuster if (\file_exists($path_lib)) { static $cachebuster_lib = null; if ($cachebuster_lib === null) { $cachebuster_lib = (include $path_lib); } // Parse module \preg_match('/^public\\/lib\\/([^\\/]+)/', $src, $matches); if (\is_array($matches) && isset($matches[1]) && \is_array($cachebuster_lib) && \array_key_exists($matches[1], $cachebuster_lib)) { // Valid cachebuster return $cachebuster_lib[$matches[1]]; } } } else { // Main cachebuster if (\file_exists($path)) { // Store cachebuster once static $cachebuster = null; if ($cachebuster === null) { $cachebuster = (include $path); } // Prepend src/ because the cachebuster task prefixes it $src = 'src/' . $src; if (\is_array($cachebuster) && \array_key_exists($src, $cachebuster)) { // Valid cachebuster return $cachebuster[$src]; } } } return $default; } /** * Make a localized array anonymous. Some plugins like WP Rocket tries to lazy load also localized scripts * and this should be avoided in some scenarios like Real Cookie Banners' banner script. * Use this instead of `wp_localize_script`. * * Settings: * * ``` * string[] makeBase64Encoded List of keys of the array object which should be converted to base64 at output time (e.g. to avoid ModSecurity issues) * boolean useCore Use `wp_localize_script` internally instead of custom localize script * string[] lazyParse A list of pathes of the array which should be lazy parsed. This could be useful to improve performance and parse as needed (e.g. huge arrays). * ``` * * @param string $handle Name of the script to attach data to. * @param string $object_name Name of the variable that will contain the data. * @param array $l10n Array of data to localize. * @param array $settings * @see https://docs.wp-rocket.me/article/1349-delay-javascript-execution#technical * @see https://developer.wordpress.org/reference/functions/wp_localize_script/ */ public function anonymous_localize_script($handle, $object_name, $l10n, $settings = []) { $settings = \wp_parse_args($settings, ['makeBase64Encoded' => [], 'useCore' => \false, 'lazyParse' => []]); if ($settings['useCore']) { return \wp_localize_script($handle, $object_name, $l10n); } $makeBase64Encoded = $settings['makeBase64Encoded']; $lazyParse = $settings['lazyParse']; // Mark the script tag with some identifier, so our helper script (added below) can read // the JSON content. See also about: https://stackoverflow.com/q/12090883/5506547 // Do not use a randomized string as it can lead to issues with cached web pages when the // inline scripts gets somehow into a combined file and the HTML is served not statically. $uuid = \wp_generate_uuid4(); $uuid = \md5(\sprintf('%s:%s:%s', $handle, $object_name, $this->getPluginConstant(Constants::PLUGIN_CONST_VERSION))); $base64Marker = 'base64-encoded:'; \add_filter('script_loader_tag', function ($tag, $scriptHandle) use($handle, $uuid, $l10n, $object_name, $makeBase64Encoded, $base64Marker, $lazyParse) { if ($scriptHandle === $handle) { if (\count($makeBase64Encoded) > 0) { \array_walk_recursive($l10n, function (&$val, $key) use($makeBase64Encoded, $base64Marker) { if (\in_array($key, $makeBase64Encoded, \true) && \is_string($val) && !empty($val)) { $val = \sprintf('%s%s', $base64Marker, \base64_encode($val)); } }); } $foundLazyParse = []; // Only work with pathes which got converted successfully foreach ($lazyParse as $arrayPath) { Utils::arrayModifyByKeyPath($l10n, $arrayPath, function ($value) use($arrayPath, &$foundLazyParse) { $foundLazyParse[] = $arrayPath; return \json_encode($value); }); } /* (() => { var receiver = %5$s; var createLazyParseProxy = (obj, key) => new Proxy(obj, { get: (target, property) => { let value = Reflect.get(target, property); if (property === key && typeof value === "string") { value = JSON.parse(value, receiver); Reflect.set(target, property, value); } return value; } }); var o = /* document.write * / JSON.parse(document.getElementById("a%1$s1-js-extra").innerHTML, receiver); %6$s window.%3$s = o; const randomId = Math.random().toString(36).substring(2); window[randomId] = n; })(); */ $tag = \sprintf(' ', $uuid, \wp_json_encode($l10n), $object_name, \join(' ', [ // TODO: shouldn't this be part of @devowl-wp/cache-invalidate? // Compatibility with most caching plugins which lazy load JavaScript 'data-skip-lazy-load="js-extra"', // Compatibility with WP Fastest Cache and "Eliminate render blocking script" // as WPFC is moving all scripts (even with `type="text/plain"`). 'data-skip-moving="true"', // Compatibility with LiteSpeed Cache and do not delay this inline script // See https://github.com/litespeedtech/lscache_wp/blob/6c95240003b89ef1d4ce190f5a96eba83528cd89/src/optimize.cls.php#L903 'data-no-defer', // Compatibility with NitroPack 'nitro-exclude', // Compatibility with WP Rocket as the filter `rocket_defer_inline_exclusions` does only check on `