defined( 'WPINC' ) || exit();
* UCSS optimization class.
class UCSS extends Base {
const LOG_TAG = '[UCSS]';
const TYPE_CLEAR_Q = 'clear_q';
* UCSS whitelist selectors.
private $_ucss_whitelist;
* Queue for UCSS generation.
public function __construct() {
$this->_summary = self::get_summary();
add_filter( 'litespeed_ucss_whitelist', [ $this->cls( 'Data' ), 'load_ucss_whitelist' ] );
* Uniform url tag for ucss usage
* @param string|false $request_url The request URL.
* @return string The URL tag.
public static function get_url_tag( $request_url = false ) {
} elseif (apply_filters('litespeed_ucss_per_pagetype', false)) {
$url_tag = Utility::page_type();
self::debug('litespeed_ucss_per_pagetype filter altered url to ' . $url_tag);
* @param string $request_url The request URL.
* @param bool $dry_run Whether to run in dry mode.
* @return string|false The UCSS filename or false.
public function load( $request_url, $dry_run = false ) {
// Check UCSS URI excludes
$ucss_exc = apply_filters( 'litespeed_ucss_exc', $this->conf( self::O_OPTM_UCSS_EXC ) );
$hit = $ucss_exc ? Utility::str_hit_array( $request_url, $ucss_exc ) : false;
self::debug( 'UCSS bypassed due to UCSS URI Exclude setting: ' . $hit );
Core::comment( 'QUIC.cloud UCSS bypassed by setting' );
$filepath_prefix = $this->_build_filepath_prefix('ucss');
$url_tag = self::get_url_tag($request_url);
$vary = $this->cls('Vary')->finalize_full_varies();
$filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'ucss');
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css';
if (file_exists($static_file)) {
self::debug2('existing ucss ' . $static_file);
// Check if is error comment inside only
$tmp = File::read($static_file);
if ( '/*' === substr( $tmp, 0, 2 ) && '*/' === substr( trim( $tmp ), -2 ) ) {
self::debug2('existing ucss is error only: ' . $tmp);
Core::comment('QUIC.cloud UCSS bypassed due to generation error ❌ ' . $filepath_prefix . $filename . '.css');
Core::comment('QUIC.cloud UCSS loaded ✅ ' . $filepath_prefix . $filename . '.css' );
return $filename . '.css';
Core::comment('QUIC.cloud UCSS in queue');
$uid = get_current_user_id();
$this->_queue = $this->load_queue('ucss');
if (count($this->_queue) > 500) {
self::debug('UCSS Queue is full - 500');
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
$this->_queue[ $queue_k ] = [
'url' => apply_filters( 'litespeed_ucss_url', $request_url ),
'user_agent' => substr( $ua, 0, 200 ),
'is_mobile' => $this->_separate_mobile(),
'is_webp' => $this->cls( 'Media' )->webp_support() ? 1 : 0,
]; // Current UA will be used to request
$this->save_queue('ucss', $this->_queue);
self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] ' . $uid);
// Prepare cache tag for later purge
Tag::add('UCSS.' . md5($queue_k));
* @return string The user agent string.
private function _get_ua() {
return ! empty( $_SERVER['HTTP_USER_AGENT'] ) ? sanitize_text_field( wp_unslash( $_SERVER['HTTP_USER_AGENT'] ) ) : '';
* @param array $url_files Array of URL file data.
* @return false|void False if queue is full.
public function add_to_q( $url_files ) {
$this->_queue = $this->load_queue('ucss');
if (count($this->_queue) > 500) {
self::debug('UCSS Queue is full - 500');
foreach ($url_files as $url_file) {
$vary = $url_file['vary'];
$request_url = $url_file['url'];
$is_mobile = $url_file['mobile'];
$is_webp = $url_file['webp'];
$url_tag = self::get_url_tag($request_url);
$queue_k = (strlen($vary) > 32 ? md5($vary) : $vary) . ' ' . $url_tag;
'url' => apply_filters( 'litespeed_ucss_url', $request_url ),
'user_agent' => substr( $ua, 0, 200 ),
'is_mobile' => $is_mobile,
]; // Current UA will be used to request
self::debug('Added queue_ucss [url_tag] ' . $url_tag . ' [UA] ' . $ua . ' [vary] ' . $vary . ' [uid] false');
$this->_queue[$queue_k] = $q;
$this->save_queue('ucss', $this->_queue);
* @param bool $keep_going Whether to continue processing.
* @return mixed The cron handler result.
public static function cron( $keep_going = false ) {
$_instance = self::cls();
return $_instance->_cron_handler( $keep_going );
* @param bool $keep_going Whether to continue processing.
* @return mixed The redirect result or void.
private function _cron_handler( $keep_going ) {
$this->_queue = $this->load_queue( 'ucss' );
if ( empty( $this->_queue ) ) {
// For cron, need to check request interval too
if (!empty($this->_summary['curr_request']) && time() - $this->_summary['curr_request'] < 300 && !$this->conf(self::O_DEBUG)) {
self::debug('Last request not done');
foreach ($this->_queue as $k => $v) {
if (!empty($v['_status'])) {
self::debug('cron job [tag] ' . $k . ' [url] ' . $v['url'] . ($v['is_mobile'] ? ' 📱 ' : '') . ' [UA] ' . $v['user_agent']);
if (!isset($v['is_webp'])) {
$res = $this->_send_req($v['url'], $k, $v['uid'], $v['user_agent'], $v['vary'], $v['url_tag'], $v['is_mobile'], $v['is_webp']);
// Status is wrong, drop this this->_queue
$this->_queue = $this->load_queue('ucss');
unset($this->_queue[$k]);
$this->save_queue('ucss', $this->_queue);
GUI::print_loading( count( $this->_queue ), 'UCSS' );
return Router::self_redirect( Router::ACTION_UCSS, self::TYPE_GEN );
// Exit queue if out of quota or service is hot
if ( 'out_of_quota' === $res || 'svc_hot' === $res ) {
$this->_queue = $this->load_queue( 'ucss' );
$this->_queue[ $k ]['_status'] = 'requested';
$this->save_queue( 'ucss', $this->_queue );
self::debug( 'Saved to queue [k] ' . $k );
// only request first one
GUI::print_loading(count($this->_queue), 'UCSS');
return Router::self_redirect(Router::ACTION_UCSS, self::TYPE_GEN);
* Send to QC API to generate UCSS
* @param string $request_url The request URL.
* @param string $queue_k The queue key.
* @param int|false $uid The user ID.
* @param string $user_agent The user agent.
* @param string $vary The vary string.
* @param string $url_tag The URL tag.
* @param bool $is_mobile Whether is mobile.
* @param bool $is_webp Whether supports webp.
* @return string|bool|null The result status.
private function _send_req( $request_url, $queue_k, $uid, $user_agent, $vary, $url_tag, $is_mobile, $is_webp ) {
// Check if has credit to push or not
$allowance = $this->cls('Cloud')->allowance(Cloud::SVC_UCSS, $err);
self::debug('❌ No credit: ' . $err);
$err && Admin_Display::error(Error::msg($err));
// Update css request status
$this->_summary['curr_request'] = time();
// Gather guest HTML to send
$html = $this->cls('CSS')->prepare_html($request_url, $user_agent, $uid);
// Parse HTML to gather all CSS content before requesting
list(, $html) = $this->prepare_css($html, $is_webp, true); // Use this to drop CSS from HTML as we don't need those CSS to generate UCSS
$filename = $this->cls('Data')->load_url_file($url_tag, $vary, 'css');
$filepath_prefix = $this->_build_filepath_prefix('css');
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filename . '.css';
self::debug('Checking combined file ' . $static_file);
if (file_exists($static_file)) {
$css = File::read($static_file);
self::debug('❌ No combined css');
'user_agent' => $user_agent,
'is_mobile' => $is_mobile ? 1 : 0, // todo:compatible w/ tablet
'is_webp' => $is_webp ? 1 : 0,
if (!isset($this->_ucss_whitelist)) {
$this->_ucss_whitelist = $this->_filter_whitelist();
$data['whitelist'] = $this->_ucss_whitelist;
self::debug('Generating: ', $data);
$json = Cloud::post(Cloud::SVC_UCSS, $data, 30);
// Old version compatibility
if (empty($json['status'])) {
if (!empty($json['ucss'])) {
$this->_save_con('ucss', $json['ucss'], $queue_k, $is_mobile, $is_webp);
// Unknown status, remove this line
if ( 'queued' !== $json['status'] ) {
$this->_summary['last_spent'] = time() - $this->_summary['curr_request'];
$this->_summary['last_request'] = $this->_summary['curr_request'];
$this->_summary['curr_request'] = 0;
* @param string $type The content type.
* @param string $css The CSS content.
* @param string $queue_k The queue key.
* @param bool $is_mobile Whether is mobile.
* @param bool $is_webp Whether supports webp.
private function _save_con( $type, $css, $queue_k, $is_mobile, $is_webp ) {
$css = apply_filters('litespeed_' . $type, $css, $queue_k);
self::debug2('con: ', $css);
if ( '/*' === substr( $css, 0, 2 ) && '*/' === substr( $css, -2 ) ) {
self::debug('❌ empty ' . $type . ' [content] ' . $css);
// continue; // Save the error info too
$filecon_md5 = md5($css);
$filepath_prefix = $this->_build_filepath_prefix($type);
$static_file = LITESPEED_STATIC_DIR . $filepath_prefix . $filecon_md5 . '.css';
File::save($static_file, $css, true);
$url_tag = $this->_queue[$queue_k]['url_tag'];
$vary = $this->_queue[$queue_k]['vary'];
self::debug2("Save URL to file [file] $static_file [vary] $vary");
$this->cls('Data')->save_url($url_tag, $vary, $type, $filecon_md5, dirname($static_file), $is_mobile, $is_webp);
Purge::add(strtoupper($type) . '.' . md5($queue_k));
* Prepare CSS from HTML for CCSS generation only. UCSS will used combined CSS directly.
* Prepare refined HTML for both CCSS and UCSS.
* @param string $html The HTML content.
* @param bool $is_webp Whether supports webp.
* @param bool $dryrun Whether to run in dry mode.
* @return array Array of CSS and HTML.
public function prepare_css( $html, $is_webp = false, $dryrun = false ) {
preg_match_all('#<link ([^>]+)/?>|<style([^>]*)>([^<]+)</style>#isU', $html, $matches, PREG_SET_ORDER);
foreach ($matches as $match) {
if (strpos($match[0], '<link') === 0) {
$attrs = Utility::parse_attr($match[1]);
if (empty($attrs['rel'])) {
if ( 'stylesheet' !== $attrs['rel'] ) {
if ( 'preload' !== $attrs['rel'] || empty( $attrs['as'] ) || 'style' !== $attrs['as'] ) {
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {
if (empty($attrs['href'])) {
// Check Google fonts hit
if (strpos($attrs['href'], 'fonts.googleapis.com') !== false) {
$html = str_replace($match[0], '', $html);
$debug_info = $attrs['href'];
// Dryrun will not load CSS but just drop them
$con = $this->cls('Optimizer')->load_file($attrs['href']);
$attrs = Utility::parse_attr($match[2]);
if (!empty($attrs['media']) && strpos($attrs['media'], 'print') !== false) {