PluginProbe
Autoptimize / 3.1.8
Autoptimize v3.1.8
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.8, at classes/autoptimizeCriticalCSSCore.php

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