PluginProbe
Autoptimize / 3.1.15
Autoptimize v3.1.15
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 / autoptimizeCriticalCSSEnqueue.php

autoptimizeCriticalCSSEnqueue.php in Autoptimize 3.1.15, at classes/autoptimizeCriticalCSSEnqueue.php

336 lines 15.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Critical CSS job enqueue logic.
4 */
5
6 if ( ! defined( 'ABSPATH' ) ) {
7 exit;
8 }
9
10 class autoptimizeCriticalCSSEnqueue {
11 /**
12 * Critical CSS object.
13 *
14 * @var object
15 */
16 protected $criticalcss;
17
18 public function __construct() {
19 $this->criticalcss = autoptimize()->criticalcss();
20 }
21
22 public function ao_ccss_enqueue( $hash = '', $path = '', $type = 'is_page' ) {
23 // Get key status.
24 $key = $this->criticalcss->key_status( false );
25
26 // Queue is available to anyone...
27 $enqueue = true;
28
29 // ... which are not the ones below.
30 if ( true === autoptimizeUtils::is_local_server() ) {
31 $enqueue = false;
32 $this->criticalcss->log( 'cant enqueue as local/ private', 3 );
33 } elseif ( 'nokey' == $key['status'] || 'invalid' == $key['status'] ) {
34 $enqueue = false;
35 $this->criticalcss->log( 'Job queuing is not available: no valid API key found.', 3 );
36 } elseif ( ! empty( $hash ) && ( is_user_logged_in() || is_feed() || is_404() || ( defined( 'DOING_AJAX' ) && DOING_AJAX ) || $this->ao_ccss_ua() || false === apply_filters( 'autoptimize_filter_ccss_enqueue_should_enqueue', true ) ) ) {
37 $enqueue = false;
38 $this->criticalcss->log( 'Job queuing is not available for WordPress\'s logged in users, feeds, error pages, ajax calls or calls from criticalcss.com itself.', 3 );
39 } elseif ( empty( $hash ) && empty( $path ) || ( ( 'is_single' !== $type ) && ( 'is_page' !== $type ) ) ) {
40 $enqueue = false;
41 $this->criticalcss->log( 'Forced job queuing failed, no path or not right type', 3 );
42 }
43
44 if ( ! $enqueue ) {
45 return;
46 }
47
48 // Continue if queue is available
49 // Attach required arrays/ vars.
50 $rules = $this->criticalcss->get_option( 'rules' );
51 $queue_raw = $this->criticalcss->get_option( 'queue_raw' );
52 $queue = $this->criticalcss->get_option( 'queue' );
53 $forcepath = $this->criticalcss->get_option( 'forcepath' );
54
55 // Get request path and page type, and initialize the queue update flag.
56 if ( ! empty( $hash ) ) {
57 $req_orig = $_SERVER['REQUEST_URI'];
58 $req_type = $this->ao_ccss_get_type();
59 } elseif ( ! empty( $path ) ) {
60 $req_orig = $path;
61 if ( '/' === $path ) {
62 $req_type = 'is_front_page';
63 } else {
64 $req_type = $type;
65 }
66 }
67 $req_path = strtok( $req_orig, '?' );
68
69 // now that we really have the path, check if there's no garbage in there (due to some themes serving a non 404 page even if the resource does not exist resulting in all sorts of nonsense rules).
70 if ( true === apply_filters( 'autoptimize_filter_ccss_enqueue_block_garbage' , true ) && str_ireplace( apply_filters( 'autoptimize_filter_ccss_enqueue_blocklist', array( '.php', 'data:text/javascript;base64', '/.', '/null', '.jpeg', '.jpg', '.png' ) ), '', $req_path ) !== $req_path ) {
71 $this->criticalcss->log( 'Job not enqueued looks like the path is just garbage; ' . $req_path, 3 );
72 return;
73 }
74
75 // Check if we have a lang param. we need to keep as WPML can switch languages based on that
76 // and that includes RTL -> LTR so diff. structure, so rules would be RTL vs LTR
77 // but this needs changes in the structure of the rule object so off by default for now
78 // as now this will simply result in conditional rules being overwritten.
79 if ( apply_filters( 'autoptimize_filter_ccss_coreenqueue_honor_lang', false ) && strpos( $req_orig, 'lang=' ) !== false ) {
80 $req_params = strtok( '?' );
81 parse_str( $req_params, $req_params_arr );
82 if ( array_key_exists( 'lang', $req_params_arr ) && ! empty( $req_params_arr['lang'] ) ) {
83 $req_path .= '?lang=' . $req_params_arr['lang'];
84 }
85 }
86
87 $job_qualify = false;
88 $target_rule = false;
89 $rule_properties = false;
90 $queue_update = false;
91
92 // Match for paths in rules.
93 foreach ( $rules['paths'] as $path => $props ) {
94
95 // Prepare rule target and log.
96 $target_rule = 'paths|' . $path;
97 $this->criticalcss->log( 'Qualifying path <' . $req_path . '> for job submission by rule <' . $target_rule . '>', 3 );
98
99 // Path match
100 // -> exact match needed for AUTO rules
101 // -> partial match OK for MANUAL rules (which have empty hash and a file with CCSS).
102 if ( $path === $req_path || ( false == $props['hash'] && false != $props['file'] && preg_match( '|' . $path . '|', $req_path ) ) ) {
103
104 // There's a path match in the rule, so job QUALIFIES with a path rule match.
105 $job_qualify = true;
106 $rule_properties = $props;
107 $this->criticalcss->log( 'Path <' . $req_path . '> QUALIFIED for job submission by rule <' . $target_rule . '>', 3 );
108
109 // Stop processing other path rules.
110 break;
111 }
112 }
113
114 // Match for types in rules if no path rule matches and if we're not enforcing paths.
115 if ( '' !== $hash && ! $job_qualify && ( ! $forcepath || ! in_array( $req_type, apply_filters( 'autoptimize_filter_ccss_coreenqueue_forcepathfortype', array( 'is_page' ) ) ) || ! apply_filters( 'autoptimize_filter_ccss_coreenqueue_ignorealltypes', false ) ) ) {
116 foreach ( $rules['types'] as $type => $props ) {
117
118 // Prepare rule target and log.
119 $target_rule = 'types|' . $type;
120 $this->criticalcss->log( 'Qualifying page type <' . $req_type . '> on path <' . $req_path . '> for job submission by rule <' . $target_rule . '>', 3 );
121
122 if ( $req_type == $type ) {
123 // Type match.
124 // There's a type match in the rule, so job QUALIFIES with a type rule match.
125 $job_qualify = true;
126 $rule_properties = $props;
127 $this->criticalcss->log( 'Page type <' . $req_type . '> on path <' . $req_path . '> QUALIFIED for job submission by rule <' . $target_rule . '>', 3 );
128
129 // Stop processing other type rules.
130 break;
131 }
132 }
133 }
134
135 if ( $job_qualify && ( ( false == $rule_properties['hash'] && false != $rule_properties['file'] ) || strpos( $req_type, 'template_' ) !== false ) ) {
136 // If job qualifies but rule hash is false and file isn't false (MANUAL rule) or if template, job does not qualify despite what previous evaluations says.
137 $job_qualify = false;
138 $this->criticalcss->log( 'Job submission DISQUALIFIED by MANUAL rule <' . $target_rule . '> with hash <' . $rule_properties['hash'] . '> and file <' . $rule_properties['file'] . '>', 3 );
139 } elseif ( ! $job_qualify && empty( $rule_properties ) ) {
140 // But if job does not qualify and rule properties are set, job qualifies as there is no matching rule for it yet
141 // Fill-in the new target rule.
142 $job_qualify = true;
143
144 // Should we switch to path-base AUTO-rules? Conditions:
145 // 1. forcepath option has to be enabled (off by default)
146 // 2. request type should be (by default, but filterable) one of is_page (removed for now: woo_is_product or woo_is_product_category).
147 if ( ( $forcepath && in_array( $req_type, apply_filters( 'autoptimize_filter_ccss_coreenqueue_forcepathfortype', array( 'is_page' ) ) ) ) || apply_filters( 'autoptimize_filter_ccss_coreenqueue_ignorealltypes', false ) || empty( $hash ) ) {
148 if ( '/' !== $req_path ) {
149 $target_rule = 'paths|' . $req_path;
150 } else {
151 // Exception; we don't want a path-based rule for "/" as that messes things up, hard-switch this to a type-based is_front_page rule.
152 $target_rule = 'types|' . 'is_front_page';
153 }
154 } else {
155 $target_rule = 'types|' . $req_type;
156 }
157 $this->criticalcss->log( 'Job submission QUALIFIED by MISSING rule for page type <' . $req_type . '> on path <' . $req_path . '>, new rule target is <' . $target_rule . '>', 3 );
158 } else {
159 // Or just log a job qualified by a matching rule.
160 $this->criticalcss->log( 'Job submission QUALIFIED by AUTO rule <' . $target_rule . '> with hash <' . $rule_properties['hash'] . '> and file <' . $rule_properties['file'] . '>', 3 );
161 }
162
163 // Submit job.
164 if ( $job_qualify ) {
165 if ( ! array_key_exists( $req_path, $queue ) ) {
166 // This is a NEW job
167 // Merge job into the queue.
168 $queue[ $req_path ] = $this->ao_ccss_define_job(
169 $req_path,
170 $target_rule,
171 $req_type,
172 $hash,
173 null,
174 null,
175 null,
176 null,
177 true
178 );
179 // Set update flag.
180 $queue_update = true;
181 } else {
182 // This is an existing job
183 // The job is still NEW, most likely this is extra CSS file for the same page that needs a hash.
184 if ( 'NEW' == $queue[ $req_path ]['jqstat'] ) {
185 // Add hash if it's not already in the job.
186 if ( ! in_array( $hash, $queue[ $req_path ]['hashes'] ) ) {
187 // Push new hash to its array and update flag.
188 $queue_update = array_push( $queue[ $req_path ]['hashes'], $hash );
189
190 // Log job update.
191 $this->criticalcss->log( 'Hashes UPDATED on local job id <' . $queue[ $req_path ]['ljid'] . '>, job status NEW, target rule <' . $queue[ $req_path ]['rtarget'] . '>, hash added: ' . $hash, 3 );
192
193 // Return from here as the hash array is already updated.
194 return true;
195 }
196 } elseif ( 'NEW' != $queue[ $req_path ]['jqstat'] && 'JOB_QUEUED' != $queue[ $req_path ]['jqstat'] && 'JOB_ONGOING' != $queue[ $req_path ]['jqstat'] ) {
197 // Allow requeuing jobs that are not NEW, JOB_QUEUED or JOB_ONGOING
198 // Merge new job keeping some previous job values.
199 $queue[ $req_path ] = $this->ao_ccss_define_job(
200 $req_path,
201 $target_rule,
202 $req_type,
203 $hash,
204 $queue[ $req_path ]['file'],
205 $queue[ $req_path ]['jid'],
206 $queue[ $req_path ]['jrstat'],
207 $queue[ $req_path ]['jvstat'],
208 false
209 );
210 // Set update flag.
211 $queue_update = true;
212 }
213 }
214
215 if ( $queue_update ) {
216 // Persist the job to the queue and return.
217 $queue_raw = json_encode( $queue );
218 update_option( 'autoptimize_ccss_queue', $queue_raw, false );
219 $this->criticalcss->flush_options();
220 return true;
221 } else {
222 // Or just return false if no job was added.
223 $this->criticalcss->log( 'A job for path <' . $req_path . '> already exist with NEW or PENDING status, skipping job creation', 3 );
224 return false;
225 }
226 }
227 }
228
229 public function ao_ccss_get_type() {
230 // Get the type of a page
231 // Attach the conditional tags array.
232 $types = $this->criticalcss->get_types();
233 $forcepath = $this->criticalcss->get_option( 'forcepath' );
234
235 // By default, a page type is false.
236 $page_type = false;
237
238 // Iterates over the array to match a type.
239 foreach ( $types as $type ) {
240 if ( is_404() ) {
241 $page_type = 'is_404';
242 break;
243 } elseif ( is_front_page() ) {
244 // identify frontpage immediately to avoid it also matching a CPT or template.
245 $page_type = 'is_front_page';
246 break;
247 } elseif ( strpos( $type, 'custom_post_' ) !== false && ( ! $forcepath || ! is_page() ) && is_singular() ) {
248 // Match custom post types and not page or page not forced to path-based.
249 if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
250 $page_type = $type;
251 break;
252 }
253 } elseif ( strpos( $type, 'template_' ) !== false && ( ! $forcepath || ! is_page() ) ) {
254 // Match templates if not page or if page is not forced to path-based.
255 if ( is_page_template( substr( $type, 9 ) ) ) {
256 $page_type = $type;
257 break;
258 }
259 } else {
260 // Match all other existing types
261 // but remove prefix to be able to check if the function exists & returns true.
262 $_type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
263 if ( function_exists( $_type ) && call_user_func( $_type ) ) {
264 // Make sure we only return for one page, not for the "paged pages" (/page/2 ..).
265 if ( ! is_page() || ! is_paged() ) {
266 $page_type = $type;
267 break;
268 }
269 }
270 }
271 }
272
273 // Return the page type.
274 return $page_type;
275 }
276
277 public function ao_ccss_define_job( $path, $target, $type, $hash, $file, $jid, $jrstat, $jvstat, $create ) {
278 // Define a job entry to be created or updated
279 // Define commom job properties.
280 $path = array();
281 $path['ljid'] = $this->ao_ccss_job_id();
282 $path['rtarget'] = $target;
283 $path['ptype'] = $type;
284 $path['hashes'] = array( $hash );
285 $path['hash'] = $hash;
286 $path['file'] = $file;
287 $path['jid'] = $jid;
288 $path['jqstat'] = 'NEW';
289 $path['jrstat'] = $jrstat;
290 $path['jvstat'] = $jvstat;
291 $path['jctime'] = microtime( true );
292 $path['jftime'] = null;
293
294 // Set operation requested.
295 if ( $create ) {
296 $operation = 'CREATED';
297 } else {
298 $operation = 'UPDATED';
299 }
300
301 // Log job creation.
302 $this->criticalcss->log( 'Job ' . $operation . ' with local job id <' . $path['ljid'] . '> for target rule <' . $target . '>', 3 );
303
304 return $path;
305 }
306
307 public function ao_ccss_job_id( $length = 6 ) {
308 // Generate random strings for the local job ID
309 // Based on https://stackoverflow.com/a/4356295 .
310 $characters = '0123456789abcdefghijklmnopqrstuvwxyz';
311 $characters_length = strlen( $characters );
312 $random_string = 'j-';
313 for ( $i = 0; $i < $length; $i++ ) {
314 $random_string .= $characters[ rand( 0, $characters_length - 1 ) ];
315 }
316 return $random_string;
317 }
318
319 public function ao_ccss_ua() {
320 // Check for criticalcss.com user agent.
321 $agent = '';
322 if ( isset( $_SERVER['HTTP_USER_AGENT'] ) ) {
323 $agent = $_SERVER['HTTP_USER_AGENT'];
324 }
325
326 // Check for UA and return TRUE when criticalcss.com is the detected UA, false when not.
327 $rtn = strpos( $agent, AO_CCSS_URL );
328 if ( 0 === $rtn ) {
329 $rtn = true;
330 } else {
331 $rtn = false;
332 }
333 return ( $rtn );
334 }
335 }
336