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 +337 -235 2.7.1 → 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 - $req_path = strtok( urldecode( $_SERVER['REQUEST_URI'] ), '?' );
74 - if ( ! empty( $ao_ccss_rules['paths'] ) ) {
75 - foreach ( $ao_ccss_rules['paths'] as $path => $rule ) {
97 + $req_path = strtok( $_SERVER['REQUEST_URI'], '?' );
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 || ( 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,131 +478,140 @@
406 478 return $key_return;
407 479 }
408 480
409 481 public function ao_ccss_key_validation( $key ) {
482 + $noptimize = $this->criticalcss->get_option( 'noptimize' );
483 +
410 484 // POST a dummy job to criticalcss.com to check for key validation
411 485 // Prepare home URL for the request.
412 486 $src_url = get_home_url();
487 +
488 + // Avoid AO optimizations if required by config or avoid lazyload if lazyload is active in AO.
489 + if ( ! empty( $noptimize ) ) {
490 + $src_url .= '/?ao_noptirocket=1';
491 + } elseif ( class_exists( 'autoptimizeImages', false ) && autoptimizeImages::should_lazyload_wrapper() ) {
492 + $src_url .= '/?ao_nolazy=1';
493 + }
494 +
413 495 $src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
414 496
415 - // Prepare the request.
416 - $url = esc_url_raw( AO_CCSS_API . 'generate' );
417 - $args = array(
418 - 'headers' => array(
419 - 'User-Agent' => 'Autoptimize CriticalCSS Power-Up v' . AO_CCSS_VER,
420 - 'Content-type' => 'application/json; charset=utf-8',
421 - 'Authorization' => 'JWT ' . $key,
422 - 'Connection' => 'close',
423 - ),
424 - // Body must be JSON.
425 - 'body' => json_encode(
426 - array(
427 - 'url' => $src_url,
428 - 'aff' => 1,
429 - 'aocssv' => AO_CCSS_VER,
430 - )
431 - ),
432 - );
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',
503 + array(
504 + 'User-Agent' => 'Autoptimize v' . AO_CCSS_VER,
505 + 'Content-type' => 'application/json; charset=utf-8',
506 + 'Authorization' => 'JWT ' . $key,
507 + 'Connection' => 'close',
508 + )
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 + );
433 523
434 - // Dispatch the request and store its response code.
435 - $req = wp_safe_remote_post( $url, $args );
436 - $code = wp_remote_retrieve_response_code( $req );
437 - $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 );
438 528
439 - if ( 200 == $code ) {
440 - // Response is OK.
441 - // Set key status as valid and log key check.
442 - update_option( 'autoptimize_ccss_keyst', 2 );
443 - 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 );
444 534
445 - // extract job-id from $body and put it in the queue as a P job
446 - // but only if no jobs and no rules!
447 - global $ao_ccss_queue;
448 - 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' );
449 539
450 - if ( 0 == count( $ao_ccss_queue ) && 0 == count( $ao_ccss_rules['types'] ) && 0 == count( $ao_ccss_rules['paths'] ) ) {
451 - if ( 'JOB_QUEUED' == $body['job']['status'] || 'JOB_ONGOING' == $body['job']['status'] ) {
452 - $jprops['ljid'] = 'firstrun';
453 - $jprops['rtarget'] = 'types|is_front_page';
454 - $jprops['ptype'] = 'is_front_page';
455 - $jprops['hashes'][] = 'dummyhash';
456 - $jprops['hash'] = 'dummyhash';
457 - $jprops['file'] = null;
458 - $jprops['jid'] = $body['job']['id'];
459 - $jprops['jqstat'] = $body['job']['status'];
460 - $jprops['jrstat'] = null;
461 - $jprops['jvstat'] = null;
462 - $jprops['jctime'] = microtime( true );
463 - $jprops['jftime'] = null;
464 - $ao_ccss_queue['/'] = $jprops;
465 - $ao_ccss_queue_raw = json_encode( $ao_ccss_queue );
466 - update_option( 'autoptimize_ccss_queue', $ao_ccss_queue_raw, false );
467 - 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 + }
468 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;
469 578 }
470 - return true;
471 - } elseif ( 401 == $code ) {
472 - // Response is unauthorized
473 - // Set key status as invalid and log key check.
474 - update_option( 'autoptimize_ccss_keyst', 1 );
475 - autoptimizeCriticalCSSCore::ao_ccss_log( 'criticalcss.com: API key is invalid, updating key status', 3 );
476 - return false;
477 579 } else {
478 - // Response unkown
479 - // Log key check attempt.
480 - autoptimizeCriticalCSSCore::ao_ccss_log( 'criticalcss.com: could not check API key status, this is a service error, body follows if any...', 2 );
481 - if ( ! empty( $body ) ) {
482 - autoptimizeCriticalCSSCore::ao_ccss_log( print_r( $body, true ), 2 );
483 - }
484 - if ( is_wp_error( $req ) ) {
485 - autoptimizeCriticalCSSCore::ao_ccss_log( $req->get_error_message(), 2 );
486 - }
580 + // localhost/ private network server, no API check possible.
487 581 return false;
488 582 }
489 583 }
490 584
491 - public static function ao_ccss_viewport() {
585 + public function ao_ccss_viewport() {
492 586 // Get viewport size
493 587 // Attach viewport option.
494 - global $ao_ccss_viewport;
588 + $viewport = $this->criticalcss->get_option( 'viewport' );
495 589
496 - // Prepare viewport array.
497 - $viewport = array();
498 -
499 - // Viewport Width.
500 - if ( ! empty( $ao_ccss_viewport['w'] ) ) {
501 - $viewport['w'] = $ao_ccss_viewport['w'];
502 - } else {
503 - $viewport['w'] = '';
504 - }
505 -
506 - // Viewport Height.
507 - if ( ! empty( $ao_ccss_viewport['h'] ) ) {
508 - $viewport['h'] = $ao_ccss_viewport['h'];
509 - } else {
510 - $viewport['h'] = '';
511 - }
512 -
513 - return $viewport;
590 + return array(
591 + 'w' => ! empty( $viewport['w'] ) ? $viewport['w'] : '',
592 + 'h' => ! empty( $viewport['h'] ) ? $viewport['h'] : '',
593 + );
514 594 }
515 595
516 - public static function ao_ccss_check_contents( $ccss ) {
596 + public function ao_ccss_check_contents( $ccss ) {
517 597 // Perform basic exploit avoidance and CSS validation.
598 + // todo; use autoptimizeStyles::check_css which is way stricter? or could that break things??
518 599 if ( ! empty( $ccss ) ) {
519 600 // Try to avoid code injection.
520 - $blacklist = array( '#!/', 'function(', '<script', '<?php' );
521 - foreach ( $blacklist as $blacklisted ) {
522 - if ( strpos( $ccss, $blacklisted ) !== false ) {
523 - 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 );
524 605 return false;
525 606 }
526 607 }
527 608
528 609 // Check for most basics CSS structures.
529 - $pinklist = array( '{', '}', ':' );
530 - foreach ( $pinklist as $needed ) {
610 + $needlist = array( '{', '}', ':' );
611 + foreach ( $needlist as $needed ) {
531 612 if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
532 - 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 );
533 614 return false;
534 615 }
535 616 }
536 617 }
@@ -538,12 +619,12 @@
538 619 // Return true if file critical CSS is sane.
539 620 return true;
540 621 }
541 622
542 - public static function ao_ccss_log( $msg, $lvl ) {
623 + public function ao_ccss_log( $msg, $lvl ) {
543 624 // Commom logging facility
544 625 // Attach debug option.
545 - global $ao_ccss_debug;
626 + $debug = $this->criticalcss->get_option( 'debug' );
546 627
547 628 // Prepare log levels, where accepted $lvl are:
548 629 // 1: II (for info)
549 630 // 2: EE (for error)
@@ -549,23 +630,22 @@
549 630 // 2: EE (for error)
550 631 // 3: DD (for debug)
551 632 // Default: UU (for unkown).
552 633 $level = false;
553 - switch ( $lvl ) {
554 - case 1:
555 - $level = 'II';
556 - break;
557 - case 2:
558 - $level = 'EE';
559 - break;
560 - case 3:
561 - // Output debug messages only if debug mode is enabled.
562 - 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:
563 643 $level = 'DD';
564 - }
565 - break;
566 - default:
567 - $level = 'UU';
644 + break;
645 + default:
646 + $level = 'UU';
647 + }
568 648 }
569 649
570 650 // Prepare and write a log message if there's a valid level.
571 651 if ( $level ) {
@@ -570,11 +650,33 @@
570 650 // Prepare and write a log message if there's a valid level.
571 651 if ( $level ) {
572 652
573 653 // Prepare message.
574 - $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>';
654 + $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>'; // @codingStandardsIgnoreLine
575 655
576 656 // Write message to log file.
577 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;
578 680 }
579 681 }
580 682 }