From 19985dbb8c0aa66dc4bf7905abc1148de909097d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anton=20Luka=20=C5=A0ijanec?= Date: Tue, 11 Jan 2022 12:35:47 +0100 Subject: prvi-commit --- admin/survey/minify/lib/Minify/App.php | 293 +++++++++++++++++ admin/survey/minify/lib/Minify/Build.php | 101 ++++++ admin/survey/minify/lib/Minify/CSS.php | 98 ++++++ admin/survey/minify/lib/Minify/CSS/Compressor.php | 275 ++++++++++++++++ admin/survey/minify/lib/Minify/CSS/UriRewriter.php | 358 +++++++++++++++++++++ admin/survey/minify/lib/Minify/CSSmin.php | 88 +++++ admin/survey/minify/lib/Minify/Cache/APC.php | 136 ++++++++ admin/survey/minify/lib/Minify/Cache/File.php | 183 +++++++++++ admin/survey/minify/lib/Minify/Cache/Memcache.php | 141 ++++++++ admin/survey/minify/lib/Minify/Cache/Null.php | 67 ++++ admin/survey/minify/lib/Minify/Cache/WinCache.php | 139 ++++++++ admin/survey/minify/lib/Minify/Cache/XCache.php | 130 ++++++++ .../minify/lib/Minify/Cache/ZendPlatform.php | 129 ++++++++ admin/survey/minify/lib/Minify/CacheInterface.php | 58 ++++ admin/survey/minify/lib/Minify/ClosureCompiler.php | 240 ++++++++++++++ .../survey/minify/lib/Minify/CommentPreserver.php | 87 +++++ admin/survey/minify/lib/Minify/Config.php | 78 +++++ admin/survey/minify/lib/Minify/Controller/Base.php | 81 +++++ .../survey/minify/lib/Minify/Controller/Files.php | 71 ++++ .../survey/minify/lib/Minify/Controller/Groups.php | 76 +++++ .../survey/minify/lib/Minify/Controller/MinApp.php | 196 +++++++++++ admin/survey/minify/lib/Minify/Controller/Page.php | 69 ++++ .../minify/lib/Minify/ControllerInterface.php | 22 ++ admin/survey/minify/lib/Minify/DebugDetector.php | 30 ++ admin/survey/minify/lib/Minify/Env.php | 127 ++++++++ admin/survey/minify/lib/Minify/HTML.php | 258 +++++++++++++++ admin/survey/minify/lib/Minify/HTML/Helper.php | 250 ++++++++++++++ admin/survey/minify/lib/Minify/ImportProcessor.php | 217 +++++++++++++ .../minify/lib/Minify/JS/ClosureCompiler.php | 237 ++++++++++++++ admin/survey/minify/lib/Minify/JS/JShrink.php | 48 +++ admin/survey/minify/lib/Minify/LessCssSource.php | 128 ++++++++ admin/survey/minify/lib/Minify/Lines.php | 209 ++++++++++++ .../minify/lib/Minify/Logger/LegacyHandler.php | 24 ++ .../minify/lib/Minify/NailgunClosureCompiler.php | 113 +++++++ admin/survey/minify/lib/Minify/Packer.php | 31 ++ admin/survey/minify/lib/Minify/ScssCssSource.php | 176 ++++++++++ .../minify/lib/Minify/ServeConfiguration.php | 71 ++++ admin/survey/minify/lib/Minify/Source.php | 219 +++++++++++++ admin/survey/minify/lib/Minify/Source/Factory.php | 197 ++++++++++++ .../minify/lib/Minify/Source/FactoryException.php | 5 + admin/survey/minify/lib/Minify/SourceInterface.php | 82 +++++ admin/survey/minify/lib/Minify/SourceSet.php | 31 ++ admin/survey/minify/lib/Minify/YUICompressor.php | 157 +++++++++ 43 files changed, 5726 insertions(+) create mode 100644 admin/survey/minify/lib/Minify/App.php create mode 100644 admin/survey/minify/lib/Minify/Build.php create mode 100644 admin/survey/minify/lib/Minify/CSS.php create mode 100644 admin/survey/minify/lib/Minify/CSS/Compressor.php create mode 100644 admin/survey/minify/lib/Minify/CSS/UriRewriter.php create mode 100644 admin/survey/minify/lib/Minify/CSSmin.php create mode 100644 admin/survey/minify/lib/Minify/Cache/APC.php create mode 100644 admin/survey/minify/lib/Minify/Cache/File.php create mode 100644 admin/survey/minify/lib/Minify/Cache/Memcache.php create mode 100644 admin/survey/minify/lib/Minify/Cache/Null.php create mode 100644 admin/survey/minify/lib/Minify/Cache/WinCache.php create mode 100644 admin/survey/minify/lib/Minify/Cache/XCache.php create mode 100644 admin/survey/minify/lib/Minify/Cache/ZendPlatform.php create mode 100644 admin/survey/minify/lib/Minify/CacheInterface.php create mode 100644 admin/survey/minify/lib/Minify/ClosureCompiler.php create mode 100644 admin/survey/minify/lib/Minify/CommentPreserver.php create mode 100644 admin/survey/minify/lib/Minify/Config.php create mode 100644 admin/survey/minify/lib/Minify/Controller/Base.php create mode 100644 admin/survey/minify/lib/Minify/Controller/Files.php create mode 100644 admin/survey/minify/lib/Minify/Controller/Groups.php create mode 100644 admin/survey/minify/lib/Minify/Controller/MinApp.php create mode 100644 admin/survey/minify/lib/Minify/Controller/Page.php create mode 100644 admin/survey/minify/lib/Minify/ControllerInterface.php create mode 100644 admin/survey/minify/lib/Minify/DebugDetector.php create mode 100644 admin/survey/minify/lib/Minify/Env.php create mode 100644 admin/survey/minify/lib/Minify/HTML.php create mode 100644 admin/survey/minify/lib/Minify/HTML/Helper.php create mode 100644 admin/survey/minify/lib/Minify/ImportProcessor.php create mode 100644 admin/survey/minify/lib/Minify/JS/ClosureCompiler.php create mode 100644 admin/survey/minify/lib/Minify/JS/JShrink.php create mode 100644 admin/survey/minify/lib/Minify/LessCssSource.php create mode 100644 admin/survey/minify/lib/Minify/Lines.php create mode 100644 admin/survey/minify/lib/Minify/Logger/LegacyHandler.php create mode 100644 admin/survey/minify/lib/Minify/NailgunClosureCompiler.php create mode 100644 admin/survey/minify/lib/Minify/Packer.php create mode 100644 admin/survey/minify/lib/Minify/ScssCssSource.php create mode 100644 admin/survey/minify/lib/Minify/ServeConfiguration.php create mode 100644 admin/survey/minify/lib/Minify/Source.php create mode 100644 admin/survey/minify/lib/Minify/Source/Factory.php create mode 100644 admin/survey/minify/lib/Minify/Source/FactoryException.php create mode 100644 admin/survey/minify/lib/Minify/SourceInterface.php create mode 100644 admin/survey/minify/lib/Minify/SourceSet.php create mode 100644 admin/survey/minify/lib/Minify/YUICompressor.php (limited to 'admin/survey/minify/lib/Minify') diff --git a/admin/survey/minify/lib/Minify/App.php b/admin/survey/minify/lib/Minify/App.php new file mode 100644 index 0000000..5ddb3e9 --- /dev/null +++ b/admin/survey/minify/lib/Minify/App.php @@ -0,0 +1,293 @@ +dir = rtrim($dir, '/\\'); + + $this->cache = function (App $app) use ($that) { + $config = $app->config; + + if ($config->cachePath instanceof Minify_CacheInterface) { + return $config->cachePath; + } + + if (!$config->cachePath || is_string($config->cachePath)) { + return new Minify_Cache_File($config->cachePath, $config->cacheFileLocking, $app->logger); + } + + $type = $that->typeOf($config->cachePath); + throw new RuntimeException('$min_cachePath must be a path or implement Minify_CacheInterface.' + . " Given $type"); + }; + + $this->config = function (App $app) { + $config = (require $app->configPath); + + if ($config instanceof Minify\Config) { + return $config; + } + + // copy from vars into properties + + $config = new Minify\Config(); + + $propNames = array_keys(get_object_vars($config)); + + $prefixer = function ($name) { + return "min_$name"; + }; + $varNames = array_map($prefixer, $propNames); + + $vars = compact($varNames); + + foreach ($varNames as $varName) { + if (isset($vars[$varName])) { + $config->{substr($varName, 4)} = $vars[$varName]; + } + } + + if ($config->documentRoot) { + // copy into env + if (empty($config->envArgs['server'])) { + $config->envArgs['server'] = $_SERVER; + } + $config->envArgs['server']['DOCUMENT_ROOT'] = $config->documentRoot; + } + + return $config; + }; + + $this->configPath = "{$this->dir}/config.php"; + + $this->controller = function (App $app) use ($that) { + $config = $app->config; + + if (empty($config->factories['controller'])) { + $ctrl = new Minify_Controller_MinApp($app->env, $app->sourceFactory, $app->logger); + } else { + $ctrl = call_user_func($config->factories['controller'], $app); + } + + if ($ctrl instanceof Minify_ControllerInterface) { + return $ctrl; + } + + $type = $that->typeOf($ctrl); + throw new RuntimeException('$min_factories["controller"] callable must return an implementation' + ." of Minify_CacheInterface. Returned $type"); + }; + + $this->docRoot = function (App $app) { + $config = $app->config; + if (empty($config->documentRoot)) { + return $app->env->getDocRoot(); + } + + return $app->env->normalizePath($config->documentRoot); + }; + + $this->env = function (App $app) { + return new Minify_Env($app->config->envArgs); + }; + + $this->errorLogHandler = function (App $app) { + $format = "%channel%.%level_name%: %message% %context% %extra%"; + $handler = new Monolog\Handler\ErrorLogHandler(); + $handler->setFormatter(new Monolog\Formatter\LineFormatter($format)); + + return $handler; + }; + + $this->groupsConfig = function (App $app) { + return (require $app->groupsConfigPath); + }; + + $this->groupsConfigPath = "{$this->dir}/groupsConfig.php"; + + $this->logger = function (App $app) use ($that) { + $value = $app->config->errorLogger; + + if ($value instanceof LoggerInterface) { + return $value; + } + + $logger = new Monolog\Logger('minify'); + + if (!$value) { + return $logger; + } + + if ($value === true || $value instanceof \FirePHP) { + $logger->pushHandler($app->errorLogHandler); + $logger->pushHandler(new Monolog\Handler\FirePHPHandler()); + + return $logger; + } + + if ($value instanceof Monolog\Handler\HandlerInterface) { + $logger->pushHandler($value); + + return $logger; + } + + // BC + if (is_object($value) && is_callable(array($value, 'log'))) { + $handler = new Minify\Logger\LegacyHandler($value); + $logger->pushHandler($handler); + + return $logger; + } + + $type = $that->typeOf($value); + throw new RuntimeException('If set, $min_errorLogger must be a PSR-3 logger or a Monolog handler.' + ." Given $type"); + }; + + $this->minify = function (App $app) use ($that) { + $config = $app->config; + + if (empty($config->factories['minify'])) { + return new \Minify($app->cache, $app->logger); + } + + $minify = call_user_func($config->factories['minify'], $app); + if ($minify instanceof \Minify) { + return $minify; + } + + $type = $that->typeOf($minify); + throw new RuntimeException('$min_factories["minify"] callable must return a Minify object.' + ." Returned $type"); + }; + + $this->serveOptions = function (App $app) { + $config = $app->config; + $env = $app->env; + + $ret = $config->serveOptions; + + $ret['minifierOptions']['text/css']['docRoot'] = $app->docRoot; + $ret['minifierOptions']['text/css']['symlinks'] = $config->symlinks; + $ret['minApp']['symlinks'] = $config->symlinks; + + // auto-add targets to allowDirs + foreach ($config->symlinks as $uri => $target) { + $ret['minApp']['allowDirs'][] = $target; + } + + if ($config->allowDebugFlag) { + $ret['debug'] = Minify_DebugDetector::shouldDebugRequest($env); + } + + if ($config->concatOnly) { + $ret['concatOnly'] = true; + } + + // check for URI versioning + if ($env->get('v') !== null || preg_match('/&\\d/', $app->env->server('QUERY_STRING'))) { + $ret['maxAge'] = 31536000; + } + + // need groups config? + if ($env->get('g') !== null) { + $ret['minApp']['groups'] = $app->groupsConfig; + } + + return $ret; + }; + + $this->sourceFactory = function (App $app) { + return new Minify_Source_Factory($app->env, $app->sourceFactoryOptions, $app->cache); + }; + + $this->sourceFactoryOptions = function (App $app) { + $serveOptions = $app->serveOptions; + $ret = array(); + + // translate legacy setting to option for source factory + if (isset($serveOptions['minApp']['noMinPattern'])) { + $ret['noMinPattern'] = $serveOptions['minApp']['noMinPattern']; + } + + if (isset($serveOptions['minApp']['allowDirs'])) { + $ret['allowDirs'] = $serveOptions['minApp']['allowDirs']; + } + + if (isset($serveOptions['checkAllowDirs'])) { + $ret['checkAllowDirs'] = $serveOptions['checkAllowDirs']; + } + + if (is_numeric($app->config->uploaderHoursBehind)) { + $ret['uploaderHoursBehind'] = $app->config->uploaderHoursBehind; + } + + return $ret; + }; + } + + public function runServer() + { + if (!$this->env->get('f') && $this->env->get('g') === null) { + // no spec given + $msg = '

No "f" or "g" parameters were detected.

'; + $url = 'https://github.com/mrclay/minify/blob/master/docs/CommonProblems.wiki.md#long-url-parameters-are-ignored'; + $defaults = $this->minify->getDefaultOptions(); + $this->minify->errorExit($defaults['badRequestHeader'], $url, $msg); + } + + $this->minify->serve($this->controller, $this->serveOptions); + } + + /** + * @param mixed $var + * @return string + */ + private function typeOf($var) + { + $type = gettype($var); + + return $type === 'object' ? get_class($var) : $type; + } +} diff --git a/admin/survey/minify/lib/Minify/Build.php b/admin/survey/minify/lib/Minify/Build.php new file mode 100644 index 0000000..177b262 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Build.php @@ -0,0 +1,101 @@ + + * // in config file + * $groupSources = array( + * 'js' => array('file1.js', 'file2.js') + * ,'css' => array('file1.css', 'file2.css', 'file3.css') + * ) + * + * // during HTML generation + * $jsBuild = new Minify_Build($groupSources['js']); + * $cssBuild = new Minify_Build($groupSources['css']); + * + * $script = ""; + * $link = ""; + * + * // in min.php + * Minify::serve('Groups', array( + * 'groups' => $groupSources + * ,'setExpires' => (time() + 86400 * 365) + * )); + * + * + * @package Minify + * @author Stephen Clay + */ +class Minify_Build +{ + + /** + * Last modification time of all files in the build + * + * @var int + */ + public $lastModified = 0; + + /** + * String to use as ampersand in uri(). Set this to '&' if + * you are not HTML-escaping URIs. + * + * @var string + */ + public static $ampersand = '&'; + + /** + * Get a time-stamped URI + * + * + * echo $b->uri('/site.js'); + * // outputs "/site.js?1678242" + * + * echo $b->uri('/scriptaculous.js?load=effects'); + * // outputs "/scriptaculous.js?load=effects&1678242" + * + * + * @param string $uri + * @param boolean $forceAmpersand (default = false) Force the use of ampersand to + * append the timestamp to the URI. + * @return string + */ + public function uri($uri, $forceAmpersand = false) + { + $sep = ($forceAmpersand || strpos($uri, '?') !== false) ? self::$ampersand : '?'; + + return "{$uri}{$sep}{$this->lastModified}"; + } + + /** + * Create a build object + * + * @param array $sources array of Minify_Source objects and/or file paths + * + */ + public function __construct($sources) + { + $max = 0; + foreach ((array)$sources as $source) { + if ($source instanceof Minify_Source) { + $max = max($max, $source->getLastModified()); + } elseif (is_string($source)) { + if (0 === strpos($source, '//')) { + $source = $_SERVER['DOCUMENT_ROOT'] . substr($source, 1); + } + if (is_file($source)) { + $max = max($max, filemtime($source)); + } + } + } + $this->lastModified = $max; + } +} diff --git a/admin/survey/minify/lib/Minify/CSS.php b/admin/survey/minify/lib/Minify/CSS.php new file mode 100644 index 0000000..91b076e --- /dev/null +++ b/admin/survey/minify/lib/Minify/CSS.php @@ -0,0 +1,98 @@ + + * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) + * + * @deprecated Use Minify_CSSmin + */ +class Minify_CSS +{ + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options available options: + * + * 'preserveComments': (default true) multi-line comments that begin + * with "/*!" will be preserved with newlines before and after to + * enhance readability. + * + * 'removeCharsets': (default true) remove all @charset at-rules + * + * 'prependRelativePath': (default null) if given, this string will be + * prepended to all relative URIs in import/url declarations + * + * 'currentDir': (default null) if given, this is assumed to be the + * directory of the current CSS file. Using this, minify will rewrite + * all relative URIs in import/url declarations to correctly point to + * the desired files. For this to work, the files *must* exist and be + * visible by the PHP process. + * + * '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.: + * + * array('//symlink' => '/real/target/path') // unix + * array('//static' => 'D:\\staticStorage') // Windows + * + * + * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT']) + * see Minify_CSS_UriRewriter::rewrite + * + * @return string + */ + public static function minify($css, $options = array()) + { + $options = array_merge(array( + 'compress' => true, + 'removeCharsets' => true, + 'preserveComments' => true, + 'currentDir' => null, + 'docRoot' => $_SERVER['DOCUMENT_ROOT'], + 'prependRelativePath' => null, + 'symlinks' => array(), + ), $options); + + if ($options['removeCharsets']) { + $css = preg_replace('/@charset[^;]+;\\s*/', '', $css); + } + + if ($options['compress']) { + if (! $options['preserveComments']) { + $css = Minify_CSS_Compressor::process($css, $options); + } else { + $processor = array('Minify_CSS_Compressor', 'process'); + $css = Minify_CommentPreserver::process($css, $processor, array($options)); + } + } + + if (! $options['currentDir'] && ! $options['prependRelativePath']) { + return $css; + } + + if ($options['currentDir']) { + $currentDir = $options['currentDir']; + $docRoot = $options['docRoot']; + $symlinks = $options['symlinks']; + + return Minify_CSS_UriRewriter::rewrite($css, $currentDir, $docRoot, $symlinks); + } + + return Minify_CSS_UriRewriter::prepend($css, $options['prependRelativePath']); + } +} diff --git a/admin/survey/minify/lib/Minify/CSS/Compressor.php b/admin/survey/minify/lib/Minify/CSS/Compressor.php new file mode 100644 index 0000000..e0ccabb --- /dev/null +++ b/admin/survey/minify/lib/Minify/CSS/Compressor.php @@ -0,0 +1,275 @@ + + * @author http://code.google.com/u/1stvamp/ (Issue 64 patch) + * + * @deprecated Use CSSmin (tubalmartin/cssmin) + */ +class Minify_CSS_Compressor +{ + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options (currently ignored) + * + * @return string + */ + public static function process($css, $options = array()) + { + $obj = new Minify_CSS_Compressor($options); + + return $obj->_process($css); + } + + /** + * @var array + */ + protected $_options = null; + + /** + * Are we "in" a hack? I.e. are some browsers targetted until the next comment? + * + * @var bool + */ + 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 + $pattern = '@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'; + $css = preg_replace_callback($pattern, array($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 + $pattern = '/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = the URL (really just a bunch of non right parenthesis) + \\s* + \\) # ) + /x'; + $css = preg_replace($pattern, 'url($1)', $css); + + // remove ws between rules and colons + $pattern = '/ + \\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'; + $css = preg_replace($pattern, '$1$2:$3', $css); + + // remove ws in selectors + $pattern = '/ + (?: # non-capture + \\s* + [^~>+,\\s]+ # selector part + \\s* + [,>+~] # combinators + )+ + \\s* + [^~>+,\\s]+ # selector part + { # open declaration block + /x'; + $css = preg_replace_callback($pattern, array($this, '_selectorsCB'), $css); + + // minimize hex colors + $pattern = '/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'; + $css = preg_replace($pattern, '$1#$2$3$4$5', $css); + + // remove spaces between font families + $pattern = '/font-family:([^;}]+)([;}])/'; + $css = preg_replace_callback($pattern, array($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) + $pattern = '/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/'; + $css = preg_replace($pattern, "$1\n$2{", $css); + + // Use newline after 1st numeric value (to limit line lengths). + $pattern = '/ + ((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value + \\s+ + /x'; + $css = preg_replace($pattern, "$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 + $pattern = '@ + ^/ # comment started like /*/ + \\s* + (\\S[\\s\\S]+?) # has at least some non-ws content + \\s* + /\\* # ends like /*/ or /**/ + @x'; + if (preg_match($pattern, $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) + { + // Issue 210: must not eliminate WS between words in unquoted families + $flags = PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY; + $pieces = preg_split('/(\'[^\']+\'|"[^"]+")/', $m[1], null, $flags); + $out = 'font-family:'; + + while (null !== ($piece = array_shift($pieces))) { + if ($piece[0] !== '"' && $piece[0] !== "'") { + $piece = preg_replace('/\\s+/', ' ', $piece); + $piece = preg_replace('/\\s?,\\s?/', ',', $piece); + } + $out .= $piece; + } + + return $out . $m[2]; + } +} diff --git a/admin/survey/minify/lib/Minify/CSS/UriRewriter.php b/admin/survey/minify/lib/Minify/CSS/UriRewriter.php new file mode 100644 index 0000000..6f69908 --- /dev/null +++ b/admin/survey/minify/lib/Minify/CSS/UriRewriter.php @@ -0,0 +1,358 @@ + + */ +class Minify_CSS_UriRewriter +{ + + /** + * rewrite() and rewriteRelative() append debugging information here + * + * @var string + */ + public static $debugText = ''; + + /** + * In CSS content, rewrite file relative URIs as root relative + * + * @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.: + * + * array('//symlink' => '/real/target/path') // unix + * array('//static' => 'D:\\staticStorage') // Windows + * + * + * @return string + */ + public static function rewrite($css, $currentDir, $docRoot = null, $symlinks = array()) + { + self::$_docRoot = self::_realpath( + $docRoot ? $docRoot : $_SERVER['DOCUMENT_ROOT'] + ); + self::$_currentDir = self::_realpath($currentDir); + self::$_symlinks = array(); + + // normalize symlinks in order to map to link + 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); + + $css = self::_owlifySvgPaths($css); + + // rewrite + $pattern = '/@import\\s+([\'"])(.*?)[\'"]/'; + $css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css); + + $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'; + $css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css); + + $css = self::_unOwlify($css); + + return $css; + } + + /** + * In CSS content, prepend a path to relative URIs + * + * @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); + + $css = self::_owlifySvgPaths($css); + + // append + $pattern = '/@import\\s+([\'"])(.*?)[\'"]/'; + $css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css); + + $pattern = '/url\\(\\s*([\'"](.*?)[\'"]|[^\\)\\s]+)\\s*\\)/'; + $css = preg_replace_callback($pattern, array(self::$className, '_processUriCB'), $css); + + $css = self::_unOwlify($css); + + self::$_prependPath = null; + + return $css; + } + + /** + * Get a root relative URI from a file relative URI + * + * + * 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' + * + * + * @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.: + * + * array('/home/foo/www/not/real/path' => '/real/target/path') // unix + * array('C:\\htdocs\\not\\real' => 'D:\\real\\target\\path') // Windows + * + * + * @return string + */ + public static function rewriteRelative($uri, $realCurrentDir, $realDocRoot, $symlinks = array()) + { + // prepend path with current dir separator (OS-independent) + $path = strtr($realCurrentDir, '/', DIRECTORY_SEPARATOR); + $path .= 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, '/\\', '//'); + $uri = self::removeDots($uri); + + self::$debugText .= "traversals removed : {$uri}\n\n"; + + return $uri; + } + + /** + * Remove instances of "./" and "../" where possible from a root-relative URI + * + * @param string $uri + * + * @return string + */ + public static function removeDots($uri) + { + $uri = str_replace('/./', '/', $uri); + // inspired by patch from Oleg Cherniy + do { + $uri = preg_replace('@/[^/]+/\\.\\./@', '/', $uri, 1, $changed); + } while ($changed); + + return $uri; + } + + /** + * 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'; + + /** + * 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, '/\\'); + } + + /** + * Directory of this stylesheet + * + * @var string + */ + private static $_currentDir = ''; + + /** + * DOC_ROOT + * + * @var string + */ + private static $_docRoot = ''; + + /** + * directory replacements to map symlink targets back to their + * source (within the document root) E.g. '/var/www/symlink' => '/var/realpath' + * + * @var array + */ + private static $_symlinks = array(); + + /** + * Path to prepend + * + * @var string + */ + private static $_prependPath = null; + + /** + * @param string $css + * + * @return string + */ + private static function _trimUrls($css) + { + $pattern = '/ + url\\( # url( + \\s* + ([^\\)]+?) # 1 = URI (assuming does not contain ")") + \\s* + \\) # ) + /x'; + + return preg_replace($pattern, 'url($1)', $css); + } + + /** + * @param array $m + * + * @return string + */ + 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); + } + + if ($uri === '') { + return $m[0]; + } + + // if not root/scheme relative and not starts with scheme + if (!preg_match('~^(/|[a-z]+\:)~', $uri)) { + // URI is file-relative: rewrite depending on options + if (self::$_prependPath === null) { + $uri = self::rewriteRelative($uri, self::$_currentDir, self::$_docRoot, self::$_symlinks); + } else { + $uri = self::$_prependPath . $uri; + if ($uri[0] === '/') { + $root = ''; + $rootRelative = $uri; + $uri = $root . self::removeDots($rootRelative); + } elseif (preg_match('@^((https?\:)?//([^/]+))/@', $uri, $m) && (false !== strpos($m[3], '.'))) { + $root = $m[1]; + $rootRelative = substr($uri, strlen($root)); + $uri = $root . self::removeDots($rootRelative); + } + } + } + + if ($isImport) { + return "@import {$quoteChar}{$uri}{$quoteChar}"; + } else { + return "url({$quoteChar}{$uri}{$quoteChar})"; + } + } + + /** + * Mungs some inline SVG URL declarations so they won't be touched + * + * @link https://github.com/mrclay/minify/issues/517 + * @see _unOwlify + * + * @param string $css + * @return string + */ + private static function _owlifySvgPaths($css) + { + $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)url(\(\s*#\w+\s*\))~'; + + return preg_replace($pattern, '$1owl$2', $css); + } + + /** + * Undo work of _owlify + * + * @see _owlifySvgPaths + * + * @param string $css + * @return string + */ + private static function _unOwlify($css) + { + $pattern = '~\b((?:clip-path|mask|-webkit-mask)\s*\:\s*)owl~'; + + return preg_replace($pattern, '$1url', $css); + } +} diff --git a/admin/survey/minify/lib/Minify/CSSmin.php b/admin/survey/minify/lib/Minify/CSSmin.php new file mode 100644 index 0000000..dcde782 --- /dev/null +++ b/admin/survey/minify/lib/Minify/CSSmin.php @@ -0,0 +1,88 @@ + + */ +class Minify_CSSmin +{ + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options available options: + * + * 'removeCharsets': (default true) remove all @charset at-rules + * + * 'prependRelativePath': (default null) if given, this string will be + * prepended to all relative URIs in import/url declarations + * + * 'currentDir': (default null) if given, this is assumed to be the + * directory of the current CSS file. Using this, minify will rewrite + * all relative URIs in import/url declarations to correctly point to + * the desired files. For this to work, the files *must* exist and be + * visible by the PHP process. + * + * '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.: + * + * array('//symlink' => '/real/target/path') // unix + * array('//static' => 'D:\\staticStorage') // Windows + * + * + * 'docRoot': (default = $_SERVER['DOCUMENT_ROOT']) + * see Minify_CSS_UriRewriter::rewrite + * + * @return string + */ + public static function minify($css, $options = array()) + { + $options = array_merge(array( + 'compress' => true, + 'removeCharsets' => true, + 'currentDir' => null, + 'docRoot' => $_SERVER['DOCUMENT_ROOT'], + 'prependRelativePath' => null, + 'symlinks' => array(), + ), $options); + + if ($options['removeCharsets']) { + $css = preg_replace('/@charset[^;]+;\\s*/', '', $css); + } + if ($options['compress']) { + $obj = new CSSmin(); + $css = $obj->run($css); + } + if (! $options['currentDir'] && ! $options['prependRelativePath']) { + return $css; + } + if ($options['currentDir']) { + return Minify_CSS_UriRewriter::rewrite( + $css + ,$options['currentDir'] + ,$options['docRoot'] + ,$options['symlinks'] + ); + } + + return Minify_CSS_UriRewriter::prepend( + $css + ,$options['prependRelativePath'] + ); + } +} diff --git a/admin/survey/minify/lib/Minify/Cache/APC.php b/admin/survey/minify/lib/Minify/Cache/APC.php new file mode 100644 index 0000000..27a57d7 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/APC.php @@ -0,0 +1,136 @@ + + * Minify::setCache(new Minify_Cache_APC()); + * + * + * @package Minify + * @author Chris Edwards + **/ +class Minify_Cache_APC implements Minify_CacheInterface +{ + + /** + * Create a Minify_Cache_APC object, to be passed to + * Minify::setCache(). + * + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + * + * @return null + */ + public function __construct($expire = 0) + { + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return apc_store($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + if (! $this->_fetch($id)) { + return false; + } + + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + return mb_strlen($this->_data, '8bit'); + } else { + return strlen($this->_data); + } + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) ? $this->_data : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) ? $this->_data : ''; + } + + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from apc, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + $ret = apc_fetch($id); + if (false === $ret) { + $this->_id = null; + + return false; + } + + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + + return true; + } +} diff --git a/admin/survey/minify/lib/Minify/Cache/File.php b/admin/survey/minify/lib/Minify/Cache/File.php new file mode 100644 index 0000000..5db1e20 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/File.php @@ -0,0 +1,183 @@ +locking = $fileLocking; + $this->path = $path; + + if (!$logger) { + $logger = new \Monolog\Logger('minify'); + } + $this->logger = $logger; + } + + /** + * Write data to cache. + * + * @param string $id cache id (e.g. a filename) + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + $flag = $this->locking ? LOCK_EX : null; + $file = $this->path . '/' . $id; + + if (! @file_put_contents($file, $data, $flag)) { + $this->logger->warning("Minify_Cache_File: Write failed to '$file'"); + } + + // write control + if ($data !== $this->fetch($id)) { + @unlink($file); + $this->logger->warning("Minify_Cache_File: Post-write read failed for '$file'"); + + return false; + } + + return true; + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id (e.g. a filename) + * + * @return int size in bytes + */ + public function getSize($id) + { + return filesize($this->path . '/' . $id); + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id (e.g. a filename) + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + $file = $this->path . '/' . $id; + + return (is_file($file) && (filemtime($file) >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id (e.g. a filename) + */ + public function display($id) + { + if (!$this->locking) { + readfile($this->path . '/' . $id); + + return; + } + + $fp = fopen($this->path . '/' . $id, 'rb'); + flock($fp, LOCK_SH); + fpassthru($fp); + flock($fp, LOCK_UN); + fclose($fp); + } + + /** + * Fetch the cached content + * + * @param string $id cache id (e.g. a filename) + * + * @return string + */ + public function fetch($id) + { + if (!$this->locking) { + return file_get_contents($this->path . '/' . $id); + } + + $fp = fopen($this->path . '/' . $id, 'rb'); + if (!$fp) { + return false; + } + + flock($fp, LOCK_SH); + $ret = stream_get_contents($fp); + flock($fp, LOCK_UN); + fclose($fp); + + return $ret; + } + + /** + * Fetch the cache path used + * + * @return string + */ + public function getPath() + { + return $this->path; + } + + /** + * Get a usable temp directory + * + * @return string + * @deprecated + */ + public static function tmp() + { + trigger_error(__METHOD__ . ' is deprecated in Minfy 3.0', E_USER_DEPRECATED); + + return sys_get_temp_dir(); + } + + /** + * Send message to the Minify logger + * @param string $msg + * @return null + * @deprecated Use $this->logger + */ + protected function _log($msg) + { + trigger_error(__METHOD__ . ' is deprecated in Minify 3.0.', E_USER_DEPRECATED); + $this->logger->warning($msg); + } +} diff --git a/admin/survey/minify/lib/Minify/Cache/Memcache.php b/admin/survey/minify/lib/Minify/Cache/Memcache.php new file mode 100644 index 0000000..726785a --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/Memcache.php @@ -0,0 +1,141 @@ + + * // fall back to disk caching if memcache can't connect + * $memcache = new Memcache; + * if ($memcache->connect('localhost', 11211)) { + * Minify::setCache(new Minify_Cache_Memcache($memcache)); + * } else { + * Minify::setCache(); + * } + * + **/ +class Minify_Cache_Memcache implements Minify_CacheInterface +{ + + /** + * Create a Minify_Cache_Memcache object, to be passed to + * Minify::setCache(). + * + * @param Memcache $memcache already-connected instance + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + */ + public function __construct($memcache, $expire = 0) + { + $this->_mc = $memcache; + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return $this->_mc->set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", 0, $this->_exp); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + if (! $this->_fetch($id)) { + return false; + } + + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + return mb_strlen($this->_data, '8bit'); + } else { + return strlen($this->_data); + } + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) ? $this->_data : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) ? $this->_data : ''; + } + + private $_mc = null; + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from memcache, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + + $ret = $this->_mc->get($id); + if (false === $ret) { + $this->_id = null; + + return false; + } + + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + + return true; + } +} diff --git a/admin/survey/minify/lib/Minify/Cache/Null.php b/admin/survey/minify/lib/Minify/Cache/Null.php new file mode 100644 index 0000000..b6f6566 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/Null.php @@ -0,0 +1,67 @@ + + * Minify::setCache(new Minify_Cache_WinCache()); + * + * + * @package Minify + * @author Matthias Fax + **/ +class Minify_Cache_WinCache implements Minify_CacheInterface +{ + /** + * Create a Minify_Cache_Wincache object, to be passed to + * Minify::setCache(). + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + * + * @throws Exception + */ + public function __construct($expire = 0) + { + if (!function_exists('wincache_ucache_info')) { + throw new Exception("WinCache for PHP is not installed to be able to use Minify_Cache_WinCache!"); + } + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return wincache_ucache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + if (!$this->_fetch($id)) { + return false; + } + + if (function_exists('mb_strlen') && ((int) ini_get('mbstring.func_overload') & 2)) { + return mb_strlen($this->_data, '8bit'); + } else { + return strlen($this->_data); + } + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) ? $this->_data : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) ? $this->_data : ''; + } + + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from WinCache, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + + $suc = false; + $ret = wincache_ucache_get($id, $suc); + if (!$suc) { + $this->_id = null; + + return false; + } + + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + + return true; + } +} \ No newline at end of file diff --git a/admin/survey/minify/lib/Minify/Cache/XCache.php b/admin/survey/minify/lib/Minify/Cache/XCache.php new file mode 100644 index 0000000..aa2a8de --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/XCache.php @@ -0,0 +1,130 @@ + + * Minify::setCache(new Minify_Cache_XCache()); + * + * + * @package Minify + * @author Elan Ruusamäe + **/ +class Minify_Cache_XCache implements Minify_CacheInterface +{ + + /** + * Create a Minify_Cache_XCache object, to be passed to + * Minify::setCache(). + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + */ + public function __construct($expire = 0) + { + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * @param string $data + * @return bool success + */ + public function store($id, $data) + { + return xcache_set($id, "{$_SERVER['REQUEST_TIME']}|{$data}", $this->_exp); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * @return int size in bytes + */ + public function getSize($id) + { + if (! $this->_fetch($id)) { + return false; + } + + if (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) { + return mb_strlen($this->_data, '8bit'); + } else { + return strlen($this->_data); + } + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * @param int $srcMtime mtime of the original source file(s) + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) ? $this->_data : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) ? $this->_data : ''; + } + + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from xcache, store in instance + * + * @param string $id + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + + $ret = xcache_get($id); + if (false === $ret) { + $this->_id = null; + + return false; + } + + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + + return true; + } +} diff --git a/admin/survey/minify/lib/Minify/Cache/ZendPlatform.php b/admin/survey/minify/lib/Minify/Cache/ZendPlatform.php new file mode 100644 index 0000000..90dfabb --- /dev/null +++ b/admin/survey/minify/lib/Minify/Cache/ZendPlatform.php @@ -0,0 +1,129 @@ + + * Minify::setCache(new Minify_Cache_ZendPlatform()); + * + * + * @package Minify + * @author Patrick van Dissel + */ +class Minify_Cache_ZendPlatform implements Minify_CacheInterface +{ + + /** + * Create a Minify_Cache_ZendPlatform object, to be passed to + * Minify::setCache(). + * + * @param int $expire seconds until expiration (default = 0 + * meaning the item will not get an expiration date) + * + */ + public function __construct($expire = 0) + { + $this->_exp = $expire; + } + + /** + * Write data to cache. + * + * @param string $id cache id + * + * @param string $data + * + * @return bool success + */ + public function store($id, $data) + { + return output_cache_put($id, "{$_SERVER['REQUEST_TIME']}|{$data}"); + } + + /** + * Get the size of a cache entry + * + * @param string $id cache id + * + * @return int size in bytes + */ + public function getSize($id) + { + return $this->_fetch($id) ? strlen($this->_data) : false; + } + + /** + * Does a valid cache entry exist? + * + * @param string $id cache id + * + * @param int $srcMtime mtime of the original source file(s) + * + * @return bool exists + */ + public function isValid($id, $srcMtime) + { + return ($this->_fetch($id) && ($this->_lm >= $srcMtime)); + } + + /** + * Send the cached content to output + * + * @param string $id cache id + */ + public function display($id) + { + echo $this->_fetch($id) ? $this->_data : ''; + } + + /** + * Fetch the cached content + * + * @param string $id cache id + * + * @return string + */ + public function fetch($id) + { + return $this->_fetch($id) ? $this->_data : ''; + } + + private $_exp = null; + + // cache of most recently fetched id + private $_lm = null; + private $_data = null; + private $_id = null; + + /** + * Fetch data and timestamp from ZendPlatform, store in instance + * + * @param string $id + * + * @return bool success + */ + private function _fetch($id) + { + if ($this->_id === $id) { + return true; + } + + $ret = output_cache_get($id, $this->_exp); + if (false === $ret) { + $this->_id = null; + + return false; + } + + list($this->_lm, $this->_data) = explode('|', $ret, 2); + $this->_id = $id; + + return true; + } +} diff --git a/admin/survey/minify/lib/Minify/CacheInterface.php b/admin/survey/minify/lib/Minify/CacheInterface.php new file mode 100644 index 0000000..4b1c51a --- /dev/null +++ b/admin/survey/minify/lib/Minify/CacheInterface.php @@ -0,0 +1,58 @@ + + * Minify_ClosureCompiler::$jarFile = '/path/to/closure-compiler-20120123.jar'; + * Minify_ClosureCompiler::$tempDir = '/tmp'; + * $code = Minify_ClosureCompiler::minify( + * $code, + * array('compilation_level' => 'SIMPLE_OPTIMIZATIONS') + * ); + * + * --compilation_level WHITESPACE_ONLY, SIMPLE_OPTIMIZATIONS, ADVANCED_OPTIMIZATIONS + * + * + * + * @package Minify + * @author Stephen Clay + * @author Elan Ruusamäe + */ +class Minify_ClosureCompiler +{ + public static $isDebug = false; + + /** + * Filepath of the Closure Compiler jar file. This must be set before + * calling minifyJs(). + * + * @var string + */ + public static $jarFile; + + /** + * Writable temp directory. This must be set before calling minifyJs(). + * + * @var string + */ + public static $tempDir; + + /** + * Filepath of "java" executable (may be needed if not in shell's PATH) + * + * @var string + */ + public static $javaExecutable = 'java'; + + /** + * Default command line options passed to closure-compiler + * + * @var array + */ + public static $defaultOptions = array( + 'charset' => 'utf-8', + 'compilation_level' => 'SIMPLE_OPTIMIZATIONS', + 'warning_level' => 'QUIET', + ); + + /** + * Minify a Javascript string + * + * @param string $js + * @param array $options (verbose is ignored) + * @see https://code.google.com/p/closure-compiler/source/browse/trunk/README + * @return string + * @throws Minify_ClosureCompiler_Exception + */ + public static function minify($js, $options = array()) + { + $min = new static(); + + return $min->process($js, $options); + } + + /** + * Process $js using $options. + * + * @param string $js + * @param array $options + * @return string + * @throws Exception + * @throws Minify_ClosureCompiler_Exception + */ + public function process($js, $options) + { + $tmpFile = $this->dumpFile(self::$tempDir, $js); + try { + $result = $this->compile($tmpFile, $options); + } catch (Exception $e) { + unlink($tmpFile); + throw $e; + } + unlink($tmpFile); + + return $result; + } + + /** + * @param string $tmpFile + * @param array $options + * @return string + * @throws Minify_ClosureCompiler_Exception + */ + protected function compile($tmpFile, $options) + { + $command = $this->getCommand($options, $tmpFile); + + return implode("\n", $this->shell($command)); + } + + /** + * @param array $userOptions + * @param string $tmpFile + * @return string + */ + protected function getCommand($userOptions, $tmpFile) + { + $args = array_merge( + $this->getCompilerCommandLine(), + $this->getOptionsCommandLine($userOptions) + ); + + return implode(' ', $args) . ' ' . escapeshellarg($tmpFile); + } + + /** + * @return array + * @throws Minify_ClosureCompiler_Exception + */ + protected function getCompilerCommandLine() + { + $this->checkJar(self::$jarFile); + $server = array( + self::$javaExecutable, + '-jar', + escapeshellarg(self::$jarFile) + ); + + return $server; + } + + /** + * @param array $userOptions + * @return array + */ + protected function getOptionsCommandLine($userOptions) + { + $args = array(); + + $options = array_merge( + static::$defaultOptions, + $userOptions + ); + + foreach ($options as $key => $value) { + $args[] = "--{$key} " . escapeshellarg($value); + } + + return $args; + } + + /** + * @param string $jarFile + * @throws Minify_ClosureCompiler_Exception + */ + protected function checkJar($jarFile) + { + if (!is_file($jarFile)) { + throw new Minify_ClosureCompiler_Exception('$jarFile(' . $jarFile . ') is not a valid file.'); + } + if (!is_readable($jarFile)) { + throw new Minify_ClosureCompiler_Exception('$jarFile(' . $jarFile . ') is not readable.'); + } + } + + /** + * @param string $tempDir + * @throws Minify_ClosureCompiler_Exception + */ + protected function checkTempdir($tempDir) + { + if (!is_dir($tempDir)) { + throw new Minify_ClosureCompiler_Exception('$tempDir(' . $tempDir . ') is not a valid direcotry.'); + } + if (!is_writable($tempDir)) { + throw new Minify_ClosureCompiler_Exception('$tempDir(' . $tempDir . ') is not writable.'); + } + } + + /** + * Write $content to a temporary file residing in $dir. + * + * @param string $dir + * @param string $content + * @return string + * @throws Minify_ClosureCompiler_Exception + */ + protected function dumpFile($dir, $content) + { + $this->checkTempdir($dir); + $tmpFile = tempnam($dir, 'cc_'); + if (!$tmpFile) { + throw new Minify_ClosureCompiler_Exception('Could not create temp file in "' . $dir . '".'); + } + file_put_contents($tmpFile, $content); + + return $tmpFile; + } + + /** + * Execute command, throw if exit code is not in $expectedCodes array + * + * @param string $command + * @param array $expectedCodes + * @return mixed + * @throws Minify_ClosureCompiler_Exception + */ + protected function shell($command, $expectedCodes = array(0)) + { + exec($command, $output, $result_code); + if (!in_array($result_code, $expectedCodes)) { + throw new Minify_ClosureCompiler_Exception("Unpexpected return code: $result_code"); + } + + return $output; + } +} + +class Minify_ClosureCompiler_Exception extends Exception +{ +} diff --git a/admin/survey/minify/lib/Minify/CommentPreserver.php b/admin/survey/minify/lib/Minify/CommentPreserver.php new file mode 100644 index 0000000..2e6599d --- /dev/null +++ b/admin/survey/minify/lib/Minify/CommentPreserver.php @@ -0,0 +1,87 @@ + + */ +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 comments will be surrounded by + * Minify_CommentPreserver::$preprend and Minify_CommentPreserver::$append. + * + * @param string $content + * @param callback $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 = array()) + { + $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 3rd 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 array($in, false, false); + } + + $beforeComment = substr($in, 0, $start); + $comment = self::$prepend . '/*!' . substr($in, $start + 3, $end - $start - 1) . self::$append; + + $endChars = (strlen($in) - $end - 2); + $afterComment = (0 === $endChars) ? '' : substr($in, -$endChars); + + return array($beforeComment, $comment, $afterComment); + } +} diff --git a/admin/survey/minify/lib/Minify/Config.php b/admin/survey/minify/lib/Minify/Config.php new file mode 100644 index 0000000..a085920 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Config.php @@ -0,0 +1,78 @@ + + */ +abstract class Minify_Controller_Base implements Minify_ControllerInterface +{ + + /** + * @var Minify_Env + */ + protected $env; + + /** + * @var Minify_Source_Factory + */ + protected $sourceFactory; + + /** + * @var LoggerInterface + */ + protected $logger; + + /** + * @param Minify_Env $env + * @param Minify_Source_Factory $sourceFactory + * @param LoggerInterface $logger + */ + public function __construct(Minify_Env $env, Minify_Source_Factory $sourceFactory, LoggerInterface $logger = null) + { + $this->env = $env; + $this->sourceFactory = $sourceFactory; + if (!$logger) { + $logger = new Logger('minify'); + } + $this->logger = $logger; + } + + /** + * Create controller sources and options for Minify::serve() + * + * @param array $options controller and Minify options + * + * @return Minify_ServeConfiguration + */ + abstract public function createConfiguration(array $options); + + /** + * Send message to the Minify logger + * + * @param string $msg + * + * @return null + * @deprecated use $this->logger + */ + public function log($msg) + { + trigger_error(__METHOD__ . ' is deprecated in Minify 3.0.', E_USER_DEPRECATED); + $this->logger->info($msg); + } + + /** + * {@inheritdoc} + */ + public function getEnv() + { + return $this->env; + } +} diff --git a/admin/survey/minify/lib/Minify/Controller/Files.php b/admin/survey/minify/lib/Minify/Controller/Files.php new file mode 100644 index 0000000..a9bb941 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Controller/Files.php @@ -0,0 +1,71 @@ + + * $options = [ + * 'checkAllowDirs' => false, // allow files to be anywhere + * ]; + * $sourceFactory = new Minify_Source_Factory($env, $options, $cache); + * $controller = new Minify_Controller_Files($env, $sourceFactory); + * $minify->serve($controller, [ + * 'files' => [ + * '//js/jquery.js', + * '//js/plugins.js', + * '/home/username/file.js', + * ], + * ]); + * + * + * @package Minify + * @author Stephen Clay + */ +class Minify_Controller_Files extends Minify_Controller_Base +{ + + /** + * Set up file sources + * + * @param array $options controller and Minify options + * @return Minify_ServeConfiguration + * + * Controller options: + * + * 'files': (required) array of complete file paths, or a single path + */ + public function createConfiguration(array $options) + { + // strip controller options + + $files = $options['files']; + // if $files is a single object, casting will break it + if (is_object($files)) { + $files = array($files); + } elseif (! is_array($files)) { + $files = (array)$files; + } + unset($options['files']); + + $sources = array(); + foreach ($files as $file) { + try { + $sources[] = $this->sourceFactory->makeSource($file); + } catch (Minify_Source_FactoryException $e) { + $this->logger->error($e->getMessage()); + + return new Minify_ServeConfiguration($options); + } + } + + return new Minify_ServeConfiguration($options, $sources); + } +} + diff --git a/admin/survey/minify/lib/Minify/Controller/Groups.php b/admin/survey/minify/lib/Minify/Controller/Groups.php new file mode 100644 index 0000000..6d4e5f4 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Controller/Groups.php @@ -0,0 +1,76 @@ + + * Minify::serve('Groups', array( + * 'groups' => array( + * 'css' => array('//css/type.css', '//css/layout.css') + * ,'js' => array('//js/jquery.js', '//js/site.js') + * ) + * )); + * + * + * If the above code were placed in /serve.php, it would enable the URLs + * /serve.php/js and /serve.php/css + * + * @package Minify + * @author Stephen Clay + */ +class Minify_Controller_Groups extends Minify_Controller_Files +{ + + /** + * Set up groups of files as sources + * + * @param array $options controller and Minify options + * + * 'groups': (required) array mapping PATH_INFO strings to arrays + * of complete file paths. @see Minify_Controller_Groups + * + * @return array Minify options + */ + public function createConfiguration(array $options) + { + // strip controller options + $groups = $options['groups']; + unset($options['groups']); + + $server = $this->env->server(); + + // mod_fcgid places PATH_INFO in ORIG_PATH_INFO + if (isset($server['ORIG_PATH_INFO'])) { + $pathInfo = substr($server['ORIG_PATH_INFO'], 1); + } elseif (isset($server['PATH_INFO'])) { + $pathInfo = substr($server['PATH_INFO'], 1); + } else { + $pathInfo = false; + } + + if (false === $pathInfo || ! isset($groups[$pathInfo])) { + // no PATH_INFO or not a valid group + $this->logger->info("Missing PATH_INFO or no group set for \"$pathInfo\""); + + return new Minify_ServeConfiguration($options); + } + + $files = $groups[$pathInfo]; + // if $files is a single object, casting will break it + if (is_object($files)) { + $files = array($files); + } elseif (! is_array($files)) { + $files = (array)$files; + } + + $options['files'] = $files; + + return parent::createConfiguration($options); + } +} + diff --git a/admin/survey/minify/lib/Minify/Controller/MinApp.php b/admin/survey/minify/lib/Minify/Controller/MinApp.php new file mode 100644 index 0000000..9a6a098 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Controller/MinApp.php @@ -0,0 +1,196 @@ + + */ +class Minify_Controller_MinApp extends Minify_Controller_Base +{ + + /** + * Set up groups of files as sources + * + * @param array $options controller and Minify options + * + * @return array Minify options + */ + public function createConfiguration(array $options) + { + // PHP insecure by default: realpath() and other FS functions can't handle null bytes. + $get = $this->env->get(); + foreach (array('g', 'b', 'f') as $key) { + if (isset($get[$key])) { + $get[$key] = str_replace("\x00", '', (string)$get[$key]); + } + } + + // filter controller options + $defaults = array( + 'groupsOnly' => false, + 'groups' => array(), + 'symlinks' => array(), + ); + $minApp = isset($options['minApp']) ? $options['minApp'] : array(); + $localOptions = array_merge($defaults, $minApp); + + unset($options['minApp']); + + // normalize $symlinks in order to map to target + $symlinks = array(); + foreach ($localOptions['symlinks'] as $link => $target) { + if (0 === strpos($link, '//')) { + $link = rtrim(substr($link, 1), '/') . '/'; + $target = rtrim($target, '/\\'); + $symlinks[$link] = $target; + } + } + + $sources = array(); + $selectionId = ''; + $firstMissing = null; + + if (isset($get['g'])) { + // add group(s) + $selectionId .= 'g=' . $get['g']; + $keys = explode(',', $get['g']); + if ($keys != array_unique($keys)) { + $this->logger->info("Duplicate group key found."); + + return new Minify_ServeConfiguration($options); + } + foreach ($keys as $key) { + if (! isset($localOptions['groups'][$key])) { + $this->logger->info("A group configuration for \"{$key}\" was not found"); + + return new Minify_ServeConfiguration($options); + } + $files = $localOptions['groups'][$key]; + // if $files is a single object, casting will break it + if (is_object($files)) { + $files = array($files); + } elseif (! is_array($files)) { + $files = (array)$files; + } + foreach ($files as $file) { + try { + $source = $this->sourceFactory->makeSource($file); + $sources[] = $source; + } catch (Minify_Source_FactoryException $e) { + $this->logger->error($e->getMessage()); + if (null === $firstMissing) { + $firstMissing = basename($file); + continue; + } else { + $secondMissing = basename($file); + $this->logger->info("More than one file was missing: '$firstMissing', '$secondMissing'"); + + return new Minify_ServeConfiguration($options); + } + } + } + } + } + if (! $localOptions['groupsOnly'] && isset($get['f'])) { + // try user files + // The following restrictions are to limit the URLs that minify will + // respond to. + + // verify at least one file, files are single comma separated, and are all same extension + $validPattern = preg_match('/^[^,]+\\.(css|less|scss|js)(?:,[^,]+\\.\\1)*$/', $get['f'], $m); + $hasComment = strpos($get['f'], '//') !== false; + $hasEscape = strpos($get['f'], '\\') !== false; + + if (!$validPattern || $hasComment || $hasEscape) { + $this->logger->info("GET param 'f' was invalid"); + + return new Minify_ServeConfiguration($options); + } + + $ext = ".{$m[1]}"; + $files = explode(',', $get['f']); + if ($files != array_unique($files)) { + $this->logger->info("Duplicate files were specified"); + + return new Minify_ServeConfiguration($options); + } + + if (isset($get['b'])) { + // check for validity + $isValidBase = preg_match('@^[^/]+(?:/[^/]+)*$@', $get['b']); + $hasDots = false !== strpos($get['b'], '..'); + $isDot = $get['b'] === '.'; + + if ($isValidBase && !$hasDots && !$isDot) { + // valid base + $base = "/{$get['b']}/"; + } else { + $this->logger->info("GET param 'b' was invalid"); + + return new Minify_ServeConfiguration($options); + } + } else { + $base = '/'; + } + + $basenames = array(); // just for cache id + foreach ($files as $file) { + $uri = $base . $file; + $path = $this->env->getDocRoot() . $uri; + + // try to rewrite path + foreach ($symlinks as $link => $target) { + if (0 === strpos($uri, $link)) { + $path = $target . DIRECTORY_SEPARATOR . substr($uri, strlen($link)); + break; + } + } + + try { + $source = $this->sourceFactory->makeSource($path); + $sources[] = $source; + $basenames[] = basename($path, $ext); + } catch (Minify_Source_FactoryException $e) { + $this->logger->error($e->getMessage()); + if (null === $firstMissing) { + $firstMissing = $uri; + continue; + } else { + $secondMissing = $uri; + $this->logger->info("More than one file was missing: '$firstMissing', '$secondMissing`'"); + + return new Minify_ServeConfiguration($options); + } + } + } + if ($selectionId) { + $selectionId .= '_f='; + } + $selectionId .= implode(',', $basenames) . $ext; + } + + if (!$sources) { + $this->logger->info("No sources to serve"); + + return new Minify_ServeConfiguration($options); + } + + if (null !== $firstMissing) { + array_unshift($sources, new Minify_Source(array( + 'id' => 'missingFile', + // should not cause cache invalidation + 'lastModified' => 0, + // due to caching, filename is unreliable. + 'content' => "/* Minify: at least one missing file. See " . Minify::URL_DEBUG . " */\n", + 'minifier' => 'Minify::nullMinifier', + ))); + } + + return new Minify_ServeConfiguration($options, $sources, $selectionId); + } +} diff --git a/admin/survey/minify/lib/Minify/Controller/Page.php b/admin/survey/minify/lib/Minify/Controller/Page.php new file mode 100644 index 0000000..8ca00d5 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Controller/Page.php @@ -0,0 +1,69 @@ + + */ +class Minify_Controller_Page extends Minify_Controller_Base +{ + + /** + * Set up source of HTML content + * + * @param array $options controller and Minify options + * @return array Minify options + * + * Controller options: + * + * 'content': (required) HTML markup + * + * 'id': (required) id of page (string for use in server-side caching) + * + * 'lastModifiedTime': timestamp of when this content changed. This + * is recommended to allow both server and client-side caching. + * + * 'minifyAll': should all CSS and Javascript blocks be individually + * minified? (default false) + */ + public function createConfiguration(array $options) + { + if (isset($options['file'])) { + $sourceSpec = array( + 'filepath' => $options['file'] + ); + $f = $options['file']; + } else { + // strip controller options + $sourceSpec = array( + 'content' => $options['content'], + 'id' => $options['id'], + ); + $f = $options['id']; + unset($options['content'], $options['id']); + } + // something like "builder,index.php" or "directory,file.html" + $selectionId = strtr(substr($f, 1 + strlen(dirname(dirname($f)))), '/\\', ',,'); + + if (isset($options['minifyAll'])) { + // this will be the 2nd argument passed to Minify_HTML::minify() + $sourceSpec['minifyOptions'] = array( + 'cssMinifier' => array('Minify_CSSmin', 'minify'), + 'jsMinifier' => array('JSMin\\JSMin', 'minify'), + ); + unset($options['minifyAll']); + } + + $sourceSpec['contentType'] = Minify::TYPE_HTML; + $sources[] = new Minify_Source($sourceSpec); + + return new Minify_ServeConfiguration($options, $sources, $selectionId); + } +} + diff --git a/admin/survey/minify/lib/Minify/ControllerInterface.php b/admin/survey/minify/lib/Minify/ControllerInterface.php new file mode 100644 index 0000000..d468635 --- /dev/null +++ b/admin/survey/minify/lib/Minify/ControllerInterface.php @@ -0,0 +1,22 @@ + + */ +class Minify_DebugDetector +{ + public static function shouldDebugRequest(Minify_Env $env) + { + if ($env->get('debug') !== null) { + return true; + } + + $cookieValue = $env->cookie('minifyDebug'); + if ($cookieValue) { + foreach (preg_split('/\\s+/', $cookieValue) as $debugUri) { + $pattern = '@' . preg_quote($debugUri, '@') . '@i'; + $pattern = str_replace(array('\\*', '\\?'), array('.*', '.'), $pattern); + if (preg_match($pattern, $env->getRequestUri())) { + return true; + } + } + } + + return false; + } +} diff --git a/admin/survey/minify/lib/Minify/Env.php b/admin/survey/minify/lib/Minify/Env.php new file mode 100644 index 0000000..3bc78f7 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Env.php @@ -0,0 +1,127 @@ +server['DOCUMENT_ROOT']; + } + + /** + * @return string + */ + public function getRequestUri() + { + return $this->server['REQUEST_URI']; + } + + public function __construct($options = array()) + { + $options = array_merge(array( + 'server' => $_SERVER, + 'get' => $_GET, + 'post' => $_POST, + 'cookie' => $_COOKIE, + ), $options); + + $this->server = $options['server']; + if (empty($this->server['DOCUMENT_ROOT'])) { + $this->server['DOCUMENT_ROOT'] = $this->computeDocRoot($options['server']); + } else { + $this->server['DOCUMENT_ROOT'] = rtrim($this->server['DOCUMENT_ROOT'], '/\\'); + } + + $this->server['DOCUMENT_ROOT'] = $this->normalizePath($this->server['DOCUMENT_ROOT']); + $this->get = $options['get']; + $this->post = $options['post']; + $this->cookie = $options['cookie']; + } + + public function server($key = null) + { + if (null === $key) { + return $this->server; + } + + return isset($this->server[$key]) ? $this->server[$key] : null; + } + + public function cookie($key = null, $default = null) + { + if (null === $key) { + return $this->cookie; + } + + return isset($this->cookie[$key]) ? $this->cookie[$key] : $default; + } + + public function get($key = null, $default = null) + { + if (null === $key) { + return $this->get; + } + + return isset($this->get[$key]) ? $this->get[$key] : $default; + } + + public function post($key = null, $default = null) + { + if (null === $key) { + return $this->post; + } + + return isset($this->post[$key]) ? $this->post[$key] : $default; + } + + /** + * turn windows-style slashes into unix-style, + * remove trailing slash + * and lowercase drive letter + * + * @param string $path absolute path + * + * @return string + */ + public function normalizePath($path) + { + $realpath = realpath($path); + if ($realpath) { + $path = $realpath; + } + + $path = str_replace('\\', '/', $path); + $path = rtrim($path, '/'); + if (substr($path, 1, 1) === ':') { + $path = lcfirst($path); + } + + return $path; + } + + protected $server; + protected $get; + protected $post; + protected $cookie; + + /** + * Compute $_SERVER['DOCUMENT_ROOT'] for IIS using SCRIPT_FILENAME and SCRIPT_NAME. + * + * @param array $server + * @return string + */ + protected function computeDocRoot(array $server) + { + if (isset($server['SERVER_SOFTWARE']) && 0 !== strpos($server['SERVER_SOFTWARE'], 'Microsoft-IIS/')) { + throw new InvalidArgumentException('DOCUMENT_ROOT is not provided and could not be computed'); + } + + $substrLength = strlen($server['SCRIPT_FILENAME']) - strlen($server['SCRIPT_NAME']); + $docRoot = substr($server['SCRIPT_FILENAME'], 0, $substrLength); + + return rtrim($docRoot, '\\'); + } +} diff --git a/admin/survey/minify/lib/Minify/HTML.php b/admin/survey/minify/lib/Minify/HTML.php new file mode 100644 index 0000000..24b14ac --- /dev/null +++ b/admin/survey/minify/lib/Minify/HTML.php @@ -0,0 +1,258 @@ + + */ +class Minify_HTML +{ + /** + * @var boolean + */ + protected $_jsCleanComments = true; + + /** + * "Minify" an HTML page + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + * + * @return string + */ + public static function minify($html, $options = array()) + { + $min = new self($html, $options); + + return $min->process(); + } + + /** + * Create a minifier object + * + * @param string $html + * + * @param array $options + * + * 'cssMinifier' : (optional) callback function to process content of STYLE + * elements. + * + * 'jsMinifier' : (optional) callback function to process content of SCRIPT + * elements. Note: the type attribute is ignored. + * + * 'jsCleanComments' : (optional) whether to remove HTML comments beginning and end of script block + * + * 'xhtml' : (optional boolean) should content be treated as XHTML1.0? If + * unset, minify will sniff for an XHTML doctype. + */ + public function __construct($html, $options = array()) + { + $this->_html = str_replace("\r\n", "\n", trim($html)); + if (isset($options['xhtml'])) { + $this->_isXhtml = (bool)$options['xhtml']; + } + if (isset($options['cssMinifier'])) { + $this->_cssMinifier = $options['cssMinifier']; + } + if (isset($options['jsMinifier'])) { + $this->_jsMinifier = $options['jsMinifier']; + } + if (isset($options['jsCleanComments'])) { + $this->_jsCleanComments = (bool)$options['jsCleanComments']; + } + } + + /** + * Minify the markeup given in the constructor + * + * @return string + */ + public function process() + { + if ($this->_isXhtml === null) { + $this->_isXhtml = (false !== strpos($this->_html, '_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']); + $this->_placeholders = array(); + + // replace SCRIPTs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/(\\s*)]*?>)([\\s\\S]*?)<\\/script>(\\s*)/iu' + ,array($this, '_removeScriptCB') + ,$this->_html); + + // replace STYLEs (and minify) with placeholders + $this->_html = preg_replace_callback( + '/\\s*]*>)([\\s\\S]*?)<\\/style>\\s*/iu' + ,array($this, '_removeStyleCB') + ,$this->_html); + + // remove HTML comments (not containing IE conditional comments). + $this->_html = preg_replace_callback( + '//u' + ,array($this, '_commentCB') + ,$this->_html); + + // replace PREs with placeholders + $this->_html = preg_replace_callback('/\\s*]*?>[\\s\\S]*?<\\/pre>)\\s*/iu' + ,array($this, '_removePreCB') + ,$this->_html); + + // replace TEXTAREAs with placeholders + $this->_html = preg_replace_callback( + '/\\s*]*?>[\\s\\S]*?<\\/textarea>)\\s*/iu' + ,array($this, '_removeTextareaCB') + ,$this->_html); + + // trim each line. + // @todo take into account attribute values that span multiple lines. + $this->_html = preg_replace('/^\\s+|\\s+$/mu', '', $this->_html); + + // remove ws around block/undisplayed elements + $this->_html = preg_replace('/\\s+(<\\/?(?:area|article|aside|base(?:font)?|blockquote|body' + .'|canvas|caption|center|col(?:group)?|dd|dir|div|dl|dt|fieldset|figcaption|figure|footer|form' + .'|frame(?:set)?|h[1-6]|head|header|hgroup|hr|html|legend|li|link|main|map|menu|meta|nav' + .'|ol|opt(?:group|ion)|output|p|param|section|t(?:able|body|head|d|h||r|foot|itle)' + .'|ul|video)\\b[^>]*>)/iu', '$1', $this->_html); + + // remove ws outside of all elements + $this->_html = preg_replace( + '/>(\\s(?:\\s*))?([^<]+)(\\s(?:\s*))?$1$2$3<' + ,$this->_html); + + // use newlines before 1st attribute in open tags (to limit line lengths) + $this->_html = preg_replace('/(<[a-z\\-]+)\\s+([^>]+>)/iu', "$1\n$2", $this->_html); + + // fill placeholders + $this->_html = str_replace( + array_keys($this->_placeholders) + ,array_values($this->_placeholders) + ,$this->_html + ); + // issue 229: multi-pass to catch scripts that didn't get replaced in textareas + $this->_html = str_replace( + array_keys($this->_placeholders) + ,array_values($this->_placeholders) + ,$this->_html + ); + + return $this->_html; + } + + protected function _commentCB($m) + { + return (0 === strpos($m[1], '[') || false !== strpos($m[1], '_replacementHash . count($this->_placeholders) . '%'; + $this->_placeholders[$placeholder] = $content; + + return $placeholder; + } + + protected $_isXhtml; + protected $_replacementHash; + protected $_placeholders = array(); + protected $_cssMinifier; + protected $_jsMinifier; + + protected function _removePreCB($m) + { + return $this->_reservePlace("_reservePlace("\\s*$)/u', '', $css); + + // remove CDATA section markers + $css = $this->_removeCdata($css); + + // minify + $minifier = $this->_cssMinifier + ? $this->_cssMinifier + : 'trim'; + $css = call_user_func($minifier, $css); + + return $this->_reservePlace($this->_needsCdata($css) + ? "{$openStyle}/**/" + : "{$openStyle}{$css}" + ); + } + + protected function _removeScriptCB($m) + { + $openScript = "_jsCleanComments) { + $js = preg_replace('/(?:^\\s*\\s*$)/u', '', $js); + } + + // remove CDATA section markers + $js = $this->_removeCdata($js); + + // minify + $minifier = $this->_jsMinifier + ? $this->_jsMinifier + : 'trim'; + $js = call_user_func($minifier, $js); + + return $this->_reservePlace($this->_needsCdata($js) + ? "{$ws1}{$openScript}/**/{$ws2}" + : "{$ws1}{$openScript}{$js}{$ws2}" + ); + } + + protected function _removeCdata($str) + { + return (false !== strpos($str, ''), '', $str) + : $str; + } + + protected function _needsCdata($str) + { + return ($this->_isXhtml && preg_match('/(?:[<&]|\\-\\-|\\]\\]>)/u', $str)); + } +} diff --git a/admin/survey/minify/lib/Minify/HTML/Helper.php b/admin/survey/minify/lib/Minify/HTML/Helper.php new file mode 100644 index 0000000..84ed45e --- /dev/null +++ b/admin/survey/minify/lib/Minify/HTML/Helper.php @@ -0,0 +1,250 @@ + + */ +class Minify_HTML_Helper +{ + public $rewriteWorks = true; + public $minAppUri = '/min'; + public $groupsConfigFile = ''; + + /** + * Get an HTML-escaped Minify URI for a group or set of files + * + * @param string|array $keyOrFiles a group key or array of filepaths/URIs + * @param array $opts options: + * 'farExpires' : (default true) append a modified timestamp for cache revving + * 'debug' : (default false) append debug flag + * 'charset' : (default 'UTF-8') for htmlspecialchars + * 'minAppUri' : (default '/min') URI of min directory + * 'rewriteWorks' : (default true) does mod_rewrite work in min app? + * 'groupsConfigFile' : specify if different + * @return string + */ + public static function getUri($keyOrFiles, $opts = array()) + { + $opts = array_merge(array( // default options + 'farExpires' => true, + 'debug' => false, + 'charset' => 'UTF-8', + 'minAppUri' => '/min', + 'rewriteWorks' => true, + 'groupsConfigFile' => self::app()->groupsConfigPath, + ), $opts); + + $h = new self; + $h->minAppUri = $opts['minAppUri']; + $h->rewriteWorks = $opts['rewriteWorks']; + $h->groupsConfigFile = $opts['groupsConfigFile']; + + if (is_array($keyOrFiles)) { + $h->setFiles($keyOrFiles, $opts['farExpires']); + } else { + $h->setGroup($keyOrFiles, $opts['farExpires']); + } + $uri = $h->getRawUri($opts['farExpires'], $opts['debug']); + + return htmlspecialchars($uri, ENT_QUOTES, $opts['charset']); + } + + /** + * Get non-HTML-escaped URI to minify the specified files + * + * @param bool $farExpires + * @param bool $debug + * @return string + */ + public function getRawUri($farExpires = true, $debug = false) + { + $path = rtrim($this->minAppUri, '/') . '/'; + if (! $this->rewriteWorks) { + $path .= '?'; + } + if (null === $this->_groupKey) { + // @todo: implement shortest uri + $path = self::_getShortestUri($this->_filePaths, $path); + } else { + $path .= "g=" . $this->_groupKey; + } + if ($debug) { + $path .= "&debug"; + } elseif ($farExpires && $this->_lastModified) { + $path .= "&" . $this->_lastModified; + } + + return $path; + } + + /** + * Set the files that will comprise the URI we're building + * + * @param array $files + * @param bool $checkLastModified + */ + public function setFiles($files, $checkLastModified = true) + { + $this->_groupKey = null; + if ($checkLastModified) { + $this->_lastModified = self::getLastModified($files); + } + // normalize paths like in /min/f= + foreach ($files as $k => $file) { + if (0 === strpos($file, '//')) { + $file = substr($file, 2); + } elseif (0 === strpos($file, '/') || 1 === strpos($file, ':\\')) { + $file = substr($file, strlen(self::app()->env->getDocRoot()) + 1); + } + $file = strtr($file, '\\', '/'); + $files[$k] = $file; + } + $this->_filePaths = $files; + } + + /** + * Set the group of files that will comprise the URI we're building + * + * @param string $key + * @param bool $checkLastModified + */ + public function setGroup($key, $checkLastModified = true) + { + $this->_groupKey = $key; + if ($checkLastModified) { + if (! $this->groupsConfigFile) { + $this->groupsConfigFile = self::app()->groupsConfigPath; + } + if (is_file($this->groupsConfigFile)) { + $gc = (require $this->groupsConfigFile); + $keys = explode(',', $key); + foreach ($keys as $key) { + if (!isset($gc[$key])) { + // this can happen if value is null + // which could be solved with array_filter + continue; + } + + $this->_lastModified = self::getLastModified($gc[$key], $this->_lastModified); + } + } + } + } + + /** + * Get the max(lastModified) of all files + * + * @param array|string $sources + * @param int $lastModified + * @return int + */ + public static function getLastModified($sources, $lastModified = 0) + { + $max = $lastModified; + $factory = self::app()->sourceFactory; + + /** @var Minify_Source $source */ + foreach ((array)$sources as $source) { + $source = $factory->makeSource($source); + $max = max($max, $source->getLastModified()); + } + + return $max; + } + + /** + * @param \Minify\App $app + * @return \Minify\App + * @internal + */ + public static function app(\Minify\App $app = null) + { + static $cached; + if ($app) { + $cached = $app; + + return $app; + } + if ($cached === null) { + $cached = (require __DIR__ . '/../../../bootstrap.php'); + } + + return $cached; + } + + protected $_groupKey = null; // if present, URI will be like g=... + protected $_filePaths = array(); + protected $_lastModified = null; + + /** + * In a given array of strings, find the character they all have at + * a particular index + * + * @param array $arr array of strings + * @param int $pos index to check + * @return mixed a common char or '' if any do not match + */ + protected static function _getCommonCharAtPos($arr, $pos) + { + if (!isset($arr[0][$pos])) { + return ''; + } + $c = $arr[0][$pos]; + $l = count($arr); + if ($l === 1) { + return $c; + } + for ($i = 1; $i < $l; ++$i) { + if ($arr[$i][$pos] !== $c) { + return ''; + } + } + + return $c; + } + + /** + * Get the shortest URI to minify the set of source files + * + * @param array $paths root-relative URIs of files + * @param string $minRoot root-relative URI of the "min" application + * @return string + */ + protected static function _getShortestUri($paths, $minRoot = '/min/') + { + $pos = 0; + $base = ''; + while (true) { + $c = self::_getCommonCharAtPos($paths, $pos); + if ($c === '') { + break; + } else { + $base .= $c; + } + ++$pos; + } + $base = preg_replace('@[^/]+$@', '', $base); + $uri = $minRoot . 'f=' . implode(',', $paths); + + if (substr($base, -1) === '/') { + // we have a base dir! + $basedPaths = $paths; + $l = count($paths); + for ($i = 0; $i < $l; ++$i) { + $basedPaths[$i] = substr($paths[$i], strlen($base)); + } + $base = substr($base, 0, strlen($base) - 1); + $bUri = $minRoot . 'b=' . $base . '&f=' . implode(',', $basedPaths); + + $uri = strlen($uri) < strlen($bUri) ? $uri : $bUri; + } + + return $uri; + } +} diff --git a/admin/survey/minify/lib/Minify/ImportProcessor.php b/admin/survey/minify/lib/Minify/ImportProcessor.php new file mode 100644 index 0000000..0f8c981 --- /dev/null +++ b/admin/survey/minify/lib/Minify/ImportProcessor.php @@ -0,0 +1,217 @@ + + * @author Simon Schick + */ +class Minify_ImportProcessor +{ + public static $filesIncluded = array(); + + public static function process($file) + { + self::$filesIncluded = array(); + self::$_isCss = (strtolower(substr($file, -4)) === '.css'); + $obj = new Minify_ImportProcessor(dirname($file)); + + return $obj->_getContent($file); + } + + // allows callback funcs to know the current directory + private $_currentDir; + + // allows callback funcs to know the directory of the file that inherits this one + private $_previewsDir; + + // allows _importCB to write the fetched content back to the obj + private $_importedContent = ''; + + private static $_isCss; + + /** + * @param String $currentDir + * @param String $previewsDir Is only used internally + */ + private function __construct($currentDir, $previewsDir = "") + { + $this->_currentDir = $currentDir; + $this->_previewsDir = $previewsDir; + } + + private function _getContent($file, $is_imported = false) + { + $file = preg_replace('~\\?.*~', '', $file); + $file = realpath($file); + if (! $file + || in_array($file, self::$filesIncluded) + || false === ($content = @file_get_contents($file))) { + // file missing, already included, or failed read + return ''; + } + self::$filesIncluded[] = realpath($file); + $this->_currentDir = dirname($file); + + // remove UTF-8 BOM if present + if (pack("CCC",0xef,0xbb,0xbf) === substr($content, 0, 3)) { + $content = substr($content, 3); + } + // ensure uniform EOLs + $content = str_replace("\r\n", "\n", $content); + + // process @imports + $pattern = '/ + @import\\s+ + (?:url\\(\\s*)? # maybe url( + [\'"]? # maybe quote + (.*?) # 1 = URI + [\'"]? # maybe end quote + (?:\\s*\\))? # maybe ) + ([a-zA-Z,\\s]*)? # 2 = media list + ; # end token + /x'; + $content = preg_replace_callback($pattern, array($this, '_importCB'), $content); + + // You only need to rework the import-path if the script is imported + if (self::$_isCss && $is_imported) { + // rewrite remaining relative URIs + $pattern = '/url\\(\\s*([^\\)\\s]+)\\s*\\)/'; + $content = preg_replace_callback($pattern, array($this, '_urlCB'), $content); + } + + return $this->_importedContent . $content; + } + + private function _importCB($m) + { + $url = $m[1]; + $mediaList = preg_replace('/\\s+/', '', $m[2]); + + if (strpos($url, '://') > 0) { + // protocol, leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor will not include remote content */"; + } + if ('/' === $url[0]) { + // protocol-relative or root path + $url = ltrim($url, '/'); + $file = realpath($_SERVER['DOCUMENT_ROOT']) . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } else { + // relative to current path + $file = $this->_currentDir . DIRECTORY_SEPARATOR + . strtr($url, '/', DIRECTORY_SEPARATOR); + } + $obj = new Minify_ImportProcessor(dirname($file), $this->_currentDir); + $content = $obj->_getContent($file, true); + if ('' === $content) { + // failed. leave in place for CSS, comment for JS + return self::$_isCss + ? $m[0] + : "/* Minify_ImportProcessor could not fetch '{$file}' */"; + } + + return (!self::$_isCss || preg_match('@(?:^$|\\ball\\b)@', $mediaList)) + ? $content + : "@media {$mediaList} {\n{$content}\n}\n"; + } + + private function _urlCB($m) + { + // $m[1] is either quoted or not + $quote = ($m[1][0] === "'" || $m[1][0] === '"') ? $m[1][0] : ''; + + $url = ($quote === '') ? $m[1] : substr($m[1], 1, strlen($m[1]) - 2); + + if ('/' !== $url[0]) { + if (strpos($url, '//') > 0) { + // probably starts with protocol, do not alter + } else { + // prepend path with current dir separator (OS-independent) + $path = $this->_currentDir + . DIRECTORY_SEPARATOR . strtr($url, '/', DIRECTORY_SEPARATOR); + // update the relative path by the directory of the file that imported this one + $url = self::getPathDiff(realpath($this->_previewsDir), $path); + } + } + + return "url({$quote}{$url}{$quote})"; + } + + /** + * @param string $from + * @param string $to + * @param string $ps + * @return string + */ + private function getPathDiff($from, $to, $ps = DIRECTORY_SEPARATOR) + { + $realFrom = $this->truepath($from); + $realTo = $this->truepath($to); + + $arFrom = explode($ps, rtrim($realFrom, $ps)); + $arTo = explode($ps, rtrim($realTo, $ps)); + while (count($arFrom) && count($arTo) && ($arFrom[0] == $arTo[0])) { + array_shift($arFrom); + array_shift($arTo); + } + + return str_pad("", count($arFrom) * 3, '..' . $ps) . implode($ps, $arTo); + } + + /** + * This function is to replace PHP's extremely buggy realpath(). + * @param string $path The original path, can be relative etc. + * @return string The resolved path, it might not exist. + * @see http://stackoverflow.com/questions/4049856/replace-phps-realpath + */ + private function truepath($path) + { + // whether $path is unix or not + $unipath = ('' === $path) || ($path{0} !== '/'); + + // attempts to detect if path is relative in which case, add cwd + if (strpos($path, ':') === false && $unipath) { + $path = $this->_currentDir . DIRECTORY_SEPARATOR . $path; + } + + // resolve path parts (single dot, double dot and double delimiters) + $path = str_replace(array('/', '\\'), DIRECTORY_SEPARATOR, $path); + $parts = array_filter(explode(DIRECTORY_SEPARATOR, $path), 'strlen'); + $absolutes = array(); + foreach ($parts as $part) { + if ('.' === $part) { + continue; + } + if ('..' === $part) { + array_pop($absolutes); + } else { + $absolutes[] = $part; + } + } + + $path = implode(DIRECTORY_SEPARATOR, $absolutes); + // resolve any symlinks + if (file_exists($path) && linkinfo($path) > 0) { + $path = readlink($path); + } + // put initial separator that could have been lost + $path = !$unipath ? '/' . $path : $path; + + return $path; + } +} diff --git a/admin/survey/minify/lib/Minify/JS/ClosureCompiler.php b/admin/survey/minify/lib/Minify/JS/ClosureCompiler.php new file mode 100644 index 0000000..05e2e33 --- /dev/null +++ b/admin/survey/minify/lib/Minify/JS/ClosureCompiler.php @@ -0,0 +1,237 @@ + + * + * @todo can use a stream wrapper to unit test this? + */ +class Minify_JS_ClosureCompiler +{ + + /** + * @var string The option key for the maximum POST byte size + */ + const OPTION_MAX_BYTES = 'maxBytes'; + + /** + * @var string The option key for additional params. @see __construct + */ + const OPTION_ADDITIONAL_OPTIONS = 'additionalParams'; + + /** + * @var string The option key for the fallback Minifier + */ + const OPTION_FALLBACK_FUNCTION = 'fallbackFunc'; + + /** + * @var string The option key for the service URL + */ + const OPTION_COMPILER_URL = 'compilerUrl'; + + /** + * @var int The default maximum POST byte size according to https://developers.google.com/closure/compiler/docs/api-ref + */ + const DEFAULT_MAX_BYTES = 200000; + + /** + * @var string[] $DEFAULT_OPTIONS The default options to pass to the compiler service + * + * @note This would be a constant if PHP allowed it + */ + private static $DEFAULT_OPTIONS = array( + 'output_format' => 'text', + 'compilation_level' => 'SIMPLE_OPTIMIZATIONS' + ); + + /** + * @var string $url URL of compiler server. defaults to Google's + */ + protected $serviceUrl = 'https://closure-compiler.appspot.com/compile'; + + /** + * @var int $maxBytes The maximum JS size that can be sent to the compiler server in bytes + */ + protected $maxBytes = self::DEFAULT_MAX_BYTES; + + /** + * @var string[] $additionalOptions Additional options to pass to the compiler service + */ + protected $additionalOptions = array(); + + /** + * @var callable Function to minify JS if service fails. Default is JSMin + */ + protected $fallbackMinifier = array('JSMin\\JSMin', 'minify'); + + /** + * Minify JavaScript code via HTTP request to a Closure Compiler API + * + * @param string $js input code + * @param array $options Options passed to __construct(). @see __construct + * + * @return string + */ + public static function minify($js, array $options = array()) + { + $obj = new self($options); + + return $obj->min($js); + } + + /** + * @param array $options Options with keys available below: + * + * fallbackFunc : (callable) function to minify if service unavailable. Default is JSMin. + * + * compilerUrl : (string) URL to closure compiler server + * + * maxBytes : (int) The maximum amount of bytes to be sent as js_code in the POST request. + * Defaults to 200000. + * + * additionalParams : (string[]) Additional parameters to pass to the compiler server. Can be anything named + * in https://developers.google.com/closure/compiler/docs/api-ref except for js_code and + * output_info + */ + public function __construct(array $options = array()) + { + if (isset($options[self::OPTION_FALLBACK_FUNCTION])) { + $this->fallbackMinifier = $options[self::OPTION_FALLBACK_FUNCTION]; + } + if (isset($options[self::OPTION_COMPILER_URL])) { + $this->serviceUrl = $options[self::OPTION_COMPILER_URL]; + } + if (isset($options[self::OPTION_ADDITIONAL_OPTIONS]) && is_array($options[self::OPTION_ADDITIONAL_OPTIONS])) { + $this->additionalOptions = $options[self::OPTION_ADDITIONAL_OPTIONS]; + } + if (isset($options[self::OPTION_MAX_BYTES])) { + $this->maxBytes = (int) $options[self::OPTION_MAX_BYTES]; + } + } + + /** + * Call the service to perform the minification + * + * @param string $js JavaScript code + * @return string + * @throws Minify_JS_ClosureCompiler_Exception + */ + public function min($js) + { + $postBody = $this->buildPostBody($js); + + if ($this->maxBytes > 0) { + $bytes = (function_exists('mb_strlen') && ((int)ini_get('mbstring.func_overload') & 2)) + ? mb_strlen($postBody, '8bit') + : strlen($postBody); + if ($bytes > $this->maxBytes) { + throw new Minify_JS_ClosureCompiler_Exception( + 'POST content larger than ' . $this->maxBytes . ' bytes' + ); + } + } + + $response = $this->getResponse($postBody); + + if (preg_match('/^Error\(\d\d?\):/', $response)) { + if (is_callable($this->fallbackMinifier)) { + // use fallback + $response = "/* Received errors from Closure Compiler API:\n$response" + . "\n(Using fallback minifier)\n*/\n"; + $response .= call_user_func($this->fallbackMinifier, $js); + } else { + throw new Minify_JS_ClosureCompiler_Exception($response); + } + } + + if ($response === '') { + $errors = $this->getResponse($this->buildPostBody($js, true)); + throw new Minify_JS_ClosureCompiler_Exception($errors); + } + + return $response; + } + + /** + * Get the response for a given POST body + * + * @param string $postBody + * @return string + * @throws Minify_JS_ClosureCompiler_Exception + */ + protected function getResponse($postBody) + { + $allowUrlFopen = preg_match('/1|yes|on|true/i', ini_get('allow_url_fopen')); + + if ($allowUrlFopen) { + $contents = file_get_contents($this->serviceUrl, false, stream_context_create(array( + 'http' => array( + 'method' => 'POST', + 'compilation_level' => 'SIMPLE', + 'output_format' => 'text', + 'output_info' => 'compiled_code', + 'header' => "Content-type: application/x-www-form-urlencoded\r\nConnection: close\r\n", + 'content' => $postBody, + 'max_redirects' => 0, + 'timeout' => 15, + ) + ))); + } elseif (defined('CURLOPT_POST')) { + $ch = curl_init($this->serviceUrl); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-type: application/x-www-form-urlencoded')); + curl_setopt($ch, CURLOPT_POSTFIELDS, $postBody); + curl_setopt($ch, CURLOPT_FOLLOWLOCATION, false); + curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15); + $contents = curl_exec($ch); + curl_close($ch); + } else { + throw new Minify_JS_ClosureCompiler_Exception( + "Could not make HTTP request: allow_url_open is false and cURL not available" + ); + } + + if (false === $contents) { + throw new Minify_JS_ClosureCompiler_Exception( + "No HTTP response from server" + ); + } + + return trim($contents); + } + + /** + * Build a POST request body + * + * @param string $js JavaScript code + * @param bool $returnErrors + * @return string + */ + protected function buildPostBody($js, $returnErrors = false) + { + return http_build_query( + array_merge( + self::$DEFAULT_OPTIONS, + $this->additionalOptions, + array( + 'js_code' => $js, + 'output_info' => ($returnErrors ? 'errors' : 'compiled_code') + ) + ), + null, + '&' + ); + } +} + +class Minify_JS_ClosureCompiler_Exception extends Exception +{ +} diff --git a/admin/survey/minify/lib/Minify/JS/JShrink.php b/admin/survey/minify/lib/Minify/JS/JShrink.php new file mode 100644 index 0000000..5eb5042 --- /dev/null +++ b/admin/survey/minify/lib/Minify/JS/JShrink.php @@ -0,0 +1,48 @@ + + * @link https://github.com/tedious/JShrink + * + */ +class JShrink +{ + /** + * 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 string[] + */ + protected static $defaultOptions = array('flaggedComments' => true); + + /** + * 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 + * + * @see JShrink\Minifier::minify() + * @return string + */ + public static function minify($js, array $options = array()) + { + $options = array_merge( + self::$defaultOptions, + $options + ); + + return \JShrink\Minifier::minify($js, $options); + } +} diff --git a/admin/survey/minify/lib/Minify/LessCssSource.php b/admin/survey/minify/lib/Minify/LessCssSource.php new file mode 100644 index 0000000..389932c --- /dev/null +++ b/admin/survey/minify/lib/Minify/LessCssSource.php @@ -0,0 +1,128 @@ +cache = $cache; + } + + /** + * Get last modified of all parsed files + * + * @return int + */ + public function getLastModified() + { + $cache = $this->getCache(); + + return $cache['lastModified']; + } + + /** + * Get content + * + * @return string + */ + public function getContent() + { + $cache = $this->getCache(); + + return $cache['compiled']; + } + + /** + * Get lessphp cache object + * + * @return array + */ + private function getCache() + { + // cache for single run + // so that getLastModified and getContent in single request do not add additional cache roundtrips (i.e memcache) + if (isset($this->parsed)) { + return $this->parsed; + } + + // check from cache first + $cache = null; + $cacheId = $this->getCacheId(); + if ($this->cache->isValid($cacheId, 0)) { + if ($cache = $this->cache->fetch($cacheId)) { + $cache = unserialize($cache); + } + } + + $less = $this->getCompiler(); + $input = $cache ? $cache : $this->filepath; + $cache = $less->cachedCompile($input); + + if (!is_array($input) || $cache['updated'] > $input['updated']) { + $cache['lastModified'] = $this->getMaxLastModified($cache); + $this->cache->store($cacheId, serialize($cache)); + } + + return $this->parsed = $cache; + } + + /** + * Calculate maximum last modified of all files, + * as the 'updated' timestamp in cache is not the same as file last modified timestamp: + * @link https://github.com/leafo/lessphp/blob/v0.4.0/lessc.inc.php#L1904 + * @return int + */ + private function getMaxLastModified($cache) + { + $lastModified = 0; + foreach ($cache['files'] as $mtime) { + $lastModified = max($lastModified, $mtime); + } + + return $lastModified; + } + + /** + * Make a unique cache id for for this source. + * + * @param string $prefix + * + * @return string + */ + private function getCacheId($prefix = 'minify') + { + $md5 = md5($this->filepath); + + return "{$prefix}_less2_{$md5}"; + } + + /** + * Get instance of less compiler + * + * @return lessc + */ + private function getCompiler() + { + $less = new lessc(); + // do not spend CPU time letting less doing minify + $less->setPreserveComments(true); + + return $less; + } +} diff --git a/admin/survey/minify/lib/Minify/Lines.php b/admin/survey/minify/lib/Minify/Lines.php new file mode 100644 index 0000000..60bf3b4 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Lines.php @@ -0,0 +1,209 @@ + + * @author Adam Pedersen (Issue 55 fix) + */ +class Minify_Lines +{ + + /** + * Add line numbers in C-style comments + * + * This uses a very basic parser easily fooled by comment tokens inside + * strings or regexes, but, otherwise, generally clean code will not be + * mangled. URI rewriting can also be performed. + * + * @param string $content + * + * @param array $options available options: + * + * 'id': (optional) string to identify file. E.g. file name/path + * + * 'currentDir': (default null) if given, this is assumed to be the + * directory of the current CSS file. Using this, minify will rewrite + * all relative URIs in import/url declarations to correctly point to + * the desired files, and prepend a comment with debugging information about + * this process. + * + * @return string + */ + public static function minify($content, $options = array()) + { + $id = (isset($options['id']) && $options['id']) ? $options['id'] : ''; + $content = str_replace("\r\n", "\n", $content); + + $lines = explode("\n", $content); + $numLines = count($lines); + // determine left padding + $padTo = strlen((string) $numLines); // e.g. 103 lines = 3 digits + $inComment = false; + $i = 0; + $newLines = array(); + + while (null !== ($line = array_shift($lines))) { + if (('' !== $id) && (0 === $i % 50)) { + if ($inComment) { + array_push($newLines, '', "/* {$id} *|", ''); + } else { + array_push($newLines, '', "/* {$id} */", ''); + } + } + + ++$i; + $newLines[] = self::_addNote($line, $i, $inComment, $padTo); + $inComment = self::_eolInComment($line, $inComment); + } + + $content = implode("\n", $newLines) . "\n"; + + // check for desired URI rewriting + if (isset($options['currentDir'])) { + Minify_CSS_UriRewriter::$debugText = ''; + $docRoot = isset($options['docRoot']) ? $options['docRoot'] : $_SERVER['DOCUMENT_ROOT']; + $symlinks = isset($options['symlinks']) ? $options['symlinks'] : array(); + + $content = Minify_CSS_UriRewriter::rewrite($content, $options['currentDir'], $docRoot, $symlinks); + + $content = "/* Minify_CSS_UriRewriter::\$debugText\n\n" + . Minify_CSS_UriRewriter::$debugText . "*/\n" + . $content; + } + + return $content; + } + + /** + * Is the parser within a C-style comment at the end of this line? + * + * @param string $line current line of code + * + * @param bool $inComment was the parser in a C-style comment at the + * beginning of the previous line? + * + * @return bool + */ + private static function _eolInComment($line, $inComment) + { + while (strlen($line)) { + if ($inComment) { + // only "*/" can end the comment + $index = self::_find($line, '*/'); + if ($index === false) { + return true; + } + + // stop comment and keep walking line + $inComment = false; + @$line = (string)substr($line, $index + 2); + continue; + } + + // look for "//" and "/*" + $single = self::_find($line, '//'); + $multi = self::_find($line, '/*'); + if ($multi === false) { + return false; + } + + if ($single === false || $multi < $single) { + // start comment and keep walking line + $inComment = true; + @$line = (string)substr($line, $multi + 2); + continue; + } + + // a single-line comment preceeded it + return false; + } + + return $inComment; + } + + /** + * Prepend a comment (or note) to the given line + * + * @param string $line current line of code + * + * @param string $note content of note/comment + * + * @param bool $inComment was the parser in a comment at the + * beginning of the line? + * + * @param int $padTo minimum width of comment + * + * @return string + */ + private static function _addNote($line, $note, $inComment, $padTo) + { + if ($inComment) { + $line = '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' *| ' . $line; + } else { + $line = '/* ' . str_pad($note, $padTo, ' ', STR_PAD_RIGHT) . ' */ ' . $line; + } + + return rtrim($line); + } + + /** + * Find a token trying to avoid false positives + * + * @param string $str String containing the token + * @param string $token Token being checked + * @return bool + */ + private static function _find($str, $token) + { + switch ($token) { + case '//': + $fakes = array( + '://' => 1, + '"//' => 1, + '\'//' => 1, + '".//' => 2, + '\'.//' => 2, + ); + break; + case '/*': + $fakes = array( + '"/*' => 1, + '\'/*' => 1, + '"//*' => 2, + '\'//*' => 2, + '".//*' => 3, + '\'.//*' => 3, + '*/*' => 1, + '\\/*' => 1, + ); + break; + default: + $fakes = array(); + } + + $index = strpos($str, $token); + $offset = 0; + + while ($index !== false) { + foreach ($fakes as $fake => $skip) { + $check = substr($str, $index - $skip, strlen($fake)); + if ($check === $fake) { + // move offset and scan again + $offset += $index + strlen($token); + $index = strpos($str, $token, $offset); + break; + } + } + // legitimate find + return $index; + } + + return $index; + } +} diff --git a/admin/survey/minify/lib/Minify/Logger/LegacyHandler.php b/admin/survey/minify/lib/Minify/Logger/LegacyHandler.php new file mode 100644 index 0000000..fe4800f --- /dev/null +++ b/admin/survey/minify/lib/Minify/Logger/LegacyHandler.php @@ -0,0 +1,24 @@ +obj = $obj; + parent::__construct(); + } + + protected function write(array $record) + { + $this->obj->log((string)$record['formatted']); + } +} diff --git a/admin/survey/minify/lib/Minify/NailgunClosureCompiler.php b/admin/survey/minify/lib/Minify/NailgunClosureCompiler.php new file mode 100644 index 0000000..683e30f --- /dev/null +++ b/admin/survey/minify/lib/Minify/NailgunClosureCompiler.php @@ -0,0 +1,113 @@ + + * @link https://github.com/martylamb/nailgun + */ +class Minify_NailgunClosureCompiler extends Minify_ClosureCompiler +{ + const NG_SERVER = 'com.martiansoftware.nailgun.NGServer'; + const CC_MAIN = 'com.google.javascript.jscomp.CommandLineRunner'; + + /** + * For some reasons Nailgun thinks that it's server + * broke the connection and returns 227 instead of 0 + * We'll just handle this here instead of fixing + * the nailgun client itself. + * + * It also sometimes breaks on 229 on the devbox. + * To complete this whole madness and made future + * 'fixes' easier I added this nice little array... + * @var array + */ + private static $NG_EXIT_CODES = array(0, 227, 229); + + /** + * Filepath of "ng" executable (from Nailgun package) + * + * @var string + */ + public static $ngExecutable = 'ng'; + + /** + * Filepath of the Nailgun jar file. + * + * @var string + */ + public static $ngJarFile; + + /** + * Get command to launch NailGun server. + * + * @return array + */ + protected function getServerCommandLine() + { + $this->checkJar(self::$ngJarFile); + $this->checkJar(self::$jarFile); + + $classPath = array( + self::$ngJarFile, + self::$jarFile, + ); + + // The command for the server that should show up in the process list + $server = array( + self::$javaExecutable, + '-server', + '-cp', implode(':', $classPath), + self::NG_SERVER, + ); + + return $server; + } + + /** + * @return array + * @throws Minify_ClosureCompiler_Exception + */ + protected function getCompilerCommandLine() + { + $server = array( + self::$ngExecutable, + escapeshellarg(self::CC_MAIN) + ); + + return $server; + } + + /** + * @param string $tmpFile + * @param array $options + * @return string + * @throws Minify_ClosureCompiler_Exception + */ + protected function compile($tmpFile, $options) + { + $this->startServer(); + + $command = $this->getCommand($options, $tmpFile); + + return implode("\n", $this->shell($command, self::$NG_EXIT_CODES)); + } + + private function startServer() + { + $serverCommand = implode(' ', $this->getServerCommandLine()); + $psCommand = $this->shell("ps -o cmd= -C " . self::$javaExecutable); + if (in_array($serverCommand, $psCommand, true)) { + // already started! + return; + } + + $this->shell("$serverCommand /dev/null 2>/dev/null & sleep 10"); + } +} \ No newline at end of file diff --git a/admin/survey/minify/lib/Minify/Packer.php b/admin/survey/minify/lib/Minify/Packer.php new file mode 100644 index 0000000..53e9a9b --- /dev/null +++ b/admin/survey/minify/lib/Minify/Packer.php @@ -0,0 +1,31 @@ +pack()); + } +} diff --git a/admin/survey/minify/lib/Minify/ScssCssSource.php b/admin/survey/minify/lib/Minify/ScssCssSource.php new file mode 100644 index 0000000..ebccbe8 --- /dev/null +++ b/admin/survey/minify/lib/Minify/ScssCssSource.php @@ -0,0 +1,176 @@ +cache = $cache; + } + + /** + * Get last modified of all parsed files + * + * @return int + */ + public function getLastModified() + { + $cache = $this->getCache(); + + return $cache['updated']; + } + + /** + * Get content + * + * @return string + */ + public function getContent() + { + $cache = $this->getCache(); + + return $cache['content']; + } + + /** + * Make a unique cache id for for this source. + * + * @param string $prefix + * + * @return string + */ + private function getCacheId($prefix = 'minify') + { + $md5 = md5($this->filepath); + + return "{$prefix}_scss_{$md5}"; + } + + /** + * Get SCSS cache object + * + * Runs the compilation if needed + * + * Implements Leafo\ScssPhp\Server logic because we need to get parsed files without parsing actual content + * + * @return array + */ + private function getCache() + { + // cache for single run + // so that getLastModified and getContent in single request do not add additional cache roundtrips (i.e memcache) + if (isset($this->parsed)) { + return $this->parsed; + } + + // check from cache first + $cache = null; + $cacheId = $this->getCacheId(); + if ($this->cache->isValid($cacheId, 0)) { + if ($cache = $this->cache->fetch($cacheId)) { + $cache = unserialize($cache); + } + } + + $input = $cache ? $cache : $this->filepath; + + if ($this->cacheIsStale($cache)) { + $cache = $this->compile($this->filepath); + } + + if (!is_array($input) || $cache['updated'] > $input['updated']) { + $this->cache->store($cacheId, serialize($cache)); + } + + return $this->parsed = $cache; + } + + /** + * Determine whether .scss file needs to be re-compiled. + * + * @param array $cache Cache object + * + * @return boolean True if compile required. + */ + private function cacheIsStale($cache) + { + if (!$cache) { + return true; + } + + $updated = $cache['updated']; + foreach ($cache['files'] as $import => $mtime) { + $filemtime = filemtime($import); + + if ($filemtime !== $mtime || $filemtime > $updated) { + return true; + } + } + + return false; + } + + /** + * Compile .scss file + * + * @param string $filename Input path (.scss) + * + * @see Server::compile() + * @return array meta data result of the compile + */ + private function compile($filename) + { + $start = microtime(true); + $scss = new Compiler(); + + // set import path directory the input filename resides + // otherwise @import statements will not find the files + // and will treat the @import line as css import + $scss->setImportPaths(dirname($filename)); + + $css = $scss->compile(file_get_contents($filename), $filename); + $elapsed = round((microtime(true) - $start), 4); + + $v = Version::VERSION; + $ts = date('r', $start); + $css = "/* compiled by scssphp $v on $ts (${elapsed}s) */\n\n" . $css; + + $imports = $scss->getParsedFiles(); + + $updated = 0; + foreach ($imports as $mtime) { + $updated = max($updated, $mtime); + } + + return array( + 'elapsed' => $elapsed, // statistic, can be dropped + 'updated' => $updated, + 'content' => $css, + 'files' => $imports, + ); + } +} diff --git a/admin/survey/minify/lib/Minify/ServeConfiguration.php b/admin/survey/minify/lib/Minify/ServeConfiguration.php new file mode 100644 index 0000000..1c30708 --- /dev/null +++ b/admin/survey/minify/lib/Minify/ServeConfiguration.php @@ -0,0 +1,71 @@ +options = $options; + $this->sources = $sources; + $this->selectionId = $selectionId; + } + + /** + * @return array + */ + public function getOptions() + { + return $this->options; + } + + /** + * @return Minify_SourceInterface[] + */ + public function getSources() + { + return $this->sources; + } + + /** + * Short name to place inside cache id + * + * The setupSources() method may choose to set this, making it easier to + * recognize a particular set of sources/settings in the cache folder. It + * will be filtered and truncated to make the final cache id <= 250 bytes. + * + * @return string + */ + public function getSelectionId() + { + return $this->selectionId; + } +} diff --git a/admin/survey/minify/lib/Minify/Source.php b/admin/survey/minify/lib/Minify/Source.php new file mode 100644 index 0000000..d94bf58 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Source.php @@ -0,0 +1,219 @@ + + */ +class Minify_Source implements Minify_SourceInterface +{ + + /** + * @var int time of last modification + */ + protected $lastModified; + + /** + * @var callback minifier function specifically for this source. + */ + protected $minifier; + + /** + * @var array minification options specific to this source. + */ + protected $minifyOptions = array(); + + /** + * @var string full path of file + */ + protected $filepath; + + /** + * @var string HTTP Content Type (Minify requires one of the constants Minify::TYPE_*) + */ + protected $contentType; + + /** + * @var string + */ + protected $content; + + /** + * @var callable + */ + protected $getContentFunc; + + /** + * @var string + */ + protected $id; + + /** + * Create a Minify_Source + * + * In the $spec array(), you can either provide a 'filepath' to an existing + * file (existence will not be checked!) or give 'id' (unique string for + * the content), 'content' (the string content) and 'lastModified' + * (unixtime of last update). + * + * @param array $spec options + */ + public function __construct($spec) + { + if (isset($spec['filepath'])) { + $ext = pathinfo($spec['filepath'], PATHINFO_EXTENSION); + switch ($ext) { + case 'js': $this->contentType = Minify::TYPE_JS; + break; + case 'less': // fallthrough + case 'scss': // fallthrough + case 'css': $this->contentType = Minify::TYPE_CSS; + break; + case 'htm': // fallthrough + case 'html': $this->contentType = Minify::TYPE_HTML; + break; + } + $this->filepath = $spec['filepath']; + $this->id = $spec['filepath']; + + // TODO ideally not touch disk in constructor + $this->lastModified = filemtime($spec['filepath']); + + if (!empty($spec['uploaderHoursBehind'])) { + // offset for Windows uploaders with out of sync clocks + $this->lastModified += round($spec['uploaderHoursBehind'] * 3600); + } + } elseif (isset($spec['id'])) { + $this->id = 'id::' . $spec['id']; + if (isset($spec['content'])) { + $this->content = $spec['content']; + } else { + $this->getContentFunc = $spec['getContentFunc']; + } + $this->lastModified = isset($spec['lastModified']) ? $spec['lastModified'] : time(); + } + if (isset($spec['contentType'])) { + $this->contentType = $spec['contentType']; + } + if (isset($spec['minifier'])) { + $this->setMinifier($spec['minifier']); + } + if (isset($spec['minifyOptions'])) { + $this->minifyOptions = $spec['minifyOptions']; + } + } + + /** + * {@inheritdoc} + */ + public function getLastModified() + { + return $this->lastModified; + } + + /** + * {@inheritdoc} + */ + public function getMinifier() + { + return $this->minifier; + } + + /** + * {@inheritdoc} + */ + public function setMinifier($minifier = null) + { + if ($minifier === '') { + error_log(__METHOD__ . " cannot accept empty string. Use 'Minify::nullMinifier' or 'trim'."); + $minifier = 'Minify::nullMinifier'; + } + if ($minifier !== null && !is_callable($minifier, true)) { + throw new InvalidArgumentException('minifier must be null or a valid callable'); + } + $this->minifier = $minifier; + } + + /** + * {@inheritdoc} + */ + public function getMinifierOptions() + { + return $this->minifyOptions; + } + + /** + * {@inheritdoc} + */ + public function setMinifierOptions(array $options) + { + $this->minifyOptions = $options; + } + + /** + * {@inheritdoc} + */ + public function getContentType() + { + return $this->contentType; + } + + /** + * {@inheritdoc} + */ + public function getContent() + { + if (null === $this->filepath) { + if (null === $this->content) { + $content = call_user_func($this->getContentFunc, $this->id); + } else { + $content = $this->content; + } + } else { + $content = file_get_contents($this->filepath); + } + + // remove UTF-8 BOM if present + if (strpos($content, "\xEF\xBB\xBF") === 0) { + return substr($content, 3); + } + + return $content; + } + + /** + * {@inheritdoc} + */ + public function getId() + { + return $this->id; + } + + /** + * {@inheritdoc} + */ + public function getFilePath() + { + return $this->filepath; + } + + /** + * {@inheritdoc} + */ + public function setupUriRewrites() + { + if ($this->filepath + && !isset($this->minifyOptions['currentDir']) + && !isset($this->minifyOptions['prependRelativePath'])) { + $this->minifyOptions['currentDir'] = dirname($this->filepath); + } + } +} diff --git a/admin/survey/minify/lib/Minify/Source/Factory.php b/admin/survey/minify/lib/Minify/Source/Factory.php new file mode 100644 index 0000000..4371931 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Source/Factory.php @@ -0,0 +1,197 @@ +env = $env; + $this->options = array_merge(array( + 'noMinPattern' => '@[-\\.]min\\.(?:[a-zA-Z]+)$@i', // matched against basename + 'fileChecker' => array($this, 'checkIsFile'), + 'resolveDocRoot' => true, + 'checkAllowDirs' => true, + 'allowDirs' => array('//'), + 'uploaderHoursBehind' => 0, + ), $options); + + // resolve // in allowDirs + $docRoot = $env->getDocRoot(); + foreach ($this->options['allowDirs'] as $i => $dir) { + if (0 === strpos($dir, '//')) { + $this->options['allowDirs'][$i] = $docRoot . substr($dir, 1); + } + } + + if ($this->options['fileChecker'] && !is_callable($this->options['fileChecker'])) { + throw new InvalidArgumentException("fileChecker option is not callable"); + } + + $this->setHandler('~\.less$~i', function ($spec) use ($cache) { + return new Minify_LessCssSource($spec, $cache); + }); + + $this->setHandler('~\.scss~i', function ($spec) use ($cache) { + return new Minify_ScssCssSource($spec, $cache); + }); + + $this->setHandler('~\.(js|css)$~i', function ($spec) { + return new Minify_Source($spec); + }); + } + + /** + * @param string $basenamePattern A pattern tested against basename. E.g. "~\.css$~" + * @param callable $handler Function that recieves a $spec array and returns a Minify_SourceInterface + */ + public function setHandler($basenamePattern, $handler) + { + $this->handlers[$basenamePattern] = $handler; + } + + /** + * @param string $file + * @return string + * + * @throws Minify_Source_FactoryException + */ + public function checkIsFile($file) + { + $realpath = realpath($file); + if (!$realpath) { + throw new Minify_Source_FactoryException("File failed realpath(): $file"); + } + + $basename = basename($file); + if (0 === strpos($basename, '.')) { + throw new Minify_Source_FactoryException("Filename starts with period (may be hidden): $basename"); + } + + if (!is_file($realpath) || !is_readable($realpath)) { + throw new Minify_Source_FactoryException("Not a file or isn't readable: $file"); + } + + return $realpath; + } + + /** + * @param mixed $spec + * + * @return Minify_SourceInterface + * + * @throws Minify_Source_FactoryException + */ + public function makeSource($spec) + { + if (is_string($spec)) { + $spec = array( + 'filepath' => $spec, + ); + } elseif ($spec instanceof Minify_SourceInterface) { + return $spec; + } + + $source = null; + + if (empty($spec['filepath'])) { + // not much we can check + return new Minify_Source($spec); + } + + if ($this->options['resolveDocRoot'] && 0 === strpos($spec['filepath'], '//')) { + $spec['filepath'] = $this->env->getDocRoot() . substr($spec['filepath'], 1); + } + + if (!empty($this->options['fileChecker'])) { + $spec['filepath'] = call_user_func($this->options['fileChecker'], $spec['filepath']); + } + + if ($this->options['checkAllowDirs']) { + $allowDirs = (array)$this->options['allowDirs']; + $inAllowedDir = false; + $filePath = $this->env->normalizePath($spec['filepath']); + foreach ($allowDirs as $allowDir) { + if (strpos($filePath, $this->env->normalizePath($allowDir)) === 0) { + $inAllowedDir = true; + } + } + + if (!$inAllowedDir) { + $allowDirsStr = implode(';', $allowDirs); + throw new Minify_Source_FactoryException("File '{$spec['filepath']}' is outside \$allowDirs " + . "($allowDirsStr). If the path is resolved via an alias/symlink, look into the " + . "\$min_symlinks option."); + } + } + + $basename = basename($spec['filepath']); + + if ($this->options['noMinPattern'] && preg_match($this->options['noMinPattern'], $basename)) { + if (preg_match('~\.(css|less)$~i', $basename)) { + $spec['minifyOptions']['compress'] = false; + // we still want URI rewriting to work for CSS + } else { + $spec['minifier'] = 'Minify::nullMinifier'; + } + } + + $hoursBehind = $this->options['uploaderHoursBehind']; + if ($hoursBehind != 0) { + $spec['uploaderHoursBehind'] = $hoursBehind; + } + + foreach ($this->handlers as $basenamePattern => $handler) { + if (preg_match($basenamePattern, $basename)) { + $source = call_user_func($handler, $spec); + break; + } + } + + if (!$source) { + throw new Minify_Source_FactoryException("Handler not found for file: $basename"); + } + + return $source; + } +} diff --git a/admin/survey/minify/lib/Minify/Source/FactoryException.php b/admin/survey/minify/lib/Minify/Source/FactoryException.php new file mode 100644 index 0000000..bbc9833 --- /dev/null +++ b/admin/survey/minify/lib/Minify/Source/FactoryException.php @@ -0,0 +1,5 @@ +getId(), $source->getMinifier(), $source->getMinifierOptions() + ); + } + + return md5(serialize($info)); + } +} diff --git a/admin/survey/minify/lib/Minify/YUICompressor.php b/admin/survey/minify/lib/Minify/YUICompressor.php new file mode 100644 index 0000000..40fd02c --- /dev/null +++ b/admin/survey/minify/lib/Minify/YUICompressor.php @@ -0,0 +1,157 @@ + + * Minify_YUICompressor::$jarFile = '/path/to/yuicompressor-2.4.6.jar'; + * Minify_YUICompressor::$tempDir = '/tmp'; + * $code = Minify_YUICompressor::minifyJs( + * $code + * ,array('nomunge' => true, 'line-break' => 1000) + * ); + * + * + * Note: In case you run out stack (default is 512k), you may increase stack size in $options: + * array('stack-size' => '2048k') + * + * @todo unit tests, $options docs + * + * @package Minify + * @author Stephen Clay + */ +class Minify_YUICompressor +{ + + /** + * Filepath of the YUI Compressor jar file. This must be set before + * calling minifyJs() or minifyCss(). + * + * @var string + */ + public static $jarFile; + + /** + * Writable temp directory. This must be set before calling minifyJs() + * or minifyCss(). + * + * @var string + */ + public static $tempDir; + + /** + * Filepath of "java" executable (may be needed if not in shell's PATH) + * + * @var string + */ + public static $javaExecutable = 'java'; + + /** + * Minify a Javascript string + * + * @param string $js + * + * @param array $options (verbose is ignored) + * + * @see http://www.julienlecomte.net/yuicompressor/README + * + * @return string + */ + public static function minifyJs($js, $options = array()) + { + return self::_minify('js', $js, $options); + } + + /** + * Minify a CSS string + * + * @param string $css + * + * @param array $options (verbose is ignored) + * + * @see http://www.julienlecomte.net/yuicompressor/README + * + * @return string + */ + public static function minifyCss($css, $options = array()) + { + return self::_minify('css', $css, $options); + } + + private static function _minify($type, $content, $options) + { + self::_prepare(); + if (! ($tmpFile = tempnam(self::$tempDir, 'yuic_'))) { + throw new Exception('Minify_YUICompressor : could not create temp file in "'.self::$tempDir.'".'); + } + + file_put_contents($tmpFile, $content); + exec(self::_getCmd($options, $type, $tmpFile), $output, $result_code); + unlink($tmpFile); + if ($result_code != 0) { + throw new Exception('Minify_YUICompressor : YUI compressor execution failed.'); + } + + return implode("\n", $output); + } + + private static function _getCmd($userOptions, $type, $tmpFile) + { + $defaults = array( + 'charset' => '', + 'line-break' => 5000, + 'type' => $type, + 'nomunge' => false, + 'preserve-semi' => false, + 'disable-optimizations' => false, + 'stack-size' => '', + ); + $o = array_merge($defaults, $userOptions); + + $cmd = self::$javaExecutable + . (!empty($o['stack-size']) ? ' -Xss' . $o['stack-size'] : '') + . ' -jar ' . escapeshellarg(self::$jarFile) + . " --type {$type}" + . (preg_match('/^[\\da-zA-Z0-9\\-]+$/', $o['charset']) + ? " --charset {$o['charset']}" + : '') + . (is_numeric($o['line-break']) && $o['line-break'] >= 0 + ? ' --line-break ' . (int)$o['line-break'] + : ''); + if ($type === 'js') { + foreach (array('nomunge', 'preserve-semi', 'disable-optimizations') as $opt) { + $cmd .= $o[$opt] + ? " --{$opt}" + : ''; + } + } + + return $cmd . ' ' . escapeshellarg($tmpFile); + } + + private static function _prepare() + { + if (! is_file(self::$jarFile)) { + throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not a valid file.'); + } + if (! is_readable(self::$jarFile)) { + throw new Exception('Minify_YUICompressor : $jarFile('.self::$jarFile.') is not readable.'); + } + if (! is_dir(self::$tempDir)) { + throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not a valid direcotry.'); + } + if (! is_writable(self::$tempDir)) { + throw new Exception('Minify_YUICompressor : $tempDir('.self::$tempDir.') is not writable.'); + } + } +} + -- cgit v1.2.3