git subrepo clone https://github.com/mailcow/mailcow-dockerized.git mailcow/src/mailcow-dockerized

subrepo: subdir:   "mailcow/src/mailcow-dockerized"
  merged:   "a832becb"
upstream: origin:   "https://github.com/mailcow/mailcow-dockerized.git"
  branch:   "master"
  commit:   "a832becb"
git-subrepo: version:  "0.4.3"
  origin:   "???"
  commit:   "???"
Change-Id: If5be2d621a211e164c9b6577adaa7884449f16b5
diff --git a/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php
new file mode 100644
index 0000000..3f40bc1
--- /dev/null
+++ b/mailcow/src/mailcow-dockerized/data/web/inc/lib/vendor/matthiasmullie/minify/src/Minify.php
@@ -0,0 +1,497 @@
+<?php
+/**
+ * Abstract minifier class
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+namespace MatthiasMullie\Minify;
+
+use MatthiasMullie\Minify\Exceptions\IOException;
+use Psr\Cache\CacheItemInterface;
+
+/**
+ * Abstract minifier class.
+ *
+ * Please report bugs on https://github.com/matthiasmullie/minify/issues
+ *
+ * @package Minify
+ * @author Matthias Mullie <minify@mullie.eu>
+ * @copyright Copyright (c) 2012, Matthias Mullie. All rights reserved
+ * @license MIT License
+ */
+abstract class Minify
+{
+    /**
+     * The data to be minified.
+     *
+     * @var string[]
+     */
+    protected $data = array();
+
+    /**
+     * Array of patterns to match.
+     *
+     * @var string[]
+     */
+    protected $patterns = array();
+
+    /**
+     * This array will hold content of strings and regular expressions that have
+     * been extracted from the JS source code, so we can reliably match "code",
+     * without having to worry about potential "code-like" characters inside.
+     *
+     * @var string[]
+     */
+    public $extracted = array();
+
+    /**
+     * Init the minify class - optionally, code may be passed along already.
+     */
+    public function __construct(/* $data = null, ... */)
+    {
+        // it's possible to add the source through the constructor as well ;)
+        if (func_num_args()) {
+            call_user_func_array(array($this, 'add'), func_get_args());
+        }
+    }
+
+    /**
+     * Add a file or straight-up code to be minified.
+     *
+     * @param string|string[] $data
+     *
+     * @return static
+     */
+    public function add($data /* $data = null, ... */)
+    {
+        // bogus "usage" of parameter $data: scrutinizer warns this variable is
+        // not used (we're using func_get_args instead to support overloading),
+        // but it still needs to be defined because it makes no sense to have
+        // this function without argument :)
+        $args = array($data) + func_get_args();
+
+        // this method can be overloaded
+        foreach ($args as $data) {
+            if (is_array($data)) {
+                call_user_func_array(array($this, 'add'), $data);
+                continue;
+            }
+
+            // redefine var
+            $data = (string) $data;
+
+            // load data
+            $value = $this->load($data);
+            $key = ($data != $value) ? $data : count($this->data);
+
+            // replace CR linefeeds etc.
+            // @see https://github.com/matthiasmullie/minify/pull/139
+            $value = str_replace(array("\r\n", "\r"), "\n", $value);
+
+            // store data
+            $this->data[$key] = $value;
+        }
+
+        return $this;
+    }
+
+    /**
+     * Add a file to be minified.
+     *
+     * @param string|string[] $data
+     *
+     * @return static
+     * 
+     * @throws IOException
+     */
+    public function addFile($data /* $data = null, ... */)
+    {
+        // bogus "usage" of parameter $data: scrutinizer warns this variable is
+        // not used (we're using func_get_args instead to support overloading),
+        // but it still needs to be defined because it makes no sense to have
+        // this function without argument :)
+        $args = array($data) + func_get_args();
+
+        // this method can be overloaded
+        foreach ($args as $path) {
+            if (is_array($path)) {
+                call_user_func_array(array($this, 'addFile'), $path);
+                continue;
+            }
+
+            // redefine var
+            $path = (string) $path;
+
+            // check if we can read the file
+            if (!$this->canImportFile($path)) {
+                throw new IOException('The file "'.$path.'" could not be opened for reading. Check if PHP has enough permissions.');
+            }
+
+            $this->add($path);
+        }
+
+        return $this;
+    }
+
+    /**
+     * Minify the data & (optionally) saves it to a file.
+     *
+     * @param string[optional] $path Path to write the data to
+     *
+     * @return string The minified data
+     */
+    public function minify($path = null)
+    {
+        $content = $this->execute($path);
+
+        // save to path
+        if ($path !== null) {
+            $this->save($content, $path);
+        }
+
+        return $content;
+    }
+
+    /**
+     * Minify & gzip the data & (optionally) saves it to a file.
+     *
+     * @param string[optional] $path  Path to write the data to
+     * @param int[optional]    $level Compression level, from 0 to 9
+     *
+     * @return string The minified & gzipped data
+     */
+    public function gzip($path = null, $level = 9)
+    {
+        $content = $this->execute($path);
+        $content = gzencode($content, $level, FORCE_GZIP);
+
+        // save to path
+        if ($path !== null) {
+            $this->save($content, $path);
+        }
+
+        return $content;
+    }
+
+    /**
+     * Minify the data & write it to a CacheItemInterface object.
+     *
+     * @param CacheItemInterface $item Cache item to write the data to
+     *
+     * @return CacheItemInterface Cache item with the minifier data
+     */
+    public function cache(CacheItemInterface $item)
+    {
+        $content = $this->execute();
+        $item->set($content);
+
+        return $item;
+    }
+
+    /**
+     * Minify the data.
+     *
+     * @param string[optional] $path Path to write the data to
+     *
+     * @return string The minified data
+     */
+    abstract public function execute($path = null);
+
+    /**
+     * Load data.
+     *
+     * @param string $data Either a path to a file or the content itself
+     *
+     * @return string
+     */
+    protected function load($data)
+    {
+        // check if the data is a file
+        if ($this->canImportFile($data)) {
+            $data = file_get_contents($data);
+
+            // strip BOM, if any
+            if (substr($data, 0, 3) == "\xef\xbb\xbf") {
+                $data = substr($data, 3);
+            }
+        }
+
+        return $data;
+    }
+
+    /**
+     * Save to file.
+     *
+     * @param string $content The minified data
+     * @param string $path    The path to save the minified data to
+     *
+     * @throws IOException
+     */
+    protected function save($content, $path)
+    {
+        $handler = $this->openFileForWriting($path);
+
+        $this->writeToFile($handler, $content);
+
+        @fclose($handler);
+    }
+
+    /**
+     * Register a pattern to execute against the source content.
+     *
+     * @param string          $pattern     PCRE pattern
+     * @param string|callable $replacement Replacement value for matched pattern
+     */
+    protected function registerPattern($pattern, $replacement = '')
+    {
+        // study the pattern, we'll execute it more than once
+        $pattern .= 'S';
+
+        $this->patterns[] = array($pattern, $replacement);
+    }
+
+    /**
+     * We can't "just" run some regular expressions against JavaScript: it's a
+     * complex language. E.g. having an occurrence of // xyz would be a comment,
+     * unless it's used within a string. Of you could have something that looks
+     * like a 'string', but inside a comment.
+     * The only way to accurately replace these pieces is to traverse the JS one
+     * character at a time and try to find whatever starts first.
+     *
+     * @param string $content The content to replace patterns in
+     *
+     * @return string The (manipulated) content
+     */
+    protected function replace($content)
+    {
+        $processed = '';
+        $positions = array_fill(0, count($this->patterns), -1);
+        $matches = array();
+
+        while ($content) {
+            // find first match for all patterns
+            foreach ($this->patterns as $i => $pattern) {
+                list($pattern, $replacement) = $pattern;
+
+                // we can safely ignore patterns for positions we've unset earlier,
+                // because we know these won't show up anymore
+                if (array_key_exists($i, $positions) == false) {
+                    continue;
+                }
+
+                // no need to re-run matches that are still in the part of the
+                // content that hasn't been processed
+                if ($positions[$i] >= 0) {
+                    continue;
+                }
+
+                $match = null;
+                if (preg_match($pattern, $content, $match, PREG_OFFSET_CAPTURE)) {
+                    $matches[$i] = $match;
+
+                    // we'll store the match position as well; that way, we
+                    // don't have to redo all preg_matches after changing only
+                    // the first (we'll still know where those others are)
+                    $positions[$i] = $match[0][1];
+                } else {
+                    // if the pattern couldn't be matched, there's no point in
+                    // executing it again in later runs on this same content;
+                    // ignore this one until we reach end of content
+                    unset($matches[$i], $positions[$i]);
+                }
+            }
+
+            // no more matches to find: everything's been processed, break out
+            if (!$matches) {
+                $processed .= $content;
+                break;
+            }
+
+            // see which of the patterns actually found the first thing (we'll
+            // only want to execute that one, since we're unsure if what the
+            // other found was not inside what the first found)
+            $discardLength = min($positions);
+            $firstPattern = array_search($discardLength, $positions);
+            $match = $matches[$firstPattern][0][0];
+
+            // execute the pattern that matches earliest in the content string
+            list($pattern, $replacement) = $this->patterns[$firstPattern];
+            $replacement = $this->replacePattern($pattern, $replacement, $content);
+
+            // figure out which part of the string was unmatched; that's the
+            // part we'll execute the patterns on again next
+            $content = (string) substr($content, $discardLength);
+            $unmatched = (string) substr($content, strpos($content, $match) + strlen($match));
+
+            // move the replaced part to $processed and prepare $content to
+            // again match batch of patterns against
+            $processed .= substr($replacement, 0, strlen($replacement) - strlen($unmatched));
+            $content = $unmatched;
+
+            // first match has been replaced & that content is to be left alone,
+            // the next matches will start after this replacement, so we should
+            // fix their offsets
+            foreach ($positions as $i => $position) {
+                $positions[$i] -= $discardLength + strlen($match);
+            }
+        }
+
+        return $processed;
+    }
+
+    /**
+     * This is where a pattern is matched against $content and the matches
+     * are replaced by their respective value.
+     * This function will be called plenty of times, where $content will always
+     * move up 1 character.
+     *
+     * @param string          $pattern     Pattern to match
+     * @param string|callable $replacement Replacement value
+     * @param string          $content     Content to match pattern against
+     *
+     * @return string
+     */
+    protected function replacePattern($pattern, $replacement, $content)
+    {
+        if (is_callable($replacement)) {
+            return preg_replace_callback($pattern, $replacement, $content, 1, $count);
+        } else {
+            return preg_replace($pattern, $replacement, $content, 1, $count);
+        }
+    }
+
+    /**
+     * Strings are a pattern we need to match, in order to ignore potential
+     * code-like content inside them, but we just want all of the string
+     * content to remain untouched.
+     *
+     * This method will replace all string content with simple STRING#
+     * placeholder text, so we've rid all strings from characters that may be
+     * misinterpreted. Original string content will be saved in $this->extracted
+     * and after doing all other minifying, we can restore the original content
+     * via restoreStrings().
+     *
+     * @param string[optional] $chars
+     * @param string[optional] $placeholderPrefix
+     */
+    protected function extractStrings($chars = '\'"', $placeholderPrefix = '')
+    {
+        // PHP only supports $this inside anonymous functions since 5.4
+        $minifier = $this;
+        $callback = function ($match) use ($minifier, $placeholderPrefix) {
+            // check the second index here, because the first always contains a quote
+            if ($match[2] === '') {
+                /*
+                 * Empty strings need no placeholder; they can't be confused for
+                 * anything else anyway.
+                 * But we still needed to match them, for the extraction routine
+                 * to skip over this particular string.
+                 */
+                return $match[0];
+            }
+
+            $count = count($minifier->extracted);
+            $placeholder = $match[1].$placeholderPrefix.$count.$match[1];
+            $minifier->extracted[$placeholder] = $match[1].$match[2].$match[1];
+
+            return $placeholder;
+        };
+
+        /*
+         * The \\ messiness explained:
+         * * Don't count ' or " as end-of-string if it's escaped (has backslash
+         * in front of it)
+         * * Unless... that backslash itself is escaped (another leading slash),
+         * in which case it's no longer escaping the ' or "
+         * * So there can be either no backslash, or an even number
+         * * multiply all of that times 4, to account for the escaping that has
+         * to be done to pass the backslash into the PHP string without it being
+         * considered as escape-char (times 2) and to get it in the regex,
+         * escaped (times 2)
+         */
+        $this->registerPattern('/(['.$chars.'])(.*?(?<!\\\\)(\\\\\\\\)*+)\\1/s', $callback);
+    }
+
+    /**
+     * This method will restore all extracted data (strings, regexes) that were
+     * replaced with placeholder text in extract*(). The original content was
+     * saved in $this->extracted.
+     *
+     * @param string $content
+     *
+     * @return string
+     */
+    protected function restoreExtractedData($content)
+    {
+        if (!$this->extracted) {
+            // nothing was extracted, nothing to restore
+            return $content;
+        }
+
+        $content = strtr($content, $this->extracted);
+
+        $this->extracted = array();
+
+        return $content;
+    }
+
+    /**
+     * Check if the path is a regular file and can be read.
+     *
+     * @param string $path
+     *
+     * @return bool
+     */
+    protected function canImportFile($path)
+    {
+        $parsed = parse_url($path);
+        if (
+            // file is elsewhere
+            isset($parsed['host']) ||
+            // file responds to queries (may change, or need to bypass cache)
+            isset($parsed['query'])
+        ) {
+            return false;
+        }
+
+        return strlen($path) < PHP_MAXPATHLEN && @is_file($path) && is_readable($path);
+    }
+
+    /**
+     * Attempts to open file specified by $path for writing.
+     *
+     * @param string $path The path to the file
+     *
+     * @return resource Specifier for the target file
+     *
+     * @throws IOException
+     */
+    protected function openFileForWriting($path)
+    {
+        if (($handler = @fopen($path, 'w')) === false) {
+            throw new IOException('The file "'.$path.'" could not be opened for writing. Check if PHP has enough permissions.');
+        }
+
+        return $handler;
+    }
+
+    /**
+     * Attempts to write $content to the file specified by $handler. $path is used for printing exceptions.
+     *
+     * @param resource $handler The resource to write to
+     * @param string   $content The content to write
+     * @param string   $path    The path to the file (for exception printing only)
+     *
+     * @throws IOException
+     */
+    protected function writeToFile($handler, $content, $path = '')
+    {
+        if (($result = @fwrite($handler, $content)) === false || ($result < strlen($content))) {
+            throw new IOException('The file "'.$path.'" could not be written to. Check your disk space and file permissions.');
+        }
+    }
+}