<?php
/**
* @version	$Id: minify_helper.php 16588 2017-07-19 07:36:53Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2010 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/

	defined('FULL_PATH') or die('restricted access!');

	class MinifyHelper extends kHelper {

		/**
		 * Debug mode mark
		 *
		 * @var bool
		 */
		var $debugMode = false;

		/**
		 * Folder, that contains produced CSS/JS files
		 *
		 * @var string
		 * @access protected
		 */
		protected $resourceFolder = '';

		public function __construct()
		{
			parent::__construct();

			$this->debugMode = $this->Application->isDebugMode(false);
			$this->resourceFolder = WRITEABLE . '/cache';
		}

		/**
		 * When used as non-block tag, then compress given files and return url to result
		 *
		 * @param Array $params
		 * @return string
		 * @access public
		 */
		public function CompressScriptTag($params)
		{
			// put to queue
			if ( array_key_exists('to', $params) ) {
				$files = $this->Application->GetVar($params['to'], '');
				$this->Application->SetVar($params['to'], $files . '|' . $params['files']);

				return '';
			}

			if ( array_key_exists('from', $params) ) {
				// get from queue
				$files = $this->Application->GetVar($params['from']);
			}
			else {
				// get from tag
				$files = $params['files'];
			}

			$files = $this->_getTemplatePaths( array_map('trim', explode('|', $files)) );

			if ( !$files ) {
				trigger_error('No files specified for compression.', E_USER_NOTICE);

				return '';
			}

			$extension = pathinfo($files[0], PATHINFO_EXTENSION);
			$save_as = isset($params['save_as']) ? $params['save_as'] : false;
			$dst_file = $this->resourceFolder . DIRECTORY_SEPARATOR . ($this->debugMode ? 'd' : 'c') . '_';

			/** @var FileHelper $file_helper */
			$file_helper = $this->Application->recallObject('FileHelper');

			if ( $save_as ) {
				$dst_file .= $save_as . ( strpos($save_as, '.') === false ? '.' . $extension : '' );
			}
			else {
				$dst_file .= $this->_getHash($file_helper->makeRelative($files)) . '.' . $extension;
			}

			$was_compressed = file_exists($dst_file);

			if ( !$was_compressed || ($this->debugMode && filemtime($dst_file) < $this->_getMaxFileDate($files)) ) {
				$string = '';
				$path_length = strlen(FULL_PATH) + 1;

				foreach ($files as $file) {
					if ( !file_exists($file) ) {
						continue;
					}

					// add filename before for easier debugging
					if ( $this->debugMode ) {
						$string .= '/* === File: ' . substr($file, $path_length) . ' === */' . "\n";
						$string .= '/* ' . str_repeat('=', strlen(substr($file, $path_length)) + 14) . ' */' . "\n\n";
					}

					// add file content
					$string .= file_get_contents($file) . "\n\n";
				}

				// replace templates base
				if ( isset($params['templates_base']) ) {
					$templates_base = $params['templates_base'];
				}
				else {
					$templates_base = $this->Application->ProcessParsedTag('m', 'TemplatesBase', Array ());
				}

				$templates_base = preg_replace('/^' . preg_quote($this->Application->BaseURL(), '/') . '/', BASE_PATH . '/', $templates_base);
				$string = str_replace('@templates_base@', rtrim($templates_base, '/'), $string);

				if ( !$this->debugMode ) {
					// don't compress merged js/css file in debug mode to allow js/css debugging
					$this->compressString($string, $extension);
				}

				// save compressed file
				file_put_contents($dst_file, $string);
			}

			return $file_helper->pathToUrl($dst_file) . '?ts=' . adodb_date('Y-m-d_H:i:s', filemtime($dst_file));
		}

		/**
		 * Returns maximal modification date across given files
		 *
		 * @param Array $files
		 * @return int
		 * @access protected
		 */
		protected function _getMaxFileDate($files)
		{
			$ret = 0;

			foreach ($files as $file) {
				if ( file_exists($file) ) {
					$ret = max($ret, filemtime($file));
				}
			}

			return $ret;
		}

		/**
		 * Returns hash string based on given files
		 *
		 * @param Array $files
		 * @return int
		 * @access protected
		 */
		protected function _getHash($files)
		{
			$hash = $files;

			if ($this->Application->isAdmin) {
				array_unshift($hash, 'A:1');
			}
			else {
				array_unshift($hash, 'A:0;T:' . $this->Application->GetVar('m_theme'));
			}

			return kUtil::crc32(implode('|', $hash));
		}

		/**
		 * Deletes compression info file
		 *
		 * @todo also delete all listed there files
		 * @access public
		 */
		public function delete()
		{
			$iterator = new DirectoryIterator($this->resourceFolder);
			/** @var DirectoryIterator $file_info */

			foreach ($iterator as $file_info) {
				if ( !$file_info->isDir() && preg_match('/^(c|d)_.*.(css|js)$/', $file_info->getFilename()) ) {
					unlink( $file_info->getPathname() );
				}
			}
		}

		/**
		 * Dumps the assets.
		 *
		 * @return void
		 */
		public function dump()
		{
			/** @var kCurlHelper $curl_helper */
			$curl_helper = $this->Application->recallObject('CurlHelper');
			$curl_helper->setOptions(array(
				CURLOPT_COOKIE => 'debug_off=1',
			));
			$curl_helper->Send($this->Application->BaseURL());
		}

		/**
		 * Compress $string based on $extension
		 *
		 * @param string $string
		 * @param string $extension
		 * @return void
		 * @access protected
		 */
		public function compressString(&$string, $extension)
		{
			$compression_engine = kUtil::getSystemConfig()->get('CompressionEngine');

			if ( !$compression_engine ) {
				// compression method not specified - use none
				return;
			}

			switch ( $compression_engine ) {
				case 'yui':
					$this->compressViaJava($string, $extension);
					break;

				case 'php':
					$this->compressViaPHP($string, $extension);
					break;
			}
		}

		/**
		 * Compresses string using YUI compressor (uses Java)
		 *
		 * @param string $string
		 * @param string $extension
		 * @return void
		 * @access protected
		 */
		protected function compressViaJava(&$string, $extension)
		{
			$tmp_file = tempnam('/tmp', 'to_compress_');
			file_put_contents($tmp_file, $string);

			$command = 'java -jar ' . dirname(__FILE__) . DIRECTORY_SEPARATOR . 'yuicompressor-2.4.8.jar --type ' . $extension . ' --charset utf-8 ' . $tmp_file;
			$string = shell_exec($command);

			unlink($tmp_file);
		}

		/**
		 * Compresses string using PHP compressor
		 *
		 * @param string $string
		 * @param string $extension
		 * @return void
		 * @access protected
		 */
		protected function compressViaPHP(&$string, $extension)
		{
			/** @var JsMinifyHelper $minifier */
			$minifier = $this->Application->makeClass($extension == 'js' ? 'JsMinifyHelper' : 'CssMinifyHelper');

			$string = $minifier->minify($string);
		}

		/**
		 * Get full paths on disk for each of given templates
		 *
		 * @param Array $templates
		 * @return Array
		 * @access protected
		 */
		protected function _getTemplatePaths($templates)
		{
			$ret = Array ();
			$reg_exp = '/^' . preg_quote($this->Application->BaseURL(), '/') . '(.*)/';

			foreach ($templates as $template) {
				if ( !$template ) {
					continue;
				}

				if ( preg_match($reg_exp, $template, $regs) ) {
					// full url (from current domain) to a file
					$ret[] = FULL_PATH . '/' . $regs[1];
				}
				elseif ( strpos($template, '{module_path}') !== false ) {
					$ret = array_merge($ret, $this->_moduleInclude($template));
				}
				else {
					$ret[] = $this->Application->TemplatesCache->GetRealFilename($template);
				}
			}

			return $ret;
		}

		/**
		 *
		 * @param string $template
		 * @return Array
		 * @access protected
		 */
		protected function _moduleInclude($template)
		{
			$ret = $included = Array ();

			foreach ($this->Application->ModuleInfo as $module_name => $module_data) {
				if ( $module_name == 'In-Portal' ) {
					continue;
				}

				$module_prefix = $this->Application->isAdmin ? mb_strtolower($module_name) . '/' : $module_data['TemplatePath'];

				if ( in_array($module_prefix, $included) ) {
					continue;
				}

				$ret[] = $this->Application->TemplatesCache->GetRealFilename(str_replace('{module_path}', $module_prefix, $template));
				$included[] = $module_prefix;
			}

			return $ret;
		}
	}
