PluginProbe
Autoptimize / 2.9.3
Autoptimize v2.9.3
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 2.9.3, at classes/autoptimizeCriticalCSSEnqueue.php

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