*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare (strict_types=1);
namespace WindPress\WindPress\Core;
use Exception;
use WIND_PRESS;
use WindPress\WindPress\Admin\AdminPage;
use WindPress\WindPress\Utils\Common;
use WindPress\WindPress\Utils\Config;
use WindPress\WindPress\Utils\Vite;
/**
* @since 3.0.0
*/
class Runtime
{
/**
* Stores the instance, implementing a Singleton pattern.
*/
private static self $instance;
/**
* The Singleton's constructor should always be private to prevent direct
* construction calls with the `new` operator.
*/
private function __construct()
{
}
/**
* Singletons should not be cloneable.
*/
private function __clone()
{
}
/**
* Singletons should not be restorable from strings.
*
* @throws Exception Cannot unserialize a singleton.
*/
public function __wakeup()
{
throw new Exception('Cannot unserialize a singleton.');
}
/**
* This is the static method that controls the access to the singleton
* instance. On the first run, it creates a singleton object and places it
* into the static property. On subsequent runs, it returns the client existing
* object stored in the static property.
*/
public static function get_instance(): self
{
if (!isset(self::$instance)) {
self::$instance = new self();
}
return self::$instance;
}
public function init()
{
if (!is_admin()) {
$is_prevent_load = apply_filters('f!windpress/core/runtime:is_prevent_load', \false);
if ($is_prevent_load) {
return;
}
$this->append_header();
}
}
/**
* Get the used Tailwind CSS version.
*
* @return int
*/
public static function tailwindcss_version()
{
return (int) apply_filters('f!windpress/core/runtime:tailwindcss_version', Config::get('general.tailwindcss.version', 4));
}
public function append_header()
{
$mode = $this->performance_mode();
// Keep the filter name for integrations that need to bypass cached CSS (e.g., builder previews).
$should_skip_cache = apply_filters('f!windpress/core/runtime:append_header.exclude_admin', \false);
add_action('wp_head', fn() => $this->print_windpress_metadata(), 1000001);
$has_cache = $this->is_cache_exists();
if ($mode === 'hybrid') {
if ($has_cache && !$should_skip_cache) {
add_action('wp_head', fn() => $this->enqueue_css_cache(\true), 1000001);
}
add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
} elseif ($mode === 'cached') {
if ($has_cache && !$should_skip_cache) {
add_action('wp_head', fn() => $this->enqueue_css_cache(), 1000001);
} else {
add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
}
} else {
add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
}
// if ($this->is_ubiquitous_panel()) {
// add_action('wp_head', fn() => $this->enqueue_front_panel(), 1_000_001);
// }
}
private function performance_mode(): string
{
$mode = Config::get('performance.mode', 'hybrid');
$mode = apply_filters('f!windpress/core/runtime:append_header.mode', $mode);
$allowed = ['cached', 'hybrid', 'compiler'];
if (!in_array($mode, $allowed, \true)) {
return 'hybrid';
}
return $mode;
}
public function is_cache_exists()
{
return file_exists(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE)) && is_readable(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
}
public function enqueue_css_cache(bool $force_inline = \false)
{
if (!$this->is_cache_exists()) {
return;
}
do_action('a!windpress/core/runtime:enqueue_css_cache.before');
$handle = WIND_PRESS::WP_OPTION . '-cached';
if ($force_inline || Config::get('performance.cache.inline_load', \false)) {
// phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file
$css_clean = file_get_contents(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
if ($css_clean === \false) {
return;
}
wp_register_style($handle, \false, [], \false);
wp_add_inline_style($handle, wp_strip_all_tags($css_clean));
wp_print_styles($handle);
} else {
$version = (string) filemtime(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
wp_register_style($handle, \WindPress\WindPress\Core\Cache::get_cache_url(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE), [], $version);
wp_print_styles($handle);
}
do_action('a!windpress/core/runtime:enqueue_css_cache.after', $handle);
}
public function getVFSHtml()
{
$volumeEntries = array_reduce(\WindPress\WindPress\Core\Volume::get_entries(), function ($carry, $entry) {
if (!empty($entry['directory'])) {
return $carry;
}
return $carry + ['/' . $entry['relative_path'] => $entry['content']];
}, []);
// Script content are base64 encoded to prevent it from being executed by the browser.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
return sprintf('', base64_encode(wp_json_encode($volumeEntries)));
}
public function enqueue_play_cdn($display = \true)
{
// Script content are base64 encoded to prevent it from being executed by the browser.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo $this->getVFSHtml();
$tailwindcss_version = static::tailwindcss_version();
do_action('a!windpress/core/runtime:enqueue_play_cdn.before');
$can_load_modules = current_user_can('manage_options', $tailwindcss_version);
if ($tailwindcss_version === 3) {
$this->enqueue_play_modules_v3($can_load_modules);
} elseif ($tailwindcss_version === 4) {
$this->enqueue_play_modules($can_load_modules);
}
wp_enqueue_script(WIND_PRESS::WP_OPTION . ':observer');
do_action('a!windpress/core/runtime:enqueue_play_cdn.after', $tailwindcss_version);
}
public function enqueue_play_modules($can_load_modules)
{
// Register the modules
$loaded_modules = [];
if ($can_load_modules) {
Vite::assets()->register('resources/packages/core/tailwindcss/play/intellisense.ts', ['handle' => WIND_PRESS::WP_OPTION . ':intellisense', 'in_footer' => \true]);
$loaded_modules[] = WIND_PRESS::WP_OPTION . ':intellisense';
Vite::assets()->register('resources/packages/core/tailwindcss/play/worker.ts', ['handle' => WIND_PRESS::WP_OPTION . ':worker', 'in_footer' => \true]);
$loaded_modules[] = WIND_PRESS::WP_OPTION . ':worker';
do_action('a!windpress/core/runtime:enqueue_play_modules.loaded_modules');
$loaded_modules = apply_filters('f!windpress/core/runtime:enqueue_play_modules.loaded_modules', $loaded_modules);
}
Vite::assets()->register('resources/packages/core/tailwindcss/play/observer.ts', ['handle' => WIND_PRESS::WP_OPTION . ':observer', 'in_footer' => \true, 'dependencies' => array_merge(['wp-i18n', 'wp-hooks'], is_array($loaded_modules) ? $loaded_modules : iterator_to_array($loaded_modules))]);
}
public function enqueue_play_modules_v3($can_load_modules)
{
// Register the modules
$loaded_modules = [];
if ($can_load_modules) {
Vite::assets()->register('resources/packages/core/tailwindcss-v3/play/intellisense.ts', ['handle' => WIND_PRESS::WP_OPTION . ':intellisense', 'in_footer' => \true]);
$loaded_modules[] = WIND_PRESS::WP_OPTION . ':intellisense';
}
Vite::assets()->register('resources/packages/core/tailwindcss-v3/play/observer.ts', ['handle' => WIND_PRESS::WP_OPTION . ':observer', 'in_footer' => \true, 'dependencies' => array_merge(['wp-i18n', 'wp-hooks'], is_array($loaded_modules) ? $loaded_modules : iterator_to_array($loaded_modules))]);
}
public function enqueue_front_panel()
{
$handle = WIND_PRESS::WP_OPTION . ':admin';
Vite::assets()->enqueue('resources/dashboard/main.ts', ['handle' => $handle, 'in_footer' => \true, 'dependencies' => ['wp-i18n', 'wp-hooks']]);
wp_set_script_translations($handle, 'windpress');
// do enqueue scripts manually as it already runned before
wp_enqueue_scripts();
}
public function is_ubiquitous_panel()
{
return Config::get('general.ubiquitous-panel.enabled', \false) && current_user_can('manage_options') && !apply_filters('f!windpress/core/runtime:append_header.ubiquitous_panel.is_prevent_load', \false);
}
public function print_windpress_metadata()
{
$metadata = $this->assets_metadata();
$metadata = apply_filters('f!windpress/core/runtime:print_windpress_metadata', $metadata);
// /**
// * @see \WP_Scripts::localize()
// */
// foreach ($metadata as $key => $value) {
// if (! is_scalar($value)) {
// continue;
// }
// $metadata[$key] = html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
// }
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
echo sprintf('', wp_json_encode($metadata));
}
public function assets_metadata()
{
$metadata = ['_version' => WIND_PRESS::VERSION, '_wp_version' => get_bloginfo('version'), '_tailwindcss_version' => static::tailwindcss_version(), '_via_wp_org' => !Common::is_updater_library_available(), 'is_ubiquitous' => $this->is_ubiquitous_panel(), 'assets' => ['url' => Vite::base_url()], 'user_data' => ['data_dir' => [
// 'path' => Volume::data_dir_path(),
'url' => \WindPress\WindPress\Core\Volume::data_dir_url(),
], 'cache_dir' => []]];
if (current_user_can('manage_options')) {
$metadata['_wpnonce'] = wp_create_nonce(WIND_PRESS::WP_OPTION);
$metadata['rest_api'] = ['nonce' => wp_create_nonce('wp_rest'), 'root' => esc_url_raw(rest_url()), 'namespace' => WIND_PRESS::REST_NAMESPACE, 'url' => esc_url_raw(rest_url(WIND_PRESS::REST_NAMESPACE))];
$metadata['site_meta'] = ['name' => get_bloginfo('name'), 'site_url' => get_site_url(), 'web_history' => AdminPage::get_page_url()];
$metadata['is_debug'] = defined('WP_DEBUG') && \WP_DEBUG;
}
return $metadata;
}
}