PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 2.2.1
GenerateBlocks v2.2.1
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / includes / class-enqueue-css.php
generateblocks / includes Last commit date
blocks 1 year ago dynamic-tags 3 months ago pattern-library 1 year ago utils 2 years ago class-do-css.php 2 years ago class-dynamic-content.php 6 months ago class-dynamic-tag-security.php 6 months ago class-enqueue-css.php 1 year ago class-legacy-attributes.php 4 years ago class-map-deprecated-attributes.php 2 years ago class-meta-handler.php 3 months ago class-plugin-update.php 1 year ago class-query-loop.php 2 years ago class-query-utils.php 3 months ago class-render-blocks.php 1 year ago class-rest.php 1 year ago class-settings.php 1 year ago dashboard.php 1 year ago defaults.php 1 year ago deprecated.php 1 year ago functions.php 6 months ago general.php 8 months ago
class-enqueue-css.php
527 lines
1 <?php
2 /**
3 * Handles the CSS Output.
4 *
5 * @package GenerateBlocks
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Enqueue our block CSS to the current page.
14 */
15 class GenerateBlocks_Enqueue_CSS {
16 /**
17 * Instance.
18 *
19 * @access private
20 * @var object Instance
21 * @since 0.1
22 */
23 private static $instance;
24
25 /**
26 * Check to see if we've made this CSS in this instance.
27 * This should only run on first load if needed.
28 *
29 * @access private
30 * @var boolean
31 */
32 private static $has_made_css = false;
33
34 /**
35 * Check to see if we've enqueued our CSS.
36 *
37 * @access private
38 * @var boolean
39 */
40 private static $has_enqueued_css = false;
41
42 /**
43 * Initiator.
44 *
45 * @since 0.1
46 * @return object initialized object of class.
47 */
48 public static function get_instance() {
49 if ( ! isset( self::$instance ) ) {
50 self::$instance = new self();
51 }
52
53 return self::$instance;
54 }
55
56 /**
57 * Constructor.
58 */
59 public function __construct() {
60 $this->add_options();
61
62 add_action( 'save_post', array( $this, 'post_update_option' ), 10, 2 );
63 add_action( 'save_post_wp_block', array( $this, 'wp_block_update' ), 10, 2 );
64 add_action( 'init', array( $this, 'enqueue_assets' ) );
65 add_filter( 'widget_update_callback', array( $this, 'force_file_regen_on_widget_save' ) );
66 add_action( 'customize_save_after', array( $this, 'force_file_regen_on_customizer_save' ) );
67 }
68
69 /**
70 * Tell our system it's ok to generate CSS.
71 */
72 private function enable_enqueue() {
73 self::$has_enqueued_css = true;
74 }
75
76 /**
77 * Check to see if we can generate CSS.
78 */
79 public static function can_enqueue() {
80 return self::$has_enqueued_css || ( function_exists( 'wp_is_block_theme' ) && wp_is_block_theme() );
81 }
82
83 /**
84 * Enqueue our front-end assets.
85 */
86 public function enqueue_assets() {
87 $dynamic_css_priority = apply_filters( 'generateblocks_dynamic_css_priority', 25 );
88
89 add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_dynamic_css' ), $dynamic_css_priority );
90 add_action( 'wp_enqueue_scripts', array( $this, 'print_inline_css' ), $dynamic_css_priority );
91 }
92
93 /**
94 * Get the current page ID.
95 */
96 public function page_id() {
97 global $post;
98
99 $id = isset( $post ) ? $post->ID : false;
100 $id = ( ! is_singular() ) ? false : $id;
101 $id = ( function_exists( 'is_shop' ) && is_shop() ) ? get_option( 'woocommerce_shop_page_id' ) : $id;
102 $id = ( is_home() ) ? get_option( 'page_for_posts' ) : $id;
103
104 return $id;
105 }
106
107 /**
108 * Determine if we're using file mode or inline mode.
109 */
110 public function mode() {
111 // Check if we're using file mode or inline mode.
112 // Default to file mode and fallback to inline if file mode is not possible.
113 $mode = apply_filters( 'generateblocks_css_print_method', 'file' );
114
115 if (
116 ( function_exists( 'is_customize_preview' ) && is_customize_preview() )
117 ||
118 is_preview()
119 ||
120 // AMP inlines all CSS, so inlining from the start improves CSS processing performance.
121 ( function_exists( 'is_amp_endpoint' ) && is_amp_endpoint() )
122 ) {
123 return 'inline';
124 }
125
126 // Additional checks for file mode.
127 if ( 'file' === $mode && $this->needs_update() ) {
128 // Only allow processing 1 file every 5 seconds.
129 $current_time = (int) time();
130 $last_time = (int) get_option( 'generateblocks_dynamic_css_time' );
131
132 if ( 5 <= ( $current_time - $last_time ) ) {
133 // Attempt to write to the file.
134 $mode = ( $this->can_write() && $this->make_css() ) ? 'file' : 'inline';
135
136 // Does again if the file exists.
137 if ( 'file' === $mode ) {
138 $mode = ( file_exists( $this->file( 'path' ) ) ) ? 'file' : 'inline';
139 }
140 }
141 }
142
143 return $mode;
144 }
145
146 /**
147 * Enqueue the dynamic CSS.
148 */
149 public function enqueue_dynamic_css() {
150 $this->enable_enqueue();
151 $page_id = $this->page_id();
152
153 if ( ! $page_id ) {
154 return;
155 }
156
157 $has_generateblocks = get_post_meta( $page_id, '_generateblocks_dynamic_css_version', true );
158
159 if ( empty( $has_generateblocks ) ) {
160 return;
161 }
162
163 if ( 'file' === $this->mode() ) {
164 if ( ! self::$has_made_css ) {
165 // Store our block IDs based on the content we find.
166 generateblocks_get_dynamic_css( '', true );
167 }
168
169 wp_enqueue_style( 'generateblocks', esc_url( $this->file( 'uri' ) ), array(), null ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
170 }
171 }
172
173 /**
174 * Print our inline CSS.
175 */
176 public function print_inline_css() {
177 $this->enable_enqueue();
178
179 if ( 'inline' === $this->mode() || ! wp_style_is( 'generateblocks', 'enqueued' ) ) {
180 // Build our CSS based on the content we find.
181 generateblocks_get_dynamic_css();
182
183 $css = generateblocks_get_frontend_block_css();
184
185 if ( empty( $css ) ) {
186 return;
187 }
188
189 // Add a "dummy" handle we can add inline styles to.
190 wp_register_style( 'generateblocks', false ); // phpcs:ignore WordPress.WP.EnqueuedResourceParameters.MissingVersion
191 wp_enqueue_style( 'generateblocks' );
192
193 $css = html_entity_decode( $css, ENT_QUOTES, 'UTF-8' );
194
195 wp_add_inline_style(
196 'generateblocks',
197 wp_strip_all_tags( $css ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
198 );
199 }
200 }
201
202 /**
203 * Make our CSS.
204 */
205 public function make_css() {
206 $page_id = $this->page_id();
207
208 if ( ! $page_id ) {
209 return false;
210 }
211
212 $has_generateblocks = get_post_meta( $page_id, '_generateblocks_dynamic_css_version', true );
213
214 if ( empty( $has_generateblocks ) ) {
215 return false;
216 }
217
218 // Build our CSS based on the content we find.
219 generateblocks_get_dynamic_css();
220
221 $content = generateblocks_get_frontend_block_css();
222
223 if ( ! $content ) {
224 return false;
225 }
226
227 // If we only have a little CSS, we should inline it.
228 $css_size = strlen( $content );
229
230 if ( $css_size < (int) apply_filters( 'generateblocks_css_inline_length', 500 ) ) {
231 return false;
232 }
233
234 $filesystem = generateblocks_get_wp_filesystem();
235
236 if ( ! $filesystem ) {
237 return false;
238 }
239
240 // Take care of domain mapping.
241 if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
242 if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
243 $mapped_domain = domain_mapping_siteurl( false );
244 $original_domain = get_original_url( 'siteurl' );
245
246 $content = str_replace( $original_domain, $mapped_domain, $content );
247 }
248 }
249
250 if ( is_writable( $this->file( 'path' ) ) || ( ! file_exists( $this->file( 'path' ) ) && is_writable( dirname( $this->file( 'path' ) ) ) ) ) {
251 $chmod_file = 0644;
252
253 if ( defined( 'FS_CHMOD_FILE' ) ) {
254 $chmod_file = FS_CHMOD_FILE;
255 }
256
257 $content = html_entity_decode( $content, ENT_QUOTES, 'UTF-8' );
258
259 if ( ! $filesystem->put_contents( $this->file( 'path' ), wp_strip_all_tags( $content ), $chmod_file ) ) {
260
261 // Fail!
262 return false;
263
264 } else {
265
266 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
267 $option[ $page_id ] = true;
268 update_option( 'generateblocks_dynamic_css_posts', $option );
269
270 // Update the 'generateblocks_dynamic_css_time' option.
271 $this->update_saved_time();
272
273 // Set flag.
274 self::$has_made_css = true;
275
276 // Success!
277 return true;
278
279 }
280 }
281 }
282
283 /**
284 * Determines if the CSS file is writable.
285 */
286 public function can_write() {
287 global $blog_id;
288
289 // Get the upload directory for this site.
290 $upload_dir = wp_get_upload_dir();
291
292 // If this is a multisite installation, append the blogid to the filename.
293 $css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
294 $page_id = $this->page_id();
295
296 if ( ! $page_id ) {
297 return false;
298 }
299
300 $file_name = '/style' . $css_blog_id . '-' . $page_id . '.css';
301 $folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
302
303 // Does the folder exist?
304 if ( file_exists( $folder_path ) ) {
305 // Folder exists, but is the folder writable?
306 if ( ! is_writable( $folder_path ) ) {
307 // Folder is not writable.
308 // Does the file exist?
309 if ( ! file_exists( $folder_path . $file_name ) ) {
310 // File does not exist, therefore it can't be created
311 // since the parent folder is not writable.
312 return false;
313 } else {
314 // File exists, but is it writable?
315 if ( ! is_writable( $folder_path . $file_name ) ) {
316 // Nope, it's not writable.
317 return false;
318 }
319 }
320 } else {
321 // The folder is writable.
322 // Does the file exist?
323 if ( file_exists( $folder_path . $file_name ) ) {
324 // File exists.
325 // Is it writable?
326 if ( ! is_writable( $folder_path . $file_name ) ) {
327 // Nope, it's not writable.
328 return false;
329 }
330 }
331 }
332 } else {
333 // Can we create the folder?
334 // returns true if yes and false if not.
335 return wp_mkdir_p( $folder_path );
336 }
337
338 // all is well!
339 return true;
340 }
341
342 /**
343 * Gets the css path or url to the stylesheet
344 *
345 * @param string $target path/url.
346 */
347 public function file( $target = 'path' ) {
348 global $blog_id;
349
350 // Get the upload directory for this site.
351 $upload_dir = wp_get_upload_dir();
352
353 // If this is a multisite installation, append the blogid to the filename.
354 $css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
355 $page_id = $this->page_id();
356
357 $file_name = 'style' . $css_blog_id . '-' . $page_id . '.css';
358 $folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
359
360 // The complete path to the file.
361 $file_path = $folder_path . DIRECTORY_SEPARATOR . $file_name;
362
363 // Get the URL directory of the stylesheet.
364 $css_uri_folder = $upload_dir['baseurl'];
365
366 $css_uri = trailingslashit( $css_uri_folder ) . 'generateblocks/' . $file_name;
367
368 // Take care of domain mapping.
369 if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
370 if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
371 $mapped_domain = domain_mapping_siteurl( false );
372 $original_domain = get_original_url( 'siteurl' );
373 $css_uri = str_replace( $original_domain, $mapped_domain, $css_uri );
374 }
375 }
376
377 $css_uri = set_url_scheme( $css_uri );
378
379 if ( 'path' === $target ) {
380 return $file_path;
381 } elseif ( 'url' === $target || 'uri' === $target ) {
382 $timestamp = ( file_exists( $file_path ) ) ? '?ver=' . filemtime( $file_path ) : '';
383 return $css_uri . $timestamp;
384 }
385 }
386
387 /**
388 * Create settings.
389 */
390 public function add_options() {
391 /**
392 * The 'generateblocks_dynamic_css_posts' option will hold an array of posts that have had their css generated.
393 * We can use that to keep track of which pages need their CSS to be recreated and which don't.
394 */
395 add_option( 'generateblocks_dynamic_css_posts', array(), '', 'yes' );
396
397 /**
398 * The 'generateblocks_dynamic_css_time' option holds the time the file writer was last used.
399 */
400 add_option( 'generateblocks_dynamic_css_time', time(), '', 'yes' );
401 }
402
403 /**
404 * Update the generateblocks_dynamic_css_posts option when a post is saved.
405 *
406 * @param int $post_id The current post ID.
407 * @param object $post The current post.
408 */
409 public function post_update_option( $post_id, $post ) {
410 $is_autosave = wp_is_post_autosave( $post_id );
411 $is_revision = wp_is_post_revision( $post_id );
412
413 if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
414 return $post_id;
415 }
416
417 if ( isset( $post->post_content ) ) {
418 if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
419 update_post_meta( $post_id, '_generateblocks_dynamic_css_version', sanitize_text_field( GENERATEBLOCKS_VERSION ) );
420 } else {
421 delete_post_meta( $post_id, '_generateblocks_dynamic_css_version' );
422 }
423
424 // Store any re-usable block IDs on the page. We need this to regenerate CSS files later if the re-usable block is changed.
425 $reusable_blocks = preg_match_all( '/wp:block {"ref":([^}]*)}/', $post->post_content, $matches );
426 $stored_reusable_blocks = array();
427
428 foreach ( $matches[1] as $match ) {
429 $stored_reusable_blocks[] = $match;
430 }
431
432 if ( ! empty( $stored_reusable_blocks ) ) {
433 $stored_reusable_blocks = array_map( 'intval', $stored_reusable_blocks );
434 update_post_meta( $post_id, '_generateblocks_reusable_blocks', $stored_reusable_blocks );
435 } else {
436 delete_post_meta( $post_id, '_generateblocks_reusable_blocks' );
437 }
438 }
439
440 // Make a new CSS file for this post on next load.
441 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
442 unset( $option[ $post_id ] );
443
444 update_option( 'generateblocks_dynamic_css_posts', $option );
445 }
446
447 /**
448 * Force regeneration of CSS files attached to pages with this re-usable block.
449 *
450 * @param int $post_id The current post ID.
451 * @param object $post The current post.
452 */
453 public function wp_block_update( $post_id, $post ) {
454 $is_autosave = wp_is_post_autosave( $post_id );
455 $is_revision = wp_is_post_revision( $post_id );
456
457 if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
458 return $post_id;
459 }
460
461 if ( isset( $post->post_content ) ) {
462 if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
463 global $wpdb;
464
465 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
466
467 $posts = $wpdb->get_col( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_generateblocks_reusable_blocks'" );
468
469 foreach ( (array) $posts as $id ) {
470 unset( $option[ $id ] );
471 }
472
473 update_option( 'generateblocks_dynamic_css_posts', $option );
474 }
475 }
476 }
477
478 /**
479 * Do we need to update the CSS file?
480 */
481 public function needs_update() {
482 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
483 $page_id = $this->page_id();
484
485 // If the CSS file does not exist then we definitely need to regenerate the CSS.
486 if ( ! file_exists( $this->file( 'path' ) ) ) {
487 return true;
488 }
489
490 return ( ! isset( $option[ $page_id ] ) || ! $option[ $page_id ] ) ? true : false;
491 }
492
493 /**
494 * Update the 'generateblocks_dynamic_css_time' option.
495 */
496 public function update_saved_time() {
497 update_option( 'generateblocks_dynamic_css_time', time() );
498 }
499
500 /**
501 * Force CSS files to regenerate after a widget has been saved.
502 *
503 * @param array $instance The current widget instance's settings.
504 */
505 public function force_file_regen_on_widget_save( $instance ) {
506 if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
507 update_option( 'generateblocks_dynamic_css_posts', array() );
508 }
509
510 return $instance;
511 }
512
513 /**
514 * Force CSS files to regenerate after the Customizer has been saved.
515 * This is necessary because force_file_regen_on_widget_save() doesn't fire in the Customizer for some reason.
516 *
517 * @todo Make this only happen when the widgets have been changed.
518 */
519 public function force_file_regen_on_customizer_save() {
520 if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
521 update_option( 'generateblocks_dynamic_css_posts', array() );
522 }
523 }
524 }
525
526 GenerateBlocks_Enqueue_CSS::get_instance();
527