// Active mapping (not expired).
$q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0";
$file_row = $wpdb->get_row( $wpdb->prepare( $q, [ $url_id, $vary, $type ] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// No change needed if filename matches.
if ( $file_row && $file_row['filename'] === $filecon_md5 ) {
// If the new file MD5 is currently marked expired elsewhere, clear those records.
$q = "DELETE FROM `$tb_url_file` WHERE filename = %s AND expired > 0";
$wpdb->query( $wpdb->prepare( $q, $filecon_md5 ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// If another live row already uses the same filename, switch current row to that filename.
$q = "SELECT id FROM `$tb_url_file` WHERE filename = %s AND expired = 0 AND id != %d LIMIT 1";
$exists_id = $wpdb->get_var( $wpdb->prepare( $q, [ $file_row['filename'], (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = "UPDATE `$tb_url_file` SET filename=%s WHERE id=%d";
$wpdb->query( $wpdb->prepare( $q, [ $filecon_md5, (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// Insert a new mapping row.
$q = "INSERT INTO `$tb_url_file` SET url_id=%d, vary=%s, filename=%s, type=%d, mobile=%d, webp=%d, expired=0";
$wpdb->query( $wpdb->prepare( $q, [ $url_id, $vary, $filecon_md5, $type, $mobile ? 1 : 0, $webp ? 1 : 0 ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// Mark previous mapping as expiring (to be deleted later).
$q = "UPDATE `$tb_url_file` SET expired=%d WHERE id=%d";
$expired = time() + ( 86400 * apply_filters( 'litespeed_url_file_expired_days', 20 ) );
$wpdb->query( $wpdb->prepare( $q, [ $expired, (int) $file_row['id'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
// Delete already-expired files for this URL.
$q = "SELECT * FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d";
$q = $wpdb->prepare( $q, [ $url_id, time() ] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$list = $wpdb->get_results( $q, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
foreach ( $list as $v ) {
$ext = 'js' === $file_type ? 'js' : 'css';
$file_to_del = trailingslashit( $path ) . $v['filename'] . '.' . $ext;
if ( file_exists( $file_to_del ) ) {
self::debug( 'Delete expired unused file: ' . $file_to_del );
wp_delete_file( $file_to_del );
$q = "DELETE FROM `$tb_url_file` WHERE url_id = %d AND expired BETWEEN 1 AND %d";
$wpdb->query( $wpdb->prepare( $q, [ $url_id, time() ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
* Load the stored filename (md5) for a given URL/vary/type, if active.
* @param string $request_url Full request URL or tag.
* @param string $vary Vary string (may be md5 if previously stored).
* @param string $file_type One of 'css','js','ccss','ucss'.
* @return string|false Filename md5 (without extension) or false if none.
public function load_url_file( $request_url, $vary, $file_type ) {
if ( strlen( $vary ) > 32 ) {
if ( ! isset( $this->_url_file_types[ $file_type ] ) ) {
$type = $this->_url_file_types[ $file_type ];
self::debug2( 'load url file: ' . $request_url );
$tb_url = $this->tb( 'url' );
$q = "SELECT * FROM `$tb_url` WHERE url=%s";
$url_row = $wpdb->get_row( $wpdb->prepare( $q, $request_url ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
$url_id = (int) $url_row['id'];
$tb_url_file = $this->tb( 'url_file' );
$q = "SELECT * FROM `$tb_url_file` WHERE url_id=%d AND vary=%s AND type=%d AND expired=0";
$file_row = $wpdb->get_row( $wpdb->prepare( $q, [ $url_id, $vary, $type ] ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
return $file_row['filename'];
* Mark all UCSS entries of one URL as expired (optionally return existing rows).
* @param string $request_url Target URL.
* @param bool $auto_q If true, return existing active rows before expiring.
* @return array Existing rows if $auto_q, otherwise empty array.
public function mark_as_expired( $request_url, $auto_q = false ) {
$tb_url = $this->tb( 'url' );
self::debug( 'Try to mark as expired: ' . $request_url );
$q = "SELECT * FROM `$tb_url` WHERE url=%s";
$url_row = $wpdb->get_row( $wpdb->prepare( $q, $request_url ), ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
self::debug( 'Mark url_id=' . $url_row['id'] . ' as expired' );
$tb_url_file = $this->tb( 'url_file' );
$existing_url_files = [];
$q = "SELECT a.*, b.url FROM `$tb_url_file` a LEFT JOIN `$tb_url` b ON b.id=a.url_id WHERE a.url_id=%d AND a.type=%d AND a.expired=0";
$q = $wpdb->prepare( $q, [ (int) $url_row['id'], $this->_url_file_types['ucss'] ] ); // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
$existing_url_files = $wpdb->get_results( $q, ARRAY_A ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
$q = "UPDATE `$tb_url_file` SET expired=%d WHERE url_id=%d AND type=%d AND expired=0";
$expired = time() + 86400 * apply_filters( 'litespeed_url_file_expired_days', 20 );
$wpdb->query( $wpdb->prepare( $q, [ $expired, (int) $url_row['id'], $this->_url_file_types['ucss'] ] ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.PreparedSQL.NotPrepared
return $existing_url_files;
* Merge CSS excludes from file into the given list.
* @param array $list_in Existing list.
public function load_css_exc( $list_in ) {
$data = $this->_load_per_line( 'css_excludes.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge CCSS selector whitelist from file into the given list.
* @param array $list_in Existing list.
public function load_ccss_whitelist( $list_in ) {
$data = $this->_load_per_line( 'ccss_whitelist.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge UCSS whitelist from file into the given list.
* @param array $list_in Existing list.
public function load_ucss_whitelist( $list_in ) {
$data = $this->_load_per_line( 'ucss_whitelist.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge JS excludes from file into the given list.
* @param array $list_in Existing list.
public function load_js_exc( $list_in ) {
$data = $this->_load_per_line( 'js_excludes.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge JS defer excludes from file into the given list.
* @param array $list_in Existing list.
public function load_js_defer_exc( $list_in ) {
$data = $this->_load_per_line( 'js_defer_excludes.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge OPTM URI excludes from file into the given list.
* @param array $list_in Existing list.
public function load_optm_uri_exc( $list_in ) {
$data = $this->_load_per_line( 'optm_uri_exc.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge ESI nonces from file into the given list.
* @param array $list_in Existing list.
public function load_esi_nonces( $list_in ) {
$data = $this->_load_per_line( 'esi.nonces.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Merge "nocacheable" cache keys from file into the given list.
* @param array $list_in Existing list.
public function load_cache_nocacheable( $list_in ) {
$data = $this->_load_per_line( 'cache_nocacheable.txt' );
$list_in = array_unique( array_filter( array_merge( $list_in, $data ) ) );
* Load a data file and return non-empty lines, stripping comments.
* @param string $file Relative filename under the plugin /data directory.
* @return array<int,string>
private function _load_per_line( $file ) {
$data = File::read( LSCWP_DIR . 'data/' . $file );
$data = explode( PHP_EOL, $data );
foreach ( $data as $v ) {
// Drop two kinds of comments.
if ( false !== strpos( $v, '##' ) ) {
$v = trim( substr( $v, 0, strpos( $v, '##' ) ) );
if ( false !== strpos( $v, '# ' ) ) {
$v = trim( substr( $v, 0, strpos( $v, '# ' ) ) );