first commit
This commit is contained in:
9
class/sacy/coffeescript.php
Normal file
9
class/sacy/coffeescript.php
Normal file
@@ -0,0 +1,9 @@
|
||||
<?php
|
||||
|
||||
class CoffeeScript
|
||||
{
|
||||
public static function build($file)
|
||||
{
|
||||
return CoffeeScript\compile($file);
|
||||
}
|
||||
}
|
||||
625
class/sacy/cssmin.php
Normal file
625
class/sacy/cssmin.php
Normal file
@@ -0,0 +1,625 @@
|
||||
<?php
|
||||
|
||||
/* Taken from minify by Ryan Grove and Steve Clay and distributed
|
||||
under the following license:
|
||||
|
||||
Copyright (c) 2008 Ryan Grove <ryan@wonko.com>
|
||||
Copyright (c) 2008 Steve Clay <steve@mrclay.org>
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of this project nor the names of its contributors may be
|
||||
used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
*/
|
||||
|
||||
class Minify_CSS
|
||||
{
|
||||
public static function minify($css, $options = [])
|
||||
{
|
||||
if (isset($options['preserveComments'])
|
||||
&& !$options['preserveComments']
|
||||
) {
|
||||
$css = Minify_CSS_Compressor::process($css, $options);
|
||||
} else {
|
||||
$css = Minify_CommentPreserver::process(
|
||||
$css, ['Minify_CSS_Compressor', 'process'], [$options]
|
||||
);
|
||||
}
|
||||
if (!isset($options['currentDir']) && !isset($options['prependRelativePath'])) {
|
||||
return $css;
|
||||
}
|
||||
if (isset($options['currentDir'])) {
|
||||
return Minify_CSS_UriRewriter::rewrite(
|
||||
$css, $options['currentDir'], isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT'], isset($options['symlinks']) ? $options['symlinks'] : []
|
||||
);
|
||||
} else {
|
||||
return Minify_CSS_UriRewriter::prepend(
|
||||
$css, $options['prependRelativePath']
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Minify_CSS_UriRewriter
|
||||
{
|
||||
/**
|
||||
* Defines which class to call as part of callbacks, change this
|
||||
* if you extend Minify_CSS_UriRewriter.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected static $className = 'Minify_CSS_UriRewriter';
|
||||
|
||||
/**
|
||||
* rewrite() and rewriteRelative() append debugging information here.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $debugText = '';
|
||||
|
||||
/**
|
||||
* Rewrite file relative URIs as root relative in CSS files.
|
||||
*
|
||||
* @param string $css
|
||||
* @param string $currentDir the directory of the current CSS file
|
||||
* @param string $docRoot the document root of the web site in which
|
||||
* the CSS file resides (default = $_SERVER['DOCUMENT_ROOT'])
|
||||
* @param array $symlinks (default = array()) If the CSS file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* target paths, where the link paths are within the document root. Because
|
||||
* paths need to be normalized for this to work, use "//" to substitute
|
||||
* the doc root in the link paths (the array keys). E.g.:
|
||||
* <code>
|
||||
* array('//symlink' => '/real/target/path') // unix
|
||||
* array('//static' => 'D:\\staticStorage') // Windows
|
||||
* </code>
|
||||
* @param bool $leave_imports don't rewrite imports. Only touch URLs
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = [], $leave_imports = false)
|
||||
{
|
||||
self::$_docRoot = self::_realpath(
|
||||
$docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT']
|
||||
);
|
||||
self::$_currentDir = self::_realpath($currentDir);
|
||||
self::$_symlinks = [];
|
||||
|
||||
// normalize symlinks
|
||||
foreach ($symlinks as $link => $target) {
|
||||
$link = ($link === '//')
|
||||
? self::$_docRoot
|
||||
: str_replace('//', self::$_docRoot.'/', $link);
|
||||
$link = strtr($link, '/', DIRECTORY_SEPARATOR);
|
||||
self::$_symlinks[$link] = self::_realpath($target);
|
||||
}
|
||||
|
||||
self::$debugText .= 'docRoot : '.self::$_docRoot."\n"
|
||||
.'currentDir : '.self::$_currentDir."\n";
|
||||
if (self::$_symlinks) {
|
||||
self::$debugText .= 'symlinks : '.var_export(self::$_symlinks, 1)."\n";
|
||||
}
|
||||
self::$debugText .= "\n";
|
||||
|
||||
$css = self::_trimUrls($css);
|
||||
|
||||
// rewrite
|
||||
if (!$leave_imports) {
|
||||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/', [self::$className, '_processUriCB'], $css);
|
||||
}
|
||||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/', [self::$className, '_processUriCB'], $css);
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepend a path to relative URIs in CSS files.
|
||||
*
|
||||
* @param string $css
|
||||
* @param string $path the path to prepend
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function prepend($css, $path)
|
||||
{
|
||||
self::$_prependPath = $path;
|
||||
|
||||
$css = self::_trimUrls($css);
|
||||
|
||||
// append
|
||||
$css = preg_replace_callback('/@import\\s+([\'"])(.*?)[\'"]/', [self::$className, '_processUriCB'], $css);
|
||||
$css = preg_replace_callback('/url\\(\\s*([^\\)\\s]+)\\s*\\)/', [self::$className, '_processUriCB'], $css);
|
||||
|
||||
self::$_prependPath = null;
|
||||
|
||||
return $css;
|
||||
}
|
||||
|
||||
/**
|
||||
* @var string directory of this stylesheet
|
||||
*/
|
||||
private static $_currentDir = '';
|
||||
|
||||
/**
|
||||
* @var string DOC_ROOT
|
||||
*/
|
||||
private static $_docRoot = '';
|
||||
|
||||
/**
|
||||
* @var array directory replacements to map symlink targets back to their
|
||||
* source (within the document root) E.g. '/var/www/symlink' => '/var/realpath'
|
||||
*/
|
||||
private static $_symlinks = [];
|
||||
|
||||
/**
|
||||
* @var string path to prepend
|
||||
*/
|
||||
private static $_prependPath;
|
||||
|
||||
private static function _trimUrls($css)
|
||||
{
|
||||
return preg_replace('/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = URI (assuming does not contain ")")
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
}
|
||||
|
||||
private static function _processUriCB($m)
|
||||
{
|
||||
// $m matched either '/@import\\s+([\'"])(.*?)[\'"]/' or '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'
|
||||
$isImport = ($m[0][0] === '@');
|
||||
// determine URI and the quote character (if any)
|
||||
if ($isImport) {
|
||||
$quoteChar = $m[1];
|
||||
$uri = $m[2];
|
||||
} else {
|
||||
// $m[1] is either quoted or not
|
||||
$quoteChar = ($m[1][0] === "'" || $m[1][0] === '"')
|
||||
? $m[1][0]
|
||||
: '';
|
||||
$uri = ($quoteChar === '')
|
||||
? $m[1]
|
||||
: substr($m[1], 1, strlen($m[1]) - 2);
|
||||
}
|
||||
// analyze URI
|
||||
if ('/' !== $uri[0] // root-relative
|
||||
&& false === strpos($uri, '//') // protocol (non-data)
|
||||
&& 0 !== strpos($uri, 'data:') // data protocol
|
||||
) {
|
||||
// URI is file-relative: rewrite depending on options
|
||||
$uri = (self::$_prependPath !== null)
|
||||
? (self::$_prependPath.$uri)
|
||||
: self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks);
|
||||
}
|
||||
|
||||
return $isImport
|
||||
? "@import {$quoteChar}{$uri}{$quoteChar}"
|
||||
: "url({$quoteChar}{$uri}{$quoteChar})";
|
||||
}
|
||||
|
||||
/**
|
||||
* Rewrite a file relative URI as root relative.
|
||||
*
|
||||
* <code>
|
||||
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||
* '../img/hello.gif'
|
||||
* , '/home/user/www/css' // path of CSS file
|
||||
* , '/home/user/www' // doc root
|
||||
* );
|
||||
* // returns '/img/hello.gif'
|
||||
*
|
||||
* // example where static files are stored in a symlinked directory
|
||||
* Minify_CSS_UriRewriter::rewriteRelative(
|
||||
* 'hello.gif'
|
||||
* , '/var/staticFiles/theme'
|
||||
* , '/home/user/www'
|
||||
* , array('/home/user/www/static' => '/var/staticFiles')
|
||||
* );
|
||||
* // returns '/static/theme/hello.gif'
|
||||
* </code>
|
||||
*
|
||||
* @param string $uri file relative URI
|
||||
* @param string $realCurrentDir realpath of the current file's directory
|
||||
* @param string $realDocRoot realpath of the site document root
|
||||
* @param array $symlinks (default = array()) If the file is stored in
|
||||
* a symlink-ed directory, provide an array of link paths to
|
||||
* real target paths, where the link paths "appear" to be within the document
|
||||
* root. E.g.:
|
||||
* <code>
|
||||
* array('/home/foo/www/not/real/path' => '/real/target/path') // unix
|
||||
* array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows
|
||||
* </code>
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = [])
|
||||
{
|
||||
// prepend path with current dir separator (OS-independent)
|
||||
$path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR)
|
||||
.DIRECTORY_SEPARATOR.strtr($uri, '/', DIRECTORY_SEPARATOR);
|
||||
|
||||
self::$debugText .= "file-relative URI : {$uri}\n"
|
||||
."path prepended : {$path}\n";
|
||||
|
||||
// "unresolve" a symlink back to doc root
|
||||
foreach ($symlinks as $link => $target) {
|
||||
if (0 === strpos($path, $target)) {
|
||||
// replace $target with $link
|
||||
$path = $link.substr($path, strlen($target));
|
||||
|
||||
self::$debugText .= "symlink unresolved : {$path}\n";
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
// strip doc root
|
||||
$path = substr($path, strlen($realDocRoot));
|
||||
|
||||
self::$debugText .= "docroot stripped : {$path}\n";
|
||||
|
||||
// fix to root-relative URI
|
||||
|
||||
$uri = strtr($path, '/\\', '//');
|
||||
|
||||
// remove /./ and /../ where possible
|
||||
$uri = str_replace('/./', '/', $uri);
|
||||
// inspired by patch from Oleg Cherniy
|
||||
do {
|
||||
$uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed);
|
||||
} while ($changed);
|
||||
|
||||
self::$debugText .= "traversals removed : {$uri}\n\n";
|
||||
|
||||
return $uri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get realpath with any trailing slash removed. If realpath() fails,
|
||||
* just remove the trailing slash.
|
||||
*
|
||||
* @param string $path
|
||||
*
|
||||
* @return mixed path with no trailing slash
|
||||
*/
|
||||
protected static function _realpath($path)
|
||||
{
|
||||
$realPath = realpath($path);
|
||||
if ($realPath !== false) {
|
||||
$path = $realPath;
|
||||
}
|
||||
|
||||
return rtrim($path, '/\\');
|
||||
}
|
||||
}
|
||||
|
||||
class Minify_CommentPreserver
|
||||
{
|
||||
/**
|
||||
* String to be prepended to each preserved comment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $prepend = "\n";
|
||||
|
||||
/**
|
||||
* String to be appended to each preserved comment.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
public static $append = "\n";
|
||||
|
||||
/**
|
||||
* Process a string outside of C-style comments that begin with "/*!".
|
||||
*
|
||||
* On each non-empty string outside these comments, the given processor
|
||||
* function will be called. The first "!" will be removed from the
|
||||
* preserved comments, and the comments will be surrounded by
|
||||
* Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append.
|
||||
*
|
||||
* @param string $content
|
||||
* @param callable $processor function
|
||||
* @param array $args array of extra arguments to pass to the processor
|
||||
* function (default = array())
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function process($content, $processor, $args = [])
|
||||
{
|
||||
$ret = '';
|
||||
while (true) {
|
||||
list($beforeComment, $comment, $afterComment) = self::_nextComment($content);
|
||||
if ('' !== $beforeComment) {
|
||||
$callArgs = $args;
|
||||
array_unshift($callArgs, $beforeComment);
|
||||
$ret .= call_user_func_array($processor, $callArgs);
|
||||
}
|
||||
if (false === $comment) {
|
||||
break;
|
||||
}
|
||||
$ret .= $comment;
|
||||
$content = $afterComment;
|
||||
}
|
||||
|
||||
return $ret;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract comments that YUI Compressor preserves.
|
||||
*
|
||||
* @param string $in input
|
||||
*
|
||||
* @return array 3 elements are returned. If a YUI comment is found, the
|
||||
* 2nd element is the comment and the 1st and 2nd are the surrounding
|
||||
* strings. If no comment is found, the entire string is returned as the
|
||||
* 1st element and the other two are false.
|
||||
*/
|
||||
private static function _nextComment($in)
|
||||
{
|
||||
if (
|
||||
false === ($start = strpos($in, '/*!'))
|
||||
|| false === ($end = strpos($in, '*/', $start + 3))
|
||||
) {
|
||||
return [$in, false, false];
|
||||
}
|
||||
$ret = [
|
||||
substr($in, 0, $start), self::$prepend.'/*'.substr($in, $start + 3, $end - $start - 1).self::$append,
|
||||
];
|
||||
$endChars = (strlen($in) - $end - 2);
|
||||
$ret[] = (0 === $endChars)
|
||||
? ''
|
||||
: substr($in, -$endChars);
|
||||
|
||||
return $ret;
|
||||
}
|
||||
}
|
||||
|
||||
class Minify_CSS_Compressor
|
||||
{
|
||||
/**
|
||||
* Minify a CSS string.
|
||||
*
|
||||
* @param string $css
|
||||
* @param array $options (currently ignored)
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function process($css, $options = [])
|
||||
{
|
||||
$obj = new self($options);
|
||||
|
||||
return $obj->_process($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* @var array options
|
||||
*/
|
||||
protected $_options;
|
||||
|
||||
/**
|
||||
* @var bool Are we "in" a hack?
|
||||
*
|
||||
* I.e. are some browsers targetted until the next comment?
|
||||
*/
|
||||
protected $_inHack = false;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*
|
||||
* @param array $options (currently ignored)
|
||||
*/
|
||||
private function __construct($options)
|
||||
{
|
||||
$this->_options = $options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Minify a CSS string.
|
||||
*
|
||||
* @param string $css
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _process($css)
|
||||
{
|
||||
$css = str_replace("\r\n", "\n", $css);
|
||||
|
||||
// preserve empty comment after '>'
|
||||
// http://www.webdevout.net/css-hacks#in_css-selectors
|
||||
$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);
|
||||
|
||||
// preserve empty comment between property and value
|
||||
// http://css-discuss.incutio.com/?page=BoxModelHack
|
||||
$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
|
||||
$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);
|
||||
|
||||
// apply callback to all valid comments (and strip out surrounding ws
|
||||
$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@', [$this, '_commentCB'], $css);
|
||||
|
||||
// remove ws around { } and last semicolon in declaration block
|
||||
$css = preg_replace('/\\s*{\\s*/', '{', $css);
|
||||
$css = preg_replace('/;?\\s*}\\s*/', '}', $css);
|
||||
|
||||
// remove ws surrounding semicolons
|
||||
$css = preg_replace('/\\s*;\\s*/', ';', $css);
|
||||
|
||||
// remove ws around urls
|
||||
$css = preg_replace('/
|
||||
url\\( # url(
|
||||
\\s*
|
||||
([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis)
|
||||
\\s*
|
||||
\\) # )
|
||||
/x', 'url($1)', $css);
|
||||
|
||||
// remove ws between rules and colons
|
||||
$css = preg_replace('/
|
||||
\\s*
|
||||
([{;]) # 1 = beginning of block or rule separator
|
||||
\\s*
|
||||
([\\*_]?[\\w\\-]+) # 2 = property (and maybe IE filter)
|
||||
\\s*
|
||||
:
|
||||
\\s*
|
||||
(\\b|[#\'"]) # 3 = first character of a value
|
||||
/x', '$1$2:$3', $css);
|
||||
|
||||
// remove ws in selectors
|
||||
$css = preg_replace_callback('/
|
||||
(?: # non-capture
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
\\s*
|
||||
[,>+~] # combinators
|
||||
)+
|
||||
\\s*
|
||||
[^~>+,\\s]+ # selector part
|
||||
{ # open declaration block
|
||||
/x', [$this, '_selectorsCB'], $css);
|
||||
|
||||
// minimize hex colors
|
||||
$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i', '$1#$2$3$4$5', $css);
|
||||
|
||||
// remove spaces between font families
|
||||
$css = preg_replace_callback('/font-family:([^;}]+)([;}])/', [$this, '_fontFamilyCB'], $css);
|
||||
|
||||
$css = preg_replace('/@import\\s+url/', '@import url', $css);
|
||||
|
||||
// replace any ws involving newlines with a single newline
|
||||
$css = preg_replace('/[ \\t]*\\n+\\s*/', "\n", $css);
|
||||
|
||||
// separate common descendent selectors w/ newlines (to limit line lengths)
|
||||
// $css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);
|
||||
|
||||
// Use newline after 1st numeric value (to limit line lengths).
|
||||
$css = preg_replace('/
|
||||
((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
|
||||
\\s+
|
||||
/x', "$1\n", $css);
|
||||
|
||||
// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
|
||||
$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);
|
||||
|
||||
return trim($css);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace what looks like a set of selectors.
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _selectorsCB($m)
|
||||
{
|
||||
// remove ws around the combinators
|
||||
return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a comment and return a replacement.
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _commentCB($m)
|
||||
{
|
||||
$hasSurroundingWs = (trim($m[0]) !== $m[1]);
|
||||
$m = $m[1];
|
||||
// $m is the comment content w/o the surrounding tokens,
|
||||
// but the return value will replace the entire comment.
|
||||
if ($m === 'keep') {
|
||||
return '/**/';
|
||||
}
|
||||
if ($m === '" "') {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*" "*/';
|
||||
}
|
||||
if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
|
||||
// component of http://tantek.com/CSS/Examples/midpass.html
|
||||
return '/*";}}/* */';
|
||||
}
|
||||
if ($this->_inHack) {
|
||||
// inversion: feeding only to one browser
|
||||
if (preg_match('@
|
||||
^/ # comment started like /*/
|
||||
\\s*
|
||||
(\\S[\\s\\S]+?) # has at least some non-ws content
|
||||
\\s*
|
||||
/\\* # ends like /*/ or /**/
|
||||
@x', $m, $n)) {
|
||||
// end hack mode after this comment, but preserve the hack and comment content
|
||||
$this->_inHack = false;
|
||||
|
||||
return "/*/{$n[1]}/**/";
|
||||
}
|
||||
}
|
||||
if (substr($m, -1) === '\\') { // comment ends like \*/
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
|
||||
return '/*\\*/';
|
||||
}
|
||||
if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
|
||||
// begin hack mode and preserve hack
|
||||
$this->_inHack = true;
|
||||
|
||||
return '/*/*/';
|
||||
}
|
||||
if ($this->_inHack) {
|
||||
// a regular comment ends hack mode but should be preserved
|
||||
$this->_inHack = false;
|
||||
|
||||
return '/**/';
|
||||
}
|
||||
|
||||
// Issue 107: if there's any surrounding whitespace, it may be important, so
|
||||
// replace the comment with a single space
|
||||
return $hasSurroundingWs // remove all other comments
|
||||
? ' '
|
||||
: '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Process a font-family listing and return a replacement.
|
||||
*
|
||||
* @param array $m regex matches
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function _fontFamilyCB($m)
|
||||
{
|
||||
$m[1] = preg_replace('/
|
||||
\\s*
|
||||
(
|
||||
"[^"]+" # 1 = family in double qutoes
|
||||
|\'[^\']+\' # or 1 = family in single quotes
|
||||
|[\\w\\-]+ # or 1 = unquoted family
|
||||
)
|
||||
\\s*
|
||||
/x', '$1', $m[1]);
|
||||
|
||||
return 'font-family:'.$m[1].$m[2];
|
||||
}
|
||||
}
|
||||
252
class/sacy/ext-translators.php
Normal file
252
class/sacy/ext-translators.php
Normal file
@@ -0,0 +1,252 @@
|
||||
<?php
|
||||
|
||||
namespace sacy;
|
||||
|
||||
abstract class ExternalProcessor
|
||||
{
|
||||
abstract protected function getCommandLine($filename, $opts = []);
|
||||
|
||||
public function transform($in, $filename, $opts = [])
|
||||
{
|
||||
$s = [
|
||||
0 => ['pipe', 'r'],
|
||||
1 => ['pipe', 'w'],
|
||||
2 => ['pipe', 'w'],
|
||||
];
|
||||
$cmd = $this->getCommandLine($filename, $opts);
|
||||
$p = proc_open($cmd, $s, $pipes, getcwd());
|
||||
if (!is_resource($p)) {
|
||||
throw new \Exception("Failed to execute {$cmd}");
|
||||
}
|
||||
|
||||
fwrite($pipes[0], $in);
|
||||
fclose($pipes[0]);
|
||||
|
||||
$out = stream_get_contents($pipes[1]);
|
||||
$err = stream_get_contents($pipes[2]);
|
||||
|
||||
fclose($pipes[1]);
|
||||
fclose($pipes[2]);
|
||||
|
||||
$r = proc_close($p);
|
||||
|
||||
if ($r != 0) {
|
||||
throw new \Exception("Command returned {$r}: {$out} {$err}");
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalProcessorRegistry
|
||||
{
|
||||
private static $transformers;
|
||||
private static $compressors;
|
||||
|
||||
public static function registerTransformer($type, $cls)
|
||||
{
|
||||
self::$transformers[$type] = $cls;
|
||||
}
|
||||
|
||||
public static function registerCompressor($type, $cls)
|
||||
{
|
||||
self::$compressors[$type] = $cls;
|
||||
}
|
||||
|
||||
private static function lookup($type, $in)
|
||||
{
|
||||
return (isset($in[$type])) ? new $in[$type]() : null;
|
||||
}
|
||||
|
||||
public static function typeIsSupported($type)
|
||||
{
|
||||
return isset(self::$transformers[$type])
|
||||
|| isset(self::$compressors[$type]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param $type string mime type of input
|
||||
*
|
||||
* @return ExternalProcessor
|
||||
*/
|
||||
public static function getTransformerForType($type)
|
||||
{
|
||||
return self::lookup($type, self::$transformers);
|
||||
}
|
||||
|
||||
/**
|
||||
* @static
|
||||
*
|
||||
* @param $type string mime type of input
|
||||
*
|
||||
* @return ExternalProcessor
|
||||
*/
|
||||
public static function getCompressorForType($type)
|
||||
{
|
||||
return self::lookup($type, self::$compressors);
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorUglify extends ExternalProcessor
|
||||
{
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_COMPRESSOR_UGLIFY)) {
|
||||
throw new Exception('SACY_COMPRESSOR_UGLIFY defined but not executable');
|
||||
}
|
||||
|
||||
return SACY_COMPRESSOR_UGLIFY;
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorCoffee extends ExternalProcessor
|
||||
{
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_TRANSFORMER_COFFEE)) {
|
||||
throw new Exception('SACY_TRANSFORMER_COFFEE defined but not executable');
|
||||
}
|
||||
|
||||
return sprintf('%s -c -s', SACY_TRANSFORMER_COFFEE);
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorEco extends ExternalProcessor
|
||||
{
|
||||
protected function getType()
|
||||
{
|
||||
return 'text/x-eco';
|
||||
}
|
||||
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_TRANSFORMER_ECO)) {
|
||||
throw new Exception('SACY_TRANSFORMER_ECO defined but not executable');
|
||||
}
|
||||
// Calling eco with the filename here. Using stdin wouldn't
|
||||
// cut it, as eco uses the filename to figure out the name of
|
||||
// the js function it outputs.
|
||||
$eco_root = $opts['eco-root'];
|
||||
|
||||
return sprintf('%s %s -p %s',
|
||||
SACY_TRANSFORMER_ECO,
|
||||
$eco_root ? sprintf('-i %s', escapeshellarg($eco_root)) : '',
|
||||
escapeshellarg($filename)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorSass extends ExternalProcessor
|
||||
{
|
||||
protected function getType()
|
||||
{
|
||||
return 'text/x-sass';
|
||||
}
|
||||
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_TRANSFORMER_SASS)) {
|
||||
throw new Exception('SACY_TRANSFORMER_SASS defined but not executable');
|
||||
}
|
||||
$libpath = $opts['library_path'] ?: [dirname($filename)];
|
||||
$libpath[] = $_SERVER['DOCUMENT_ROOT'] ?: getcwd();
|
||||
|
||||
$path =
|
||||
implode(' ', array_map(function ($p) {
|
||||
return '-I '.escapeshellarg($p);
|
||||
}, array_unique($libpath)));
|
||||
|
||||
return sprintf('%s --cache-location=%s -s %s %s',
|
||||
SACY_TRANSFORMER_SASS,
|
||||
escapeshellarg(sys_get_temp_dir()),
|
||||
$this->getType() == 'text/x-scss' ? '--scss' : '',
|
||||
$path
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorScss extends ProcessorSass
|
||||
{
|
||||
protected function getType()
|
||||
{
|
||||
return 'text/x-scss';
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorLess extends ExternalProcessor
|
||||
{
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_TRANSFORMER_LESS)) {
|
||||
throw new \Exception('SACY_TRANSFORMER_LESS defined but not executable');
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
'%s -I%s -',
|
||||
SACY_TRANSFORMER_LESS,
|
||||
escapeshellarg(dirname($filename))
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorJSX extends ExternalProcessor
|
||||
{
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
if (!is_executable(SACY_TRANSFORMER_JSX)) {
|
||||
throw new Exception('SACY_TRANSFORMER_JSX defined but not executable');
|
||||
}
|
||||
|
||||
return SACY_TRANSFORMER_JSX;
|
||||
}
|
||||
}
|
||||
|
||||
class ProcessorFontIcons extends ExternalProcessor
|
||||
{
|
||||
protected function getCommandLine($filename, $opts = [])
|
||||
{
|
||||
return SACY_TRANSFORMER_FONTICONS." compile 'templates/icons/' -F -c {$filename}";
|
||||
}
|
||||
|
||||
public function transform($in, $filename, $opts = [])
|
||||
{
|
||||
$css = 'data/tmp/cache/icons.css';
|
||||
|
||||
if (@filemtime($filename) > @filemtime($css)) {
|
||||
parent::transform($in, $filename, $opts);
|
||||
}
|
||||
|
||||
return str_replace('./icons', '/data/tmp/cache/icons', file_get_contents($css));
|
||||
}
|
||||
}
|
||||
|
||||
if (defined('SACY_COMPRESSOR_UGLIFY')) {
|
||||
ExternalProcessorRegistry::registerCompressor('text/javascript', 'sacy\ProcessorUglify');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_COFFEE')) {
|
||||
ExternalProcessorRegistry::registerTransformer('text/coffeescript', 'sacy\ProcessorCoffee');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_ECO')) {
|
||||
ExternalProcessorRegistry::registerTransformer('text/x-eco', 'sacy\ProcessorEco');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_SASS')) {
|
||||
ExternalProcessorRegistry::registerTransformer('text/x-sass', 'sacy\ProcessorSass');
|
||||
ExternalProcessorRegistry::registerTransformer('text/x-scss', 'sacy\ProcessorScss');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_FONTICONS')) {
|
||||
ExternalProcessorRegistry::registerTransformer('image/svg+xml', 'sacy\ProcessorFontIcons');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_LESS')) {
|
||||
ExternalProcessorRegistry::registerTransformer('text/x-less', 'sacy\ProcessorLess');
|
||||
}
|
||||
|
||||
if (defined('SACY_TRANSFORMER_JSX')) {
|
||||
ExternalProcessorRegistry::registerTransformer('text/x-jsx', 'sacy\ProcessorJSX');
|
||||
}
|
||||
52
class/sacy/fragment-cache.php
Normal file
52
class/sacy/fragment-cache.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace sacy;
|
||||
|
||||
class FileCache
|
||||
{
|
||||
private $cache_dir;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->cache_dir = implode(DIRECTORY_SEPARATOR, [
|
||||
ASSET_COMPILE_OUTPUT_DIR,
|
||||
'fragments',
|
||||
]);
|
||||
if (!is_dir($this->cache_dir)) {
|
||||
if (!@mkdir($this->cache_dir, 0755, true)) {
|
||||
throw new Exception('Failed to create fragments cache directory');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function key2file($key)
|
||||
{
|
||||
if (!preg_match('#^[0-9a-z]+$#', $key)) {
|
||||
throw new Exception('Invalid cache key');
|
||||
}
|
||||
|
||||
return implode(DIRECTORY_SEPARATOR, [
|
||||
$this->cache_dir,
|
||||
preg_replace('#^([0-9a-f]{2})([0-9a-f]{2})(.*)$#u', '\1/\2/\3', $key),
|
||||
]);
|
||||
}
|
||||
|
||||
public function get($key)
|
||||
{
|
||||
return null;
|
||||
$p = $this->key2file($key);
|
||||
|
||||
return file_exists($p) ? @file_get_contents($p) : null;
|
||||
}
|
||||
|
||||
public function set($key, $value)
|
||||
{
|
||||
return true;
|
||||
$p = $this->key2file($key);
|
||||
if (!@mkdir(dirname($p), 0755, true)) {
|
||||
throw new Exception("Failed to create fragment cache dir: {$p}");
|
||||
}
|
||||
|
||||
return @file_put_contents($p, $value);
|
||||
}
|
||||
}
|
||||
710
class/sacy/jsmin.php
Normal file
710
class/sacy/jsmin.php
Normal file
@@ -0,0 +1,710 @@
|
||||
<?php
|
||||
|
||||
class JSMin
|
||||
{
|
||||
public static function minify($js)
|
||||
{
|
||||
return Minifier::minify($js);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minifier.
|
||||
*
|
||||
* Usage - Minifier::minify($js);
|
||||
* Usage - Minifier::minify($js, $options);
|
||||
* Usage - Minifier::minify($js, array('flaggedComments' => false));
|
||||
*
|
||||
* @author Robert Hafner <tedivm@tedivm.com>
|
||||
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
|
||||
*/
|
||||
class Minifier
|
||||
{
|
||||
/**
|
||||
* The input javascript to be minified.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $input;
|
||||
|
||||
/**
|
||||
* Length of input javascript.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $len = 0;
|
||||
|
||||
/**
|
||||
* The location of the character (in the input string) that is next to be
|
||||
* processed.
|
||||
*
|
||||
* @var int
|
||||
*/
|
||||
protected $index = 0;
|
||||
|
||||
/**
|
||||
* The first of the characters currently being looked at.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $a = '';
|
||||
|
||||
/**
|
||||
* The next character being looked at (after a);.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $b = '';
|
||||
|
||||
/**
|
||||
* This character is only active when certain look ahead actions take place.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $c;
|
||||
|
||||
/**
|
||||
* This character is only active when certain look ahead actions take place.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $last_char;
|
||||
|
||||
/**
|
||||
* This character is only active when certain look ahead actions take place.
|
||||
*
|
||||
* @var string
|
||||
*/
|
||||
protected $output;
|
||||
|
||||
/**
|
||||
* Contains the options for the current minification process.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $options;
|
||||
|
||||
/**
|
||||
* These characters are used to define strings.
|
||||
*/
|
||||
protected $stringDelimiters = ['\'' => true, '"' => true, '`' => true];
|
||||
|
||||
/**
|
||||
* Contains the default options for minification. This array is merged with
|
||||
* the one passed in by the user to create the request specific set of
|
||||
* options (stored in the $options attribute).
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected static $defaultOptions = ['flaggedComments' => true];
|
||||
|
||||
protected static $keywords = ['delete', 'do', 'for', 'in', 'instanceof', 'return', 'typeof', 'yield'];
|
||||
|
||||
/**
|
||||
* Contains lock ids which are used to replace certain code patterns and
|
||||
* prevent them from being minified.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $locks = [];
|
||||
|
||||
/**
|
||||
* Takes a string containing javascript and removes unneeded characters in
|
||||
* order to shrink the code without altering it's functionality.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*
|
||||
* @return bool|string
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function minify($js, $options = [])
|
||||
{
|
||||
try {
|
||||
$jshrink = new Minifier();
|
||||
$js = $jshrink->lock($js);
|
||||
$js = ltrim($jshrink->minifyToString($js, $options));
|
||||
$js = $jshrink->unlock($js);
|
||||
unset($jshrink);
|
||||
|
||||
return $js;
|
||||
} catch (\Exception $e) {
|
||||
if (isset($jshrink)) {
|
||||
// Since the breakdownScript function probably wasn't finished
|
||||
// we clean it out before discarding it.
|
||||
$jshrink->clean();
|
||||
unset($jshrink);
|
||||
}
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes a javascript string and outputs only the required characters,
|
||||
* stripping out all unneeded characters.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*/
|
||||
protected function minifyToString($js, $options)
|
||||
{
|
||||
$this->initialize($js, $options);
|
||||
$this->loop();
|
||||
$this->clean();
|
||||
|
||||
return $this->output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes internal variables, normalizes new lines,.
|
||||
*
|
||||
* @param string $js The raw javascript to be minified
|
||||
* @param array $options Various runtime options in an associative array
|
||||
*/
|
||||
protected function initialize($js, $options)
|
||||
{
|
||||
$this->options = array_merge(static::$defaultOptions, $options);
|
||||
$this->input = $js;
|
||||
|
||||
// We add a newline to the end of the script to make it easier to deal
|
||||
// with comments at the bottom of the script- this prevents the unclosed
|
||||
// comment error that can otherwise occur.
|
||||
$this->input .= PHP_EOL;
|
||||
|
||||
// save input length to skip calculation every time
|
||||
$this->len = strlen($this->input);
|
||||
|
||||
// Populate "a" with a new line, "b" with the first character, before
|
||||
// entering the loop
|
||||
$this->a = "\n";
|
||||
$this->b = "\n";
|
||||
$this->last_char = "\n";
|
||||
$this->output = '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Characters that can't stand alone preserve the newline.
|
||||
*
|
||||
* @var array
|
||||
*/
|
||||
protected $noNewLineCharacters = [
|
||||
'(' => true,
|
||||
'-' => true,
|
||||
'+' => true,
|
||||
'[' => true,
|
||||
'#' => true,
|
||||
'@' => true];
|
||||
|
||||
protected function echo($char)
|
||||
{
|
||||
$this->output .= $char;
|
||||
$this->last_char = $char[-1];
|
||||
}
|
||||
|
||||
/**
|
||||
* The primary action occurs here. This function loops through the input string,
|
||||
* outputting anything that's relevant and discarding anything that is not.
|
||||
*/
|
||||
protected function loop()
|
||||
{
|
||||
while ($this->a !== false && !is_null($this->a) && $this->a !== '') {
|
||||
switch ($this->a) {
|
||||
// new lines
|
||||
case "\r":
|
||||
case "\n":
|
||||
// if the next line is something that can't stand alone preserve the newline
|
||||
if ($this->b !== false && isset($this->noNewLineCharacters[$this->b])) {
|
||||
$this->echo($this->a);
|
||||
$this->saveString();
|
||||
break;
|
||||
}
|
||||
|
||||
// if B is a space we skip the rest of the switch block and go down to the
|
||||
// string/regex check below, resetting $this->b with getReal
|
||||
if ($this->b === ' ') {
|
||||
break;
|
||||
}
|
||||
|
||||
// otherwise we treat the newline like a space
|
||||
|
||||
// no break
|
||||
case ' ':
|
||||
if (static::isAlphaNumeric($this->b)) {
|
||||
$this->echo($this->a);
|
||||
}
|
||||
|
||||
$this->saveString();
|
||||
break;
|
||||
|
||||
default:
|
||||
switch ($this->b) {
|
||||
case "\r":
|
||||
case "\n":
|
||||
if (strpos('}])+-"\'', $this->a) !== false) {
|
||||
$this->echo($this->a);
|
||||
$this->saveString();
|
||||
break;
|
||||
} else {
|
||||
if (static::isAlphaNumeric($this->a)) {
|
||||
$this->echo($this->a);
|
||||
$this->saveString();
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case ' ':
|
||||
if (!static::isAlphaNumeric($this->a)) {
|
||||
break;
|
||||
}
|
||||
|
||||
// no break
|
||||
default:
|
||||
// check for some regex that breaks stuff
|
||||
if ($this->a === '/' && ($this->b === '\'' || $this->b === '"')) {
|
||||
$this->saveRegex();
|
||||
continue 3;
|
||||
}
|
||||
|
||||
$this->echo($this->a);
|
||||
$this->saveString();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// do reg check of doom
|
||||
$this->b = $this->getReal();
|
||||
|
||||
if ($this->b == '/') {
|
||||
$valid_tokens = "(,=:[!&|?\n";
|
||||
|
||||
// Find last "real" token, excluding spaces.
|
||||
$last_token = $this->a;
|
||||
if ($last_token == ' ') {
|
||||
$last_token = $this->last_char;
|
||||
}
|
||||
|
||||
if (strpos($valid_tokens, $last_token) !== false) {
|
||||
// Regex can appear unquoted after these symbols
|
||||
$this->saveRegex();
|
||||
} elseif ($this->endsInKeyword()) {
|
||||
// This block checks for the "return" token before the slash.
|
||||
$this->saveRegex();
|
||||
}
|
||||
}
|
||||
|
||||
// if (($this->b == '/' && strpos('(,=:[!&|?', $this->a) !== false)) {
|
||||
// $this->saveRegex();
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resets attributes that do not need to be stored between requests so that
|
||||
* the next request is ready to go. Another reason for this is to make sure
|
||||
* the variables are cleared and are not taking up memory.
|
||||
*/
|
||||
protected function clean()
|
||||
{
|
||||
unset($this->input);
|
||||
$this->len = 0;
|
||||
$this->index = 0;
|
||||
$this->a = $this->b = '';
|
||||
unset($this->c);
|
||||
unset($this->options);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the next string for processing based off of the current index.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
protected function getChar()
|
||||
{
|
||||
// Check to see if we had anything in the look ahead buffer and use that.
|
||||
if (isset($this->c)) {
|
||||
$char = $this->c;
|
||||
unset($this->c);
|
||||
} else {
|
||||
// Otherwise we start pulling from the input.
|
||||
$char = $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
|
||||
// If the next character doesn't exist return false.
|
||||
if (isset($char) && $char === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Otherwise increment the pointer and use this char.
|
||||
$this->index++;
|
||||
}
|
||||
|
||||
// Convert all line endings to unix standard.
|
||||
// `\r\n` converts to `\n\n` and is minified.
|
||||
if ($char == "\r") {
|
||||
$char = "\n";
|
||||
}
|
||||
|
||||
// Normalize all whitespace except for the newline character into a
|
||||
// standard space.
|
||||
if ($char !== "\n" && $char < "\x20") {
|
||||
return ' ';
|
||||
}
|
||||
|
||||
return $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function returns the next character without moving the index forward.
|
||||
*
|
||||
* @return string The next character
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function peek()
|
||||
{
|
||||
if ($this->index >= $this->len) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$char = $this->input[$this->index];
|
||||
// Convert all line endings to unix standard.
|
||||
// `\r\n` converts to `\n\n` and is minified.
|
||||
if ($char == "\r") {
|
||||
$char = "\n";
|
||||
}
|
||||
|
||||
// Normalize all whitespace except for the newline character into a
|
||||
// standard space.
|
||||
if ($char !== "\n" && $char < "\x20") {
|
||||
return ' ';
|
||||
}
|
||||
|
||||
// Return the next character but don't push the index.
|
||||
return $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* This function gets the next "real" character. It is essentially a wrapper
|
||||
* around the getChar function that skips comments. This has significant
|
||||
* performance benefits as the skipping is done using native functions (ie,
|
||||
* c code) rather than in script php.
|
||||
*
|
||||
* @return string next 'real' character to be processed
|
||||
*
|
||||
* @throws \RuntimeException
|
||||
*/
|
||||
protected function getReal()
|
||||
{
|
||||
$startIndex = $this->index;
|
||||
$char = $this->getChar();
|
||||
|
||||
// Check to see if we're potentially in a comment
|
||||
if ($char !== '/') {
|
||||
return $char;
|
||||
}
|
||||
|
||||
$this->c = $this->getChar();
|
||||
|
||||
if ($this->c === '/') {
|
||||
$this->processOneLineComments($startIndex);
|
||||
|
||||
return $this->getReal();
|
||||
} elseif ($this->c === '*') {
|
||||
$this->processMultiLineComments($startIndex);
|
||||
|
||||
return $this->getReal();
|
||||
}
|
||||
|
||||
return $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removed one line comments, with the exception of some very specific types of
|
||||
* conditional comments.
|
||||
*
|
||||
* @param int $startIndex The index point where "getReal" function started
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function processOneLineComments($startIndex)
|
||||
{
|
||||
$thirdCommentString = $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
|
||||
// kill rest of line
|
||||
$this->getNext("\n");
|
||||
|
||||
unset($this->c);
|
||||
|
||||
if ($thirdCommentString == '@') {
|
||||
$endPoint = $this->index - $startIndex;
|
||||
$this->c = "\n".substr($this->input, $startIndex, $endPoint);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips multiline comments where appropriate, and includes them where needed.
|
||||
* Conditional comments and "license" style blocks are preserved.
|
||||
*
|
||||
* @param int $startIndex The index point where "getReal" function started
|
||||
*
|
||||
* @return void
|
||||
*
|
||||
* @throws \RuntimeException Unclosed comments will throw an error
|
||||
*/
|
||||
protected function processMultiLineComments($startIndex)
|
||||
{
|
||||
$this->getChar(); // current C
|
||||
$thirdCommentString = $this->getChar();
|
||||
|
||||
// Detect a completely empty comment, ie `/**/`
|
||||
if ($thirdCommentString == '*') {
|
||||
$peekChar = $this->peek();
|
||||
if ($peekChar == '/') {
|
||||
$this->index++;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// kill everything up to the next */ if it's there
|
||||
if ($this->getNext('*/')) {
|
||||
$this->getChar(); // get *
|
||||
$this->getChar(); // get /
|
||||
$char = $this->getChar(); // get next real character
|
||||
|
||||
// Now we reinsert conditional comments and YUI-style licensing comments
|
||||
if (($this->options['flaggedComments'] && $thirdCommentString === '!')
|
||||
|| ($thirdCommentString === '@')) {
|
||||
// If conditional comments or flagged comments are not the first thing in the script
|
||||
// we need to echo a and fill it with a space before moving on.
|
||||
if ($startIndex > 0) {
|
||||
$this->echo($this->a);
|
||||
$this->a = ' ';
|
||||
|
||||
// If the comment started on a new line we let it stay on the new line
|
||||
if ($this->input[$startIndex - 1] === "\n") {
|
||||
$this->echo("\n");
|
||||
}
|
||||
}
|
||||
|
||||
$endPoint = ($this->index - 1) - $startIndex;
|
||||
$this->echo(substr($this->input, $startIndex, $endPoint));
|
||||
|
||||
$this->c = $char;
|
||||
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
$char = false;
|
||||
}
|
||||
|
||||
if ($char === false) {
|
||||
throw new \RuntimeException('Unclosed multiline comment at position: '.($this->index - 2));
|
||||
}
|
||||
|
||||
// if we're here c is part of the comment and therefore tossed
|
||||
$this->c = $char;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pushes the index ahead to the next instance of the supplied string. If it
|
||||
* is found the first character of the string is returned and the index is set
|
||||
* to it's position.
|
||||
*
|
||||
* @param string $string
|
||||
*
|
||||
* @return string|false returns the first character of the string or false
|
||||
*/
|
||||
protected function getNext($string)
|
||||
{
|
||||
// Find the next occurrence of "string" after the current position.
|
||||
$pos = strpos($this->input, $string, $this->index);
|
||||
|
||||
// If it's not there return false.
|
||||
if ($pos === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Adjust position of index to jump ahead to the asked for string
|
||||
$this->index = $pos;
|
||||
|
||||
// Return the first character of that string.
|
||||
return $this->index < $this->len ? $this->input[$this->index] : false;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a javascript string is detected this function crawls for the end of
|
||||
* it and saves the whole string.
|
||||
*
|
||||
* @throws \RuntimeException Unclosed strings will throw an error
|
||||
*/
|
||||
protected function saveString()
|
||||
{
|
||||
$startpos = $this->index;
|
||||
|
||||
// saveString is always called after a gets cleared, so we push b into
|
||||
// that spot.
|
||||
$this->a = $this->b;
|
||||
|
||||
// If this isn't a string we don't need to do anything.
|
||||
if (!isset($this->stringDelimiters[$this->a])) {
|
||||
return;
|
||||
}
|
||||
|
||||
// String type is the quote used, " or '
|
||||
$stringType = $this->a;
|
||||
|
||||
// Echo out that starting quote
|
||||
$this->echo($this->a);
|
||||
|
||||
// Loop until the string is done
|
||||
// Grab the very next character and load it into a
|
||||
while (($this->a = $this->getChar()) !== false) {
|
||||
switch ($this->a) {
|
||||
// If the string opener (single or double quote) is used
|
||||
// output it and break out of the while loop-
|
||||
// The string is finished!
|
||||
case $stringType:
|
||||
break 2;
|
||||
|
||||
// New lines in strings without line delimiters are bad- actual
|
||||
// new lines will be represented by the string \n and not the actual
|
||||
// character, so those will be treated just fine using the switch
|
||||
// block below.
|
||||
case "\n":
|
||||
if ($stringType === '`') {
|
||||
$this->echo($this->a);
|
||||
} else {
|
||||
throw new \RuntimeException('Unclosed string at position: '.$startpos);
|
||||
}
|
||||
break;
|
||||
|
||||
// Escaped characters get picked up here. If it's an escaped new line it's not really needed
|
||||
case '\\':
|
||||
// a is a slash. We want to keep it, and the next character,
|
||||
// unless it's a new line. New lines as actual strings will be
|
||||
// preserved, but escaped new lines should be reduced.
|
||||
$this->b = $this->getChar();
|
||||
|
||||
// If b is a new line we discard a and b and restart the loop.
|
||||
if ($this->b === "\n") {
|
||||
break;
|
||||
}
|
||||
|
||||
// echo out the escaped character and restart the loop.
|
||||
$this->echo($this->a.$this->b);
|
||||
break;
|
||||
|
||||
// Since we're not dealing with any special cases we simply
|
||||
// output the character and continue our loop.
|
||||
default:
|
||||
$this->echo($this->a);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When a regular expression is detected this function crawls for the end of
|
||||
* it and saves the whole regex.
|
||||
*
|
||||
* @throws \RuntimeException Unclosed regex will throw an error
|
||||
*/
|
||||
protected function saveRegex()
|
||||
{
|
||||
if ($this->a != ' ') {
|
||||
$this->echo($this->a);
|
||||
}
|
||||
|
||||
$this->echo($this->b);
|
||||
|
||||
while (($this->a = $this->getChar()) !== false) {
|
||||
if ($this->a === '/') {
|
||||
break;
|
||||
}
|
||||
|
||||
if ($this->a === '\\') {
|
||||
$this->echo($this->a);
|
||||
$this->a = $this->getChar();
|
||||
}
|
||||
|
||||
if ($this->a === "\n") {
|
||||
throw new \RuntimeException('Unclosed regex pattern at position: '.$this->index);
|
||||
}
|
||||
|
||||
$this->echo($this->a);
|
||||
}
|
||||
$this->b = $this->getReal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks to see if a character is alphanumeric.
|
||||
*
|
||||
* @param string $char Just one character
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected static function isAlphaNumeric($char)
|
||||
{
|
||||
return preg_match('/^[\w\$\pL]$/', $char) === 1 || $char == '/';
|
||||
}
|
||||
|
||||
protected function endsInKeyword()
|
||||
{
|
||||
// When this function is called A is not yet assigned to output.
|
||||
$testOutput = $this->output.$this->a;
|
||||
|
||||
foreach (static::$keywords as $keyword) {
|
||||
if (preg_match('/[^\w]'.$keyword.'[ ]?$/i', $testOutput) === 1) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace patterns in the given string and store the replacement.
|
||||
*
|
||||
* @param string $js The string to lock
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function lock($js)
|
||||
{
|
||||
/* lock things like <code>"asd" + ++x;</code> */
|
||||
$lock = '"LOCK---'.crc32(time()).'"';
|
||||
|
||||
$matches = [];
|
||||
preg_match('/([+-])(\s+)([+-])/S', $js, $matches);
|
||||
if (empty($matches)) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
$this->locks[$lock] = $matches[2];
|
||||
|
||||
$js = preg_replace('/([+-])\s+([+-])/S', "$1{$lock}$2", $js);
|
||||
/* -- */
|
||||
|
||||
return $js;
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace "locks" with the original characters.
|
||||
*
|
||||
* @param string $js The string to unlock
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
protected function unlock($js)
|
||||
{
|
||||
if (empty($this->locks)) {
|
||||
return $js;
|
||||
}
|
||||
|
||||
foreach ($this->locks as $lock => $replacement) {
|
||||
$js = str_replace($lock, $replacement, $js);
|
||||
}
|
||||
|
||||
return $js;
|
||||
}
|
||||
}
|
||||
28
class/sacy/phpsass.php
Normal file
28
class/sacy/phpsass.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace sacy;
|
||||
|
||||
class PhpSassSacy
|
||||
{
|
||||
public static function isAvailable()
|
||||
{
|
||||
return extension_loaded('sass')/* && defined('SASS_FLAVOR') && SASS_FLAVOR == 'sensational' */
|
||||
;
|
||||
}
|
||||
|
||||
public static function compile($file, $fragment, $load_path)
|
||||
{
|
||||
$sass = new \Sass();
|
||||
|
||||
$load_path = array_map(function ($x) {
|
||||
return getcwd().$x;
|
||||
}, $load_path);
|
||||
$sass->setIncludePath(implode(':', $load_path));
|
||||
|
||||
if ($file) {
|
||||
return $sass->compile_file($file);
|
||||
} else {
|
||||
return $sass->compile($fragment);
|
||||
}
|
||||
}
|
||||
}
|
||||
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