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(
'',
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(
'',
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(
'',
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["\'])?
# fetch path
(?P.+?)
# 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;
}
}