208 lines
4.0 KiB
PHP
208 lines
4.0 KiB
PHP
|
<?php
|
||
|
|
||
|
namespace Sphere\Debloat\OptimizeJs;
|
||
|
|
||
|
use Sphere\Debloat\Util;
|
||
|
use Sphere\Debloat\Base\Asset;
|
||
|
|
||
|
/**
|
||
|
* Class for handling scripts.
|
||
|
*
|
||
|
* @author asadkn
|
||
|
* @since 1.0.0
|
||
|
*/
|
||
|
class Script extends Asset
|
||
|
{
|
||
|
public $defer = false;
|
||
|
public $async = false;
|
||
|
public $delay = false;
|
||
|
|
||
|
/**
|
||
|
* Dependencies to mirror from core script.
|
||
|
*
|
||
|
* @var array
|
||
|
*/
|
||
|
public $deps = [];
|
||
|
|
||
|
/**
|
||
|
* Inner content for inline scripts
|
||
|
*
|
||
|
* @var string
|
||
|
*/
|
||
|
public $content = '';
|
||
|
|
||
|
/**
|
||
|
* @var array
|
||
|
*/
|
||
|
public $attrs = [];
|
||
|
|
||
|
/**
|
||
|
* Original HTML tag.
|
||
|
*/
|
||
|
public $orig_html = '';
|
||
|
|
||
|
/**
|
||
|
* Whether the script is dirty and needs rendering.
|
||
|
*
|
||
|
* @var boolean
|
||
|
*/
|
||
|
public $render = false;
|
||
|
|
||
|
/**
|
||
|
* @var callable|null
|
||
|
*/
|
||
|
protected $minifier;
|
||
|
|
||
|
public function __construct(string $id, string $url = '', string $orig_html = '')
|
||
|
{
|
||
|
$this->id = $id;
|
||
|
$this->url = $url;
|
||
|
$this->orig_url = $url;
|
||
|
$this->orig_html = $orig_html;
|
||
|
|
||
|
if (!$this->id) {
|
||
|
$this->id = md5($this->url ?: uniqid('debloat_script', true));
|
||
|
}
|
||
|
|
||
|
// Has to be processed early as it might have to be searched for things like defer.
|
||
|
$this->process_content();
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* 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);
|
||
|
|
||
|
// Only process scripts of type javascript or missing type (assumed JS by browser).
|
||
|
if (isset($attrs['type']) && strpos($attrs['type'], 'javascript') === false) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
$script = new self($attrs['id'] ?? '', $attrs['src'] ?? '', $tag);
|
||
|
|
||
|
// Note: We keep 'id' just in case if there was an original id.
|
||
|
$script->attrs = \array_diff_key($attrs, array_flip(['src', 'defer', 'async']));
|
||
|
|
||
|
if (isset($attrs['defer'])) {
|
||
|
$script->defer = true;
|
||
|
}
|
||
|
|
||
|
if (isset($attrs['async'])) {
|
||
|
$script->async = true;
|
||
|
}
|
||
|
|
||
|
return $script;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Render the HTML.
|
||
|
*
|
||
|
* @return string
|
||
|
*/
|
||
|
public function render()
|
||
|
{
|
||
|
// Add src if available.
|
||
|
if ($this->url) {
|
||
|
$this->attrs += [
|
||
|
'src' => $this->get_content_url(),
|
||
|
'id' => $this->id,
|
||
|
];
|
||
|
}
|
||
|
|
||
|
$this->process_delay();
|
||
|
|
||
|
// Setting local to avoid mutating.
|
||
|
$content = $this->content;
|
||
|
|
||
|
if (!$this->url && $content && is_callable($this->minifier)) {
|
||
|
$content = call_user_func($this->minifier, $content);
|
||
|
}
|
||
|
|
||
|
// Add defer for external scripts. For inline scripts, use data uri in src.
|
||
|
if ($this->defer) {
|
||
|
$this->attrs['defer'] = true;
|
||
|
|
||
|
if (!$this->url) {
|
||
|
$this->attrs['src'] = 'data:text/javascript;base64,' . base64_encode($content);
|
||
|
$content = '';
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if ($this->async) {
|
||
|
$this->attrs['async'] = true;
|
||
|
}
|
||
|
|
||
|
$render_atts = implode(' ', $this->render_attrs($this->attrs));
|
||
|
$new_tag = sprintf(
|
||
|
'<script%1$s>%2$s</script>',
|
||
|
$render_atts ? " $render_atts" : '',
|
||
|
!$this->url ? $content : ''
|
||
|
);
|
||
|
|
||
|
return $new_tag;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set the inner content, if any.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function process_content()
|
||
|
{
|
||
|
if (!$this->url) {
|
||
|
$this->content = preg_replace('#<script[^>]*>(.+?)</script>\s*$#is', '\\1', $this->orig_html);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Set a callable minifier function.
|
||
|
*
|
||
|
* @param callable $minifier
|
||
|
* @return void
|
||
|
*/
|
||
|
public function set_minifier($minifier)
|
||
|
{
|
||
|
$this->minifier = $minifier;
|
||
|
}
|
||
|
|
||
|
/**
|
||
|
* Process and add delayed script attributes if needed.
|
||
|
*
|
||
|
* @return void
|
||
|
*/
|
||
|
public function process_delay()
|
||
|
{
|
||
|
if (!$this->delay) {
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Defer / async shouldn't be enabled anymore. Delay load logic implies deferred anyways.
|
||
|
$this->defer = false;
|
||
|
$this->async = false;
|
||
|
|
||
|
// Add delay.
|
||
|
$this->attrs['data-debloat-delay'] = 1;
|
||
|
|
||
|
// Normal script with a URL.
|
||
|
if (!empty($this->attrs['src'])) {
|
||
|
$this->attrs['data-src'] = $this->attrs['src'];
|
||
|
}
|
||
|
// Inline script with content.
|
||
|
elseif ($this->content) {
|
||
|
|
||
|
if (!empty($this->attrs['type'])) {
|
||
|
$this->attrs['data-type'] = $this->attrs['type'];
|
||
|
}
|
||
|
|
||
|
$this->attrs['type'] = 'text/debloat-script';
|
||
|
}
|
||
|
|
||
|
unset($this->attrs['src']);
|
||
|
}
|
||
|
|
||
|
}
|