PluginProbe
Autoptimize / 3.1.2
Autoptimize v3.1.2
2.2.2 2.3.0 2.3.1 2.3.2 2.3.3 2.3.4 2.4.0 2.4.1 2.4.2 2.4.3 2.4.4 2.5.0 2.5.1 2.6.0 2.6.1 2.6.2 2.7.0 2.7.1 2.7.2 2.7.3 2.7.4 2.7.5 2.7.6 2.7.7 2.7.8 All 107 releases
autoptimize / classes / autoptimizeCriticalCSSCore.php

autoptimizeCriticalCSSCore.php in Autoptimize 3.1.2, at classes/autoptimizeCriticalCSSCore.php

628 lines 27.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Critical CSS Core logic:
4 * gets called by AO core, checks the rules and if a matching rule is found returns the associated CCSS.
5 */
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
11 class autoptimizeCriticalCSSCore {
12 /**
13 * Critical CSS page types.
14 *
15 * @var array
16 */
17 protected $_types = null;
18
19 public function __construct() {
20 $this->criticalcss = autoptimize()->criticalcss();
21 $this->run();
22 }
23
24 public function run() {
25 $css_defer = $this->criticalcss->get_option( 'css_defer' );
26 $deferjquery = $this->criticalcss->get_option( 'deferjquery' );
27 $unloadccss = $this->criticalcss->get_option( 'unloadccss' );
28
29 if ( ! $css_defer ) {
30 return;
31 }
32
33 // add all filters to do CCSS
34 // Set AO behavior: disable minification to avoid double minifying and caching.
35 add_filter( 'autoptimize_filter_css_critcss_minify', '__return_false' );
36 add_filter( 'autoptimize_filter_css_defer_inline', array( $this, 'ao_ccss_frontend' ), 10, 1 );
37
38 // Add the action to enqueue jobs for CriticalCSS cron.
39 if ( $this->criticalcss->is_api_active() ) {
40 add_action( 'autoptimize_action_css_hash', array( $this->criticalcss, 'enqueue' ), 10, 1 );
41 }
42
43 // conditionally add the filter to defer jquery and others but only if not done so in autoptimizeScripts.
44 $_native_defer = false;
45 if ( 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_js_defer_not_aggregate' ) && 'on' === autoptimizeOptionWrapper::get_option( 'autoptimize_js_defer_inline' ) ) {
46 $_native_defer = true;
47 }
48 if ( $deferjquery && ! $_native_defer ) {
49 add_filter( 'autoptimize_html_after_minify', array( $this, 'ao_ccss_defer_jquery' ), 11, 1 );
50 }
51
52 // conditionally add filter to unload the CCSS.
53 if ( $unloadccss ) {
54 add_filter( 'autoptimize_html_after_minify', array( $this, 'ao_ccss_unloadccss' ), 12, 1 );
55 }
56
57 // Order paths by length, as longest ones have greater priority in the rules.
58 $rules = $this->criticalcss->get_option( 'rules' );
59 if ( ! empty( $rules['paths'] ) ) {
60 $keys = array_map( 'strlen', array_keys( $rules['paths'] ) );
61 array_multisort( $keys, SORT_DESC, $rules['paths'] );
62 // TODO: Not sure what we're doing here. Sorted the $keys,
63 // but they don't seem to be used anywhere.
64 }
65
66 // Add an array with default WordPress's conditional tags
67 // NOTE: these tags are sorted.
68 $this->_types = $this->get_ao_ccss_core_types();
69
70 // Extend conditional tags on plugin initalization.
71 add_action( apply_filters( 'autoptimize_filter_ccss_extend_types_hook', 'init' ), array( $this, 'ao_ccss_extend_types' ) );
72
73 // When autoptimize cache is cleared, also clear transient cache for page templates.
74 add_action( 'autoptimize_action_cachepurged', array( $this, 'ao_ccss_clear_page_tpl_cache' ), 10, 0 );
75 }
76
77 public function ao_ccss_frontend( $inlined ) {
78 // Apply CriticalCSS to frontend pages
79 // Attach types and settings arrays.
80 $rules = $this->criticalcss->get_option( 'rules' );
81 $additional = $this->criticalcss->get_option( 'additional' );
82 $loggedin = $this->criticalcss->get_option( 'loggedin' );
83 $debug = $this->criticalcss->get_option( 'debug' );
84 $no_ccss = '';
85 $additional = autoptimizeStyles::sanitize_css( $additional );
86
87 // Only if keystatus is OK and option to add CCSS for logged on users is on or user is not logged in.
88 if ( $loggedin || ! is_user_logged_in() ) {
89 // Check for a valid CriticalCSS based on path to return its contents.
90 $req_path = strtok( $_SERVER['REQUEST_URI'], '?' );
91 if ( ! empty( $rules['paths'] ) ) {
92 foreach ( $rules['paths'] as $path => $rule ) {
93 // explicit match OR partial match if MANUAL rule.
94 if ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && ( $req_path == $path || urldecode( $req_path ) == $path || ( apply_filters( 'autoptimize_filter_ccss_core_path_partial_match', true ) && false == $rule['hash'] && false != $rule['file'] && strpos( $req_path, str_replace( site_url(), '', $path ) ) !== false ) ) ) {
95 if ( file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
96 $_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
97 if ( 'none' != $_ccss_contents ) {
98 if ( $debug ) {
99 $_ccss_contents = '/* PATH: ' . $path . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
100 }
101 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
102 } else {
103 $no_ccss = 'none';
104 }
105 }
106 }
107 }
108 }
109
110 // Check for a valid CriticalCSS based on conditional tags to return its contents.
111 if ( ! empty( $rules['types'] ) && 'none' !== $no_ccss ) {
112 // order types-rules by the order of the original $ao_ccss_types array so as not to depend on the order in which rules were added.
113 $rules['types'] = array_replace( array_intersect_key( array_flip( $this->_types ), $rules['types'] ), $rules['types'] );
114 $is_front_page = is_front_page();
115
116 foreach ( $rules['types'] as $type => $rule ) {
117 if ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && in_array( $type, $this->_types ) && file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
118 $_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
119 if ( $is_front_page && 'is_front_page' == $type ) {
120 if ( 'none' != $_ccss_contents ) {
121 if ( $debug ) {
122 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
123 }
124 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
125 } else {
126 $no_ccss = 'none';
127 }
128 } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && strpos( $type, 'custom_post_' ) === 0 && ! $is_front_page ) {
129 if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
130 if ( 'none' != $_ccss_contents ) {
131 if ( $debug ) {
132 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
133 }
134 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
135 } else {
136 $no_ccss = 'none';
137 }
138 }
139 } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && 0 === strpos( $type, 'template_' ) && ! $is_front_page ) {
140 if ( is_page_template( substr( $type, 9 ) ) ) {
141 if ( 'none' != $_ccss_contents ) {
142 if ( $debug ) {
143 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
144 }
145 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
146 } else {
147 $no_ccss = 'none';
148 }
149 }
150 } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && ! $is_front_page ) {
151 // all "normal" conditional tags, core + woo + buddypress + edd + bbpress
152 // but we have to remove the prefix for the non-core ones for them to function.
153 $type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
154 if ( function_exists( $type ) && call_user_func( $type ) ) {
155 if ( 'none' != $_ccss_contents ) {
156 if ( $debug ) {
157 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
158 }
159 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
160 } else {
161 $no_ccss = 'none';
162 }
163 }
164 }
165 }
166 }
167 }
168 }
169
170 // Finally, inline the default CriticalCSS if any or else the entire CSS for the page
171 // This also applies to logged in users if the option to add CCSS for logged in users has been disabled.
172 if ( ! empty( $inlined ) && 'none' !== $no_ccss ) {
173 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $inlined . $additional );
174 } else {
175 add_filter( 'autoptimize_filter_css_inline', '__return_true' );
176 return;
177 }
178 }
179
180 public function ao_ccss_defer_jquery( $in ) {
181 $loggedin = $this->criticalcss->get_option( 'loggedin' );
182
183 // defer all linked and inline JS.
184 if ( ( ! is_user_logged_in() || $loggedin ) && preg_match_all( '#<script.*>(.*)</script>#Usmi', $in, $matches, PREG_SET_ORDER ) ) {
185 foreach ( $matches as $match ) {
186 if ( str_replace( apply_filters( 'autoptimize_filter_ccss_core_defer_exclude', array( 'data-noptimize="1"', 'data-cfasync="false"', 'data-pagespeed-no-defer' ) ), '', $match[0] ) !== $match[0] ) {
187 // do not touch JS with noptimize/ cfasync/ pagespeed-no-defer flags.
188 continue;
189 } elseif ( '' !== $match[1] && ( ! preg_match( '/<script.* type\s?=.*>/', $match[0] ) || preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $match[0] ) ) ) {
190 // base64-encode and defer all inline JS.
191 $base64_js = '<script defer src="data:text/javascript;base64,' . base64_encode( $match[1] ) . '"></script>';
192 $in = str_replace( $match[0], $base64_js, $in );
193 } elseif ( str_replace( array( ' defer', ' async' ), '', $match[0] ) === $match[0] ) {
194 // and defer linked JS unless already deferred or asynced.
195 $new_match = str_replace( '<script ', '<script defer ', $match[0] );
196 $in = str_replace( $match[0], $new_match, $in );
197 }
198 }
199 }
200 return $in;
201 }
202
203 public function ao_ccss_unloadccss( $html_in ) {
204 // set media attrib of inline CCSS to none at onLoad to avoid it impacting full CSS (rarely needed).
205 $_unloadccss_js = apply_filters( 'autoptimize_filter_ccss_core_unloadccss_js', '<script>window.addEventListener("load", function(event) {var el = document.getElementById("aoatfcss"); if(el) el.media = "none";})</script>' );
206
207 if ( false !== strpos( $html_in, $_unloadccss_js . '</body>' ) ) {
208 return $html_in;
209 }
210
211 return str_replace( '</body>', $_unloadccss_js . '</body>', $html_in );
212 }
213
214 /**
215 * Get the types array.
216 *
217 * @return array|null
218 */
219 public function get_types() {
220 return $this->_types;
221 }
222
223 public function ao_ccss_extend_types() {
224 // Extend contidional tags
225 // Attach the conditional tags array.
226
227 // in some cases $ao_ccss_types is empty and/or not an array, this should work around that problem.
228 if ( empty( $this->_types ) || ! is_array( $this->_types ) ) {
229 $this->_types = $this->get_ao_ccss_core_types();
230 $this->ao_ccss_log( 'Empty types array in extend, refetching array with core conditionals.', 3 );
231 }
232
233 // Custom Post Types.
234 $cpts = get_post_types(
235 array(
236 'public' => true,
237 '_builtin' => false,
238 ),
239 'names',
240 'and'
241 );
242 foreach ( $cpts as $cpt ) {
243 array_unshift( $this->_types, 'custom_post_' . $cpt );
244 }
245
246 // Templates.
247 // Transient cache to avoid frequent disk reads.
248 $templates = get_transient( 'autoptimize_ccss_page_templates' );
249 if ( ! $templates ) {
250 $templates = wp_get_theme()->get_page_templates();
251 set_transient( 'autoptimize_ccss_page_templates', $templates, HOUR_IN_SECONDS );
252 }
253 foreach ( $templates as $tplfile => $tplname ) {
254 array_unshift( $this->_types, 'template_' . $tplfile );
255 }
256
257 // bbPress tags.
258 if ( function_exists( 'is_bbpress' ) ) {
259 $this->_types = array_merge(
260 array(
261 'bbp_is_bbpress',
262 'bbp_is_favorites',
263 'bbp_is_forum_archive',
264 'bbp_is_replies_created',
265 'bbp_is_reply_edit',
266 'bbp_is_reply_move',
267 'bbp_is_search',
268 'bbp_is_search_results',
269 'bbp_is_single_forum',
270 'bbp_is_single_reply',
271 'bbp_is_single_topic',
272 'bbp_is_single_user',
273 'bbp_is_single_user_edit',
274 'bbp_is_single_view',
275 'bbp_is_subscriptions',
276 'bbp_is_topic_archive',
277 'bbp_is_topic_edit',
278 'bbp_is_topic_merge',
279 'bbp_is_topic_split',
280 'bbp_is_topic_tag',
281 'bbp_is_topic_tag_edit',
282 'bbp_is_topics_created',
283 'bbp_is_user_home',
284 'bbp_is_user_home_edit',
285 ),
286 $this->_types
287 );
288 }
289
290 // BuddyPress tags.
291 if ( function_exists( 'is_buddypress' ) ) {
292 $this->_types = array_merge(
293 array(
294 'bp_is_activation_page',
295 'bp_is_activity',
296 'bp_is_blogs',
297 'bp_is_buddypress',
298 'bp_is_change_avatar',
299 'bp_is_create_blog',
300 'bp_is_friend_requests',
301 'bp_is_friends',
302 'bp_is_friends_activity',
303 'bp_is_friends_screen',
304 'bp_is_group_admin_page',
305 'bp_is_group_create',
306 'bp_is_group_forum',
307 'bp_is_group_forum_topic',
308 'bp_is_group_home',
309 'bp_is_group_invites',
310 'bp_is_group_leave',
311 'bp_is_group_members',
312 'bp_is_group_single',
313 'bp_is_groups',
314 'bp_is_messages',
315 'bp_is_messages_compose_screen',
316 'bp_is_messages_conversation',
317 'bp_is_messages_inbox',
318 'bp_is_messages_sentbox',
319 'bp_is_my_activity',
320 'bp_is_my_blogs',
321 'bp_is_notices',
322 'bp_is_profile_edit',
323 'bp_is_register_page',
324 'bp_is_settings_component',
325 'bp_is_user',
326 'bp_is_user_profile',
327 'bp_is_wire',
328 ),
329 $this->_types
330 );
331 }
332
333 // Easy Digital Downloads (EDD) tags.
334 if ( function_exists( 'edd_is_checkout' ) ) {
335 $this->_types = array_merge(
336 array(
337 'edd_is_checkout',
338 'edd_is_failed_transaction_page',
339 'edd_is_purchase_history_page',
340 'edd_is_success_page',
341 ),
342 $this->_types
343 );
344 }
345
346 // WooCommerce tags.
347 if ( class_exists( 'WooCommerce' ) ) {
348 $this->_types = array_merge(
349 array(
350 'woo_is_account_page',
351 'woo_is_cart',
352 'woo_is_checkout',
353 'woo_is_product',
354 'woo_is_product_category',
355 'woo_is_product_tag',
356 'woo_is_shop',
357 'woo_is_wc_endpoint_url',
358 'woo_is_woocommerce',
359 ),
360 $this->_types
361 );
362 }
363 }
364
365 public function get_ao_ccss_core_types() {
366 return array(
367 'is_404',
368 'is_front_page',
369 'is_home',
370 'is_page',
371 'is_single',
372 'is_category',
373 'is_author',
374 'is_archive',
375 'is_search',
376 'is_attachment',
377 'is_sticky',
378 'is_paged',
379 );
380 }
381
382 public function ao_ccss_key_status( $render ) {
383 // Provide key status
384 // Get key and key status.
385 $key = $this->criticalcss->get_option( 'key' );
386 $key_status = $this->criticalcss->get_option( 'keyst' );
387
388 // Prepare returned variables.
389 $key_return = array();
390 $status = false;
391
392 if ( $key && 2 == $key_status ) {
393 // Key exists and its status is valid.
394 // Set valid key status.
395 $status = 'valid';
396 $status_msg = __( 'Valid' );
397 $color = '#46b450'; // Green.
398 $message = null;
399 } elseif ( $key && 1 == $key_status ) {
400 // Key exists but its validation has failed.
401 // Set invalid key status.
402 $status = 'invalid';
403 $status_msg = __( 'Invalid' );
404 $color = '#dc3232'; // Red.
405 $message = __( 'Your API key is invalid. Please enter a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> key.', 'autoptimize' );
406 } elseif ( $key && ! $key_status ) {
407 // Key exists but it has no valid status yet
408 // Perform key validation.
409 $key_check = $this->ao_ccss_key_validation( $key );
410
411 // Key is valid, set valid status.
412 if ( $key_check ) {
413 $status = 'valid';
414 $status_msg = __( 'Valid' );
415 $color = '#46b450'; // Green.
416 $message = null;
417 } else {
418 // Key is invalid, set invalid status.
419 $status = 'invalid';
420 $status_msg = __( 'Invalid' );
421 $color = '#dc3232'; // Red.
422 if ( get_option( 'autoptimize_ccss_keyst' ) == 1 ) {
423 $message = __( 'Your API key is invalid. Please enter a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> key.', 'autoptimize' );
424 } else {
425 $message = __( 'Something went wrong when checking your API key, make sure you server can communicate with https://criticalcss.com and/ or try again later.', 'autoptimize' );
426 }
427 }
428 } else {
429 // No key nor status
430 // Set no key status.
431 $status = 'nokey';
432 $status_msg = __( 'None' );
433 $color = '#ffb900'; // Yellow.
434 $message = __( 'Please enter a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> API key to start.', 'autoptimize' );
435 }
436
437 // Fill returned values.
438 $key_return['status'] = $status;
439 // Provide rendering information if required.
440 if ( $render ) {
441 $key_return['stmsg'] = $status_msg;
442 $key_return['color'] = $color;
443 $key_return['msg'] = $message;
444 }
445
446 // Return key status.
447 return $key_return;
448 }
449
450 public function ao_ccss_key_validation( $key ) {
451 $noptimize = $this->criticalcss->get_option( 'noptimize' );
452
453 // POST a dummy job to criticalcss.com to check for key validation
454 // Prepare home URL for the request.
455 $src_url = get_home_url();
456
457 // Avoid AO optimizations if required by config or avoid lazyload if lazyload is active in AO.
458 if ( ! empty( $noptimize ) ) {
459 $src_url .= '?ao_noptirocket=1';
460 } elseif ( class_exists( 'autoptimizeImages', false ) && autoptimizeImages::should_lazyload_wrapper() ) {
461 $src_url .= '?ao_nolazy=1';
462 }
463
464 $src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
465
466 // Prepare the request.
467 $url = esc_url_raw( AO_CCSS_API . 'generate' );
468 $args = array(
469 'headers' => apply_filters(
470 'autoptimize_ccss_cron_api_generate_headers',
471 array(
472 'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
473 'Content-type' => 'application/json; charset=utf-8',
474 'Authorization' => 'JWT ' . $key,
475 'Connection' => 'close',
476 )
477 ),
478 // Body must be JSON.
479 'body' => json_encode(
480 apply_filters(
481 'autoptimize_ccss_cron_api_generate_body',
482 array(
483 'url' => $src_url,
484 'aff' => 1,
485 'aocssv' => AO_CCSS_VER,
486 )
487 )
488 ),
489 );
490
491 // Dispatch the request and store its response code.
492 $req = wp_safe_remote_post( $url, $args );
493 $code = wp_remote_retrieve_response_code( $req );
494 $body = json_decode( wp_remote_retrieve_body( $req ), true );
495
496 if ( 200 == $code ) {
497 // Response is OK.
498 // Set key status as valid and log key check.
499 update_option( 'autoptimize_ccss_keyst', 2 );
500 $this->ao_ccss_log( 'criticalcss.com: API key is valid, updating key status', 3 );
501
502 // extract job-id from $body and put it in the queue as a P job
503 // but only if no jobs and no rules!
504 $queue = $this->criticalcss->get_option( 'queue' );
505 $rules = $this->criticalcss->get_option( 'rules' );
506
507 if ( 0 == count( $queue ) && 0 == count( $rules['types'] ) && 0 == count( $rules['paths'] ) ) {
508 if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
509 $jprops['ljid'] = 'firstrun';
510 $jprops['rtarget'] = 'types|is_front_page';
511 $jprops['ptype'] = 'is_front_page';
512 $jprops['hashes'][] = 'dummyhash';
513 $jprops['hash'] = 'dummyhash';
514 $jprops['file'] = null;
515 $jprops['jid'] = $body['job']['id'];
516 $jprops['jqstat'] = $body['job']['status'];
517 $jprops['jrstat'] = null;
518 $jprops['jvstat'] = null;
519 $jprops['jctime'] = microtime( true );
520 $jprops['jftime'] = null;
521 $queue['/'] = $jprops;
522 $queue_raw = json_encode( $queue );
523 update_option( 'autoptimize_ccss_queue', $queue_raw, false );
524 $this->ao_ccss_log( 'Created P job for is_front_page based on API key check response.', 3 );
525 }
526 }
527 return true;
528 } elseif ( 401 == $code ) {
529 // Response is unauthorized
530 // Set key status as invalid and log key check.
531 update_option( 'autoptimize_ccss_keyst', 1 );
532 $this->ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
533 return false;
534 } else {
535 // Response unkown
536 // Log key check attempt.
537 $this->ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
538 if ( ! empty( $body ) ) {
539 $this->ao_ccss_log( print_r( $body, true ), 2 );
540 }
541 if ( is_wp_error( $req ) ) {
542 $this->ao_ccss_log( $req->get_error_message(), 2 );
543 }
544 return false;
545 }
546 }
547
548 public function ao_ccss_viewport() {
549 // Get viewport size
550 // Attach viewport option.
551 $viewport = $this->criticalcss->get_option( 'viewport' );
552
553 return array(
554 'w' => ! empty( $viewport['w'] ) ? $viewport['w'] : '',
555 'h' => ! empty( $viewport['h'] ) ? $viewport['h'] : '',
556 );
557 }
558
559 public function ao_ccss_check_contents( $ccss ) {
560 // Perform basic exploit avoidance and CSS validation.
561 if ( ! empty( $ccss ) ) {
562 // Try to avoid code injection.
563 $blocklist = array( '#!/', 'function(', '<script', '<?php', '</style', ' onload=', ' onerror=', ' onmouse', ' onscroll=', ' onclick=' );
564 foreach ( $blocklist as $blocklisted ) {
565 if ( stripos( $ccss, $blocklisted ) !== false ) {
566 $this->ao_ccss_log( 'Critical CSS received contained blocklisted content.', 2 );
567 return false;
568 }
569 }
570
571 // Check for most basics CSS structures.
572 $needlist = array( '{', '}', ':' );
573 foreach ( $needlist as $needed ) {
574 if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
575 $this->ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
576 return false;
577 }
578 }
579 }
580
581 // Return true if file critical CSS is sane.
582 return true;
583 }
584
585 public function ao_ccss_log( $msg, $lvl ) {
586 // Commom logging facility
587 // Attach debug option.
588 $debug = $this->criticalcss->get_option( 'debug' );
589
590 // Prepare log levels, where accepted $lvl are:
591 // 1: II (for info)
592 // 2: EE (for error)
593 // 3: DD (for debug)
594 // Default: UU (for unkown).
595 $level = false;
596 if ( $debug ) {
597 switch ( $lvl ) {
598 case 1:
599 $level = 'II';
600 break;
601 case 2:
602 $level = 'EE';
603 break;
604 case 3:
605 $level = 'DD';
606 break;
607 default:
608 $level = 'UU';
609 }
610 }
611
612 // Prepare and write a log message if there's a valid level.
613 if ( $level ) {
614
615 // Prepare message.
616 $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>'; // @codingStandardsIgnoreLine
617
618 // Write message to log file.
619 error_log( $message, 3, AO_CCSS_LOG );
620 }
621 }
622
623 public function ao_ccss_clear_page_tpl_cache() {
624 // Clears transient cache for page templates.
625 delete_transient( 'autoptimize_ccss_page_templates' );
626 }
627 }
628