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