<?php
namespace SpeedyCache;
if(!defined('ABSPATH')){
die('HACKING ATTEMPT!');
}
use \SpeedyCache\lib\Minify;
use \SpeedyCache\Util;
class CSS{
static function minify(&$content){
global $speedycache;
if(empty($content)){
return;
}
preg_match_all('/<link\s+([^>]+[\s"\'])?href\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.css(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>/Umsi', $content, $tags, PREG_SET_ORDER);
if(empty($tags)){
return;
}
if(empty($_SERVER['HTTP_HOST'])){
return;
}
$site_host = str_replace('www.', '', sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])));
$site_url = site_url();
foreach($tags as $tag){
if(empty($tag['url'])){
continue;
}
$url = $tag['url'];
if(self::is_excluded($url)) continue;
// We don't want to minify already minified css
if(strpos($url, '.min.css') !== FALSE){
continue;
}
// We wont process any css that is not present on this WordPress install
if(strpos($url, $site_host) === FALSE){
continue;
}
$file_path = Util::url_to_path($url);
if(!file_exists($file_path)){
continue;
}
$file_name = self::file_name($file_path);
$asset_path = Util::cache_path('assets');
if(!is_dir($asset_path)){
mkdir($asset_path, 0755, true);
touch($asset_path . 'index.html');
}
$minified_path = $asset_path.$file_name;
// If we already have a minified file then we dont need to process it again.
if(!file_exists($minified_path)){
$minified = new Minify\CSS($file_path);
$minified = $minified->minify();
$minified = self::fix_relative_path($minified, $url);
file_put_contents($minified_path, $minified);
}
$minified_url = Util::path_to_url($minified_path);
$content = str_replace($tag['url'], $minified_url, $content);
// TODO: check if there is a preload.
}
}
static function file_name($path){
$file_hash = md5_file($path);
$file_name = substr($file_hash, 0, 16) . '-' . basename($path);
return $file_name;
}
static function combine(&$content){
if(empty($content)){
return;
}
preg_match_all('/<link\s+([^>]+[\s"\'])?href\s*=\s*[\'"]\s*?(?<url>[^\'"]+\.css(?:\?[^\'"]*)?)\s*?[\'"]([^>]+)?\/?>/Umsi', $content, $tags, PREG_SET_ORDER);
if(empty($tags)){
return;
}
if(empty($_SERVER['HTTP_HOST'])){
return;
}
$site_host = str_replace('www.', '', sanitize_text_field(wp_unslash($_SERVER['HTTP_HOST'])));
$site_url = site_url();
$combined_css = '';
$prev_tag = '';
$tags = array_reverse($tags);
foreach($tags as $tag){
if(empty($tag['url'])){
continue;
}
$url = $tag['url'];
if(self::is_excluded($url)) continue;
// We wont process any css that is not present on this WordPress install
if(strpos($url, $site_host) === FALSE){
continue;
}
$file_path = Util::url_to_path($url);
if(!file_exists($file_path) || !is_readable($file_path)){
continue;
}
$new_css = file_get_contents($file_path);
$new_css = self::fix_relative_path($new_css, $url);
$combined_css = $new_css . "\n" . $combined_css;
// Removing the CSS which has already been combined, as we will add the combined file at the top after title.
if(!empty($prev_tag)){
$content = str_replace($prev_tag, '', $content);
}
// We remove the previous tag, in current iteration, so at last we have a tag to replace wirh the combined script.
$prev_tag = $tag[0];
//TODO: Need to remove any preload added by any plugin or a theme.
}
if(empty($combined_css)){
return;
}
// Creating Combined file name
$file_name = md5($combined_css);
$file_name = substr($file_name, 0, 16) . '-combined.css';
$asset_path = Util::cache_path('assets');
if(!is_dir($asset_path)){
mkdir($asset_path, 0755, true);
touch($asset_path . 'index.html');
}
$combined_path = $asset_path.$file_name;
file_put_contents($combined_path, $combined_css);
$final_url = Util::path_to_url($combined_path);
// Injecting the Combined CSS
if(!empty($prev_tag)){
$content = str_replace($prev_tag, '<link rel="stylesheet" href="'.esc_url($final_url).'" />', $content);
return;
}
$content = str_replace('</title>', "</title>\n".'<link rel="stylesheet" href="'.esc_url($final_url).'" />', $content);
}
static function is_excluded($url){
$excludes = get_option('speedycache_exclude', []);
if(empty($excludes)){
return false;
}
foreach($excludes as $exclude){
if(empty($exclude['type'])){
continue;
}
if($exclude['type'] !== 'css'){
continue;
}
if(empty($exclude['content'])){
continue;
}
if(preg_match('/'.preg_quote($exclude['content'], '/').'/', $url)){
return true;
}
}
return false;
}
static function fix_relative_path($content, $base_url){
$content = preg_replace_callback('/url\(\s*["\']?(?!http|https|\/\/)([^"\')]+)["\']?\s*\)/i', function($matches) use ($base_url) {
$relative_path = $matches[1];
$relative_path = trim($relative_path, '/');
$base_path = Util::url_to_path($base_url);
if(strpos($relative_path, '..') == 0){
$absolute_path = realpath(dirname($base_path) . '/' . $relative_path);
$absolute_url = Util::path_to_url($absolute_path);
}
if(empty($absolute_url)){
$absolute_url = $relative_path;
}
return 'url("' . $absolute_url . '")';
}, $content);
return $content;
}
}