PluginProbe
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables / 2.1.0
TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables v2.1.0
2.3.0 2.2.14 2.2.13 2.2.12 2.2.11 2.2.10 2.2.9 2.2.8 2.2.7 2.2.6 2.2.5 2.2.4 2.2.3 trunk 1.0.0 1.0.1 2.0.0 2.0.1 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.2.2
table-builder-block / includes / Hooks / AssetGenerator.php

AssetGenerator.php in TableKit – WordPress Table Builder for Data Tables, WooCommerce Product Tables & Post Tables 2.1.0, at includes/Hooks/AssetGenerator.php

397 lines 12.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace TableBuilder\Hooks;
4
5 defined('ABSPATH') || exit;
6
7 class AssetGenerator
8 {
9
10 use \TableBuilder\Traits\Singleton;
11
12 /**
13 * Defining css
14 */
15 public $css = '';
16
17 /**
18 * Defining fonts
19 */
20 protected $fonts = array();
21
22 /**
23 * AssetGenerator class constructor.
24 * private for singleton
25 *
26 * @return void
27 * @since 1.0.0
28 */
29 public function __construct()
30 {
31 add_action('save_post', array($this, 'save_post_hook'), 10, 3);
32 add_filter('render_block_data', array($this, 'set_blocks_css'), 10);
33 add_filter('wp_resource_hints', array($this, 'fonts_resource_hints'), 10, 2);
34 add_action('wp_enqueue_scripts', array($this, 'enqueue_scripts'), 10);
35 add_action('enqueue_block_assets', array($this, 'block_assets'), 10);
36 }
37
38 /**
39 * Filters an array of blocks and returns only those where the block name contains 'tablebuilder'.
40 *
41 * @param array $blocks An array of blocks. Each block is an associative array that must contain a 'blockName' key. Default is an empty array.
42 * @return array Returns an array of blocks where the block name contains 'tablebuilder'.
43 */
44 public function filter_blocks($blocks = array())
45 {
46 $filtered_blocks = [];
47
48 foreach ($blocks as $block) {
49 if (isset($block['blockName']) && strpos($block['blockName'], 'tablebuilder') !== false) {
50 $filtered_blocks[] = $block;
51 }
52
53 if (!empty($block['innerBlocks'])) {
54 $filtered_blocks = array_merge($filtered_blocks, $this->filter_blocks($block['innerBlocks']));
55 }
56 }
57
58 return $filtered_blocks;
59 }
60
61 /**
62 * Minify CSS by condensing white spaces and removing comments.
63 *
64 * @param string $css The input CSS.
65 * @return string Minified CSS.
66 */
67 public function minimize_css($css)
68 {
69 if (trim($css) === '') {
70 return $css;
71 }
72
73 return preg_replace(
74 array(
75 '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')|\/\*(?!\!)(?>.*?\*\/)|^\s*|\s*$#s',
76 '#("(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\'|\/\*(?>.*?\*\/))|\s*+;\s*+(})\s*+|\s*+([*$~^|]?+=|[{};,>~]|\s(?![0-9\.])|!important\b)\s*+|([[(:])\s++|\s++([])])|\s++(:)\s*+(?!(?>[^{}"\']++|"(?:[^"\\\]++|\\\.)*+"|\'(?:[^\'\\\\]++|\\\.)*+\')*+{)|^\s++|\s++\z|(\s)\s+#si',
77 '#(?<=[\s:])(0)(cm|em|ex|in|mm|pc|pt|px|vh|vw|%)#si',
78 '#:(0\s+0|0\s+0\s+0\s+0)(?=[;\}]|\!important)#i',
79 '#(background-position):0(?=[;\}])#si',
80 '#(?<=[\s:,\-])0+\.(\d+)#s',
81 '#(\/\*(?>.*?\*\/))|(?<!content\:)([\'"])([a-z_][a-z0-9\-_]*?)\2(?=[\s\{\}\];,])#si',
82 '#(\/\*(?>.*?\*\/))|(\burl\()([\'"])([^\s]+?)\3(\))#si',
83 '#(?<=[\{;])(border|outline):none(?=[;\}\!])#',
84 '#(\/\*(?>.*?\*\/))|(^|[\{\}])(?:[^\s\{\}]+)\{\}#s',
85 ),
86 array(
87 '$1',
88 '$1$2$3$4$5$6$7',
89 '$1',
90 ':0',
91 '$1:0 0',
92 '.$1',
93 '$1$3',
94 '$1$2$4$5',
95 '$1$2$3',
96 '$1:0',
97 '$1$2',
98 ),
99 $css
100 );
101 }
102
103 /**
104 * Fires once a post has been saved.
105 *
106 * @param int $post_id The ID of the post.
107 * @param WP_Post $post The post object.
108 * @param bool $update Whether this is an existing post being updated.
109 * @return void
110 */
111 public function save_post_hook($post_id, $post, $update)
112 {
113 // Bail out if is draft, revision, or autosave
114 if ('auto-draft' === $post->post_status || wp_is_post_revision($post_id) || defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) {
115 return;
116 }
117
118 // Skip if this is an autosave or update
119 if (!$update) {
120 return;
121 }
122
123 $post = get_post($post_id);
124 $parse_blocks = $this->filter_blocks(parse_blocks($post->post_content));
125
126 if ($parse_blocks) {
127 $fse = in_array($post->post_type, ['wp_template_part', 'wp_template']);
128 if ($fse) {
129 $this->set_fonts(null, $this->generate_fse_assets(), true);
130 } else {
131 $this->set_fonts($post_id, $parse_blocks);
132 }
133 }
134 }
135
136 /**
137 * Generate assets for templates.
138 *
139 * @return array $filtered_blocks The filtered blocks for FSE templates.
140 */
141 protected function generate_fse_assets()
142 {
143 $args = [
144 'post_type' => ['wp_template_part', 'wp_template'],
145 'posts_per_page' => -1,
146 ];
147
148 $posts = get_posts($args);
149 $merged_blocks = [];
150
151 foreach ($posts as $post) {
152 $merged_blocks = array_merge($merged_blocks, parse_blocks($post->post_content));
153 }
154
155 return $this->filter_blocks($merged_blocks);
156 }
157
158 /**
159 * Set the fonts for a given post or Full Site Editing (FSE) template.
160 *
161 * @param int $post_id The ID of the post or FSE template.
162 * @param array $blocks An array of blocks.
163 * @param bool $fse Whether this is an FSE template.
164 * @return void
165 */
166 protected function set_fonts($post_id, $blocks, $fse = false)
167 {
168 $fonts = [];
169
170 foreach ($blocks as $block) {
171 if (isset($block['attrs'])) {
172 $typographies = array_filter($block['attrs'], function ($key) {
173 return str_contains(strtolower($key), 'typography');
174 }, ARRAY_FILTER_USE_KEY);
175
176 if (!empty($typographies)) {
177 foreach ($typographies as $typography) {
178 $font_weight = !empty($typography['fontWeight']['value']) ? $typography['fontWeight']['value'] : 400;
179 if (!empty($typography['fontFamily']['value'])) {
180 $fonts[$typography['fontFamily']['value']][] = $font_weight;
181 }
182 }
183 }
184 }
185 }
186
187 // Update fonts
188 if (!empty($fonts)) {
189 if ($fse) {
190 update_option('table_builder_fse_fonts', $fonts);
191 } else {
192 update_post_meta($post_id, 'table_builder_posts_fonts', $fonts);
193 }
194 } else {
195 if ($fse) {
196 delete_option('table_builder_fse_fonts');
197 } else {
198 delete_post_meta($post_id, 'table_builder_posts_fonts');
199 }
200 }
201 }
202
203 /**
204 * Combine block assets (CSS and JS) based on the used blocks.
205 *
206 * @param array $parsed_block The parsed block.
207 * @return string Combined CSS content.
208 */
209 protected function combine_blocks_assets($parsed_block = [])
210 {
211 $blocks_css = [];
212
213 if (isset($parsed_block['blockName']) && strpos($parsed_block['blockName'], 'tablebuilder') !== false) {
214 if (isset($parsed_block['attrs']['blocksCSS'])) {
215 foreach ($parsed_block['attrs']['blocksCSS'] as $device => $css) {
216 if (!isset($blocks_css[$device])) {
217 $blocks_css[$device] = '';
218 }
219
220 if (is_string($css)) {
221 $blocks_css[$device] .= $css;
222 }
223 }
224 }
225
226 // block typography
227 $this->set_typography($parsed_block);
228
229 // block common style
230 if (isset($parsed_block['attrs']['commonStyle'])) {
231 foreach ($parsed_block['attrs']['commonStyle'] as $device => $css) {
232 if (!isset($blocks_css[$device])) {
233 $blocks_css[$device] = '';
234 }
235
236 $blocks_css[$device] .= $css;
237 }
238 }
239 }
240
241 // Concatenate CSS content into a single string
242 $css_content = '';
243 $is_custom_styles_added = false;
244 $device_list = \TableBuilder\Helpers\Utils::get_device_list();
245
246 if (!empty($blocks_css)) {
247 foreach ($device_list as $device) {
248 foreach ($blocks_css as $key => $block) {
249 if (!empty($block) && trim($block) !== '') {
250 $direction = $device['direction'] ?? 'max';
251 $width = $device['value'] ?? '';
252 $device_key = strtolower($device['slug'] ?? '');
253
254 if ('base' === $device['value'] && 'desktop' === $key) {
255 $css_content .= $block;
256 } elseif (!empty($direction) && !empty($width) && $device_key === $key) {
257 $css_content .= "@media ({$direction}-width: {$width}px) {" . trim($block) . '}';
258 }
259
260 if ('customStyles' === $key && !$is_custom_styles_added) {
261 $is_custom_styles_added = true;
262 $css_content .= $block;
263 }
264 }
265 }
266 }
267 }
268
269 return $css_content;
270 }
271
272 protected function set_typography($parsed_block)
273 {
274 if (isset($parsed_block['attrs'])) {
275 $typographies = array_filter(
276 $parsed_block['attrs'],
277 function ($key) {
278 $key = strtolower($key);
279 return str_contains($key, 'typography') || str_contains($key, 'typo');
280 },
281 ARRAY_FILTER_USE_KEY
282 );
283
284 if (! empty($typographies)) {
285 foreach ($typographies as $typography) {
286 $font_weight = ! empty($typography['fontWeight']['value']) ? $typography['fontWeight']['value'] : 400;
287 if (! empty($typography['fontFamily']['value'])) {
288 $this->fonts[$typography['fontFamily']['value']][] = $font_weight;
289 }
290 }
291 }
292 }
293 }
294
295 /**
296 * Generate Google Fonts URL.
297 *
298 * @return string|bool Google Fonts URL or false if no fonts.
299 */
300 protected function generate_fonts_url()
301 {
302 if (!empty($this->fonts)) {
303 $font_families = [];
304 $font_url = 'https://fonts.googleapis.com/css2?family=';
305
306 // Remove duplicates and sort the fonts
307 $all_fonts = array_map(function ($arr) {
308 $arr = array_unique($arr);
309 sort($arr);
310 return $arr;
311 }, $this->fonts);
312
313 foreach ($all_fonts as $font => $weights) {
314 $weights = array_map(function ($weight) {
315 $invalid_list = ['normal', 'inherit', 'initial'];
316 return in_array($weight, $invalid_list) ? '400' : $weight;
317 }, $weights);
318 sort($weights);
319 $font_families[] = str_replace(' ', '+', $font) . ':wght@' . implode(';', array_unique($weights));
320 }
321
322 $font_url .= implode('&family=', $font_families);
323 $font_url .= '&display=swap';
324
325 return $font_url;
326 }
327
328 return false;
329 }
330
331 /**
332 * Sets the CSS for the blocks.
333 *
334 * @param array $parsed_block The parsed block data.
335 * @return array The modified parsed block data.
336 */
337 public function set_blocks_css($parsed_block)
338 {
339 $css_content = $this->combine_blocks_assets($parsed_block);
340 if (!empty($css_content)) {
341 $this->css .= $css_content;
342 }
343 return $parsed_block;
344 }
345
346 /**
347 * Add preconnect for Google Fonts.
348 *
349 * @param array $urls URLs to print for resource hints.
350 * @param string $relation_type The relation type the URLs are printed.
351 * @return array
352 */
353 public function fonts_resource_hints($urls, $relation_type)
354 {
355 if (wp_style_is('table-builder-google-fonts', 'queue') && 'preconnect' === $relation_type) {
356 $urls[] = [
357 'href' => 'https://fonts.gstatic.com',
358 'crossorigin',
359 ];
360 }
361
362 return $urls;
363 }
364
365 /**
366 * Enqueues the Google Fonts stylesheet if available.
367 * Enqueues inline styles for the TableBuilder frontend.
368 */
369 public function enqueue_scripts()
370 {
371 global $post;
372
373 if (!wp_is_block_theme() && !empty($post->post_content)) {
374 do_blocks($post->post_content);
375 }
376
377 $fonts_url = $this->generate_fonts_url();
378 if ($fonts_url) {
379 wp_enqueue_style('table-builder-google-fonts', $fonts_url, false, null);
380 }
381
382 if ($this->css) {
383 wp_add_inline_style('table-builder-style', $this->css);
384 }
385 }
386
387 /**
388 * Enqueues block assets (CSS & JS).
389 *
390 * @return void
391 */
392 public function block_assets()
393 {
394 wp_enqueue_style('table-builder-style', get_stylesheet_uri());
395 }
396 }
397