first commit
This commit is contained in:
966
class/sacy/sacy.php
Normal file
966
class/sacy/sacy.php
Normal file
@@ -0,0 +1,966 @@
|
||||
<?php
|
||||
|
||||
namespace sacy;
|
||||
|
||||
if (!defined('____SACY_BUNDLED')) {
|
||||
include_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'ext-translators.php']);
|
||||
}
|
||||
|
||||
if (!class_exists('JSMin') && !ExternalProcessorRegistry::typeIsSupported('text/javascript')) {
|
||||
include_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'jsmin.php']);
|
||||
}
|
||||
|
||||
if (!class_exists('Minify_CSS')) {
|
||||
include_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'cssmin.php']);
|
||||
}
|
||||
|
||||
if (!class_exists('lessc') && !ExternalProcessorRegistry::typeIsSupported('text/x-less')) {
|
||||
$less = implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'lessc.inc.php']);
|
||||
if (file_exists($less)) {
|
||||
include_once $less;
|
||||
}
|
||||
}
|
||||
|
||||
if (function_exists('CoffeeScript\compile')) {
|
||||
include_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'coffeescript.php']);
|
||||
} elseif (!ExternalProcessorRegistry::typeIsSupported('text/coffeescript')) {
|
||||
$coffee = implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), '..', 'coffeescript', 'coffeescript.php']);
|
||||
if (file_exists($coffee)) {
|
||||
include_once $coffee;
|
||||
include_once implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), 'coffeescript.php']);
|
||||
}
|
||||
}
|
||||
|
||||
if (!class_exists('SassParser') && !ExternalProcessorRegistry::typeIsSupported('text/x-sass')) {
|
||||
$sass = implode(DIRECTORY_SEPARATOR, [dirname(__FILE__), '..', 'sass', 'SassParser.php']);
|
||||
if (file_exists($sass)) {
|
||||
include_once $sass;
|
||||
}
|
||||
}
|
||||
|
||||
class Exception extends \Exception
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
* An earlier experiment contained a real framework for tag
|
||||
* and parser registration. In the end, this turned out
|
||||
* to be much too complex if we just need to support two tags
|
||||
* for two types of resources.
|
||||
*/
|
||||
|
||||
class WorkUnitExtractor
|
||||
{
|
||||
private $_cfg;
|
||||
|
||||
public function __construct(Config $config)
|
||||
{
|
||||
$this->_cfg = $config;
|
||||
}
|
||||
|
||||
public function getAcceptedWorkUnits($tags)
|
||||
{
|
||||
$work_units = [];
|
||||
foreach ($tags as $tag) {
|
||||
$r = $this->workUnitFromTag($tag['tag'], $tag['attrdata'], $tag['content']);
|
||||
if ($r === false) {
|
||||
continue;
|
||||
} // handler has declined
|
||||
$r = array_merge($r, [
|
||||
'page_order' => $tag['page_order'],
|
||||
'position' => $tag['index'],
|
||||
'length' => strlen($tag['tagdata']),
|
||||
'tag' => $tag['tag'],
|
||||
]);
|
||||
$work_units[] = $r;
|
||||
}
|
||||
|
||||
return $work_units;
|
||||
}
|
||||
|
||||
public function workUnitFromTag($tag, $attrdata, $content)
|
||||
{
|
||||
switch ($tag) {
|
||||
case 'link':
|
||||
case 'style':
|
||||
$fn = 'extract_style_unit';
|
||||
break;
|
||||
case 'script':
|
||||
$fn = 'extract_script_unit';
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Cannot handle tag: ({$tag})");
|
||||
}
|
||||
|
||||
return $this->$fn($tag, $attrdata, $content);
|
||||
}
|
||||
|
||||
private function extract_attrs($attstr)
|
||||
{
|
||||
// The attribute name regex is too relaxed, but let's
|
||||
// compromise and keep it simple.
|
||||
$attextract = '#([a-z\-]+)\s*=\s*(["\'])\s*(.*?)\s*\2#';
|
||||
if (!preg_match_all($attextract, $attstr, $m)) {
|
||||
return false;
|
||||
}
|
||||
$res = [];
|
||||
foreach ($m[1] as $idx => $name) {
|
||||
$res[strtolower($name)] = $m[3][$idx];
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
private function urlToFile($ref)
|
||||
{
|
||||
$u = parse_url($ref);
|
||||
if ($u === false) {
|
||||
return false;
|
||||
}
|
||||
if (isset($u['host']) || isset($u['scheme'])) {
|
||||
return $ref;
|
||||
}
|
||||
|
||||
if ($this->_cfg->get('query_strings') == 'ignore') {
|
||||
if (isset($u['query'])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$ref = $u['path'];
|
||||
$path = [$_SERVER['DOCUMENT_ROOT']];
|
||||
if (isAdministration()) {
|
||||
$cfg = \KupShop\KupShopBundle\Config::get();
|
||||
$path[] = $cfg['Path']['admin'];
|
||||
}
|
||||
$path[] = $ref;
|
||||
|
||||
return realpath(implode(DIRECTORY_SEPARATOR, $path));
|
||||
}
|
||||
|
||||
private function extract_style_unit($tag, $attrdata, $content)
|
||||
{
|
||||
$attrs = $this->extract_attrs($attrdata);
|
||||
$attrs['type'] = strtolower($attrs['type'] ?? '');
|
||||
|
||||
// invalid markup
|
||||
if ($tag == 'link' && !empty($content)) {
|
||||
return false;
|
||||
}
|
||||
if ($tag == 'style' && empty($content)) {
|
||||
return false;
|
||||
}
|
||||
if ($tag == 'link' && empty($attrs['href'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not a stylesheet
|
||||
if ($tag == 'link' && strtolower($attrs['rel']) != 'stylesheet') {
|
||||
return false;
|
||||
}
|
||||
|
||||
// type attribute required
|
||||
if (!isset($attrs['type'])) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not one of the supported types
|
||||
if (!in_array(strtolower($attrs['type']), CssRenderHandler::supportedTransformations())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// in debug mode 3, only transform
|
||||
if ($this->_cfg->getDebugMode() == 3
|
||||
&& !CssRenderHandler::willTransformType($attrs['type'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!isset($attrs['media'])) {
|
||||
$attrs['media'] = '';
|
||||
}
|
||||
|
||||
$include = null;
|
||||
if (isset($attrs['include'])) {
|
||||
$include = explode(';', $attrs['include']);
|
||||
}
|
||||
|
||||
$path = null;
|
||||
if (empty($content)) {
|
||||
$path = $this->urlToFile($attrs['href']);
|
||||
if ($path === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
$group = serialize($this->_cfg->get('merge_tags') ? [$attrs['media'], $attrs['type']] : [$attrs['media']]);
|
||||
|
||||
return [
|
||||
'group' => $group,
|
||||
'file' => $path,
|
||||
'content' => $content,
|
||||
'type' => $attrs['type'],
|
||||
'paths' => $include,
|
||||
];
|
||||
}
|
||||
|
||||
private function validTag($attrs)
|
||||
{
|
||||
$types = array_merge(['text/javascript', 'application/javascript'], JavaScriptRenderHandler::supportedTransformations());
|
||||
|
||||
return in_array($attrs['type'], $types);
|
||||
}
|
||||
|
||||
private function extract_script_unit($tag, $attrdata, $content)
|
||||
{
|
||||
$attrs = $this->extract_attrs($attrdata);
|
||||
if (empty($attrs['type'])) {
|
||||
$attrs['type'] = 'text/javascript';
|
||||
}
|
||||
$attrs['type'] = strtolower($attrs['type']);
|
||||
if ($this->_cfg->getDebugMode() == 3
|
||||
&& !JavaScriptRenderHandler::willTransformType($attrs['type'])
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->validTag($attrs)) {
|
||||
$path = null;
|
||||
if (!$content) {
|
||||
$path = $this->urlToFile($attrs['src']);
|
||||
if ($path === false) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return [
|
||||
'group' => '',
|
||||
'content' => $content,
|
||||
'file' => $path,
|
||||
'data' => $this->parseDataAttrs($attrs),
|
||||
'type' => $attrs['type'],
|
||||
];
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private function parseDataAttrs($attrs)
|
||||
{
|
||||
$data = [];
|
||||
|
||||
foreach ($attrs as $key => $value) {
|
||||
// Compromising again here on the valid
|
||||
// format of the attr key, to keep the
|
||||
// regex simple.
|
||||
if (preg_match('#^data-([a-z\-]+)$#', $key, $match)) {
|
||||
$name = $match[1];
|
||||
$data[$name] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
return $data;
|
||||
}
|
||||
}
|
||||
|
||||
class Config
|
||||
{
|
||||
private $params;
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return $this->params[$key] ?? null;
|
||||
}
|
||||
|
||||
public function __construct($params = null)
|
||||
{
|
||||
$this->params['query_strings'] = defined('SACY_QUERY_STRINGS') ? SACY_QUERY_STRINGS : 'ignore';
|
||||
$this->params['write_headers'] = defined('SACY_WRITE_HEADERS') ? SACY_WRITE_HEADERS : true;
|
||||
$this->params['debug_toggle'] = defined('SACY_DEBUG_TOGGLE') ? SACY_DEBUG_TOGGLE : '_sacy_debug';
|
||||
$this->params['merge_tags'] = false;
|
||||
|
||||
if (is_array($params)) {
|
||||
$this->setParams($params);
|
||||
}
|
||||
}
|
||||
|
||||
public function getDebugMode()
|
||||
{
|
||||
if ($this->params['debug_toggle'] === false) {
|
||||
return 0;
|
||||
}
|
||||
if (isset($_GET[$this->params['debug_toggle']])) {
|
||||
return intval($_GET[$this->params['debug_toggle']]);
|
||||
}
|
||||
if (isset($_COOKIE[$this->params['debug_toggle']])) {
|
||||
return intval($_COOKIE[$this->params['debug_toggle']]);
|
||||
}
|
||||
if (isDevelopment()) {
|
||||
return 3;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public function setParams($params)
|
||||
{
|
||||
foreach ($params as $key => $value) {
|
||||
if (!in_array($key, ['merge_tags', 'query_strings', 'write_headers', 'debug_toggle', 'block_ref'])) {
|
||||
throw new Exception("Invalid option: {$key}");
|
||||
}
|
||||
}
|
||||
if (isset($params['query_strings']) && !in_array($params['query_strings'], ['force-handle', 'ignore'])) {
|
||||
throw new Exception('Invalid setting for query_strings: '.$params['query_strings']);
|
||||
}
|
||||
if (isset($params['write_headers']) && !in_array($params['write_headers'], [true, false], true)) {
|
||||
throw new Exception('Invalid setting for write_headers: '.$params['write_headers']);
|
||||
}
|
||||
$params['merge_tags'] = (bool) getVal('merge_tags', $params);
|
||||
|
||||
$this->params = array_merge($this->params, $params);
|
||||
}
|
||||
}
|
||||
|
||||
class CacheRenderer
|
||||
{
|
||||
private $_cfg;
|
||||
private $_source_file;
|
||||
|
||||
/** @var FileCache */
|
||||
private $fragment_cache;
|
||||
|
||||
private $rendered_bits;
|
||||
|
||||
public function __construct(Config $config, $source_file)
|
||||
{
|
||||
$this->_cfg = $config;
|
||||
$this->_source_file = $source_file;
|
||||
$this->rendered_bits = [];
|
||||
|
||||
$class = defined('SACY_FRAGMENT_CACHE_CLASS') ?
|
||||
SACY_FRAGMENT_CACHE_CLASS :
|
||||
'sacy\FileCache';
|
||||
$this->fragment_cache = new $class();
|
||||
|
||||
foreach (['get', 'set'] as $m) {
|
||||
if (!method_exists($this->fragment_cache, $m)) {
|
||||
throw new Exception('Invalid fragment cache class specified');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function allowMergedTransformOnly($tag)
|
||||
{
|
||||
return $tag == 'script';
|
||||
}
|
||||
|
||||
public function renderWorkUnits($tag, $cat, $work_units)
|
||||
{
|
||||
switch ($tag) {
|
||||
case 'link':
|
||||
case 'style':
|
||||
$fn = 'render_style_units';
|
||||
break;
|
||||
case 'script':
|
||||
$fn = 'render_script_units';
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Cannot handle tag: {$tag}");
|
||||
}
|
||||
|
||||
return $this->$fn($work_units, $cat);
|
||||
}
|
||||
|
||||
public function getRenderedAssets()
|
||||
{
|
||||
return array_reverse($this->rendered_bits);
|
||||
}
|
||||
|
||||
private function render_style_units($work_units, $cat)
|
||||
{
|
||||
// we can do this because tags are grouped by the presence of a file or not
|
||||
$cs = '';
|
||||
if ($cat) {
|
||||
$c = unserialize($cat);
|
||||
$cs = $cat ? sprintf(' media="%s"', htmlspecialchars($c[0], ENT_QUOTES)) : '';
|
||||
}
|
||||
if ($work_units[0]['file']) {
|
||||
if ($res = $this->generate_file_cache($work_units, new CssRenderHandler($this->_cfg, $this->_source_file))) {
|
||||
$res = sprintf('<link rel="stylesheet" type="text/css"%s href="%s" />'."\n", $cs, htmlspecialchars($res, ENT_QUOTES));
|
||||
}
|
||||
} else {
|
||||
$res = $this->generate_content_cache($work_units, new CssRenderHandler($this->_cfg, $this->_source_file));
|
||||
$res = sprintf('<style type="text/css"%s>%s</style>'."\n", $cs, $res);
|
||||
}
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
private function render_script_units($work_units, $cat)
|
||||
{
|
||||
if ($work_units[0]['file']) {
|
||||
if ($res = $this->generate_file_cache($work_units, new JavaScriptRenderHandler($this->_cfg, $this->_source_file))) {
|
||||
$this->rendered_bits[] = ['type' => 'file', 'src' => $res];
|
||||
|
||||
return sprintf('<script type="text/javascript" src="%s"></script>'."\n", htmlspecialchars($res, ENT_QUOTES));
|
||||
}
|
||||
} else {
|
||||
$res = $this->generate_content_cache($work_units, new JavaScriptRenderHandler($this->_cfg, $this->_source_file));
|
||||
if ($res) {
|
||||
$this->rendered_bits[] = ['type' => 'string', 'content' => $res];
|
||||
}
|
||||
|
||||
return sprintf('<script type="text/javascript">%s</script>'."\n", $res);
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
private function generate_content_cache($work_units, CacheRenderHandler $rh)
|
||||
{
|
||||
$content = implode("\n", array_map(function ($u) {
|
||||
return $u['content'];
|
||||
}, $work_units));
|
||||
$key = md5($content.$this->_cfg->getDebugMode());
|
||||
if ($d = $this->fragment_cache->get($key)) {
|
||||
return $d;
|
||||
}
|
||||
$output = [];
|
||||
foreach ($work_units as $w) {
|
||||
$output[] = $rh->getOutput($w);
|
||||
}
|
||||
$output = implode("\n", $output);
|
||||
$this->fragment_cache->set($key, $output);
|
||||
|
||||
return $output;
|
||||
}
|
||||
|
||||
private function content_key_for_mtime_key($key, $work_units)
|
||||
{
|
||||
if (!(defined('SACY_USE_CONTENT_BASED_CACHE') && SACY_USE_CONTENT_BASED_CACHE)) {
|
||||
return $key;
|
||||
}
|
||||
|
||||
$cache_key = 'ck-for-mkey-'.$key;
|
||||
|
||||
$ck = $this->fragment_cache->get($cache_key);
|
||||
if (!$ck) {
|
||||
$ck = '';
|
||||
foreach ($work_units as $f) {
|
||||
$ck = md5($ck.md5_file($f['file']));
|
||||
foreach ($f['additional_files'] as $af) {
|
||||
$ck = md5($ck.md5_file($af));
|
||||
}
|
||||
}
|
||||
$ck = "{$ck}-content";
|
||||
$this->fragment_cache->set($cache_key, $ck);
|
||||
}
|
||||
|
||||
return $ck;
|
||||
}
|
||||
|
||||
private function generate_file_cache($work_units, CacheRenderHandler $rh)
|
||||
{
|
||||
if (!is_dir(ASSET_COMPILE_OUTPUT_DIR)) {
|
||||
if (!@mkdir(ASSET_COMPILE_OUTPUT_DIR, 0755, true)) {
|
||||
throw new Exception('Failed to create output directory');
|
||||
}
|
||||
}
|
||||
|
||||
$f = function ($f) {
|
||||
return createScriptURL_Text(pathinfo($f['file'], PATHINFO_FILENAME));
|
||||
};
|
||||
|
||||
$ident = implode('-', array_map($f, $work_units));
|
||||
if (strlen($ident) > 120) {
|
||||
$ident = 'many-files-'.md5($ident);
|
||||
}
|
||||
$max = 0;
|
||||
$idents = [];
|
||||
foreach ($work_units as &$f) {
|
||||
$idents[] = [
|
||||
$f['group'], $f['file'], $f['type'], $f['tag'],
|
||||
];
|
||||
$f['additional_files'] = $rh->getAdditionalFiles($f);
|
||||
$max = max($max, filemtime($f['file']));
|
||||
foreach ($f['additional_files'] as $af) {
|
||||
$max = max($max, filemtime($af));
|
||||
}
|
||||
unset($f);
|
||||
}
|
||||
|
||||
// not using the actual content for quicker access
|
||||
$key = md5($max.serialize($idents).$rh->getConfig()->getDebugMode());
|
||||
$key = $this->content_key_for_mtime_key($key, $work_units);
|
||||
|
||||
$cfile = ASSET_COMPILE_OUTPUT_DIR.DIRECTORY_SEPARATOR."{$ident}-{$key}".$rh->getFileExtension();
|
||||
$pub = ASSET_COMPILE_URL_ROOT."/{$ident}-{$key}".$rh->getFileExtension();
|
||||
|
||||
if (file_exists($cfile) && ($rh->getConfig()->getDebugMode() != 2)) {
|
||||
return $pub;
|
||||
}
|
||||
|
||||
$this->write_cache($cfile, $work_units, $rh);
|
||||
|
||||
return $pub;
|
||||
}
|
||||
|
||||
private function write_cache($cfile, $files, CacheRenderHandler $rh)
|
||||
{
|
||||
$tmpfile = $this->write_cache_tmpfile($cfile, $files, $rh);
|
||||
|
||||
if ($tmpfile) {
|
||||
$ts = time();
|
||||
|
||||
$this->write_compressed_cache($tmpfile, $cfile, $ts);
|
||||
|
||||
if (rename($tmpfile, $cfile)) {
|
||||
chmod($cfile, 0644);
|
||||
touch($cfile, $ts);
|
||||
} else {
|
||||
trigger_error("Cannot write file: {$cfile}", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
return (bool) $tmpfile;
|
||||
}
|
||||
|
||||
private function write_compressed_cache($tmpfile, $cfile, $ts)
|
||||
{
|
||||
if (!function_exists('gzencode')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tmp_compressed = "{$tmpfile}.gz";
|
||||
file_put_contents($tmp_compressed, gzencode(file_get_contents($tmpfile), 9));
|
||||
|
||||
$compressed = "{$cfile}.gz";
|
||||
if (rename($tmp_compressed, $compressed)) {
|
||||
touch($compressed, $ts);
|
||||
} else {
|
||||
trigger_error("Cannot write compressed file: {$compressed}", E_USER_WARNING);
|
||||
}
|
||||
}
|
||||
|
||||
private function write_cache_tmpfile($cfile, $files, CacheRenderHandler $rh)
|
||||
{
|
||||
$tmpfile = tempnam(dirname($cfile), $cfile);
|
||||
|
||||
$fhc = @fopen($tmpfile, 'w+');
|
||||
if (!$fhc) {
|
||||
trigger_error("Cannot write to temporary file: {$tmpfile}", E_USER_WARNING);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($rh->getConfig()->get('write_headers')) {
|
||||
$rh->writeHeader($fhc, $files);
|
||||
}
|
||||
|
||||
$res = true;
|
||||
$merge = (bool) $rh->getConfig()->get('merge_tags');
|
||||
|
||||
if ($merge) {
|
||||
$rh->startWrite();
|
||||
}
|
||||
|
||||
foreach ($files as $file) {
|
||||
try {
|
||||
$rh->processFile($fhc, $file);
|
||||
} catch (\Exception $e) {
|
||||
getRaven()->captureException($e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
if ($merge) {
|
||||
$rh->endWrite($fhc);
|
||||
}
|
||||
|
||||
fclose($fhc);
|
||||
|
||||
return $res ? $tmpfile : null;
|
||||
}
|
||||
}
|
||||
|
||||
interface CacheRenderHandler
|
||||
{
|
||||
public function __construct(Config $cfg, $source_file);
|
||||
|
||||
public function getFileExtension();
|
||||
|
||||
public static function willTransformType($type);
|
||||
|
||||
public function writeHeader($fh, $work_units);
|
||||
|
||||
public function getAdditionalFiles($work_unit);
|
||||
|
||||
public function processFile($fh, $work_unit);
|
||||
|
||||
public function startWrite();
|
||||
|
||||
public function endWrite($fh);
|
||||
|
||||
public function getOutput($work_unit);
|
||||
|
||||
public function getConfig();
|
||||
}
|
||||
|
||||
abstract class ConfiguredRenderHandler implements CacheRenderHandler
|
||||
{
|
||||
private $_cfg;
|
||||
private $_source_file;
|
||||
|
||||
public function __construct(Config $cfg, $source_file)
|
||||
{
|
||||
$this->_cfg = $cfg;
|
||||
$this->_source_file = $source_file;
|
||||
}
|
||||
|
||||
protected function getSourceFile()
|
||||
{
|
||||
return $this->_source_file;
|
||||
}
|
||||
|
||||
public function getConfig()
|
||||
{
|
||||
return $this->_cfg;
|
||||
}
|
||||
|
||||
public static function willTransformType($type)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public function startWrite()
|
||||
{
|
||||
}
|
||||
|
||||
public function endWrite($fh)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
class JavaScriptRenderHandler extends ConfiguredRenderHandler
|
||||
{
|
||||
public static function supportedTransformations()
|
||||
{
|
||||
$supported = [];
|
||||
|
||||
if (function_exists('CoffeeScript\compile') || ExternalProcessorRegistry::typeIsSupported('text/coffeescript')) {
|
||||
$supported[] = 'text/coffeescript';
|
||||
}
|
||||
|
||||
if (ExternalProcessorRegistry::typeIsSupported('text/x-eco')) {
|
||||
$supported[] = 'text/x-eco';
|
||||
}
|
||||
|
||||
if (ExternalProcessorRegistry::typeIsSupported('text/x-jsx')) {
|
||||
$supported[] = 'text/x-jsx';
|
||||
}
|
||||
|
||||
return $supported;
|
||||
}
|
||||
|
||||
public static function willTransformType($type)
|
||||
{
|
||||
// transforming everything but plain old CSS
|
||||
return in_array($type, self::supportedTransformations());
|
||||
}
|
||||
|
||||
public function getFileExtension()
|
||||
{
|
||||
return '.js';
|
||||
}
|
||||
|
||||
public function writeHeader($fh, $work_units)
|
||||
{
|
||||
fwrite($fh, "/*\nsacy javascript cache dump \n\n");
|
||||
fwrite($fh, "This dump has been created from the following files:\n");
|
||||
foreach ($work_units as $file) {
|
||||
fprintf($fh, " - %s\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $file['file']));
|
||||
}
|
||||
fwrite($fh, "*/\n\n");
|
||||
}
|
||||
|
||||
public function getOutput($work_unit)
|
||||
{
|
||||
$debug = $this->getConfig()->getDebugMode() == 3;
|
||||
if ($work_unit['file']) {
|
||||
$js = @file_get_contents($work_unit['file']);
|
||||
if (!$js) {
|
||||
return '/* error accessing file */';
|
||||
}
|
||||
$source_file = $work_unit['file'];
|
||||
} else {
|
||||
$js = $work_unit['content'];
|
||||
$source_file = $this->getSourceFile();
|
||||
}
|
||||
|
||||
if ($work_unit['type'] == 'text/coffeescript') {
|
||||
$js = ExternalProcessorRegistry::typeIsSupported('text/coffeescript') ?
|
||||
ExternalProcessorRegistry::getTransformerForType('text/coffeescript')->transform($js, $source_file) :
|
||||
\Coffeescript::build($js);
|
||||
} elseif ($work_unit['type'] == 'text/x-eco') {
|
||||
$eco = ExternalProcessorRegistry::getTransformerForType('text/x-eco');
|
||||
$js = $eco->transform($js, $source_file, $work_unit['data']);
|
||||
} elseif ($work_unit['type'] == 'text/x-jsx') {
|
||||
$jsx = ExternalProcessorRegistry::getTransformerForType('text/x-jsx');
|
||||
$js = $jsx->transform($js, $source_file, $work_unit['data']);
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
return $js;
|
||||
} else {
|
||||
return ExternalProcessorRegistry::typeIsSupported('text/javascript') ?
|
||||
ExternalProcessorRegistry::getCompressorForType('text/javascript')->transform($js, $source_file) :
|
||||
\JSMin::minify($js);
|
||||
}
|
||||
}
|
||||
|
||||
public function processFile($fh, $work_unit)
|
||||
{
|
||||
if ($this->getConfig()->get('write_headers')) {
|
||||
fprintf($fh, "\n/* %s */\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $work_unit['file']));
|
||||
}
|
||||
fwrite($fh, $this->getOutput($work_unit));
|
||||
}
|
||||
|
||||
public function getAdditionalFiles($work_unit)
|
||||
{
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
class CssRenderHandler extends ConfiguredRenderHandler
|
||||
{
|
||||
private $to_process = [];
|
||||
private $collecting = false;
|
||||
|
||||
public static function supportedTransformations()
|
||||
{
|
||||
$res = ['', 'text/css'];
|
||||
if (class_exists('lessc') || ExternalProcessorRegistry::typeIsSupported('text/x-less')) {
|
||||
$res[] = 'text/x-less';
|
||||
}
|
||||
if (class_exists('SassParser') || ExternalProcessorRegistry::typeIsSupported('text/x-sass')) {
|
||||
$res = array_merge($res, ['text/x-sass', 'text/x-scss']);
|
||||
}
|
||||
if (PhpSassSacy::isAvailable()) {
|
||||
$res[] = 'text/x-scss';
|
||||
}
|
||||
if (ExternalProcessorRegistry::typeIsSupported('image/svg+xml')) {
|
||||
$res[] = 'image/svg+xml';
|
||||
}
|
||||
|
||||
return array_unique($res);
|
||||
}
|
||||
|
||||
public function getFileExtension()
|
||||
{
|
||||
return '.css';
|
||||
}
|
||||
|
||||
public static function willTransformType($type)
|
||||
{
|
||||
// transforming everything but plain old CSS
|
||||
return !in_array($type, ['', 'text/css']);
|
||||
}
|
||||
|
||||
public function writeHeader($fh, $work_units)
|
||||
{
|
||||
fwrite($fh, "/*\nsacy css cache dump \n\n");
|
||||
fwrite($fh, "This dump has been created from the following files:\n");
|
||||
foreach ($work_units as $file) {
|
||||
fprintf($fh, " - %s\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $file['file']));
|
||||
}
|
||||
fwrite($fh, "*/\n\n");
|
||||
}
|
||||
|
||||
public function processFile($fh, $work_unit)
|
||||
{
|
||||
// for now: only support collecting for scss and sass
|
||||
if (!in_array($work_unit['type'], ['text/x-scss', 'text/x-sass'])) {
|
||||
$this->collecting = false;
|
||||
}
|
||||
if ($this->collecting) {
|
||||
$content = @file_get_contents($work_unit['file']);
|
||||
if (!$content) {
|
||||
$content = "/* error accessing file {$work_unit['file']} */";
|
||||
}
|
||||
|
||||
$content = \Minify_CSS_UriRewriter::rewrite(
|
||||
$content,
|
||||
dirname($work_unit['file']),
|
||||
$_SERVER['DOCUMENT_ROOT'],
|
||||
[],
|
||||
true
|
||||
);
|
||||
|
||||
$this->to_process[] = [
|
||||
'file' => $work_unit['file'],
|
||||
'content' => $content,
|
||||
'type' => $work_unit['type'],
|
||||
];
|
||||
} else {
|
||||
if ($this->getConfig()->get('write_headers')) {
|
||||
fprintf($fh, "\n/* %s */\n", str_replace($_SERVER['DOCUMENT_ROOT'], '<root>', $work_unit['file']));
|
||||
}
|
||||
|
||||
fwrite($fh, $this->getOutput($work_unit));
|
||||
}
|
||||
}
|
||||
|
||||
public function endWrite($fh)
|
||||
{
|
||||
if (!$this->collecting) {
|
||||
return;
|
||||
}
|
||||
$content = '';
|
||||
$incpath = [];
|
||||
foreach ($this->to_process as $job) {
|
||||
$content .= $job['content'];
|
||||
$incpath[] = dirname($job['file']);
|
||||
}
|
||||
|
||||
fwrite($fh, $this->getOutput([
|
||||
'content' => $content,
|
||||
'type' => $this->to_process[0]['type'],
|
||||
'paths' => $incpath,
|
||||
]));
|
||||
}
|
||||
|
||||
public function getOutput($work_unit)
|
||||
{
|
||||
$debug = $this->getConfig()->getDebugMode() == 3;
|
||||
|
||||
if ($work_unit['file']) {
|
||||
$css = @file_get_contents($work_unit['file']);
|
||||
if (!$css) {
|
||||
return '/* error accessing file */';
|
||||
}
|
||||
$source_file = $work_unit['file'];
|
||||
} else {
|
||||
$css = $work_unit['content'];
|
||||
$source_file = $this->getSourceFile();
|
||||
}
|
||||
|
||||
if (ExternalProcessorRegistry::typeIsSupported($work_unit['type'])) {
|
||||
$opts = [];
|
||||
if ($work_unit['paths']) {
|
||||
$opts['library_path'] = $work_unit['paths'];
|
||||
}
|
||||
$css = ExternalProcessorRegistry::getTransformerForType($work_unit['type'])
|
||||
->transform($css, $source_file, $opts);
|
||||
} else {
|
||||
if ($work_unit['type'] == 'text/x-less') {
|
||||
$less = new \lessc();
|
||||
$less->importDir = dirname($source_file).'/'; // lessphp concatenates without a /
|
||||
$css = $less->parse($css);
|
||||
}
|
||||
if (PhpSassSacy::isAvailable() && $work_unit['type'] == 'text/x-scss') {
|
||||
$css = PhpSassSacy::compile($work_unit['file'], $css, $work_unit['paths'] ?: [dirname($source_file)]);
|
||||
} elseif (in_array($work_unit['type'], ['text/x-scss', 'text/x-sass'])) {
|
||||
$config = [
|
||||
'cache' => false, // no need. WE are the cache!
|
||||
'debug_info' => $debug,
|
||||
'line' => $debug,
|
||||
'load_paths' => $work_unit['paths'] ?: [dirname($source_file)],
|
||||
'filename' => $source_file,
|
||||
'quiet' => true,
|
||||
'style' => $debug ? 'nested' : 'compressed',
|
||||
];
|
||||
$sass = new \SassParser($config);
|
||||
$css = $sass->toCss($css, false); // isFile?
|
||||
}
|
||||
}
|
||||
|
||||
if ($debug) {
|
||||
return \Minify_CSS_UriRewriter::rewrite(
|
||||
$css,
|
||||
dirname($source_file),
|
||||
$_SERVER['DOCUMENT_ROOT'],
|
||||
[]
|
||||
);
|
||||
} else {
|
||||
return \Minify_CSS::minify($css, [
|
||||
'currentDir' => dirname($source_file),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
private function extract_import_file($parent_type, $parent_file, $cssdata)
|
||||
{
|
||||
$f = null;
|
||||
if (preg_match('#^\s*url\((["\'])([^\1]+)\1#', $cssdata, $matches)) {
|
||||
$f = $matches[2];
|
||||
} elseif (preg_match('#^\s*(["\'])([^\1]+)\1#', $cssdata, $matches)) {
|
||||
$f = $matches[2];
|
||||
}
|
||||
$path_info = pathinfo($parent_file);
|
||||
if (in_array($parent_type, ['text/x-scss', 'text/x-sass'])) {
|
||||
$ext = preg_quote($path_info['extension'], '#');
|
||||
if (!preg_match("#.{$ext}\$#", $f)) {
|
||||
$f .= '.'.$path_info['extension'];
|
||||
}
|
||||
|
||||
$mixin = $path_info['dirname'].DIRECTORY_SEPARATOR."_{$f}";
|
||||
if (file_exists($mixin)) {
|
||||
return $mixin;
|
||||
}
|
||||
} elseif ($parent_type == 'text/x-less') {
|
||||
// less only inlines @import's of .less files (see: http://lesscss.org/#-importing)
|
||||
if (!preg_match('#\.less$', $f)) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
$f = $path_info['dirname'].DIRECTORY_SEPARATOR.$f;
|
||||
|
||||
return file_exists($f) ? $f : null;
|
||||
}
|
||||
|
||||
private function find_imports($type, $file, $level)
|
||||
{
|
||||
$level++;
|
||||
if (!in_array($type, ['text/x-scss', 'text/x-sass', 'text/x-less'])) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if ($level > 10) {
|
||||
throw new Exception("CSS Include nesting level of {$level} too deep");
|
||||
}
|
||||
$fh = fopen($file, 'r');
|
||||
$res = [];
|
||||
while (false !== ($line = fgets($fh))) {
|
||||
if (preg_match('#^\s*$#', $line)) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('#^\s*@import(.*)$#', $line, $matches)) {
|
||||
$f = $this->extract_import_file($type, $file, $matches[1]);
|
||||
if ($f) {
|
||||
$res[] = $f;
|
||||
$res = array_merge($res, $this->find_imports($type, $f, $level));
|
||||
}
|
||||
}
|
||||
}
|
||||
fclose($fh);
|
||||
|
||||
return $res;
|
||||
}
|
||||
|
||||
public function getAdditionalFiles($work_unit)
|
||||
{
|
||||
$level = 0;
|
||||
|
||||
return $this->find_imports($work_unit['type'], $work_unit['file'], $level);
|
||||
}
|
||||
|
||||
public function startWrite()
|
||||
{
|
||||
$this->to_process = [];
|
||||
$this->collecting = true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user