PluginProbe
Autoptimize / 3.1.4
Autoptimize v3.1.4
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.4, at classes/autoptimizeCriticalCSSCore.php

634 lines 28.3 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 if ( true !== autoptimizeUtils::is_local_server( parse_url( $src_url, PHP_URL_HOST ) ) ) {
467 // Prepare the request.
468 $url = esc_url_raw( AO_CCSS_API . 'generate' );
469 $args = array(
470 'headers' => apply_filters(
471 'autoptimize_ccss_cron_api_generate_headers',
472 array(
473 'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
474 'Content-type' => 'application/json; charset=utf-8',
475 'Authorization' => 'JWT ' . $key,
476 'Connection' => 'close',
477 )
478 ),
479 // Body must be JSON.
480 'body' => json_encode(
481 apply_filters(
482 'autoptimize_ccss_cron_api_generate_body',
483 array(
484 'url' => $src_url,
485 'aff' => 1,
486 'aocssv' => AO_CCSS_VER,
487 )
488 ),
489 JSON_UNESCAPED_SLASHES
490 ),
491 );
492
493 // Dispatch the request and store its response code.
494 $req = wp_safe_remote_post( $url, $args );
495 $code = wp_remote_retrieve_response_code( $req );
496 $body = json_decode( wp_remote_retrieve_body( $req ), true );
497
498 if ( 200 == $code ) {
499 // Response is OK.
500 // Set key status as valid and log key check.
501 update_option( 'autoptimize_ccss_keyst', 2 );
502 $this->ao_ccss_log( 'criticalcss.com: API key is valid, updating key status', 3 );
503
504 // extract job-id from $body and put it in the queue as a P job
505 // but only if no jobs and no rules!
506 $queue = $this->criticalcss->get_option( 'queue' );
507 $rules = $this->criticalcss->get_option( 'rules' );
508
509 if ( 0 == count( $queue ) && 0 == count( $rules['types'] ) && 0 == count( $rules['paths'] ) ) {
510 if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
511 $jprops['ljid'] = 'firstrun';
512 $jprops['rtarget'] = 'types|is_front_page';
513 $jprops['ptype'] = 'is_front_page';
514 $jprops['hashes'][] = 'dummyhash';
515 $jprops['hash'] = 'dummyhash';
516 $jprops['file'] = null;
517 $jprops['jid'] = $body['job']['id'];
518 $jprops['jqstat'] = $body['job']['status'];
519 $jprops['jrstat'] = null;
520 $jprops['jvstat'] = null;
521 $jprops['jctime'] = microtime( true );
522 $jprops['jftime'] = null;
523 $queue['/'] = $jprops;
524 $queue_raw = json_encode( $queue );
525 update_option( 'autoptimize_ccss_queue', $queue_raw, false );
526 $this->ao_ccss_log( 'Created P job for is_front_page based on API key check response.', 3 );
527 }
528 }
529 return true;
530 } elseif ( 401 == $code ) {
531 // Response is unauthorized
532 // Set key status as invalid and log key check.
533 update_option( 'autoptimize_ccss_keyst', 1 );
534 $this->ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
535 return false;
536 } else {
537 // Response unkown
538 // Log key check attempt.
539 $this->ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
540 if ( ! empty( $body ) ) {
541 $this->ao_ccss_log( print_r( $body, true ), 2 );
542 }
543 if ( is_wp_error( $req ) ) {
544 $this->ao_ccss_log( $req->get_error_message(), 2 );
545 }
546 return false;
547 }
548 } else {
549 // localhost/ private network server, no API check possible.
550 return false;
551 }
552 }
553
554 public function ao_ccss_viewport() {
555 // Get viewport size
556 // Attach viewport option.
557 $viewport = $this->criticalcss->get_option( 'viewport' );
558
559 return array(
560 'w' => ! empty( $viewport['w'] ) ? $viewport['w'] : '',
561 'h' => ! empty( $viewport['h'] ) ? $viewport['h'] : '',
562 );
563 }
564
565 public function ao_ccss_check_contents( $ccss ) {
566 // Perform basic exploit avoidance and CSS validation.
567 if ( ! empty( $ccss ) ) {
568 // Try to avoid code injection.
569 $blocklist = array( '#!/', 'function(', '<script', '<?php', '</style', ' onload=', ' onerror=', ' onmouse', ' onscroll=', ' onclick=' );
570 foreach ( $blocklist as $blocklisted ) {
571 if ( stripos( $ccss, $blocklisted ) !== false ) {
572 $this->ao_ccss_log( 'Critical CSS received contained blocklisted content.', 2 );
573 return false;
574 }
575 }
576
577 // Check for most basics CSS structures.
578 $needlist = array( '{', '}', ':' );
579 foreach ( $needlist as $needed ) {
580 if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
581 $this->ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
582 return false;
583 }
584 }
585 }
586
587 // Return true if file critical CSS is sane.
588 return true;
589 }
590
591 public function ao_ccss_log( $msg, $lvl ) {
592 // Commom logging facility
593 // Attach debug option.
594 $debug = $this->criticalcss->get_option( 'debug' );
595
596 // Prepare log levels, where accepted $lvl are:
597 // 1: II (for info)
598 // 2: EE (for error)
599 // 3: DD (for debug)
600 // Default: UU (for unkown).
601 $level = false;
602 if ( $debug ) {
603 switch ( $lvl ) {
604 case 1:
605 $level = 'II';
606 break;
607 case 2:
608 $level = 'EE';
609 break;
610 case 3:
611 $level = 'DD';
612 break;
613 default:
614 $level = 'UU';
615 }
616 }
617
618 // Prepare and write a log message if there's a valid level.
619 if ( $level ) {
620
621 // Prepare message.
622 $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>'; // @codingStandardsIgnoreLine
623
624 // Write message to log file.
625 error_log( $message, 3, AO_CCSS_LOG );
626 }
627 }
628
629 public function ao_ccss_clear_page_tpl_cache() {
630 // Clears transient cache for page templates.
631 delete_transient( 'autoptimize_ccss_page_templates' );
632 }
633 }
634