summaryrefslogtreecommitdiffstats
path: root/admin/survey/minify/lib/Minify/HTML
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--admin/survey/minify/lib/Minify/HTML.php258
-rw-r--r--admin/survey/minify/lib/Minify/HTML/Helper.php250
2 files changed, 508 insertions, 0 deletions
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 @@
+<?php
+/**
+ * Class Minify_HTML
+ * @package Minify
+ */
+
+/**
+ * Compress HTML
+ *
+ * This is a heavy regex-based removal of whitespace, unnecessary comments and
+ * tokens. IE conditional comments are preserved. There are also options to have
+ * STYLE and SCRIPT blocks compressed by callback functions.
+ *
+ * A test suite is available.
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ */
+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, '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML'));
+ }
+
+ $this->_replacementHash = 'MINIFYHTML' . md5($_SERVER['REQUEST_TIME']);
+ $this->_placeholders = array();
+
+ // replace SCRIPTs (and minify) with placeholders
+ $this->_html = preg_replace_callback(
+ '/(\\s*)<script(\\b[^>]*?>)([\\s\\S]*?)<\\/script>(\\s*)/iu'
+ ,array($this, '_removeScriptCB')
+ ,$this->_html);
+
+ // replace STYLEs (and minify) with placeholders
+ $this->_html = preg_replace_callback(
+ '/\\s*<style(\\b[^>]*>)([\\s\\S]*?)<\\/style>\\s*/iu'
+ ,array($this, '_removeStyleCB')
+ ,$this->_html);
+
+ // remove HTML comments (not containing IE conditional comments).
+ $this->_html = preg_replace_callback(
+ '/<!--([\\s\\S]*?)-->/u'
+ ,array($this, '_commentCB')
+ ,$this->_html);
+
+ // replace PREs with placeholders
+ $this->_html = preg_replace_callback('/\\s*<pre(\\b[^>]*?>[\\s\\S]*?<\\/pre>)\\s*/iu'
+ ,array($this, '_removePreCB')
+ ,$this->_html);
+
+ // replace TEXTAREAs with placeholders
+ $this->_html = preg_replace_callback(
+ '/\\s*<textarea(\\b[^>]*?>[\\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*))?</u'
+ ,'>$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], '<!['))
+ ? $m[0]
+ : '';
+ }
+
+ protected function _reservePlace($content)
+ {
+ $placeholder = '%' . $this->_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("<pre{$m[1]}");
+ }
+
+ protected function _removeTextareaCB($m)
+ {
+ return $this->_reservePlace("<textarea{$m[1]}");
+ }
+
+ protected function _removeStyleCB($m)
+ {
+ $openStyle = "<style{$m[1]}";
+ $css = $m[2];
+ // remove HTML comments
+ $css = preg_replace('/(?:^\\s*<!--|-->\\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}/*<![CDATA[*/{$css}/*]]>*/</style>"
+ : "{$openStyle}{$css}</style>"
+ );
+ }
+
+ protected function _removeScriptCB($m)
+ {
+ $openScript = "<script{$m[2]}";
+ $js = $m[3];
+
+ // whitespace surrounding? preserve at least one space
+ $ws1 = ($m[1] === '') ? '' : ' ';
+ $ws2 = ($m[4] === '') ? '' : ' ';
+
+ // remove HTML comments (and ending "//" if present)
+ if ($this->_jsCleanComments) {
+ $js = preg_replace('/(?:^\\s*<!--\\s*|\\s*(?:\\/\\/)?\\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}/*<![CDATA[*/{$js}/*]]>*/</script>{$ws2}"
+ : "{$ws1}{$openScript}{$js}</script>{$ws2}"
+ );
+ }
+
+ protected function _removeCdata($str)
+ {
+ return (false !== strpos($str, '<![CDATA['))
+ ? str_replace(array('<![CDATA[', ']]>'), '', $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 @@
+<?php
+/**
+ * Class Minify_HTML_Helper
+ * @package Minify
+ */
+
+/**
+ * Helpers for writing Minify URIs into HTML
+ *
+ * @package Minify
+ * @author Stephen Clay <steve@mrclay.org>
+ */
+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=<paths>
+ 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;
+ }
+}