<?php
/**@license setplus: a lightweight HTML editor
* @author  Levente Hunyadi
* @version 0.2.0
* @remarks Copyright (C) 2010-2011 Levente Hunyadi
* @remarks Licensed under GNU/GPLv3, see http://www.gnu.org/licenses/gpl-3.0.html
* @see     http://hunyadi.info.hu/projects/setplus
**/

/*
* setplus: a lightweight HTML editor
* Copyright 2010-2011 Levente Hunyadi
*
* setplus is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* setplus is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with setplus.  If not, see <http://www.gnu.org/licenses/>.
*/

// no direct access
defined( '_JEXEC' ) or die( 'Restricted access' );

class plgEditorsSetPlusInstallerScript {
	function __construct($parent) {

	}

	function install($parent) {

	}

	function uninstall($parent) {

	}

	function update($parent) {

	}

	function preflight($type, $parent) {

	}

	function postflight($type, $parent) {
		switch ($type) {
			case 'install':  // runs after installation is complete
			case 'update':
				$path = JPATH_ROOT.DS.'media'.DS.'editors'.DS.'setplus'.DS.'css';

				// minify javascript files
				$files = self::scandirext($path, array('css'));
				foreach ($files as $file) {
					if (!preg_match('/\.min\.css$/', $file)) {
						file_put_contents($path.DS.basename($file,'.css').'.min.css', Minify_CSS_Compressor::process(file_get_contents($path.DS.$file)));
					}
				}

				// substitute image URLs with data URIs
				$files = self::scandirext($path, array('css'));
				foreach ($files as $file) {
					$filepath = $path.DS.$file;
					$contents = plgEditorsSetPlusUriSubstitution::replace(file_get_contents($filepath), $path, $count);
					if ($count > 0) {
						file_put_contents($filepath, $contents);
					}
				}
			}
	}

	private static function scandirext($folder, $extensions = false) {
		$files = array();

		$allfiles = scandir($folder);
		if ($allfiles !== false) {
			foreach ($allfiles as $file) {
				if ($extensions !== false && in_array(pathinfo($file, PATHINFO_EXTENSION), $extensions)) {
					$files[] = $file;
				}
			}
		}

		return $files;
	}
}

class plgEditorsSetPlusUriSubstitution {
	/**
	* The root path w.r.t. relative URLs are to be interpreted.
	*/
	private $root;

	private function __construct($root) {
		$this->root = $root;
	}

	public static function replace($contents, $path, &$count) {
		return preg_replace_callback(
			'#url\(("(?!\w+:)[^"]+"|\'(?!\w+:)[^\']+\'|(?!\w+:)[^()]+)\)#',  // (?!\w+:) is a negative lookahead assertion to skip absolute URLs
			array(new self($path), 'preg_replace'),
			$contents, -1, $count
		);
	}

	/**
	* Replaces an image URL reference with a data URI.
	* @param {array} $match A CSS url() function match.
	* This function is to be passed to preg_replace_callback.
	*/
	public function preg_replace($match) {
		$url = $match[1];
		if ($url[0] == '"' || $url == "'") {  // unquote quoted strings
			$url = trim($url, $url[0]);
		}
		$path = $this->root.DS.str_replace('/', DS, $url);
		if (file_exists($path) && ($imagedata = getimagesize($path)) !== false) {
			return 'url("'.self::datauri($imagedata['mime'], file_get_contents($path)).'")';
		} else {
			return $match[0];
		}
	}

	/**
	* Converts raw data to a data URI.
	* @param {string} $mime Content MIME type.
	* @param {string} $data Content raw data.
	*/
	private static function datauri($mime, $data) {
		return 'data:' . $mime . ';base64,' . base64_encode($data);
	}
}

/**
* Class Minify_CSS_Compressor
* @package Minify
*/

/**
* Compress CSS
*
* This is a heavy regex-based removal of whitespace, unnecessary
* comments and tokens, and some CSS value minimization, where practical.
* Many steps have been taken to avoid breaking comment-based hacks,
* including the ie5/mac filter (and its inversion), but expect tricky
* hacks involving comment tokens in 'content' value strings to break
* minimization badly. A test suite is available.
*
* @package Minify
* @author Stephen Clay <steve@mrclay.org>
* @author http://code.google.com/u/1stvamp/ (Issue 64 patch)
*/
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 options
	*/
	protected $_options = null;

	/**
	* @var bool Are we "in" a hack?
	*
	* I.e. are some browsers targetted until the next comment?
	*/
	protected $_inHack = false;


	/**
	* Constructor
	*
	* @param array $options (currently ignored)
	*
	* @return null
	*/
	private function __construct($options) {
		$this->_options = $options;
	}

	/**
	* Minify a CSS string
	*
	* @param string $css
	*
	* @return string
	*/
	protected function _process($css)
	{
		$css = str_replace("\r\n", "\n", $css);

		// preserve empty comment after '>'
		// http://www.webdevout.net/css-hacks#in_css-selectors
		$css = preg_replace('@>/\\*\\s*\\*/@', '>/*keep*/', $css);

		// preserve empty comment between property and value
		// http://css-discuss.incutio.com/?page=BoxModelHack
		$css = preg_replace('@/\\*\\s*\\*/\\s*:@', '/*keep*/:', $css);
		$css = preg_replace('@:\\s*/\\*\\s*\\*/@', ':/*keep*/', $css);

		// apply callback to all valid comments (and strip out surrounding ws
		$css = preg_replace_callback('@\\s*/\\*([\\s\\S]*?)\\*/\\s*@'
			,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
		$css = preg_replace('/
				url\\(      # url(
				\\s*
				([^\\)]+?)  # 1 = the URL (really just a bunch of non right parenthesis)
				\\s*
				\\)         # )
			/x', 'url($1)', $css);

		// remove ws between rules and colons
		$css = preg_replace('/
				\\s*
				([{;])              # 1 = beginning of block or rule separator
				\\s*
				([\\*_]?[\\w\\-]+)  # 2 = property (and maybe IE filter)
				\\s*
				:
				\\s*
				(\\b|[#\'"-])        # 3 = first character of a value
			/x', '$1$2:$3', $css);

		// remove ws in selectors
		$css = preg_replace_callback('/
				(?:              # non-capture
					\\s*
					[^~>+,\\s]+  # selector part
					\\s*
					[,>+~]       # combinators
				)+
				\\s*
				[^~>+,\\s]+      # selector part
				{                # open declaration block
			/x'
			,array($this, '_selectorsCB'), $css);

		// minimize hex colors
		$css = preg_replace('/([^=])#([a-f\\d])\\2([a-f\\d])\\3([a-f\\d])\\4([\\s;\\}])/i'
			, '$1#$2$3$4$5', $css);

		// remove spaces between font families
		$css = preg_replace_callback('/font-family:([^;}]+)([;}])/'
			,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)
		$css = preg_replace('/([\\w#\\.\\*]+)\\s+([\\w#\\.\\*]+){/', "$1\n$2{", $css);

		// Use newline after 1st numeric value (to limit line lengths).
		$css = preg_replace('/
			((?:padding|margin|border|outline):\\d+(?:px|em)?) # 1 = prop : 1st numeric value
			\\s+
			/x'
			,"$1\n", $css);

		// prevent triggering IE6 bug: http://www.crankygeek.com/ie6pebug/
		$css = preg_replace('/:first-l(etter|ine)\\{/', ':first-l$1 {', $css);

		return trim($css);
	}

	/**
	* Replace what looks like a set of selectors
	*
	* @param array $m regex matches
	*
	* @return string
	*/
	protected function _selectorsCB($m)
	{
		// remove ws around the combinators
		return preg_replace('/\\s*([,>+~])\\s*/', '$1', $m[0]);
	}

	/**
	* Process a comment and return a replacement
	*
	* @param array $m regex matches
	*
	* @return string
	*/
	protected function _commentCB($m)
	{
		$hasSurroundingWs = (trim($m[0]) !== $m[1]);
		$m = $m[1];
		// $m is the comment content w/o the surrounding tokens,
		// but the return value will replace the entire comment.
		if ($m === 'keep') {
			return '/**/';
		}
		if ($m === '" "') {
			// component of http://tantek.com/CSS/Examples/midpass.html
			return '/*" "*/';
		}
		if (preg_match('@";\\}\\s*\\}/\\*\\s+@', $m)) {
			// component of http://tantek.com/CSS/Examples/midpass.html
			return '/*";}}/* */';
		}
		if ($this->_inHack) {
			// inversion: feeding only to one browser
			if (preg_match('@
					^/               # comment started like /*/
					\\s*
					(\\S[\\s\\S]+?)  # has at least some non-ws content
					\\s*
					/\\*             # ends like /*/ or /**/
				@x', $m, $n)) {
				// end hack mode after this comment, but preserve the hack and comment content
				$this->_inHack = false;
				return "/*/{$n[1]}/**/";
			}
		}
		if (substr($m, -1) === '\\') { // comment ends like \*/
			// begin hack mode and preserve hack
			$this->_inHack = true;
			return '/*\\*/';
		}
		if ($m !== '' && $m[0] === '/') { // comment looks like /*/ foo */
			// begin hack mode and preserve hack
			$this->_inHack = true;
			return '/*/*/';
		}
		if ($this->_inHack) {
			// a regular comment ends hack mode but should be preserved
			$this->_inHack = false;
			return '/**/';
		}
		// Issue 107: if there's any surrounding whitespace, it may be important, so
		// replace the comment with a single space
		return $hasSurroundingWs // remove all other comments
			? ' '
			: '';
	}

	/**
	* Process a font-family listing and return a replacement
	*
	* @param array $m regex matches
	*
	* @return string
	*/
	protected function _fontFamilyCB($m)
	{
		$m[1] = preg_replace('/
				\\s*
				(
					"[^"]+"      # 1 = family in double qutoes
					|\'[^\']+\'  # or 1 = family in single quotes
					|[\\w\\-]+   # or 1 = unquoted family
				)
				\\s*
			/x', '$1', $m[1]);
		return 'font-family:' . $m[1] . $m[2];
	}
}
