PluginProbe
WindPress – Tailwind CSS integration for WordPress / 3.2.89
WindPress – Tailwind CSS integration for WordPress v3.2.89
3.2.89 3.2.88 3.2.87 3.2.86 3.2.85 3.2.84 3.2.83 3.2.82 3.2.81 trunk 3.0.0 3.0.1 3.0.10 3.0.11 3.0.12 3.0.13 3.0.14 3.0.15 3.0.16 3.0.17 3.0.2 3.0.3 3.0.4 3.0.5 3.0.6 All 143 releases
← All changes | src/Core/Runtime.php +140 -42 3.0.33.2.89 View file →
@@ -12,14 +12,14 @@
12 12 namespace WindPress\WindPress\Core;
13 13
14 14 use Exception;
15 15 use WIND_PRESS;
16 -use WindPress\WindPress\Utils\AssetVite;
16 +use WindPress\WindPress\Admin\AdminPage;
17 +use WindPress\WindPress\Utils\Common;
17 18 use WindPress\WindPress\Utils\Config;
19 +use WindPress\WindPress\Utils\Vite;
18 20 /**
19 21 * @since 3.0.0
20 - *
21 - * TODO: This class is not yet implemented, it's just temporary for Universal Editor testing purpose.
22 22 */
23 23 class Runtime
24 24 {
25 25 /**
@@ -53,9 +53,9 @@
53 53 * instance. On the first run, it creates a singleton object and places it
54 54 * into the static property. On subsequent runs, it returns the client existing
55 55 * object stored in the static property.
56 56 */
57 - public static function get_instance() : self
57 + public static function get_instance(): self
58 58 {
59 59 if (!isset(self::$instance)) {
60 60 self::$instance = new self();
61 61 }
@@ -62,10 +62,10 @@
62 62 return self::$instance;
63 63 }
64 64 public function init()
65 65 {
66 - if (!\is_admin()) {
67 - $is_prevent_load = \apply_filters('f!windpress/core/runtime:is_prevent_load', \false);
66 + if (!is_admin()) {
67 + $is_prevent_load = apply_filters('f!windpress/core/runtime:is_prevent_load', \false);
68 68 if ($is_prevent_load) {
69 69 return;
70 70 }
71 71 $this->append_header();
@@ -70,72 +70,170 @@
70 70 }
71 71 $this->append_header();
72 72 }
73 73 }
74 + /**
75 + * Get the used Tailwind CSS version.
76 + *
77 + * @return int
78 + */
79 + public static function tailwindcss_version()
80 + {
81 + return (int) apply_filters('f!windpress/core/runtime:tailwindcss_version', Config::get('general.tailwindcss.version', 4));
82 + }
74 83 public function append_header()
75 84 {
76 - $is_cache_enabled = Config::get('performance.cache.enabled', \false);
77 - $is_cache_enabled = \apply_filters('f!windpress/core/runtime:append_header.cache_enabled', $is_cache_enabled);
78 - $is_exclude_admin = Config::get('performance.cache.exclude_admin', \false) && \current_user_can('manage_options');
79 - $is_exclude_admin = \apply_filters('f!windpress/core/runtime:append_header.exclude_admin', $is_exclude_admin);
80 - if ($is_cache_enabled && $this->is_cache_exists() && !$is_exclude_admin) {
81 - \add_action('wp_head', fn() => $this->enqueue_css_cache(), 1000001);
85 + $mode = $this->performance_mode();
86 + // Keep the filter name for integrations that need to bypass cached CSS (e.g., builder previews).
87 + $should_skip_cache = apply_filters('f!windpress/core/runtime:append_header.exclude_admin', \false);
88 + add_action('wp_head', fn() => $this->print_windpress_metadata(), 1000001);
89 + $has_cache = $this->is_cache_exists();
90 + if ($mode === 'hybrid') {
91 + if ($has_cache && !$should_skip_cache) {
92 + add_action('wp_head', fn() => $this->enqueue_css_cache(\true), 1000001);
93 + }
94 + add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
95 + } elseif ($mode === 'cached') {
96 + if ($has_cache && !$should_skip_cache) {
97 + add_action('wp_head', fn() => $this->enqueue_css_cache(), 1000001);
98 + } else {
99 + add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
100 + }
82 101 } else {
83 - \add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
102 + add_action('wp_head', fn() => $this->enqueue_play_cdn(), 1000001);
84 103 }
85 - if (Config::get('general.ubiquitous-panel.enabled', \true) && \current_user_can('manage_options') && !\apply_filters('f!windpress/core/runtime:append_header.ubiquitous_panel.is_prevent_load', \false)) {
86 - \add_action('wp_head', fn() => $this->enqueue_front_panel(), 1000001);
104 + // if ($this->is_ubiquitous_panel()) {
105 + // add_action('wp_head', fn() => $this->enqueue_front_panel(), 1_000_001);
106 + // }
107 + }
108 + private function performance_mode(): string
109 + {
110 + $mode = Config::get('performance.mode', 'hybrid');
111 + $mode = apply_filters('f!windpress/core/runtime:append_header.mode', $mode);
112 + $allowed = ['cached', 'hybrid', 'compiler'];
113 + if (!in_array($mode, $allowed, \true)) {
114 + return 'hybrid';
87 115 }
116 + return $mode;
88 117 }
89 118 public function is_cache_exists()
90 119 {
91 - 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));
120 + 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));
92 121 }
93 - public function enqueue_css_cache()
122 + public function enqueue_css_cache(bool $force_inline = \false)
94 123 {
95 124 if (!$this->is_cache_exists()) {
96 125 return;
97 126 }
127 + do_action('a!windpress/core/runtime:enqueue_css_cache.before');
98 128 $handle = WIND_PRESS::WP_OPTION . '-cached';
99 - if (Config::get('performance.cache.inline_load', \false)) {
129 + if ($force_inline || Config::get('performance.cache.inline_load', \false)) {
100 130 // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file
101 - $css_clean = \file_get_contents(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
131 + $css_clean = file_get_contents(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
102 132 if ($css_clean === \false) {
103 133 return;
104 134 }
105 - \wp_register_style($handle, \false, [], \false);
106 - \wp_add_inline_style($handle, \wp_strip_all_tags($css_clean));
107 - \wp_print_styles($handle);
135 + wp_register_style($handle, \false, [], \false);
136 + wp_add_inline_style($handle, wp_strip_all_tags($css_clean));
137 + wp_print_styles($handle);
108 138 } else {
109 - $version = (string) \filemtime(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
110 - \wp_register_style($handle, \WindPress\WindPress\Core\Cache::get_cache_url(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE), [], $version);
111 - \wp_print_styles($handle);
139 + $version = (string) filemtime(\WindPress\WindPress\Core\Cache::get_cache_path(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE));
140 + wp_register_style($handle, \WindPress\WindPress\Core\Cache::get_cache_url(\WindPress\WindPress\Core\Cache::CSS_CACHE_FILE), [], $version);
141 + wp_print_styles($handle);
112 142 }
143 + do_action('a!windpress/core/runtime:enqueue_css_cache.after', $handle);
113 144 }
145 + public function getVFSHtml()
146 + {
147 + $volumeEntries = array_reduce(\WindPress\WindPress\Core\Volume::get_entries(), function ($carry, $entry) {
148 + if (!empty($entry['directory'])) {
149 + return $carry;
150 + }
151 + return $carry + ['/' . $entry['relative_path'] => $entry['content']];
152 + }, []);
153 + // Script content are base64 encoded to prevent it from being executed by the browser.
154 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
155 + return sprintf('<script id="windpress:vfs" type="text/plain">%s</script>', base64_encode(wp_json_encode($volumeEntries)));
156 + }
114 157 public function enqueue_play_cdn($display = \true)
115 158 {
116 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file
117 - $stub_main_css = \file_get_contents(\dirname(WIND_PRESS::FILE) . '/stubs/main.css');
118 - $main_css = $stub_main_css;
119 - $main_css_path = \wp_upload_dir()['basedir'] . WIND_PRESS::DATA_DIR . 'main.css';
120 - if (\file_exists($main_css_path)) {
121 - // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents -- Local file
122 - $main_css = \file_get_contents($main_css_path);
123 - }
124 159 // Script content are base64 encoded to prevent it from being executed by the browser.
125 160 // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
126 - echo \sprintf("<script id=\"windpress:tw-main-css\" type=\"text/tailwindcss\">%s</script>", \esc_html(\base64_encode($main_css)));
127 - AssetVite::get_instance()->enqueue_asset('assets/packages/core/tailwind/play/autocomplete.js', ['handle' => WIND_PRESS::WP_OPTION . ':autocomplete', 'in-footer' => \true]);
128 - AssetVite::get_instance()->enqueue_asset('assets/packages/core/tailwind/play/sort.js', ['handle' => WIND_PRESS::WP_OPTION . ':sort', 'in-footer' => \true]);
129 - AssetVite::get_instance()->enqueue_asset('assets/packages/core/tailwind/play/classname-to-css.js', ['handle' => WIND_PRESS::WP_OPTION . ':classname-to-css', 'in-footer' => \true]);
130 - AssetVite::get_instance()->enqueue_asset('assets/packages/core/tailwind/play/observer.js', ['handle' => WIND_PRESS::WP_OPTION . ':observer', 'in-footer' => \true]);
161 + echo $this->getVFSHtml();
162 + $tailwindcss_version = static::tailwindcss_version();
163 + do_action('a!windpress/core/runtime:enqueue_play_cdn.before');
164 + $can_load_modules = current_user_can('manage_options', $tailwindcss_version);
165 + if ($tailwindcss_version === 3) {
166 + $this->enqueue_play_modules_v3($can_load_modules);
167 + } elseif ($tailwindcss_version === 4) {
168 + $this->enqueue_play_modules($can_load_modules);
169 + }
170 + wp_enqueue_script(WIND_PRESS::WP_OPTION . ':observer');
171 + do_action('a!windpress/core/runtime:enqueue_play_cdn.after', $tailwindcss_version);
131 172 }
173 + public function enqueue_play_modules($can_load_modules)
174 + {
175 + // Register the modules
176 + $loaded_modules = [];
177 + if ($can_load_modules) {
178 + Vite::assets()->register('resources/packages/core/tailwindcss/play/intellisense.ts', ['handle' => WIND_PRESS::WP_OPTION . ':intellisense', 'in_footer' => \true]);
179 + $loaded_modules[] = WIND_PRESS::WP_OPTION . ':intellisense';
180 + Vite::assets()->register('resources/packages/core/tailwindcss/play/worker.ts', ['handle' => WIND_PRESS::WP_OPTION . ':worker', 'in_footer' => \true]);
181 + $loaded_modules[] = WIND_PRESS::WP_OPTION . ':worker';
182 + do_action('a!windpress/core/runtime:enqueue_play_modules.loaded_modules');
183 + $loaded_modules = apply_filters('f!windpress/core/runtime:enqueue_play_modules.loaded_modules', $loaded_modules);
184 + }
185 + 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))]);
186 + }
187 + public function enqueue_play_modules_v3($can_load_modules)
188 + {
189 + // Register the modules
190 + $loaded_modules = [];
191 + if ($can_load_modules) {
192 + Vite::assets()->register('resources/packages/core/tailwindcss-v3/play/intellisense.ts', ['handle' => WIND_PRESS::WP_OPTION . ':intellisense', 'in_footer' => \true]);
193 + $loaded_modules[] = WIND_PRESS::WP_OPTION . ':intellisense';
194 + }
195 + 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))]);
196 + }
132 197 public function enqueue_front_panel()
133 198 {
134 199 $handle = WIND_PRESS::WP_OPTION . ':admin';
135 - AssetVite::get_instance()->enqueue_asset('assets/apps/dashboard/main.js', ['handle' => $handle, 'in_footer' => \true]);
136 - \wp_set_script_translations($handle, 'windpress');
137 - \wp_localize_script($handle, 'windpress', ['_version' => WIND_PRESS::VERSION, '_wpnonce' => \wp_create_nonce(WIND_PRESS::WP_OPTION), '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))], 'assets' => ['url' => AssetVite::asset_base_url()], 'site_meta' => ['name' => \get_bloginfo('name'), 'site_url' => \get_site_url()], 'is_universal' => \true]);
200 + Vite::assets()->enqueue('resources/dashboard/main.ts', ['handle' => $handle, 'in_footer' => \true, 'dependencies' => ['wp-i18n', 'wp-hooks']]);
201 + wp_set_script_translations($handle, 'windpress');
138 202 // do enqueue scripts manually as it already runned before
139 - \wp_enqueue_scripts();
203 + wp_enqueue_scripts();
204 + }
205 + public function is_ubiquitous_panel()
206 + {
207 + 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);
208 + }
209 + public function print_windpress_metadata()
210 + {
211 + $metadata = $this->assets_metadata();
212 + $metadata = apply_filters('f!windpress/core/runtime:print_windpress_metadata', $metadata);
213 + // /**
214 + // * @see \WP_Scripts::localize()
215 + // */
216 + // foreach ($metadata as $key => $value) {
217 + // if (! is_scalar($value)) {
218 + // continue;
219 + // }
220 + // $metadata[$key] = html_entity_decode((string) $value, ENT_QUOTES, 'UTF-8');
221 + // }
222 + // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
223 + echo sprintf('<script id="windpress:metadata">var windpress = %s;</script>', wp_json_encode($metadata));
224 + }
225 + public function assets_metadata()
226 + {
227 + $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' => [
228 + // 'path' => Volume::data_dir_path(),
229 + 'url' => \WindPress\WindPress\Core\Volume::data_dir_url(),
230 + ], 'cache_dir' => []]];
231 + if (current_user_can('manage_options')) {
232 + $metadata['_wpnonce'] = wp_create_nonce(WIND_PRESS::WP_OPTION);
233 + $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))];
234 + $metadata['site_meta'] = ['name' => get_bloginfo('name'), 'site_url' => get_site_url(), 'web_history' => AdminPage::get_page_url()];
235 + $metadata['is_debug'] = defined('WP_DEBUG') && \WP_DEBUG;
236 + }
237 + return $metadata;
140 238 }
141 239 }