PluginProbe
Autoptimize / 3.0.2
Autoptimize v3.0.2
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.0.2, at classes/autoptimizeCriticalCSSEnqueue.php

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