PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 1.9.0
GenerateBlocks v1.9.0
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 2 years ago pattern-library 2 years ago utils 2 years ago class-do-css.php 2 years ago class-dynamic-content.php 2 years ago class-enqueue-css.php 3 years ago class-legacy-attributes.php 4 years ago class-map-deprecated-attributes.php 2 years ago class-plugin-update.php 5 years ago class-query-loop.php 2 years ago class-render-blocks.php 3 years ago class-rest.php 2 years ago class-settings.php 2 years ago dashboard.php 3 years ago defaults.php 2 years ago functions.php 2 years ago general.php 2 years ago
class-enqueue-css.php
523 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 wp_add_inline_style(
194 'generateblocks',
195 wp_strip_all_tags( $css ) // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
196 );
197 }
198 }
199
200 /**
201 * Make our CSS.
202 */
203 public function make_css() {
204 $page_id = $this->page_id();
205
206 if ( ! $page_id ) {
207 return false;
208 }
209
210 $has_generateblocks = get_post_meta( $page_id, '_generateblocks_dynamic_css_version', true );
211
212 if ( empty( $has_generateblocks ) ) {
213 return false;
214 }
215
216 // Build our CSS based on the content we find.
217 generateblocks_get_dynamic_css();
218
219 $content = generateblocks_get_frontend_block_css();
220
221 if ( ! $content ) {
222 return false;
223 }
224
225 // If we only have a little CSS, we should inline it.
226 $css_size = strlen( $content );
227
228 if ( $css_size < (int) apply_filters( 'generateblocks_css_inline_length', 500 ) ) {
229 return false;
230 }
231
232 $filesystem = generateblocks_get_wp_filesystem();
233
234 if ( ! $filesystem ) {
235 return false;
236 }
237
238 // Take care of domain mapping.
239 if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
240 if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
241 $mapped_domain = domain_mapping_siteurl( false );
242 $original_domain = get_original_url( 'siteurl' );
243
244 $content = str_replace( $original_domain, $mapped_domain, $content );
245 }
246 }
247
248 if ( is_writable( $this->file( 'path' ) ) || ( ! file_exists( $this->file( 'path' ) ) && is_writable( dirname( $this->file( 'path' ) ) ) ) ) {
249 $chmod_file = 0644;
250
251 if ( defined( 'FS_CHMOD_FILE' ) ) {
252 $chmod_file = FS_CHMOD_FILE;
253 }
254
255 if ( ! $filesystem->put_contents( $this->file( 'path' ), wp_strip_all_tags( $content ), $chmod_file ) ) {
256
257 // Fail!
258 return false;
259
260 } else {
261
262 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
263 $option[ $page_id ] = true;
264 update_option( 'generateblocks_dynamic_css_posts', $option );
265
266 // Update the 'generateblocks_dynamic_css_time' option.
267 $this->update_saved_time();
268
269 // Set flag.
270 self::$has_made_css = true;
271
272 // Success!
273 return true;
274
275 }
276 }
277 }
278
279 /**
280 * Determines if the CSS file is writable.
281 */
282 public function can_write() {
283 global $blog_id;
284
285 // Get the upload directory for this site.
286 $upload_dir = wp_get_upload_dir();
287
288 // If this is a multisite installation, append the blogid to the filename.
289 $css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
290 $page_id = $this->page_id();
291
292 if ( ! $page_id ) {
293 return false;
294 }
295
296 $file_name = '/style' . $css_blog_id . '-' . $page_id . '.css';
297 $folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
298
299 // Does the folder exist?
300 if ( file_exists( $folder_path ) ) {
301 // Folder exists, but is the folder writable?
302 if ( ! is_writable( $folder_path ) ) {
303 // Folder is not writable.
304 // Does the file exist?
305 if ( ! file_exists( $folder_path . $file_name ) ) {
306 // File does not exist, therefore it can't be created
307 // since the parent folder is not writable.
308 return false;
309 } else {
310 // File exists, but is it writable?
311 if ( ! is_writable( $folder_path . $file_name ) ) {
312 // Nope, it's not writable.
313 return false;
314 }
315 }
316 } else {
317 // The folder is writable.
318 // Does the file exist?
319 if ( file_exists( $folder_path . $file_name ) ) {
320 // File exists.
321 // Is it writable?
322 if ( ! is_writable( $folder_path . $file_name ) ) {
323 // Nope, it's not writable.
324 return false;
325 }
326 }
327 }
328 } else {
329 // Can we create the folder?
330 // returns true if yes and false if not.
331 return wp_mkdir_p( $folder_path );
332 }
333
334 // all is well!
335 return true;
336 }
337
338 /**
339 * Gets the css path or url to the stylesheet
340 *
341 * @param string $target path/url.
342 */
343 public function file( $target = 'path' ) {
344 global $blog_id;
345
346 // Get the upload directory for this site.
347 $upload_dir = wp_get_upload_dir();
348
349 // If this is a multisite installation, append the blogid to the filename.
350 $css_blog_id = ( is_multisite() && $blog_id > 1 ) ? '_blog-' . $blog_id : null;
351 $page_id = $this->page_id();
352
353 $file_name = 'style' . $css_blog_id . '-' . $page_id . '.css';
354 $folder_path = $upload_dir['basedir'] . DIRECTORY_SEPARATOR . 'generateblocks';
355
356 // The complete path to the file.
357 $file_path = $folder_path . DIRECTORY_SEPARATOR . $file_name;
358
359 // Get the URL directory of the stylesheet.
360 $css_uri_folder = $upload_dir['baseurl'];
361
362 $css_uri = trailingslashit( $css_uri_folder ) . 'generateblocks/' . $file_name;
363
364 // Take care of domain mapping.
365 if ( defined( 'DOMAIN_MAPPING' ) && DOMAIN_MAPPING ) {
366 if ( function_exists( 'domain_mapping_siteurl' ) && function_exists( 'get_original_url' ) ) {
367 $mapped_domain = domain_mapping_siteurl( false );
368 $original_domain = get_original_url( 'siteurl' );
369 $css_uri = str_replace( $original_domain, $mapped_domain, $css_uri );
370 }
371 }
372
373 $css_uri = set_url_scheme( $css_uri );
374
375 if ( 'path' === $target ) {
376 return $file_path;
377 } elseif ( 'url' === $target || 'uri' === $target ) {
378 $timestamp = ( file_exists( $file_path ) ) ? '?ver=' . filemtime( $file_path ) : '';
379 return $css_uri . $timestamp;
380 }
381 }
382
383 /**
384 * Create settings.
385 */
386 public function add_options() {
387 /**
388 * The 'generateblocks_dynamic_css_posts' option will hold an array of posts that have had their css generated.
389 * We can use that to keep track of which pages need their CSS to be recreated and which don't.
390 */
391 add_option( 'generateblocks_dynamic_css_posts', array(), '', 'yes' );
392
393 /**
394 * The 'generateblocks_dynamic_css_time' option holds the time the file writer was last used.
395 */
396 add_option( 'generateblocks_dynamic_css_time', time(), '', 'yes' );
397 }
398
399 /**
400 * Update the generateblocks_dynamic_css_posts option when a post is saved.
401 *
402 * @param int $post_id The current post ID.
403 * @param object $post The current post.
404 */
405 public function post_update_option( $post_id, $post ) {
406 $is_autosave = wp_is_post_autosave( $post_id );
407 $is_revision = wp_is_post_revision( $post_id );
408
409 if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
410 return $post_id;
411 }
412
413 if ( isset( $post->post_content ) ) {
414 if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
415 update_post_meta( $post_id, '_generateblocks_dynamic_css_version', sanitize_text_field( GENERATEBLOCKS_VERSION ) );
416 } else {
417 delete_post_meta( $post_id, '_generateblocks_dynamic_css_version' );
418 }
419
420 // Store any re-usable block IDs on the page. We need this to regenerate CSS files later if the re-usable block is changed.
421 $reusable_blocks = preg_match_all( '/wp:block {"ref":([^}]*)}/', $post->post_content, $matches );
422 $stored_reusable_blocks = array();
423
424 foreach ( $matches[1] as $match ) {
425 $stored_reusable_blocks[] = $match;
426 }
427
428 if ( ! empty( $stored_reusable_blocks ) ) {
429 $stored_reusable_blocks = array_map( 'intval', $stored_reusable_blocks );
430 update_post_meta( $post_id, '_generateblocks_reusable_blocks', $stored_reusable_blocks );
431 } else {
432 delete_post_meta( $post_id, '_generateblocks_reusable_blocks' );
433 }
434 }
435
436 // Make a new CSS file for this post on next load.
437 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
438 unset( $option[ $post_id ] );
439
440 update_option( 'generateblocks_dynamic_css_posts', $option );
441 }
442
443 /**
444 * Force regeneration of CSS files attached to pages with this re-usable block.
445 *
446 * @param int $post_id The current post ID.
447 * @param object $post The current post.
448 */
449 public function wp_block_update( $post_id, $post ) {
450 $is_autosave = wp_is_post_autosave( $post_id );
451 $is_revision = wp_is_post_revision( $post_id );
452
453 if ( $is_autosave || $is_revision || ! current_user_can( 'edit_post', $post_id ) ) {
454 return $post_id;
455 }
456
457 if ( isset( $post->post_content ) ) {
458 if ( strpos( $post->post_content, 'wp:generateblocks' ) !== false ) {
459 global $wpdb;
460
461 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
462
463 $posts = $wpdb->get_col( "SELECT post_id FROM $wpdb->postmeta WHERE meta_key = '_generateblocks_reusable_blocks'" );
464
465 foreach ( (array) $posts as $id ) {
466 unset( $option[ $id ] );
467 }
468
469 update_option( 'generateblocks_dynamic_css_posts', $option );
470 }
471 }
472 }
473
474 /**
475 * Do we need to update the CSS file?
476 */
477 public function needs_update() {
478 $option = get_option( 'generateblocks_dynamic_css_posts', array() );
479 $page_id = $this->page_id();
480
481 // If the CSS file does not exist then we definitely need to regenerate the CSS.
482 if ( ! file_exists( $this->file( 'path' ) ) ) {
483 return true;
484 }
485
486 return ( ! isset( $option[ $page_id ] ) || ! $option[ $page_id ] ) ? true : false;
487 }
488
489 /**
490 * Update the 'generateblocks_dynamic_css_time' option.
491 */
492 public function update_saved_time() {
493 update_option( 'generateblocks_dynamic_css_time', time() );
494 }
495
496 /**
497 * Force CSS files to regenerate after a widget has been saved.
498 *
499 * @param array $instance The current widget instance's settings.
500 */
501 public function force_file_regen_on_widget_save( $instance ) {
502 if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
503 update_option( 'generateblocks_dynamic_css_posts', array() );
504 }
505
506 return $instance;
507 }
508
509 /**
510 * Force CSS files to regenerate after the Customizer has been saved.
511 * This is necessary because force_file_regen_on_widget_save() doesn't fire in the Customizer for some reason.
512 *
513 * @todo Make this only happen when the widgets have been changed.
514 */
515 public function force_file_regen_on_customizer_save() {
516 if ( function_exists( 'wp_use_widgets_block_editor' ) && wp_use_widgets_block_editor() ) {
517 update_option( 'generateblocks_dynamic_css_posts', array() );
518 }
519 }
520 }
521
522 GenerateBlocks_Enqueue_CSS::get_instance();
523