PluginProbe
Autoptimize / trunk
Autoptimize vtrunk
3.1.16 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 All 108 releases
← All changes | classes/autoptimizeCriticalCSSCore.php +328 -238 2.7.2 → trunk View file →
@@ -8,82 +8,109 @@
8 8 exit;
9 9 }
10 10
11 11 class autoptimizeCriticalCSSCore {
12 - public function __construct()
13 - {
14 - // fetch all options at once and populate them individually explicitely as globals.
15 - $all_options = autoptimizeCriticalCSSBase::fetch_options();
16 - foreach ( $all_options as $_option => $_value ) {
17 - global ${$_option};
18 - ${$_option} = $_value;
19 - }
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;
20 25
26 + public function __construct() {
27 + $this->criticalcss = autoptimize()->criticalcss();
21 28 $this->run();
22 29 }
23 30
24 31 public function run() {
25 - global $ao_css_defer;
26 - global $ao_ccss_deferjquery;
27 - global $ao_ccss_key;
32 + $css_defer = $this->criticalcss->get_option( 'css_defer' );
33 + $deferjquery = $this->criticalcss->get_option( 'deferjquery' );
34 + $unloadccss = $this->criticalcss->get_option( 'unloadccss' );
28 35
29 - // add all filters to do CCSS if key present.
30 - if ( $ao_css_defer && isset( $ao_ccss_key ) && ! empty( $ao_ccss_key ) ) {
31 - // Set AO behavior: disable minification to avoid double minifying and caching.
32 - add_filter( 'autoptimize_filter_css_critcss_minify', '__return_false' );
33 - add_filter( 'autoptimize_filter_css_defer_inline', array( $this, 'ao_ccss_frontend' ), 10, 1 );
36 + if ( ! $css_defer ) {
37 + return;
38 + }
34 39
35 - // Add the action to enqueue jobs for CriticalCSS cron.
36 - add_action( 'autoptimize_action_css_hash', array( 'autoptimizeCriticalCSSEnqueue', 'ao_ccss_enqueue' ), 10, 1 );
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 );
37 44
38 - // conditionally add the filter to defer jquery and others.
39 - if ( $ao_ccss_deferjquery ) {
40 - add_filter( 'autoptimize_html_after_minify', array( $this, 'ao_ccss_defer_jquery' ), 11, 1 );
41 - }
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 + }
42 49
43 - // Order paths by length, as longest ones have greater priority in the rules.
44 - if ( ! empty( $ao_ccss_rules['paths'] ) ) {
45 - $keys = array_map( 'strlen', array_keys( $ao_ccss_rules['paths'] ) );
46 - array_multisort( $keys, SORT_DESC, $ao_ccss_rules['paths'] );
47 - }
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 + }
48 58
49 - // Add an array with default WordPress's conditional tags
50 - // NOTE: these tags are sorted.
51 - global $ao_ccss_types;
52 - $ao_ccss_types = $this->get_ao_ccss_core_types();
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 + }
53 63
54 - // Extend conditional tags on plugin initalization.
55 - add_action( apply_filters( 'autoptimize_filter_ccss_extend_types_hook', 'init' ), array( $this, 'ao_ccss_extend_types' ) );
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.
56 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 );
57 82 }
58 83
59 84 public function ao_ccss_frontend( $inlined ) {
60 85 // Apply CriticalCSS to frontend pages
61 86 // Attach types and settings arrays.
62 - global $ao_ccss_types;
63 - global $ao_ccss_rules;
64 - global $ao_ccss_additional;
65 - global $ao_ccss_loggedin;
66 - global $ao_ccss_debug;
67 - global $ao_ccss_keyst;
68 - $no_ccss = '';
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 );
69 93
70 94 // Only if keystatus is OK and option to add CCSS for logged on users is on or user is not logged in.
71 - if ( ( $ao_ccss_keyst && 2 == $ao_ccss_keyst ) && ( $ao_ccss_loggedin || ! is_user_logged_in() ) ) {
95 + if ( $loggedin || ! is_user_logged_in() ) {
72 96 // Check for a valid CriticalCSS based on path to return its contents.
73 97 $req_path = strtok( $_SERVER['REQUEST_URI'], '?' );
74 - if ( ! empty( $ao_ccss_rules['paths'] ) ) {
75 - foreach ( $ao_ccss_rules['paths'] as $path => $rule ) {
98 + if ( ! empty( $rules['paths'] ) ) {
99 + foreach ( $rules['paths'] as $path => $rule ) {
76 100 // explicit match OR partial match if MANUAL rule.
77 - if ( $req_path == $path || urldecode( $req_path ) == $path || ( false == $rule['hash'] && false != $rule['file'] && strpos( $req_path, str_replace( site_url(), '', $path ) ) !== false ) ) {
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 ) ) ) {
78 102 if ( file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
79 - $_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
103 + $_ccss_contents = $this->ao_ccss_file_get_contents( AO_CCSS_DIR . $rule['file'] );
80 104 if ( 'none' != $_ccss_contents ) {
81 - if ( $ao_ccss_debug ) {
105 + if ( $debug ) {
82 106 $_ccss_contents = '/* PATH: ' . $path . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
83 107 }
84 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
108 + return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
85 109 } else {
110 + if ( $debug ) {
111 + $this->criticalcss->log( 'Path based rule with value "none" found.', 3 );
112 + }
86 113 $no_ccss = 'none';
87 114 }
88 115 }
89 116 }
@@ -90,58 +117,70 @@
90 117 }
91 118 }
92 119
93 120 // Check for a valid CriticalCSS based on conditional tags to return its contents.
94 - if ( ! empty( $ao_ccss_rules['types'] ) && 'none' !== $no_ccss ) {
121 + if ( ! empty( $rules['types'] ) && 'none' !== $no_ccss ) {
95 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.
96 - $ao_ccss_rules['types'] = array_replace( array_intersect_key( array_flip( $ao_ccss_types ), $ao_ccss_rules['types'] ), $ao_ccss_rules['types'] );
97 - $is_front_page = is_front_page();
123 + $rules['types'] = array_replace( array_intersect_key( array_flip( $this->_types ), $rules['types'] ), $rules['types'] );
124 + $is_front_page = is_front_page();
98 125
99 - foreach ( $ao_ccss_rules['types'] as $type => $rule ) {
100 - if ( in_array( $type, $ao_ccss_types ) && file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
101 - $_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
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 = $this->ao_ccss_file_get_contents( AO_CCSS_DIR . $rule['file'] );
102 129 if ( $is_front_page && 'is_front_page' == $type ) {
103 130 if ( 'none' != $_ccss_contents ) {
104 - if ( $ao_ccss_debug ) {
131 + if ( $debug ) {
105 132 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
106 133 }
107 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
134 + return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
108 135 } else {
136 + if ( $debug ) {
137 + $this->criticalcss->log( 'Conditional rule for is_front_page with value "none" found.', 3 );
138 + }
109 139 $no_ccss = 'none';
110 140 }
111 - } elseif ( strpos( $type, 'custom_post_' ) === 0 && ! $is_front_page ) {
141 + } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && strpos( $type, 'custom_post_' ) === 0 && ! $is_front_page ) {
112 142 if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
113 143 if ( 'none' != $_ccss_contents ) {
114 - if ( $ao_ccss_debug ) {
144 + if ( $debug ) {
115 145 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
116 146 }
117 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
147 + return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
118 148 } else {
149 + if ( $debug ) {
150 + $this->criticalcss->log( 'Conditional rule custom_post with value "none" found.', 3 );
151 + }
119 152 $no_ccss = 'none';
120 153 }
121 154 }
122 - } elseif ( 0 === strpos( $type, 'template_' ) && ! $is_front_page ) {
155 + } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && 0 === strpos( $type, 'template_' ) && ! $is_front_page ) {
123 156 if ( is_page_template( substr( $type, 9 ) ) ) {
124 157 if ( 'none' != $_ccss_contents ) {
125 - if ( $ao_ccss_debug ) {
158 + if ( $debug ) {
126 159 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
127 160 }
128 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
161 + return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
129 162 } else {
163 + if ( $debug ) {
164 + $this->criticalcss->log( 'Conditional rule for template with value "none" found.', 3 );
165 + }
130 166 $no_ccss = 'none';
131 167 }
132 168 }
133 - } elseif ( ! $is_front_page ) {
169 + } elseif ( ( $this->criticalcss->is_api_active() || $this->criticalcss->is_rule_manual( $rule ) ) && ! $is_front_page ) {
134 170 // all "normal" conditional tags, core + woo + buddypress + edd + bbpress
135 171 // but we have to remove the prefix for the non-core ones for them to function.
136 172 $type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
137 173 if ( function_exists( $type ) && call_user_func( $type ) ) {
138 174 if ( 'none' != $_ccss_contents ) {
139 - if ( $ao_ccss_debug ) {
175 + if ( $debug ) {
140 176 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
141 177 }
142 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
178 + return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $additional );
143 179 } else {
180 + if ( $debug ) {
181 + $this->criticalcss->log( 'Conditional rule for ' . $type . ' with value "none" found.', 3 );
182 + }
144 183 $no_ccss = 'none';
145 184 }
146 185 }
147 186 }
@@ -152,10 +191,16 @@
152 191
153 192 // Finally, inline the default CriticalCSS if any or else the entire CSS for the page
154 193 // This also applies to logged in users if the option to add CCSS for logged in users has been disabled.
155 194 if ( ! empty( $inlined ) && 'none' !== $no_ccss ) {
156 - return apply_filters( 'autoptimize_filter_ccss_core_ccss', $inlined . $ao_ccss_additional );
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 );
157 199 } else {
200 + if ( $debug ) {
201 + $this->criticalcss->log( 'No matching CCSS found, switching to inlining full CSS.', 3 );
202 + }
158 203 add_filter( 'autoptimize_filter_css_inline', '__return_true' );
159 204 return;
160 205 }
161 206 }
@@ -160,17 +205,22 @@
160 205 }
161 206 }
162 207
163 208 public function ao_ccss_defer_jquery( $in ) {
164 - // try to defer all JS (main goal being jquery.js as AO by default does not aggregate that).
165 - if ( ( ! is_user_logged_in() || $ao_ccss_loggedin ) && preg_match_all( '#<script.*>(.*)</script>#Usmi', $in, $matches, PREG_SET_ORDER ) ) {
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 ) ) {
166 213 foreach ( $matches as $match ) {
167 - if ( ( ! preg_match( '/<script.* type\s?=.*>/', $match[0] ) || preg_match( '/type\s*=\s*[\'"]?(?:text|application)\/(?:javascript|ecmascript)[\'"]?/i', $match[0] ) ) && '' !== $match[1] && ( false !== strpos( $match[1], 'jQuery' ) || false !== strpos( $match[1], '$' ) ) ) {
168 - // inline js that requires jquery, wrap deferring JS around it to defer it.
169 - $new_match = 'var aoDeferInlineJQuery=function(){' . $match[1] . '}; if (document.readyState === "loading") {document.addEventListener("DOMContentLoaded", aoDeferInlineJQuery);} else {aoDeferInlineJQuery();}';
170 - $in = str_replace( $match[1], $new_match, $in );
171 - } elseif ( '' === $match[1] && false !== strpos( $match[0], 'src=' ) && false === strpos( $match[0], 'defer' ) ) {
172 - // linked non-aggregated JS, defer it.
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.
173 223 $new_match = str_replace( '<script ', '<script defer ', $match[0] );
174 224 $in = str_replace( $match[0], $new_match, $in );
175 225 }
176 226 }
@@ -177,17 +227,36 @@
177 227 }
178 228 return $in;
179 229 }
180 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 +
181 251 public function ao_ccss_extend_types() {
182 252 // Extend contidional tags
183 253 // Attach the conditional tags array.
184 - global $ao_ccss_types;
185 254
186 255 // in some cases $ao_ccss_types is empty and/or not an array, this should work around that problem.
187 - if ( empty( $ao_ccss_types ) || ! is_array( $ao_ccss_types ) ) {
188 - $ao_ccss_types = get_ao_ccss_core_types();
189 - autoptimizeCriticalCSSCore::ao_ccss_log( 'Empty types array in extend, refetching array with core conditionals.', 3 );
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 );
190 259 }
191 260
192 261 // Custom Post Types.
193 262 $cpts = get_post_types(
@@ -198,20 +267,25 @@
198 267 'names',
199 268 'and'
200 269 );
201 270 foreach ( $cpts as $cpt ) {
202 - array_unshift( $ao_ccss_types, 'custom_post_' . $cpt );
271 + array_unshift( $this->_types, 'custom_post_' . $cpt );
203 272 }
204 273
205 274 // Templates.
206 - $templates = wp_get_theme()->get_page_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 + }
207 281 foreach ( $templates as $tplfile => $tplname ) {
208 - array_unshift( $ao_ccss_types, 'template_' . $tplfile );
282 + array_unshift( $this->_types, 'template_' . $tplfile );
209 283 }
210 284
211 285 // bbPress tags.
212 286 if ( function_exists( 'is_bbpress' ) ) {
213 - $ao_ccss_types = array_merge(
287 + $this->_types = array_merge(
214 288 array(
215 289 'bbp_is_bbpress',
216 290 'bbp_is_favorites',
217 291 'bbp_is_forum_archive',
@@ -235,15 +309,16 @@
235 309 'bbp_is_topic_tag_edit',
236 310 'bbp_is_topics_created',
237 311 'bbp_is_user_home',
238 312 'bbp_is_user_home_edit',
239 - ), $ao_ccss_types
313 + ),
314 + $this->_types
240 315 );
241 316 }
242 317
243 318 // BuddyPress tags.
244 319 if ( function_exists( 'is_buddypress' ) ) {
245 - $ao_ccss_types = array_merge(
320 + $this->_types = array_merge(
246 321 array(
247 322 'bp_is_activation_page',
248 323 'bp_is_activity',
249 324 'bp_is_blogs',
@@ -277,27 +352,29 @@
277 352 'bp_is_settings_component',
278 353 'bp_is_user',
279 354 'bp_is_user_profile',
280 355 'bp_is_wire',
281 - ), $ao_ccss_types
356 + ),
357 + $this->_types
282 358 );
283 359 }
284 360
285 361 // Easy Digital Downloads (EDD) tags.
286 362 if ( function_exists( 'edd_is_checkout' ) ) {
287 - $ao_ccss_types = array_merge(
363 + $this->_types = array_merge(
288 364 array(
289 365 'edd_is_checkout',
290 366 'edd_is_failed_transaction_page',
291 367 'edd_is_purchase_history_page',
292 368 'edd_is_success_page',
293 - ), $ao_ccss_types
369 + ),
370 + $this->_types
294 371 );
295 372 }
296 373
297 374 // WooCommerce tags.
298 375 if ( class_exists( 'WooCommerce' ) ) {
299 - $ao_ccss_types = array_merge(
376 + $this->_types = array_merge(
300 377 array(
301 378 'woo_is_account_page',
302 379 'woo_is_cart',
303 380 'woo_is_checkout',
@@ -306,44 +383,36 @@
306 383 'woo_is_product_tag',
307 384 'woo_is_shop',
308 385 'woo_is_wc_endpoint_url',
309 386 'woo_is_woocommerce',
310 - ), $ao_ccss_types
387 + ),
388 + $this->_types
311 389 );
312 390 }
313 391 }
314 392
315 393 public function get_ao_ccss_core_types() {
316 - global $ao_ccss_types;
317 - if ( empty( $ao_ccss_types ) || ! is_array( $ao_ccss_types ) ) {
318 - return array(
319 - 'is_404',
320 - 'is_archive',
321 - 'is_author',
322 - 'is_category',
323 - 'is_front_page',
324 - 'is_home',
325 - 'is_page',
326 - 'is_post',
327 - 'is_search',
328 - 'is_attachment',
329 - 'is_single',
330 - 'is_sticky',
331 - 'is_paged',
332 - );
333 - } else {
334 - return $ao_ccss_types;
335 - }
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 + );
336 408 }
337 409
338 - public static function ao_ccss_key_status( $render ) {
410 + public function ao_ccss_key_status( $render ) {
339 411 // Provide key status
340 412 // Get key and key status.
341 - global $ao_ccss_key;
342 - global $ao_ccss_keyst;
343 - $self = new self();
344 - $key = $ao_ccss_key;
345 - $key_status = $ao_ccss_keyst;
413 + $key = $this->criticalcss->get_option( 'key' );
414 + $key_status = $this->criticalcss->get_option( 'keyst' );
346 415
347 416 // Prepare returned variables.
348 417 $key_return = array();
349 418 $status = false;
@@ -351,9 +420,9 @@
351 420 if ( $key && 2 == $key_status ) {
352 421 // Key exists and its status is valid.
353 422 // Set valid key status.
354 423 $status = 'valid';
355 - $status_msg = __( 'Valid' );
424 + $status_msg = esc_html__( 'Valid' );
356 425 $color = '#46b450'; // Green.
357 426 $message = null;
358 427 } elseif ( $key && 1 == $key_status ) {
359 428 // Key exists but its validation has failed.
@@ -358,31 +427,33 @@
358 427 } elseif ( $key && 1 == $key_status ) {
359 428 // Key exists but its validation has failed.
360 429 // Set invalid key status.
361 430 $status = 'invalid';
362 - $status_msg = __( 'Invalid' );
431 + $status_msg = esc_html__( 'Invalid' );
363 432 $color = '#dc3232'; // Red.
364 - $message = __( 'Your API key is invalid. Please enter a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> key.', 'autoptimize' );
433 + // Translators: link to criticalcss.com page.
434 + $message = sprintf( esc_html__( 'Your API key is invalid. Please enter a valid %1$scriticalcss.com%2$s key.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
365 435 } elseif ( $key && ! $key_status ) {
366 436 // Key exists but it has no valid status yet
367 437 // Perform key validation.
368 - $key_check = $self->ao_ccss_key_validation( $key );
438 + $key_check = $this->ao_ccss_key_validation( $key );
369 439
370 440 // Key is valid, set valid status.
371 441 if ( $key_check ) {
372 442 $status = 'valid';
373 - $status_msg = __( 'Valid' );
443 + $status_msg = esc_html__( 'Valid' );
374 444 $color = '#46b450'; // Green.
375 445 $message = null;
376 446 } else {
377 447 // Key is invalid, set invalid status.
378 448 $status = 'invalid';
379 - $status_msg = __( 'Invalid' );
449 + $status_msg = esc_html__( 'Invalid' );
380 450 $color = '#dc3232'; // Red.
381 451 if ( get_option( 'autoptimize_ccss_keyst' ) == 1 ) {
382 - $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 + // Translators: link to criticalcss.com page.
453 + $message = sprintf( esc_html__( 'Your API key is invalid. Please enter a valid %1$scriticalcss.com%2$s key.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
383 454 } else {
384 - $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' );
455 + $message = esc_html__( 'Something went wrong when checking your API key, make sure you server can communicate with https://criticalcss.com and/ or try again later.', 'autoptimize' );
385 456 }
386 457 }
387 458 } else {
388 459 // No key nor status
@@ -387,11 +458,12 @@
387 458 } else {
388 459 // No key nor status
389 460 // Set no key status.
390 461 $status = 'nokey';
391 - $status_msg = __( 'None' );
462 + $status_msg = esc_html__( 'None' );
392 463 $color = '#ffb900'; // Yellow.
393 - $message = __( 'Please enter a valid <a href="https://criticalcss.com/?aff=1" target="_blank">criticalcss.com</a> API key to start.', 'autoptimize' );
464 + // Translators: link to criticalcss.com page.
465 + $message = sprintf( esc_html__( 'Please enter a valid %1$scriticalcss.com%2$s API key to start.', 'autoptimize' ), '<a href="https://criticalcss.com/?aff=1" target="_blank">', '</a>' );
394 466 }
395 467
396 468 // Fill returned values.
397 469 $key_return['status'] = $status;
@@ -406,9 +478,9 @@
406 478 return $key_return;
407 479 }
408 480
409 481 public function ao_ccss_key_validation( $key ) {
410 - global $ao_ccss_noptimize;
482 + $noptimize = $this->criticalcss->get_option( 'noptimize' );
411 483
412 484 // POST a dummy job to criticalcss.com to check for key validation
413 485 // Prepare home URL for the request.
414 486 $src_url = get_home_url();
@@ -413,136 +485,133 @@
413 485 // Prepare home URL for the request.
414 486 $src_url = get_home_url();
415 487
416 488 // Avoid AO optimizations if required by config or avoid lazyload if lazyload is active in AO.
417 - if ( ! empty( $ao_ccss_noptimize ) ) {
418 - $src_url .= '?ao_noptirocket=1';
489 + if ( ! empty( $noptimize ) ) {
490 + $src_url .= '/?ao_noptirocket=1';
419 491 } elseif ( class_exists( 'autoptimizeImages', false ) && autoptimizeImages::should_lazyload_wrapper() ) {
420 - $src_url .= '?ao_nolazy=1';
492 + $src_url .= '/?ao_nolazy=1';
421 493 }
422 494
423 495 $src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
424 496
425 - // Prepare the request.
426 - $url = esc_url_raw( AO_CCSS_API . 'generate' );
427 - $args = array(
428 - 'headers' => array(
429 - 'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
430 - 'Content-type' => 'application/json; charset=utf-8',
431 - 'Authorization' => 'JWT ' . $key,
432 - 'Connection' => 'close',
433 - ),
434 - // Body must be JSON.
435 - 'body' => json_encode(
436 - apply_filters( 'autoptimize_ccss_cron_api_generate_body',
497 + if ( true !== autoptimizeUtils::is_local_server( parse_url( $src_url, PHP_URL_HOST ) ) ) {
498 + // Prepare the request.
499 + $url = esc_url_raw( AO_CCSS_API . 'generate' );
500 + $args = array(
501 + 'headers' => apply_filters(
502 + 'autoptimize_ccss_cron_api_generate_headers',
437 503 array(
438 - 'url' => $src_url,
439 - 'aff' => 1,
440 - 'aocssv' => AO_CCSS_VER,
504 + 'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
505 + 'Content-type' => 'application/json; charset=utf-8',
506 + 'Authorization' => 'JWT ' . $key,
507 + 'Connection' => 'close',
441 508 )
442 - )
443 - ),
444 - );
509 + ),
510 + // Body must be JSON.
511 + 'body' => json_encode(
512 + apply_filters(
513 + 'autoptimize_ccss_cron_api_generate_body',
514 + array(
515 + 'url' => $src_url,
516 + 'aff' => 1,
517 + 'aocssv' => AO_CCSS_VER,
518 + )
519 + ),
520 + JSON_UNESCAPED_SLASHES
521 + ),
522 + );
445 523
446 - // Dispatch the request and store its response code.
447 - $req = wp_safe_remote_post( $url, $args );
448 - $code = wp_remote_retrieve_response_code( $req );
449 - $body = json_decode( wp_remote_retrieve_body( $req ), true );
524 + // Dispatch the request and store its response code.
525 + $req = wp_safe_remote_post( $url, $args );
526 + $code = wp_remote_retrieve_response_code( $req );
527 + $body = json_decode( wp_remote_retrieve_body( $req ), true );
450 528
451 - if ( 200 == $code ) {
452 - // Response is OK.
453 - // Set key status as valid and log key check.
454 - update_option( 'autoptimize_ccss_keyst', 2 );
455 - autoptimizeCriticalCSSCore::ao_ccss_log( 'criticalcss.com: API key is valid, updating key status', 3 );
529 + if ( 200 == $code ) {
530 + // Response is OK.
531 + // Set key status as valid and log key check.
532 + update_option( 'autoptimize_ccss_keyst', 2 );
533 + $this->ao_ccss_log( 'criticalcss.com: API key is valid, updating key status', 3 );
456 534
457 - // extract job-id from $body and put it in the queue as a P job
458 - // but only if no jobs and no rules!
459 - global $ao_ccss_queue;
460 - global $ao_ccss_rules;
535 + // extract job-id from $body and put it in the queue as a P job
536 + // but only if no jobs and no rules!
537 + $queue = $this->criticalcss->get_option( 'queue' );
538 + $rules = $this->criticalcss->get_option( 'rules' );
461 539
462 - if ( 0 == count( $ao_ccss_queue ) && 0 == count( $ao_ccss_rules['types'] ) && 0 == count( $ao_ccss_rules['paths'] ) ) {
463 - if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
464 - $jprops['ljid'] = 'firstrun';
465 - $jprops['rtarget'] = 'types|is_front_page';
466 - $jprops['ptype'] = 'is_front_page';
467 - $jprops['hashes'][] = 'dummyhash';
468 - $jprops['hash'] = 'dummyhash';
469 - $jprops['file'] = null;
470 - $jprops['jid'] = $body['job']['id'];
471 - $jprops['jqstat'] = $body['job']['status'];
472 - $jprops['jrstat'] = null;
473 - $jprops['jvstat'] = null;
474 - $jprops['jctime'] = microtime( true );
475 - $jprops['jftime'] = null;
476 - $ao_ccss_queue['/'] = $jprops;
477 - $ao_ccss_queue_raw = json_encode( $ao_ccss_queue );
478 - update_option( 'autoptimize_ccss_queue', $ao_ccss_queue_raw, false );
479 - autoptimizeCriticalCSSCore::ao_ccss_log( 'Created P job for is_front_page based on API key check response.', 3 );
540 + if ( ( empty( $queue ) || 0 == count( $queue ) ) && ( empty( $rules ) || ( is_array( $rules ) && 0 == count( $rules['types'] ) && 0 == count( $rules['paths'] ) ) ) ) {
541 + if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
542 + $jprops['ljid'] = 'firstrun';
543 + $jprops['rtarget'] = 'types|is_front_page';
544 + $jprops['ptype'] = 'is_front_page';
545 + $jprops['hashes'][] = 'dummyhash';
546 + $jprops['hash'] = 'dummyhash';
547 + $jprops['file'] = null;
548 + $jprops['jid'] = $body['job']['id'];
549 + $jprops['jqstat'] = $body['job']['status'];
550 + $jprops['jrstat'] = null;
551 + $jprops['jvstat'] = null;
552 + $jprops['jctime'] = microtime( true );
553 + $jprops['jftime'] = null;
554 + $queue['/'] = $jprops;
555 + $queue_raw = json_encode( $queue );
556 + update_option( 'autoptimize_ccss_queue', $queue_raw, false );
557 + $this->ao_ccss_log( 'Created P job for is_front_page based on API key check response.', 3 );
558 + }
480 559 }
560 + return true;
561 + } elseif ( 401 == $code ) {
562 + // Response is unauthorized
563 + // Set key status as invalid and log key check.
564 + update_option( 'autoptimize_ccss_keyst', 1 );
565 + $this->ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
566 + return false;
567 + } else {
568 + // Response unkown
569 + // Log key check attempt.
570 + $this->ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
571 + if ( ! empty( $body ) ) {
572 + $this->ao_ccss_log( print_r( $body, true ), 2 );
573 + }
574 + if ( is_wp_error( $req ) ) {
575 + $this->ao_ccss_log( $req->get_error_message(), 2 );
576 + }
577 + return false;
481 578 }
482 - return true;
483 - } elseif ( 401 == $code ) {
484 - // Response is unauthorized
485 - // Set key status as invalid and log key check.
486 - update_option( 'autoptimize_ccss_keyst', 1 );
487 - autoptimizeCriticalCSSCore::ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
488 - return false;
489 579 } else {
490 - // Response unkown
491 - // Log key check attempt.
492 - autoptimizeCriticalCSSCore::ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
493 - if ( ! empty( $body ) ) {
494 - autoptimizeCriticalCSSCore::ao_ccss_log( print_r( $body, true ), 2 );
495 - }
496 - if ( is_wp_error( $req ) ) {
497 - autoptimizeCriticalCSSCore::ao_ccss_log( $req->get_error_message(), 2 );
498 - }
580 + // localhost/ private network server, no API check possible.
499 581 return false;
500 582 }
501 583 }
502 584
503 - public static function ao_ccss_viewport() {
585 + public function ao_ccss_viewport() {
504 586 // Get viewport size
505 587 // Attach viewport option.
506 - global $ao_ccss_viewport;
588 + $viewport = $this->criticalcss->get_option( 'viewport' );
507 589
508 - // Prepare viewport array.
509 - $viewport = array();
510 -
511 - // Viewport Width.
512 - if ( ! empty( $ao_ccss_viewport['w'] ) ) {
513 - $viewport['w'] = $ao_ccss_viewport['w'];
514 - } else {
515 - $viewport['w'] = '';
516 - }
517 -
518 - // Viewport Height.
519 - if ( ! empty( $ao_ccss_viewport['h'] ) ) {
520 - $viewport['h'] = $ao_ccss_viewport['h'];
521 - } else {
522 - $viewport['h'] = '';
523 - }
524 -
525 - return $viewport;
590 + return array(
591 + 'w' => ! empty( $viewport['w'] ) ? $viewport['w'] : '',
592 + 'h' => ! empty( $viewport['h'] ) ? $viewport['h'] : '',
593 + );
526 594 }
527 595
528 - public static function ao_ccss_check_contents( $ccss ) {
596 + public function ao_ccss_check_contents( $ccss ) {
529 597 // Perform basic exploit avoidance and CSS validation.
598 + // todo; use autoptimizeStyles::check_css which is way stricter? or could that break things??
530 599 if ( ! empty( $ccss ) ) {
531 600 // Try to avoid code injection.
532 - $blacklist = array( '#!/', 'function(', '<script', '<?php' );
533 - foreach ( $blacklist as $blacklisted ) {
534 - if ( strpos( $ccss, $blacklisted ) !== false ) {
535 - autoptimizeCriticalCSSCore::ao_ccss_log( 'Critical CSS received contained blacklisted content.', 2 );
601 + $blocklist = array( '#!/', 'function(', '<script', '<?php', '<?=', '</style', 'onload=', 'onerror=', ' onmouse', 'onscroll=', 'onclick=' );
602 + foreach ( $blocklist as $blocklisted ) {
603 + if ( stripos( $ccss, $blocklisted ) !== false ) {
604 + $this->ao_ccss_log( 'Critical CSS received contained blocklisted content.', 2 );
536 605 return false;
537 606 }
538 607 }
539 608
540 609 // Check for most basics CSS structures.
541 - $pinklist = array( '{', '}', ':' );
542 - foreach ( $pinklist as $needed ) {
610 + $needlist = array( '{', '}', ':' );
611 + foreach ( $needlist as $needed ) {
543 612 if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
544 - autoptimizeCriticalCSSCore::ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
613 + $this->ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
545 614 return false;
546 615 }
547 616 }
548 617 }
@@ -550,12 +619,12 @@
550 619 // Return true if file critical CSS is sane.
551 620 return true;
552 621 }
553 622
554 - public static function ao_ccss_log( $msg, $lvl ) {
623 + public function ao_ccss_log( $msg, $lvl ) {
555 624 // Commom logging facility
556 625 // Attach debug option.
557 - global $ao_ccss_debug;
626 + $debug = $this->criticalcss->get_option( 'debug' );
558 627
559 628 // Prepare log levels, where accepted $lvl are:
560 629 // 1: II (for info)
561 630 // 2: EE (for error)
@@ -561,23 +630,22 @@
561 630 // 2: EE (for error)
562 631 // 3: DD (for debug)
563 632 // Default: UU (for unkown).
564 633 $level = false;
565 - switch ( $lvl ) {
566 - case 1:
567 - $level = 'II';
568 - break;
569 - case 2:
570 - $level = 'EE';
571 - break;
572 - case 3:
573 - // Output debug messages only if debug mode is enabled.
574 - if ( $ao_ccss_debug ) {
634 + if ( $debug ) {
635 + switch ( $lvl ) {
636 + case 1:
637 + $level = 'II';
638 + break;
639 + case 2:
640 + $level = 'EE';
641 + break;
642 + case 3:
575 643 $level = 'DD';
576 - }
577 - break;
578 - default:
579 - $level = 'UU';
644 + break;
645 + default:
646 + $level = 'UU';
647 + }
580 648 }
581 649
582 650 // Prepare and write a log message if there's a valid level.
583 651 if ( $level ) {
@@ -582,11 +650,33 @@
582 650 // Prepare and write a log message if there's a valid level.
583 651 if ( $level ) {
584 652
585 653 // Prepare message.
586 - $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>';
654 + $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>'; // @codingStandardsIgnoreLine
587 655
588 656 // Write message to log file.
589 657 error_log( $message, 3, AO_CCSS_LOG );
658 + }
659 + }
660 +
661 + public function ao_ccss_clear_page_tpl_cache() {
662 + // Clears transient cache for page templates.
663 + delete_transient( 'autoptimize_ccss_page_templates' );
664 + }
665 +
666 + public function ao_ccss_file_get_contents( $critcssfile ) {
667 + // Resolve the path to collapse any traversal attempts like '../'
668 + $requested_path = realpath( $critcssfile );
669 +
670 + // Validate: path must exist, must be a .css file, and must remain inside AO_CCSS_DIR
671 + if ( $requested_path && str_starts_with( $requested_path, AO_CCSS_DIR ) && pathinfo( $requested_path, PATHINFO_EXTENSION ) === 'css' ) {
672 + $_ccss_contents = file_get_contents( $requested_path );
673 +
674 + // Sanitize: if contents looks fishy, strip all tags.
675 + if ( false === $this->criticalcss->check_contents( $_ccss_contents ) ) {
676 + $_ccss_contents = autoptimizeStyles::sanitize_css( $_ccss_contents );
677 + }
678 +
679 + return $_ccss_contents;
590 680 }
591 681 }
592 682 }