first
This commit is contained in:
106
wp-content/plugins/debloat/inc/optimize-css/google-fonts.php
Normal file
106
wp-content/plugins/debloat/inc/optimize-css/google-fonts.php
Normal file
@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\OptimizeCss;
|
||||
|
||||
use Sphere\Debloat\Plugin;
|
||||
|
||||
/**
|
||||
* Add a few Google Font optimizations.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class GoogleFonts
|
||||
{
|
||||
public $active = false;
|
||||
protected $renders = [];
|
||||
|
||||
public function enable()
|
||||
{
|
||||
$this->active = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add resource hints for preconnect and so on.
|
||||
*
|
||||
* @param string $html Full DOM HTML.
|
||||
* @return void
|
||||
*/
|
||||
public function add_hints($html)
|
||||
{
|
||||
if (!$this->active || !Plugin::options()->optimize_gfonts) {
|
||||
return $html;
|
||||
}
|
||||
|
||||
// preconnect with dns-prefetch fallback for Firefox. Due to safari bug, can't be in same rel.
|
||||
$hint = '<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />';
|
||||
$hint .= '<link rel="dns-prefetch" href="https://fonts.gstatic.com" />';
|
||||
|
||||
// Only needed if not inlined.
|
||||
if (!Plugin::options()->optimize_css || !Plugin::options()->optimize_gfonts_inline) {
|
||||
$hint .= '<link rel="preconnect" href="https://fonts.googleapis.com" crossorigin />';
|
||||
$hint .= '<link rel="dns-prefetch" href="https://fonts.googleapis.com" />';
|
||||
}
|
||||
|
||||
// Add in to head.
|
||||
$html = str_replace('<head>', '<head>' . $hint, $html);
|
||||
|
||||
// No longer needed as preconnect's better and we always want to use https.
|
||||
$html = str_replace("<link rel='dns-prefetch' href='//fonts.googleapis.com' />", '', $html);
|
||||
|
||||
return $html;
|
||||
}
|
||||
|
||||
/**
|
||||
* Late processing and injecting data in HTML DOM.
|
||||
*
|
||||
* @param string $html
|
||||
* @return string
|
||||
*/
|
||||
public function render_in_dom($html)
|
||||
{
|
||||
if ($this->renders) {
|
||||
$html = str_replace('<head>', '<head>' . implode('', $this->renders), $html);
|
||||
}
|
||||
|
||||
$html = $this->add_hints($html);
|
||||
return $html;
|
||||
}
|
||||
|
||||
public function optimize(Stylesheet $sheet)
|
||||
{
|
||||
if (!$sheet->is_google_fonts()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Set normal render if optimizations are disabled.
|
||||
if (!Plugin::options()->optimize_gfonts) {
|
||||
$sheet->render_type = 'normal';
|
||||
return;
|
||||
}
|
||||
|
||||
$sheet->delay_type = 'preload';
|
||||
if (strpos($sheet->url, 'display=') === false) {
|
||||
$sheet->url .= '&display=swap';
|
||||
}
|
||||
}
|
||||
|
||||
public function do_render(Stylesheet $sheet)
|
||||
{
|
||||
return $sheet->render();
|
||||
|
||||
// $orig_media = $sheet->media;
|
||||
|
||||
// // If no display= or if display=auto.
|
||||
// if (!preg_match('/display=(?!auto)/', $sheet->url)) {
|
||||
// $sheet->media = 'x';
|
||||
// }
|
||||
|
||||
// // Add the JS to render with display: swap on mobile.
|
||||
// $extra = "<script>const e = document.currentScript.previousElementSibling; window.innerWidth > 1024 || (e.href+='&display=swap'); e.media='" . esc_js($orig_media) ."'</script>";
|
||||
// $this->renders[] = $sheet->render() . $extra;
|
||||
|
||||
// // Return empty space to strip out the tag.
|
||||
// return ' ';
|
||||
}
|
||||
}
|
251
wp-content/plugins/debloat/inc/optimize-css/optimize-css.php
Normal file
251
wp-content/plugins/debloat/inc/optimize-css/optimize-css.php
Normal file
@ -0,0 +1,251 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
296
wp-content/plugins/debloat/inc/optimize-css/stylesheet.php
Normal file
296
wp-content/plugins/debloat/inc/optimize-css/stylesheet.php
Normal file
@ -0,0 +1,296 @@
|
||||
<?php
|
||||
|
||||
namespace Sphere\Debloat\OptimizeCss;
|
||||
|
||||
use Sphere\Debloat\Base\Asset;
|
||||
use Sphere\Debloat\Util;
|
||||
|
||||
/**
|
||||
* Value object for stylehseets.
|
||||
*
|
||||
* @author asadkn
|
||||
* @since 1.0.0
|
||||
*/
|
||||
class Stylesheet extends Asset
|
||||
{
|
||||
public $has_cache = false;
|
||||
|
||||
/**
|
||||
* Specify a different id to use while rendering the script.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $render_id = '';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $file;
|
||||
|
||||
/**
|
||||
* CSS content.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $content = '';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
public $parsed_data;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
public $render_type = '';
|
||||
public $delay_type = 'onload';
|
||||
|
||||
/**
|
||||
* Whether delay load exists. Some sheets may have a different render_type but
|
||||
* may still need to be delayed in addition.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
public $has_delay = false;
|
||||
|
||||
/**
|
||||
* Media type for this stylesheet.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $media;
|
||||
|
||||
/**
|
||||
* Pre-rendered content
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public $render_string;
|
||||
|
||||
public $original_size = 0;
|
||||
public $new_size = 0;
|
||||
|
||||
/**
|
||||
* Whether the stylesheet is a google fonts link.
|
||||
*
|
||||
* @var boolean
|
||||
*/
|
||||
protected $is_google_fonts = false;
|
||||
|
||||
public function __construct(string $id, string $url)
|
||||
{
|
||||
$this->id = $id;
|
||||
$this->orig_url = $url;
|
||||
|
||||
// Cleaned URL.
|
||||
$this->url = html_entity_decode($url, ENT_COMPAT | ENT_SUBSTITUTE);
|
||||
|
||||
if (!$this->id) {
|
||||
$this->id = md5($this->url);
|
||||
}
|
||||
|
||||
$this->is_google_fonts = stripos($this->url, 'fonts.googleapis.com/css') !== false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory method: Create an instance provided an HTML tag.
|
||||
*
|
||||
* @param string $tag
|
||||
* @return boolean|self
|
||||
*/
|
||||
public static function from_tag(string $tag)
|
||||
{
|
||||
$attrs = Util\parse_attrs($tag);
|
||||
|
||||
if (!isset($attrs['href'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$sheet = new self($attrs['id'] ?? '', $attrs['href']);
|
||||
$sheet->media = $attrs['media'] ?? '';
|
||||
|
||||
return $sheet;
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
if ($this->render_string) {
|
||||
return $this->render_string;
|
||||
}
|
||||
|
||||
$attrs = [
|
||||
'rel' => 'stylesheet',
|
||||
'id' => $this->render_id ?: $this->id,
|
||||
];
|
||||
|
||||
if ($this->media) {
|
||||
$attrs['media'] = $this->media;
|
||||
}
|
||||
|
||||
/**
|
||||
* 1. Render type 'delay' at onload or delay via JS for later.
|
||||
*/
|
||||
if ($this->render_type === 'delay') {
|
||||
if ($this->media === 'print') {
|
||||
return '';
|
||||
}
|
||||
|
||||
if ($this->delay_type === 'onload') {
|
||||
$media = $this->media ?? 'all';
|
||||
$attrs = array_replace($attrs, [
|
||||
'media' => 'print',
|
||||
'href' => $this->get_content_url(),
|
||||
'onload' => "this.media='{$media}'"
|
||||
]);
|
||||
|
||||
} else if ($this->delay_type === 'preload') {
|
||||
$attrs = array_replace($attrs, [
|
||||
'rel' => 'preload',
|
||||
'as' => 'style',
|
||||
'href' => $this->get_content_url(),
|
||||
'onload' => "this.rel='stylesheet'"
|
||||
]);
|
||||
|
||||
} else {
|
||||
/**
|
||||
* Other types of delayed loads are handled via JS, so add the relevant data.
|
||||
*/
|
||||
$attrs += [
|
||||
'data-debloat-delay' => true,
|
||||
'data-href' => $this->get_content_url()
|
||||
];
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<link %1$s/>',
|
||||
implode(' ', $this->render_attrs($attrs, ['onload']))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 2. Render type 'inline' when the CSS has to be inlined.
|
||||
*/
|
||||
if ($this->render_type === 'inline') {
|
||||
if (!$this->content) {
|
||||
return '';
|
||||
}
|
||||
|
||||
unset($attrs['rel']);
|
||||
|
||||
if ($attrs['media'] === 'all') {
|
||||
unset($attrs['media']);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'<style %1$s>%2$s</style>',
|
||||
implode(' ', $this->render_attrs($attrs)),
|
||||
$this->content
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 3. Normal render. Usually for stylesheets that just have to be minified.
|
||||
*/
|
||||
if ($this->render_type === 'normal') {
|
||||
$attrs += [
|
||||
'href' => $this->get_content_url()
|
||||
];
|
||||
|
||||
return sprintf(
|
||||
'<link %1$s/>',
|
||||
implode(' ', $this->render_attrs($attrs))
|
||||
);
|
||||
}
|
||||
|
||||
// Default to nothing.
|
||||
return '';
|
||||
}
|
||||
|
||||
public function set_render($type, $render_string = '')
|
||||
{
|
||||
$this->render_type = $type;
|
||||
|
||||
if ($render_string) {
|
||||
$this->render_string = $render_string;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not stylesheet is delayed or has a delayed factor to it (such as
|
||||
* remove_css + delay combo).
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function has_delay()
|
||||
{
|
||||
return $this->has_delay || $this->render_type === 'delay';
|
||||
}
|
||||
|
||||
public function convert_urls()
|
||||
{
|
||||
// Original base URL.
|
||||
$base_url = preg_replace('#[^/]+\?.*$#', '', $this->url);
|
||||
|
||||
// Borrowed and modified from MatthiasMullie\Minify\CSS.
|
||||
$regex = '/
|
||||
# open url()
|
||||
url\(
|
||||
\s*
|
||||
|
||||
# open path enclosure
|
||||
(?P<quotes>["\'])?
|
||||
|
||||
# fetch path
|
||||
(?P<path>.+?)
|
||||
|
||||
# close path enclosure, conditional
|
||||
(?(quotes)(?P=quotes))
|
||||
|
||||
\s*
|
||||
# close url()
|
||||
\)
|
||||
/ix';
|
||||
|
||||
preg_match_all($regex, $this->content, $matches, PREG_SET_ORDER);
|
||||
if (!$matches) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($matches as $match) {
|
||||
$url = trim($match['path']);
|
||||
|
||||
if (substr($url, 0, 5) === 'data:') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$parsed_url = parse_url($url);
|
||||
|
||||
// Skip known host and protocol-relative paths.
|
||||
if (!empty($parsed_url['host']) || empty($parsed_url['path']) || $parsed_url['path'][0] === '/') {
|
||||
continue;
|
||||
}
|
||||
|
||||
$new_url = $base_url . $url;
|
||||
|
||||
// URLs with quotes, #, brackets or characters above 0x7e should be quoted.
|
||||
// Restore original quotes.
|
||||
// Ref: https://developer.mozilla.org/en-US/docs/Web/CSS/url()#syntax
|
||||
if (preg_match('/[\s\)\'"#\x{7f}-\x{9f}]/u', $new_url)) {
|
||||
$new_url = $match['quotes'] . $new_url . $match['quotes'];
|
||||
}
|
||||
|
||||
$new_url = 'url(' . $new_url. ')';
|
||||
$this->content = str_replace($match[0], $new_url, $this->content);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the style is a google fonts URL.
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public function is_google_fonts()
|
||||
{
|
||||
return $this->is_google_fonts;
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user