PluginProbe
Autoptimize / 2.7.1
Autoptimize v2.7.1
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 / autoptimizeCriticalCSSCore.php

autoptimizeCriticalCSSCore.php in Autoptimize 2.7.1, at classes/autoptimizeCriticalCSSCore.php

581 lines 25.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Critical CSS Core logic:
4 * gets called by AO core, checks the rules and if a matching rule is found returns the associated CCSS.
5 */
6
7 if ( ! defined( 'ABSPATH' ) ) {
8 exit;
9 }
10
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 }
20
21 $this->run();
22 }
23
24 public function run() {
25 global $ao_css_defer;
26 global $ao_ccss_deferjquery;
27 global $ao_ccss_key;
28
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 );
34
35 // Add the action to enqueue jobs for CriticalCSS cron.
36 add_action( 'autoptimize_action_css_hash', array( 'autoptimizeCriticalCSSEnqueue', 'ao_ccss_enqueue' ), 10, 1 );
37
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 }
42
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 }
48
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();
53
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' ) );
56 }
57 }
58
59 public function ao_ccss_frontend( $inlined ) {
60 // Apply CriticalCSS to frontend pages
61 // 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 = '';
69
70 // 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() ) ) {
72 // 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 ) {
76 // 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 ) ) {
78 if ( file_exists( AO_CCSS_DIR . $rule['file'] ) ) {
79 $_ccss_contents = file_get_contents( AO_CCSS_DIR . $rule['file'] );
80 if ( 'none' != $_ccss_contents ) {
81 if ( $ao_ccss_debug ) {
82 $_ccss_contents = '/* PATH: ' . $path . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
83 }
84 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
85 } else {
86 $no_ccss = 'none';
87 }
88 }
89 }
90 }
91 }
92
93 // Check for a valid CriticalCSS based on conditional tags to return its contents.
94 if ( ! empty( $ao_ccss_rules['types'] ) && 'none' !== $no_ccss ) {
95 // 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();
98
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'] );
102 if ( $is_front_page && 'is_front_page' == $type ) {
103 if ( 'none' != $_ccss_contents ) {
104 if ( $ao_ccss_debug ) {
105 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
106 }
107 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
108 } else {
109 $no_ccss = 'none';
110 }
111 } elseif ( strpos( $type, 'custom_post_' ) === 0 && ! $is_front_page ) {
112 if ( get_post_type( get_the_ID() ) === substr( $type, 12 ) ) {
113 if ( 'none' != $_ccss_contents ) {
114 if ( $ao_ccss_debug ) {
115 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
116 }
117 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
118 } else {
119 $no_ccss = 'none';
120 }
121 }
122 } elseif ( 0 === strpos( $type, 'template_' ) && ! $is_front_page ) {
123 if ( is_page_template( substr( $type, 9 ) ) ) {
124 if ( 'none' != $_ccss_contents ) {
125 if ( $ao_ccss_debug ) {
126 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
127 }
128 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
129 } else {
130 $no_ccss = 'none';
131 }
132 }
133 } elseif ( ! $is_front_page ) {
134 // all "normal" conditional tags, core + woo + buddypress + edd + bbpress
135 // but we have to remove the prefix for the non-core ones for them to function.
136 $type = str_replace( array( 'woo_', 'bp_', 'bbp_', 'edd_' ), '', $type );
137 if ( function_exists( $type ) && call_user_func( $type ) ) {
138 if ( 'none' != $_ccss_contents ) {
139 if ( $ao_ccss_debug ) {
140 $_ccss_contents = '/* TYPES: ' . $type . ' hash: ' . $rule['hash'] . ' file: ' . $rule['file'] . ' */ ' . $_ccss_contents;
141 }
142 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $_ccss_contents . $ao_ccss_additional );
143 } else {
144 $no_ccss = 'none';
145 }
146 }
147 }
148 }
149 }
150 }
151 }
152
153 // Finally, inline the default CriticalCSS if any or else the entire CSS for the page
154 // This also applies to logged in users if the option to add CCSS for logged in users has been disabled.
155 if ( ! empty( $inlined ) && 'none' !== $no_ccss ) {
156 return apply_filters( 'autoptimize_filter_ccss_core_ccss', $inlined . $ao_ccss_additional );
157 } else {
158 add_filter( 'autoptimize_filter_css_inline', '__return_true' );
159 return;
160 }
161 }
162
163 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 ) ) {
166 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.
173 $new_match = str_replace( '<script ', '<script defer ', $match[0] );
174 $in = str_replace( $match[0], $new_match, $in );
175 }
176 }
177 }
178 return $in;
179 }
180
181 public function ao_ccss_extend_types() {
182 // Extend contidional tags
183 // Attach the conditional tags array.
184 global $ao_ccss_types;
185
186 // 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 );
190 }
191
192 // Custom Post Types.
193 $cpts = get_post_types(
194 array(
195 'public' => true,
196 '_builtin' => false,
197 ),
198 'names',
199 'and'
200 );
201 foreach ( $cpts as $cpt ) {
202 array_unshift( $ao_ccss_types, 'custom_post_' . $cpt );
203 }
204
205 // Templates.
206 $templates = wp_get_theme()->get_page_templates();
207 foreach ( $templates as $tplfile => $tplname ) {
208 array_unshift( $ao_ccss_types, 'template_' . $tplfile );
209 }
210
211 // bbPress tags.
212 if ( function_exists( 'is_bbpress' ) ) {
213 $ao_ccss_types = array_merge(
214 array(
215 'bbp_is_bbpress',
216 'bbp_is_favorites',
217 'bbp_is_forum_archive',
218 'bbp_is_replies_created',
219 'bbp_is_reply_edit',
220 'bbp_is_reply_move',
221 'bbp_is_search',
222 'bbp_is_search_results',
223 'bbp_is_single_forum',
224 'bbp_is_single_reply',
225 'bbp_is_single_topic',
226 'bbp_is_single_user',
227 'bbp_is_single_user_edit',
228 'bbp_is_single_view',
229 'bbp_is_subscriptions',
230 'bbp_is_topic_archive',
231 'bbp_is_topic_edit',
232 'bbp_is_topic_merge',
233 'bbp_is_topic_split',
234 'bbp_is_topic_tag',
235 'bbp_is_topic_tag_edit',
236 'bbp_is_topics_created',
237 'bbp_is_user_home',
238 'bbp_is_user_home_edit',
239 ), $ao_ccss_types
240 );
241 }
242
243 // BuddyPress tags.
244 if ( function_exists( 'is_buddypress' ) ) {
245 $ao_ccss_types = array_merge(
246 array(
247 'bp_is_activation_page',
248 'bp_is_activity',
249 'bp_is_blogs',
250 'bp_is_buddypress',
251 'bp_is_change_avatar',
252 'bp_is_create_blog',
253 'bp_is_friend_requests',
254 'bp_is_friends',
255 'bp_is_friends_activity',
256 'bp_is_friends_screen',
257 'bp_is_group_admin_page',
258 'bp_is_group_create',
259 'bp_is_group_forum',
260 'bp_is_group_forum_topic',
261 'bp_is_group_home',
262 'bp_is_group_invites',
263 'bp_is_group_leave',
264 'bp_is_group_members',
265 'bp_is_group_single',
266 'bp_is_groups',
267 'bp_is_messages',
268 'bp_is_messages_compose_screen',
269 'bp_is_messages_conversation',
270 'bp_is_messages_inbox',
271 'bp_is_messages_sentbox',
272 'bp_is_my_activity',
273 'bp_is_my_blogs',
274 'bp_is_notices',
275 'bp_is_profile_edit',
276 'bp_is_register_page',
277 'bp_is_settings_component',
278 'bp_is_user',
279 'bp_is_user_profile',
280 'bp_is_wire',
281 ), $ao_ccss_types
282 );
283 }
284
285 // Easy Digital Downloads (EDD) tags.
286 if ( function_exists( 'edd_is_checkout' ) ) {
287 $ao_ccss_types = array_merge(
288 array(
289 'edd_is_checkout',
290 'edd_is_failed_transaction_page',
291 'edd_is_purchase_history_page',
292 'edd_is_success_page',
293 ), $ao_ccss_types
294 );
295 }
296
297 // WooCommerce tags.
298 if ( class_exists( 'WooCommerce' ) ) {
299 $ao_ccss_types = array_merge(
300 array(
301 'woo_is_account_page',
302 'woo_is_cart',
303 'woo_is_checkout',
304 'woo_is_product',
305 'woo_is_product_category',
306 'woo_is_product_tag',
307 'woo_is_shop',
308 'woo_is_wc_endpoint_url',
309 'woo_is_woocommerce',
310 ), $ao_ccss_types
311 );
312 }
313 }
314
315 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 }
336 }
337
338 public static function ao_ccss_key_status( $render ) {
339 // Provide key status
340 // 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;
346
347 // Prepare returned variables.
348 $key_return = array();
349 $status = false;
350
351 if ( $key && 2 == $key_status ) {
352 // Key exists and its status is valid.
353 // Set valid key status.
354 $status = 'valid';
355 $status_msg = __( 'Valid' );
356 $color = '#46b450'; // Green.
357 $message = null;
358 } elseif ( $key && 1 == $key_status ) {
359 // Key exists but its validation has failed.
360 // Set invalid key status.
361 $status = 'invalid';
362 $status_msg = __( 'Invalid' );
363 $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' );
365 } elseif ( $key && ! $key_status ) {
366 // Key exists but it has no valid status yet
367 // Perform key validation.
368 $key_check = $self->ao_ccss_key_validation( $key );
369
370 // Key is valid, set valid status.
371 if ( $key_check ) {
372 $status = 'valid';
373 $status_msg = __( 'Valid' );
374 $color = '#46b450'; // Green.
375 $message = null;
376 } else {
377 // Key is invalid, set invalid status.
378 $status = 'invalid';
379 $status_msg = __( 'Invalid' );
380 $color = '#dc3232'; // Red.
381 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' );
383 } 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' );
385 }
386 }
387 } else {
388 // No key nor status
389 // Set no key status.
390 $status = 'nokey';
391 $status_msg = __( 'None' );
392 $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' );
394 }
395
396 // Fill returned values.
397 $key_return['status'] = $status;
398 // Provide rendering information if required.
399 if ( $render ) {
400 $key_return['stmsg'] = $status_msg;
401 $key_return['color'] = $color;
402 $key_return['msg'] = $message;
403 }
404
405 // Return key status.
406 return $key_return;
407 }
408
409 public function ao_ccss_key_validation( $key ) {
410 // POST a dummy job to criticalcss.com to check for key validation
411 // Prepare home URL for the request.
412 $src_url = get_home_url();
413 $src_url = apply_filters( 'autoptimize_filter_ccss_cron_srcurl', $src_url );
414
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 );
433
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 );
438
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 );
444
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;
449
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 );
468 }
469 }
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 } 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 }
487 return false;
488 }
489 }
490
491 public static function ao_ccss_viewport() {
492 // Get viewport size
493 // Attach viewport option.
494 global $ao_ccss_viewport;
495
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;
514 }
515
516 public static function ao_ccss_check_contents( $ccss ) {
517 // Perform basic exploit avoidance and CSS validation.
518 if ( ! empty( $ccss ) ) {
519 // 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 );
524 return false;
525 }
526 }
527
528 // Check for most basics CSS structures.
529 $pinklist = array( '{', '}', ':' );
530 foreach ( $pinklist as $needed ) {
531 if ( false === strpos( $ccss, $needed ) && 'none' !== $ccss ) {
532 autoptimizeCriticalCSSCore::ao_ccss_log( 'Critical CSS received did not seem to contain real CSS.', 2 );
533 return false;
534 }
535 }
536 }
537
538 // Return true if file critical CSS is sane.
539 return true;
540 }
541
542 public static function ao_ccss_log( $msg, $lvl ) {
543 // Commom logging facility
544 // Attach debug option.
545 global $ao_ccss_debug;
546
547 // Prepare log levels, where accepted $lvl are:
548 // 1: II (for info)
549 // 2: EE (for error)
550 // 3: DD (for debug)
551 // Default: UU (for unkown).
552 $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 ) {
563 $level = 'DD';
564 }
565 break;
566 default:
567 $level = 'UU';
568 }
569
570 // Prepare and write a log message if there's a valid level.
571 if ( $level ) {
572
573 // Prepare message.
574 $message = date( 'c' ) . ' - [' . $level . '] ' . htmlentities( $msg ) . '<br>';
575
576 // Write message to log file.
577 error_log( $message, 3, AO_CCSS_LOG );
578 }
579 }
580 }
581