251 lines
5.8 KiB
PHP
251 lines
5.8 KiB
PHP
<?php
|
|
|
|
namespace Sphere\Debloat;
|
|
use Sphere\Debloat\OptimizeCss\Stylesheet;
|
|
|
|
/**
|
|
* Process stylesheets to debloat and optimize CSS.
|
|
*
|
|
* @author asadkn
|
|
* @since 1.0.0
|
|
*/
|
|
class OptimizeCss
|
|
{
|
|
/**
|
|
* @var \DOMDocument
|
|
*/
|
|
protected $dom;
|
|
protected $html;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $stylesheets;
|
|
|
|
/**
|
|
* @var array
|
|
*/
|
|
protected $exclude_sheets;
|
|
|
|
public function __construct(\DOMDocument $dom, string $raw_html)
|
|
{
|
|
$this->dom = $dom;
|
|
$this->html = $raw_html;
|
|
}
|
|
|
|
public function process()
|
|
{
|
|
$this->find_stylesheets();
|
|
|
|
// Setup optimization excludes.
|
|
$exclude = Util\option_to_array(Plugin::options()->optimize_css_excludes);
|
|
$this->exclude_sheets = apply_filters('debloat/optimize_css_excludes', $exclude);
|
|
|
|
// Remove CSS first, if any.
|
|
if ($this->should_remove_css()) {
|
|
$remove_css = new RemoveCss($this->stylesheets, $this->dom, $this->html);
|
|
$this->html = $remove_css->process();
|
|
}
|
|
|
|
/**
|
|
* Process and replace stylesheets with CSS cleaned.
|
|
*/
|
|
$has_delayed = false;
|
|
$has_gfonts = false;
|
|
$replacements = [];
|
|
|
|
/** @var Stylesheet $sheet */
|
|
foreach ($this->stylesheets as $sheet) {
|
|
$replacement = '';
|
|
|
|
// Optimizations such as min and inline.
|
|
$this->optimize($sheet);
|
|
|
|
if (Plugin::options()->optimize_gfonts && $sheet->is_google_fonts()) {
|
|
$replacement = Plugin::google_fonts()->do_render($sheet);
|
|
$has_gfonts = true;
|
|
}
|
|
|
|
if (!$replacement) {
|
|
// Get the rendered stylesheet to replace original with.
|
|
$replacement = $sheet->render();
|
|
}
|
|
|
|
if ($replacement) {
|
|
// Util\debug_log('Replacing: ' . print_r($sheet, true));
|
|
$replacements[$sheet->orig_url] = $replacement;
|
|
|
|
if ($sheet->has_delay()) {
|
|
$has_delayed = true;
|
|
|
|
// onload and preload type doesn't need prefetch; both use a non-JS method.
|
|
if (!in_array($sheet->delay_type, ['onload', 'preload'])) {
|
|
Plugin::delay_load()->add_preload($sheet);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Found Google Fonts.
|
|
if ($sheet->is_google_fonts()) {
|
|
$has_gfonts = true;
|
|
}
|
|
|
|
// Free up memory.
|
|
$sheet->content = null;
|
|
$sheet->parsed_data = null;
|
|
}
|
|
|
|
/**
|
|
* Make stylesheet replacements, if any. Slightly more efficient in one go.
|
|
*/
|
|
if ($replacements) {
|
|
$urls = array_map('preg_quote', array_keys($replacements));
|
|
|
|
// Using callback to prevent issues with backreferences such as $1 or \0 in replacement string.
|
|
$this->html = preg_replace_callback(
|
|
'#<link[^>]*href=(?:"|\'|)('. implode('|', $urls) .')(?:"|\'|\s)[^>]*>#Usi',
|
|
function ($match) use ($replacements) {
|
|
if (!empty($replacements[ $match[1] ])) {
|
|
return $replacements[ $match[1] ];
|
|
}
|
|
|
|
return $match[0];
|
|
},
|
|
$this->html
|
|
);
|
|
}
|
|
|
|
if ($has_delayed) {
|
|
Plugin::delay_load()->enable(
|
|
Plugin::options()->delay_css_type === 'onload' ? true : null
|
|
);
|
|
}
|
|
|
|
if ($has_gfonts) {
|
|
Plugin::google_fonts()->enable();
|
|
$this->html = Plugin::google_fonts()->render_in_dom($this->html);
|
|
}
|
|
|
|
return $this->html;
|
|
}
|
|
|
|
/**
|
|
* Apply CSS optimizes such as minify and inline, if enabled.
|
|
*
|
|
* @param Stylesheet $sheet
|
|
* @return void
|
|
*/
|
|
public function optimize(Stylesheet $sheet)
|
|
{
|
|
if (!$this->should_optimize($sheet)) {
|
|
return;
|
|
}
|
|
|
|
// We're going to use onload delay method (non-JS) fixing render blocking.
|
|
$sheet->delay_type = 'onload';
|
|
$sheet->set_render('delay');
|
|
|
|
// Should the sheet be minified.
|
|
$minify = Plugin::options()->optimize_css_minify;
|
|
|
|
// For inline CSS, minification is enforced.
|
|
if (Plugin::options()->optimize_css_to_inline) {
|
|
$minify = true;
|
|
}
|
|
|
|
// Will optimize if it's a google font. Note: Has to be done before minify.
|
|
Plugin::google_fonts()->optimize($sheet);
|
|
|
|
// Google Fonts inline also relies on minification.
|
|
if ($sheet->is_google_fonts() && Plugin::options()->optimize_gfonts_inline) {
|
|
$minify = true;
|
|
}
|
|
|
|
if ($minify) {
|
|
$minifier = new Minifier($sheet);
|
|
$minifier->process();
|
|
}
|
|
|
|
if (Plugin::options()->optimize_css_to_inline) {
|
|
if (!$sheet->content) {
|
|
$file = Plugin::file_system()->url_to_local($sheet->get_content_url());
|
|
|
|
if ($file) {
|
|
$sheet->content = Plugin::file_system()->get_contents($file);
|
|
}
|
|
}
|
|
|
|
// If we have content by now.
|
|
if ($sheet->content) {
|
|
$sheet->set_render('inline');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Determine if stylesheet should be optimized, based on exclusion and inclusion
|
|
* rules and settings.
|
|
*
|
|
* @param Stylesheet $sheet
|
|
* @return boolean
|
|
*/
|
|
public function should_optimize(Stylesheet $sheet)
|
|
{
|
|
// Only go ahead if optimizations are enabled and remove css hasn't happened.
|
|
if (!Plugin::options()->optimize_css || $sheet->render_type === 'remove_css') {
|
|
return false;
|
|
}
|
|
|
|
// Debugging scripts. Can't be minified, so can't be inline either.
|
|
if (defined('SCRIPT_DEBUG') && SCRIPT_DEBUG) {
|
|
return;
|
|
}
|
|
|
|
// Handle manual excludes first.
|
|
if ($this->exclude_sheets) {
|
|
foreach ($this->exclude_sheets as $exclude) {
|
|
if (Util\asset_match($exclude, $sheet)) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Find all the stylesheet links.
|
|
*
|
|
* @return Stylesheet[]
|
|
*/
|
|
public function find_stylesheets()
|
|
{
|
|
$stylesheets = [];
|
|
|
|
// Note: Can't use DOM parser as html entities in the URLs will be removed and
|
|
// replacing won't be possible later.
|
|
preg_match_all('#<link[^>]*stylesheet[^>]*>#Usi', $this->html, $matches);
|
|
|
|
foreach ($matches[0] as $sheet) {
|
|
$sheet = Stylesheet::from_tag($sheet);
|
|
|
|
if ($sheet) {
|
|
$stylesheets[] = $sheet;
|
|
}
|
|
}
|
|
|
|
$this->stylesheets = $stylesheets;
|
|
return $this->stylesheets;
|
|
}
|
|
|
|
/**
|
|
* Should unused CSS be removed.
|
|
*
|
|
* @return boolean
|
|
*/
|
|
public function should_remove_css()
|
|
{
|
|
$valid = Plugin::options()->remove_css && Plugin::process()->check_enabled(Plugin::options()->remove_css_on);
|
|
return apply_filters('debloat/should_remove_css', $valid);
|
|
}
|
|
} |