<?php
/**
 * @version $Id: ef4_jmframework.php 173 2017-11-07 19:44:36Z szymon $
 * @package JMFramework
 * @copyright Copyright (C) 2012 DJ-Extensions.com LTD, All rights reserved.
 * @license http://www.gnu.org/licenses GNU/GPL
 * @author url: http://dj-extensions.com
 * @author email contact@dj-extensions.com
 * @developer Michal Olczyk - michal.olczyk@design-joomla.eu
 *
 * JMFramework 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.
 *
 * JMFramework 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 JMFramework. If not, see <http://www.gnu.org/licenses/>.
 *
 */

defined('_JEXEC') or die('Restricted access');

class plgSystemEF4_JMFramework extends JPlugin
{
	private $template;
	private $updatesURL = 'https://www.joomla-monster.com/updates/templates.xml';
	protected $debug = false;
	protected $_debug = array();
	
	public function __construct(&$subject, $config = array()) {
		if (!defined('DS')) {
			define('DS', DIRECTORY_SEPARATOR);
		}
		parent::__construct($subject, $config);
		
	}
	
	/**
	 * Check ordering of the plugins
	 */
	function onAfterInitialise(){
		
		$app = JFactory::getApplication();
		$db = JFactory::getDbo();
		
		// check and change the order of the plugins only in the back-end
		if($app->isAdmin()) {
			
			$db->setQuery("SELECT extension_id, element, ordering FROM #__extensions WHERE folder='system' AND element IN ('ef4_jmframework','djjquerymonster')");
			$plugins = $db->loadObjectList('element');
			
			if(isset($plugins['djjquerymonster'])) {
				
				if($plugins['djjquerymonster']->ordering >= $plugins['ef4_jmframework']->ordering) {
					
					$db->setQuery("UPDATE #__extensions SET ordering=".($plugins['ef4_jmframework']->ordering - 1)." WHERE extension_id=".$plugins['djjquerymonster']->extension_id);
					$db->query();
					//$app->enqueueMessage('DJ-jQueryMonster plugin order ');
				}
			}
		} /*else if(JPluginHelper::isEnabled('system', 'cache')) {
		// If system page cache plugin is enabled we need to force to disable it when Theme Customizer is enabled
		$tc = $app->input->post->get('tc','-1');
		if($tc=='-1') $tc = $app->getUserState('themer.switch');
		if($tc === '1') {
		$app->enqueueMessage('Page cache disabled');
		}
		}*/
	}
	
	/**
	 *
	 * We need to specially prepare the form because we're merging templateDetails.xml from a template and params.xml from the plugin.
	 * @param JForm $form
	 * @param mixed $data
	 */
	function onContentPrepareForm($form, $data)
	{
		$app = JFactory::getApplication();
		$doc = JFactory::getDocument();
		$this->template = $this->getTemplateName();
		
		if ($this->template && ( ($app->isAdmin() && $form->getName() == 'com_templates.style') || ($app->isSite() && ($form->getName() == 'com_config.templates' || $form->getName() == 'com_templates.style')) )) {
			jimport('joomla.filesystem.path');
			//JForm::addFormPath( dirname(__FILE__) . DS. 'includes' . DS .'assets' . DS . 'admin' . DS . 'params');
			$plg_file = JPath::find(dirname(__FILE__) . DS. 'includes' . DS .'assets' . DS . 'admin' . DS . 'params', 'template.xml');
			$tpl_file = JPath::find(JPATH_ROOT . DS. 'templates' . DS . $this->template, 'templateDetails.xml');
			$default_settings_file = JPATH_ROOT . DS. 'templates' . DS . $this->template . DS . 'templateDefaults.json';
			
			if (!$plg_file) {
				return false;
			}
			
			// params.xml should be loaded first and templateDetails.xml afterwards
			if ($tpl_file) {
				$form->loadFile($plg_file, false, '//form');
				$form->loadFile($tpl_file, false, '//config');
				$form->loadFile($tpl_file, false, '//tplconfig//fields[@name="params"]');
			} else {
				$form->loadFile($plg_file, false, '//form');
			}
			
			// for users' own safety, we don't allow some things to be changed in the front-end
			if ($app->isSite()) {
				$jmstorage_fields = $form->getFieldset('jmstorage');
				foreach ($jmstorage_fields as $name => $field){
					$form->removeField($name, 'params');
				}
				$form->removeField('config', 'params');
				
				$jmlayoutbuilder_fields = $form->getFieldset('jmlayoutbuilder');
				foreach ($jmlayoutbuilder_fields as $name => $field){
					$form->removeField($name, 'params');
				}
				$form->removeField('layout', 'params');
			}
			
			// Hiding a notice to enable this plugin. If plugin is disabled then the notice is visible. That's it.
			if ($app->isAdmin()) {
				$doc->addStyleDeclaration('#jm-ef3plugin-info, .jm-row > .jm-notice {display: none !important}');
			}
			
			if (JFile::exists($default_settings_file)) {
				$settings_json = JFile::read($default_settings_file);
				if ($settings_json) {
					$defaults = json_decode($settings_json, true);
					if ($defaults && is_array($defaults)) {
						foreach ($form->getFieldset() as $field) {
							$field_name = $field->__get('fieldname');
							if (array_key_exists($field_name, $defaults) && is_scalar($defaults[$field_name])) {
								$form->setFieldAttribute($field_name, 'default', $defaults[$field_name], $field->__get('group'));
							}
						}
						/*if (!empty($data) && isset($data->params)) {
						 foreach ($data->params as $param_name => $param_value) {
						 if (empty($param_value) && array_key_exists($param_name, $defaults)  && is_scalar($defaults[$param_name])) {
						 $data->params[$param_name] = $defaults[$param_name];
						 }
						 }
						 }*/
					}
				}
			}
		}
	}
	
	/**
	 *
	 * Preparing default values
	 * @param string $context
	 * @param mixed $data
	 */
	function onContentPrepareData($context, $data)
	{
		$app = JFactory::getApplication();
		$doc = JFactory::getDocument();
		$this->template = $this->getTemplateName();
		
		if ($this->template && ( ($app->isAdmin() && $context == 'com_templates.style') || ($app->isSite() && $context == 'com_config.templates') )) {
			jimport('joomla.filesystem.path');
			
			$default_settings_file = JPATH_ROOT . DS. 'templates' . DS . $this->template . DS . 'templateDefaults.json';
			
			if (JFile::exists($default_settings_file)) {
				$settings_json = JFile::read($default_settings_file);
				if ($settings_json) {
					
					$defaults = json_decode($settings_json, true);
					if ($defaults && is_array($defaults)) {
						if (!empty($data) && isset($data->params)) {
							
							if (!is_array($data->params)) {
								if (is_object($data->params)) {
									$data->params = JArrayHelper::fromObject($data->params);
								} else {
									$data->params = array();
								}
							}
							
							foreach ($defaults as $param_name => $param_value) {
								if (empty($data->params[$param_name])) {
									$data->params[$param_name] = $defaults[$param_name];
								}
							}
						}
					}
				}
			}
		}
	}
	
	
	/**
	 * After the routing we can determine which template is being used.
	 * The plugin works only with specially prepared Joomla Monster templates.
	 */
	function onAfterRoute(){
		$app = JFactory::getApplication();
		
		if ($this->params->get('cfg_check_updates', true)) {
			$this->checkUpdates();
		}
		
		// If it's not Joomla Monster template, the $template will be false.
		$template = $this->getTemplateName();
		if ($template) {
			
			// This plugin's directory
			define('JMF_FRAMEWORK_PATH', dirname(__FILE__));
			
			// Plugin's URL
			define('JMF_FRAMEWORK_URL', JURI::root(true).'/plugins/system/ef4_jmframework');
			
			// Name of the template
			define('JMF_TPL', $template);
			
			// Path to template's directory
			define('JMF_TPL_PATH', JPATH_ROOT.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.$template);
			
			// Template directory's URL
			define('JMF_TPL_URL', JURI::root(true). '/templates/' . $template);
			
			// Flag that informs that plugin is active
			define('JMF_EXEC', 'JMF');
			
			// Admin assets' URL
			define('JMF_ASSETS', JURI::root(true).'/plugins/system/ef4_jmframework/includes/assets/admin/');
			
			// Flag for DJ-jQueryMonster plugin compatibility
			define('JMF_JQUERYMONSTER', 1);
			
			// Clearing ThemeCustomiser settings and removing data from local storage.
			// The sooner we do that the better. Afterwards user will be redirected to a proper URL - without "tcr" parameter
			if ($app->input->getInt('tcr') == 1) {
				$app->setUserState(JMF_TPL.'.themer.state', null);
				JURI::reset();
				$uri = JURI::getInstance();
				$uri->delVar('tcr');
				$redir = $uri->toString();
				JURI::reset();
				
				$html = '';
				$redirScr = "
				if (typeof(window.localStorage) !== 'undefined') {
					try {
						window.localStorage.clear();
					} catch(_) {}
				}
				document.location.href='" . str_replace("'", "&apos;", $redir) . "';
				";
				
				if (headers_sent()) {
					$html = "<script>".$redirScr."</script>";
				} else {
					$html = '<html><head>';
					$html .= '<meta http-equiv="content-type" content="text/html; charset=' . JFactory::getApplication()->charSet . '" />';
					$html .= '<script>'.$redirScr.'</script>';
					$html .= '</head><body></body></html>';
				}
				
				echo $html;
				
				$app->close();
				return true;
			}
			
			$this->loadLanguage();
			
			$this->template = $template;
			
			if ($app->isSite()) {
				require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR.'libraries'.DIRECTORY_SEPARATOR.'template.php';
				include_once JMF_TPL_PATH.DIRECTORY_SEPARATOR.'lib'.DIRECTORY_SEPARATOR.'jm_template.php';
				$className = false;
				if (class_exists('JMTemplate')) {
					$className = 'JMTemplate';
				} else if (class_exists('JMTemplate'.ucfirst(str_replace('-', '', JMF_TPL)))) {
					$className = 'JMTemplate'.ucfirst(str_replace('-', '', JMF_TPL));
				}
				
				$lang = JFactory::getLanguage();
				
				$lang->load('tpl_'.$this->template, JPATH_ADMINISTRATOR, 'en-GB', false, true)
				||  $lang->load('tpl_'.$this->template, JMF_TPL_PATH, 'en-GB', false, true);
				
				$lang->load('tpl_'.$this->template, JPATH_ADMINISTRATOR, null, true, true)
				||  $lang->load('tpl_'.$this->template, JMF_TPL_PATH, null, true, true);
				
				if ($className !== false) {
					$doc = JFactory::getDocument();
					if ($doc instanceof JDocumentHTML) {
						$jmf = new $className($doc, true);
						$jmf->ajax(); // check for ajax requests
					}
				}
				
				$this->debug = $app->getTemplate(true)->params->get('debug', 0) ? true : false;
				
			} else {
				require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR.'libraries'.DIRECTORY_SEPARATOR.'template.php';
				require_once dirname(__FILE__).DIRECTORY_SEPARATOR.'includes'.DIRECTORY_SEPARATOR.'libraries'.DIRECTORY_SEPARATOR.'admin.php';
				$doc = JFactory::getDocument();
				$jmf = new JMFAdminTemplate($doc);
				$jmf->ajax(); // check for ajax requests
				
				$queue = $app->getMessageQueue();
				
				if ($app->input->getString('view') == 'style'
					&& $app->input->getString('option') == 'com_templates'
					&& $app->input->getCmd('jmajax', false) == false
					&& $app->input->getCmd('jmtask', false) == false) {
						
						if ($jmf->params->get('devmode', false)) {
							$app->enqueueMessage(JText::_('PLG_SYSTEM_JMFRAMEWORK_WARNING_DEV_MODE_ENABLED'), 'message');
						}
						$unwritable = $this->checkTemplateFolders();
						if ($unwritable && is_array($unwritable)) {
							$message = JText::_('PLG_SYSTEM_JMFRAMEWORK_WARNING_FOLDER_ISSUES');
							foreach ($unwritable as $folder) {
								$message .= '<br />' . $folder;
							}
							$app->enqueueMessage($message, 'error');
						}
					}
			}
		}
	}
	
	/**
	 * Loading template's language file
	 */
	function onAfterRender() {
		$app = JFactory::getApplication();
		
		if (!$this->template) {
			return;
		}
		
		if ($app->isAdmin()) {
			$this->loadLanguage('tpl_'.$this->template, JPATH_ROOT);
			return;
		}
		
		$params = $app->getTemplate(true)->params;
		$htmlCompress = (int)$params->get('htmlCompress', 0);
		$lazyLoading = (int)$params->get('lazyLoading', 0);
				
		$documentFormat = $app->input->getCmd('format', 'html');
		
		// preparing images and iframes tags for lazy loading
		if($lazyLoading && $app->input->get('tmpl')!='component' && ($documentFormat == 'html' || is_null($documentFormat))) {
				
			//$timer_start = microtime();
			$body = JResponse::getBody();
			preg_match_all('/<img[^>]*>/i', $body, $matches);
			
			$skips = explode("\n", $params->get('lazyExclude'));
			$skips[] = 'data-src='; // djmediatools or other with own lazy images loading implemented
			$skips[] = 'lazyOff'; // class name to disable lazy images loading
			
			foreach($matches[0] as $key => $img) {
		
				$exclude = false;
				foreach($skips as $skip) {
					$skip = trim($skip);
					if(empty($skip)) continue;
					if(stripos($img, $skip) !== FALSE) {
						$exclude = true;
						break;
					}
				}
				if($exclude) continue;
		 
				// get the src of the image
				preg_match('#\ssrc="(/|[a-zA-Z0-9\-]+:)?([^"]+)"#', $img, $match);
				if(empty($match[2])) continue;
				$src = $match[1].$match[2];
				
				if(strcasecmp(substr($src, 0, 4), 'http') != 0) {
					$path = $src;
					if(empty($match[1])) { // add root to the path
						$path = JPath::clean(JPATH_ROOT.'/'.$src);
						$src = JURI::root(true).'/'.$src;
					} else if($match[1] == '/') {
						$path = JPath::clean(JPATH_ROOT.$src);
					}
					$size = @getimagesize($path);
				} else {
					$size = @getimagesize($src);
				}
		
				if($size === FALSE) continue;
		
				if((int)$size[0] < (int)$params->get('lazyWidth', 50) || (int)$size[1] < (int)$params->get('lazyHeight', 50)) continue;
		
				// we create the svg inline blank image with proper dimentions and aspect radio
				$lazyimg = ' src="'."data:image/svg+xml;charset=utf-8,%3Csvg xmlns%3D'http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg' width%3D'".$size[0]."' height%3D'".$size[1]."' viewBox%3D'0 0 ".$size[0]." ".$size[1]."'%2F%3E".'" data-original="'.$src.'"';
		
				$lazyimg = str_replace($match[0], $lazyimg, $img);
				//$body = '<pre>'.str_replace('<', '&lt;', $lazyimg).print_r($match, true).'</pre>';
				$body = str_replace($img, $lazyimg, $body);
			}
			
			$blank = ' src="about:blank"';
			preg_match_all('/<iframe[^>]*>/i', $body, $matches);
		
			foreach($matches[0] as $iframe) {
					
				if(strpos($iframe, 'data-src') !== FALSE) continue;
					
				preg_match('/\ssrc="([^"]+)"/', $iframe, $match);
				if(empty($match[1])) continue;
				$src = $match[1];
		
				$lazyiframe = $blank.' data-original="'.$src.'"';
				$lazyiframe = str_replace($match[0], $lazyiframe, $iframe);
				$body = str_replace($iframe, $lazyiframe, $body);
			}
			
			JResponse::setBody($body);
			//$timer_stop = microtime();
			//die($timer_stop - $timer_start);
		}
		// djcustom - end
				
		if ($htmlCompress > 0 && ($documentFormat == 'html' || is_null($documentFormat))) {
			if (version_compare(JVERSION, '3.2.3', '>=')) {
				$html = $app->getBody();
			} else {
				$html = JResponse::getBody();
			}
			
			if (!class_exists('Minify_HTML')) {
				require_once JPath::clean(JMF_FRAMEWORK_PATH.'/includes/libraries/minify/HTMLMin.php');
			}
			
			$options = array();
			
			$options['jsCleanComments'] = true;
			
			$this->debug('Response size before HTML compression', number_format(strlen($html) / 1024, 2) .' KB');
			
			try {
				$optimizedHtml = Minify_HTML::minify($html, $options);
			} catch(Exception $e) {
				$optimizedHtml = $html;
			}
			
			$this->debug('Response size after HTML compression', number_format(strlen($optimizedHtml) / 1024, 2) .' KB');
			
			/*if ($htmlCompress > 1) {
			 $optimizedHtml = str_replace("\n", ' ', $optimizedHtml);
			 }*/
			
			if (version_compare(JVERSION, '3.2.3', '>=')) {
				$app->setBody($optimizedHtml);
			} else {
				JResponse::setBody($optimizedHtml);
			}
		}
		
		if($this->debug && ($documentFormat == 'html' || is_null($documentFormat))) {
			$body = JResponse::getBody();
			$this->renderDebug($body);
			JResponse::setBody($body);
		}
	}
	
	/**
	 * Adding some scripts required in template's configuration
	 */
	function onBeforeRender(){
		$app = JFactory::getApplication();
		$template = $this->getTemplateName();
		if ($template && ($app->isAdmin() || ($app->input->get('option') == 'com_config' && $app->input->get('view') == 'templates' ) )) {
			
			$document = JFactory::getDocument();
			
			if ($app->isAdmin()) {
				$document->addStyleSheet(JMF_ASSETS . 'css/admin.css');
			}
			$document->addScript(JMF_ASSETS . 'js/jmoptiongroups.js');
			//$document->addScript(JMF_ASSETS . 'js/jmspacer.js');
			//$document->addScript(JMF_TPL_ASSETS . 'js/jmconfig.js');
			$document->addScript(JMF_ASSETS . 'js/jscolor.js');
			//$document->addScript(JMF_ASSETS . 'js/misc.js');
			
			//$document->addScript('http://code.jquery.com/jquery-latest.js');
		}
		
	}
	
	/**
	 * Here go all the actions that have to be performed right before document's HEAD has been rendered.
	 */
	function onBeforeCompileHead(){
		
		$app = JFactory::getApplication();
		$document = JFactory::getDocument();
		
		// Don't proceed when current template is not compatible with EF4 Framework or we are in the Joomla back-end
		if (empty($this->template) || $app->isAdmin()) {
			return true;
		}
		
		$params = $app->getTemplate(true)->params;
		
		// Handling Facebook's Open Graph
		if ((bool)$params->get('facebookOpenGraph', true)) {
			$fbAppId = $params->get('facebookOpenGraphAppID', false);
			$this->addOpenGraph($fbAppId);
		}
		
		// Removing obsolete CSS stylesheets
		$css_to_remove = $app->get('jm_remove_stylesheets', array());
		if (!empty($css_to_remove) && is_array($css_to_remove)) {
			foreach($document->_styleSheets as $url => $cssData) {
				foreach($css_to_remove as $oursUrl => $replacement) {
					if (strpos($url, $oursUrl) !== false) {
						unset($document->_styleSheets[$url]);
						if ($replacement && is_array($replacement) && isset($replacement['url']) && isset($replacement['type'])) {
							switch($replacement['type']) {
								case 'css' : $document->addStyleSheet($replacement['url'], 'text/css'); break;
								case 'less' : $document->addHeadLink($replacement['url'], 'stylesheet/less'); break;
								default: break;
							}
						}
					}
				}
			}
			$app->set('jm_remove_stylesheets', false);
		}
		
		$themer = false;
		if ($tpl = JMFTemplate::getInstance()) {
			$themer = ($tpl->params->get('themermode', false) == '1') ? true : false;
			$customPath = JPath::clean(JMF_TPL_PATH.'/less/custom.less');
			$customCss = JPath::clean(JMF_TPL_PATH.'/css/custom.css');
			if (JFile::exists($customPath)) {
				$tpl->addCompiledStyleSheet($customPath, true, $themer);
			} else if (JFile::exists($customCss)) {
				$tpl->addStyleSheet(JMF_TPL_URL.'/css/custom.css');
			}
		}
		
		$cssCompress = $params->get('cssCompress','0')=='1' ? true : false;
		$jsCompress = $params->get('jsCompress','0')=='1' ? true : false;
		
		// Don't compress CSS/JS when Development Mode or Joomla Debugging is enabled
		if($themer || $params->get('devmode',0) || JDEBUG || $app->input->get('option')=='com_config') {
			return true;
		}
		
		$this->debug('onBeforeCompileHead event START');
		
		if($jsCompress) { // it's used only for backward compatibility with DJ-jQueryMonster plugin less than 1.3.1
			$scripts = $document->_scripts;
			$newscripts = array();
			foreach($scripts as $url => $data) {
				// remove DJ-jQueryMonster placeholder for compressed javascript
				if(strstr($url, 'DJHOLDER_EF4COMPRESS') === false) {
					$newscripts[$url] = $data;
				}
			}
			$document->_scripts = $newscripts;
		}
		
		// Defer scripts loading excluding the jquery, mootools and selected scripts
		$canDefer = preg_match('/(?i)msie [6-9]/',$_SERVER['HTTP_USER_AGENT']) ? false : $params->get('jsDefer','0')=='1';
		//$excludeViews = array();//array('edit','form','additem','itemform','cart','checkout','contact','profileedit','renewitem','query');
		if($canDefer) { //&& !in_array($app->input->get('view'), $excludeViews)) {
			
			$scripts = $document->_scripts;
			$newscripts = array();
			$skips = explode("\n", $params->get('skipDefer'));
			$skips[] = 'DJHOLDER_JQUERY';
			$skips[] = 'DJHOLDER_NOCONFLICT';
			$skips[] = 'DJHOLDER_EF4COMPRESS';
			$skips[] = 'media/jui/js/jquery.min.js';
			$skips[] = 'media/jui/js/jquery-noconflict.js';
			$skips[] = 'media/system/js/mootools-core.js';
			$skips[] = 'media/system/js/core.js';
			$skips[] = 'media/system/js/calendar.js';
			$skips[] = 'media/system/js/calendar-setup.js';
			$skips[] = 'media/editors';
			$skips[] = 'components/com_virtuemart/assets/js';
			$skips[] = 'modules/mod_virtuemart_cart/assets/js';
			$skips[] = '//maps.google.com/maps/api/js';
			
			$_defered = array();
			$_nodefered = array();
			
			foreach($scripts as $url => $data) {
				
				$defer = true;
				// skip excluded scripts from defer loading
				foreach($skips as $skip) {
					$skip = trim($skip);
					if(empty($skip)) continue;
					//$this->debug("URL: ".$url."\nSKIP: ".$skip."\nCMP: ".(strstr($url, $skip)!==false ? 'TRUE':'FALSE'));
					if(stristr($url, $skip)!==false) {
						$defer = false;
						break;
					}
				}
				if($defer) {
					if(isset($data['defer']) && $data['defer'] != true) $_defered[] = $url;
					$data['defer'] = true;
				} else {
					$_nodefered[] = $url;
				}
				$newscripts[$url] = $data;
			}
			
			$this->debug('Defer attribute added to the following scripts', $_defered);
			$this->debug('Following scripts are excluded from defer loading', $_nodefered);
			
			$document->_scripts = $newscripts;
		}
		
		// Preparing cache folder for CSS/JS compressed files
		if (JFolder::exists(JMF_TPL_PATH.DIRECTORY_SEPARATOR.'cache') == false) {
			if (!JFolder::create(JMF_TPL_PATH.DIRECTORY_SEPARATOR.'cache')) {
				if (JDEBUG) {
					throw new Exception(JText::_('PLG_SYSTEM_JMFRAMEWORK_CACHE_FOLDER_NOT_ACCESSIBLE'));
				} else {
					return false;
				}
			}
		}
		
		// Handling CSS minifications and compression.
		if($cssCompress) {
			
			$styles = $document->_styleSheets;
			$compress = array();
			$mtime = 0;
			
			//$this->debug('JDocument::_styleSheets array', $styles);
			$this->debug('JDocument::_styleSheets array before compression', array_keys($styles));
			
			foreach($styles as $url => $style) {
				
				// Getting stylesheet path
				$path = $this->getPath($url);
				if(!$path || !JFile::exists($path)) continue;
				
				// Getting the last modification time of stylesheet
				$ftime = filemtime($path);
				if($ftime > $mtime) $mtime = $ftime;
				
				$compress[$url] = $path;
			}
			
			$this->debug('Style sheets to be compressed and merged', array_keys($compress));
			
			$key = md5(serialize($compress));
			
			$stylepath = JPath::clean(JMF_TPL_PATH.'/cache/jmf_'.$key.'.css');
			$cachetime = JFile::exists($stylepath) ? filemtime($stylepath) : 0;
			$styleurl  = JMF_TPL_URL.'/cache/jmf_'.$key.'.css';
			
			// Minify and merge stylesheets only if minified stylesheet isn't cached already or one of the stylesheets was modified
			if(!JFile::exists($stylepath) || $mtime > $cachetime) {
				
				require_once JPath::clean(JMF_FRAMEWORK_PATH.'/includes/libraries/minify/CSSmin.php');
				$cssmin = new CSSmin();
				$css = array();
				//$css[] = "/* EF4 CSSmin */";
				//$css[] = " * --------------------------------------- */";
				
				foreach($compress as $url => $path) {
					$src = JFile::read($path);
					$src = $this->updateUrls($src, dirname($url));
					//$css[] = "\n/* src: ".$url." */";
					$css[] = $cssmin->run($src, 1024);
				}
				
				$css = implode("\n", $css);
				JFile::write($stylepath, $css);
				
				$this->debug('New merged style sheet has been created', $styleurl);
			} else {
				$this->debug('Merged style sheet exists and it\'s up to date', $styleurl);
			}
			
			// Removing all merged stylesheets from the head and adding the minified stylesheet instead
			if(JFile::exists($stylepath)) {
				
				$newstyles = array();
				if($app->input->get('inlinecss')=='1') {
					$document->_style['text/css'] .= file_get_contents($stylepath);
				} else {
					$newstyles[$styleurl.'?v='.$mtime] = array('mime' => 'text/css');
				}
				
				foreach ($styles as $url => $data) {
					if(!array_key_exists($url, $compress)) $newstyles[$url] = $data;
				}
				
				$this->debug('JDocument::_styleSheets array after compression', array_keys($newstyles));
				
				$document->_styleSheets = $newstyles;
			}
			
		}
		
		// Handling JS minifications and compression.
		if($jsCompress) {
			
			require_once JPath::clean(JMF_FRAMEWORK_PATH.'/includes/libraries/minify/JSMin.php');
			
			$scripts = $document->_scripts;
			$newscripts = array();
			$compress = array('noattr' => array(), 'async' => array(), 'defer' => array());
			$mtime = array('noattr' => 0, 'async' => 0, 'defer' => 0);
			
			//$this->debug('JDocument::_scripts array', $scripts);
			$this->debug('JDocument::_scripts array before compression', array_keys($scripts));
			
			foreach($scripts as $url => $data) {
				
				// Getting script path
				$path = $this->getPath($url);
				if(!$path) { // external or excluded from merging
					if(count($compress['noattr'])) {
						$this->debug('Scripts to be compressed and merged', array_keys($compress['noattr']));
						$mergedUrl = $this->compressJS($compress['noattr'], $mtime['noattr']);
						$newscripts[$mergedUrl] = array('mime' => 'text/javascript', 'defer' => false, 'async' => false);
						$compress['noattr'] = array();
						$mtime['noattr'] = 0;
					}
					$newscripts[$url] = $data;
					continue;
				} else if(!JFile::exists($path)) continue;
				
				$idx = isset($data['async']) && $data['async'] ? 'async' : isset($data['defer']) && $data['defer'] ? 'defer' : 'noattr';
				
				// Getting the last modification time of script
				$ftime = filemtime($path);
				if($ftime > $mtime[$idx]) $mtime[$idx] = $ftime;
				
				$compress[$idx][$url] = $path;
			}
			
			if(count($compress['noattr'])) {
				$this->debug('Scripts to be compressed and merged', array_keys($compress['noattr']));
				$mergedUrl = $this->compressJS($compress['noattr'], $mtime['noattr']);
				$newscripts[$mergedUrl] = array('mime' => 'text/javascript', 'defer' => false, 'async' => false);
			}
			
			if(count($compress['defer'])) {
				$this->debug('Scripts with DEFER attribute to be compressed and merged', array_keys($compress['defer']));
				$mergedUrl = $this->compressJS($compress['defer'], $mtime['defer']);
				$newscripts[$mergedUrl] = array('mime' => 'text/javascript', 'defer' => true, 'async' => false);
			}
			
			if(count($compress['async'])) {
				$this->debug('Scripts with ASYNC attribute to be compressed and merged', array_keys($compress['async']));
				$mergedUrl = $this->compressJS($compress['async'], $mtime['async']);
				$newscripts[$mergedUrl] = array('mime' => 'text/javascript', 'defer' => true, 'async' => true);
			}
			
			$this->debug('JDocument::_scripts array after compression', array_keys($newscripts));
			
			$document->_scripts = $newscripts;
		}
		
		$this->debug('onBeforeCompileHead event END');
	}
	
	/**
	 * Merging and compressing passed scripts into one script and returning url of merged script with timestamp
	 * @param array $scripts
	 * @param int $mtime
	 * @return string
	 */
	function compressJS($scripts, $mtime) {
		
		$key = md5(serialize($scripts));
		
		$scriptpath = JPath::clean(JMF_TPL_PATH.'/cache/jmf_'.$key.'.js');
		$cachetime = JFile::exists($scriptpath) ? filemtime($scriptpath) : 0;
		$scripturl  = JMF_TPL_URL.'/cache/jmf_'.$key.'.js?v='.$mtime;
		
		// Minify and merge scripts only if minified script isn't cached already or one of the scripts was modified
		if(!JFile::exists($scriptpath) || $mtime > $cachetime) {
			
			$js = array();
			//$js[] = "/* EF4 JSMin */";
			//$js[] = " * -------------------------------------- */";
			
			foreach($scripts as $url => $path) {
				$src = JFile::read($path);
				//$js[] = "\n/* src: " . $url . " */";
				$js[] = JSMin::minify($src).";";
			}
			
			$js = implode("\n", $js);
			JFile::write($scriptpath, $js);
			$this->debug('New merged script has been created', $scripturl);
		} else {
			$this->debug('Merged script exists and it\'s up to date', $scripturl);
		}
		
		return $scripturl;
	}
	
	/**
	 * Updating the URLs inside stylesheets for compatibility with minified stylesheet location
	 */
	function updateUrls($src, $url){
		
		$app = JFactory::getApplication();
		
		// make sure url is root relative or absolute
		$url = ($url[0] === '/' || strpos($url, '://') !== false) ? $url : JURI::root(true) . '/' . $url;
		
		// replace image urls
		preg_match_all('/url\\(\\s*([^\\)\\s]+)\\s*\\)/', $src, $matches, PREG_SET_ORDER);
		
		foreach($matches as $match) {
			
			$uri = $match[1];
			
			if($uri[0] === "'" || $uri[0] === '"') {
				$uri = substr($uri, 1, strlen($uri) - 2);
			}
			
			if ($uri[0] !== '/' && strpos($uri, '://') === false && strpos($uri, 'data:') !== 0) {
				
				$uri = $url . '/' . $uri;
				// replace the url
				$src = str_replace($match[0], "url('$uri')", $src);
			}
		}
		
		// replace imported stylesheet urls
		preg_match_all('/@import\\s+[\'"](.*?)[\'"]/', $src, $matches, PREG_SET_ORDER);
		
		foreach($matches as $match) {
			
			$uri = $match[1];
			
			if($uri[0] === "'" || $uri[0] === '"') {
				$uri = substr($uri, 1, strlen($uri) - 2);
			}
			
			if ($uri[0] !== '/' && strpos($uri, '://') === false && strpos($uri, 'data:') !== 0) {
				
				$uri = $url . '/' . $uri;
				// replace the url
				$src = str_replace($match[0], "@import '$uri'", $src);
			}
		}
		
		return $src;
	}
	
	/**
	 * Getting the fixed path to the CSS/JS file which is allowed to be merged
	 */
	function getPath($url) {
		
		$app = JFactory::getApplication();
		$params = $app->getTemplate(true)->params;
		$skips = explode("\n", $params->get('skipCompress'));
		$skips[] = 'media/editors';
		
		foreach($skips as $skip) {
			$skip = trim($skip);
			if(empty($skip)) continue;
			
			if(stristr($url, $skip)!==false) {
				$this->debug('Script/style sheet is excluded from compression', $url);
				return false;
			}
		}
		
		if(substr($url, 0, 2) === '//'){
			$url = JURI::getInstance()->getScheme() . ':' . $url;
		}
		
		if (preg_match('/^https?\:/', $url)) {
			if (strpos($url, JURI::base()) === false){
				// external css
				$this->debug('External script/style sheet is excluded from compression', $url);
				return false;
			}
			$path = JPath::clean(JPATH_ROOT . '/' . substr($url, strlen(JURI::base())));
		} else {
			$path = JPath::clean(JPATH_ROOT . '/' . (JURI::root(true) && strpos($url, JURI::root(true)) === 0 ? substr($url, strlen(JURI::root(true))) : $url));
		}
		
		$path = preg_replace('/\?.*/', '', $path);
		
		if(is_file($path)) {
			return $path;
		}
		
		$this->debug('Script/style sheet doesn\'t exist', $url);
		return false;
	}
	
	/**
	 * Establishing the current template in testing if it supports EF4 Framework
	 */
	function getTemplateName() {
		$app = JFactory::getApplication();
		$template = false;
		if ($app->isSite()) {
			$template = $app->getTemplate(null);
			
			$activeMenu = JFactory::getApplication()->getMenu()->getActive();
			$template_style_id = 0;
			if ( !is_null($activeMenu) ) {
				$template_style_id = (int) $activeMenu->template_style_id;
			}
			
			if( $template_style_id > 0 ){
				JTable::addIncludePath(JPATH_ADMINISTRATOR . '/components/com_templates/tables');
				$style = JTable::getInstance('Style', 'TemplatesTable');
				if ($style->load($template_style_id)) {
					$template = $style->template;
					JFactory::getApplication()->setTemplate($style->template, $style->params);
				}
			}
			
		} else {
			$option = $app->input->get('option', null, 'string');
			$view = $app->input->get('view', null, 'string');
			$task = $app->input->get('task', '', 'string');
			$controller = current(explode('.',$task));
			$id = $app->input->get('id', null, 'int');
			if ($option == 'com_templates' && ($view == 'style' || $controller == 'style' || $task == 'apply' || $task == 'save' || $task == 'save2copy') && $id > 0) {
				$db = JFactory::getDbo();
				
				$query = $db->getQuery(true);
				
				$query->select('template');
				$query->from('#__template_styles');
				$query->where('id='.$id);
				
				$db->setQuery($query);
				$template = $db->loadResult();
			}
		}
		
		if ($template) {
			jimport('joomla.filesystem.file');
			$path = JPATH_ROOT.DIRECTORY_SEPARATOR.'templates'.DIRECTORY_SEPARATOR.$template.DIRECTORY_SEPARATOR.'templateDetails.xml';
			if (JFile::exists($path)) {
				$xml = JInstaller::parseXMLInstallFile($path);
				if (array_key_exists('group', $xml)){
					if ($xml['group'] == 'jmf-ef4') {
						return $template;
					}
				}
			}
		}
		
		return false;
	}
	
	public function checkTemplateFolders() {
		if (!defined('JMF_TPL_PATH')) {
			return false;
		}
		
		$folders = array(
			'assets',
			'assets/config',
			'assets/layout',
			'assets/style',
			'cache',
			'css',
			'fonts',
			'images',
			'less'
		);
		$errors = array();
		foreach($folders as $folder) {
			$path = JPath::clean(JMF_TPL_PATH . '/' . $folder);
			$nicePath = '/templates/'.JMF_TPL.'/' . $folder;
			if (JFolder::exists($path) == false) {
				$errors[] = '<strong>' . JText::_('PLG_SYSTEM_JMFRAMEWORK_WARNING_FOLDER_NOT_EXISTS') . '</strong>: <code>' . $nicePath . '</code>';
			} else if (is_writable($path) == false) {
				$errors[] = '<strong>' . JText::_('PLG_SYSTEM_JMFRAMEWORK_WARNING_FOLDER_NOT_WRITABLE') . '</strong>: <code>' . $nicePath . '</code>';
			}
		}
		
		return (count($errors) > 0) ? $errors : false;
	}
	
	/**
	 * Initialising JMFOpenGraph class
	 */
	protected function addOpenGraph($appId = null) {
		require_once JMF_FRAMEWORK_PATH.JPath::clean('/includes/libraries/opengraph/opengraph.php');
		JMFOpenGraph::applyTags($appId);
	}
	
	/**
	 * Checking for templates updates - pop-up/modal interface
	 */
	protected function checkUpdates($force = false) {
		$app = JFactory::getApplication();
		if ($app->isSite()) {
			return;
		}
		
		$option = $app->input->getCmd('option', null);
		$view = $app->input->getCmd('view', null);
		$task = $app->input->getCmd('task', null);
		$user = JFactory::getUser();
		
		if ($view || $option || $task || $user->guest) {
			return;
		}
		
		$autorised = (bool)($user->authorise('core.admin') || $user->authorise('core.manage'));
		if (!$autorised) {
			return;
		}
		
		$document = JFactory::getDocument();
		if ($document instanceof JDocumentHTML) {
			$this->loadLanguage();
			
			JHtml::_('jquery.framework');
			JHtml::_('bootstrap.framework');
			
			$document->addScript(JUri::root() . 'plugins/system/ef4_jmframework/includes/assets/admin/js/jmupdate.js');
			$settings = array();
			$settings['url'] = JUri::base(false).'index.php?option=com_ajax&group=system&plugin=EF4_JMFrameworkCheckUpdates&format=json&action=check_updates';
			$settings['lang'] = array();
			$settings['lang']['updates_available'] = JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_AVAILABLE');
			$settings['lang']['update_button'] = JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_BUTTON');
			$settings['lang']['modal_header'] = JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_HEADER');
			$plgLink = JRoute::_('index.php?option=com_plugins&view=plugins&filter_search=EF4');
			$settings['lang']['modal_footer'] = JText::sprintf('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_FOOTER', $plgLink);
			
			$script = '
				jQuery(document).ready(function(){
						JMFrameworkUpdate.checkUpdates('.json_encode($settings).');
				});
			';
			$document->addScriptDeclaration($script);
		}
		
	}
	
	/*
	 * Checking for templates updates - retrieving data from remote XML.
	 */
	public function onAjaxEF4_JMFrameworkCheckUpdates(){
		$app = JFactory::getApplication();
		$db = JFactory::getDbo();
		$this->loadLanguage();
		
		$updates = array();
		$updatesCount = 0;
		$customInfo = null;
		
		$output = array(
			'updates' => 0,
			'html' => '',
			'error' => null
		);
		
		
		$query = $db->getQuery(true)
		->select('*')
		->from('#__extensions')
		->where('type='.$db->quote('template').' AND enabled=1')
		->where('manifest_cache LIKE '.$db->quote('%"group":"jmf-ef4"%'))
		->order('name asc');
		
		$db->setQuery($query);
		$templates = $db->loadObjectList();
		
		if (empty($templates)) {
			return $output;
		}
		
		$cacheOpt = array(
			'defaultgroup' => 'plugin',
			'browsercache' => false,
			'caching'	  => true
		);
		
		$cacheKey = 'plg_system_ef4_jmframework.updates';
		$cache = JCache::getInstance('output', $cacheOpt);
		// 60 minutes
		$cache->setLifeTime(60);
		
		$body = $cache->get($cacheKey);
		
		if ($body === false) {
			
			// We need to refresh Manifest cache in order to be sure
			// that we are comparing the most accurate template version.
			// We do it here, when XML updates cache is empty,
			// so that database is not too exploited.
			$installer = JInstaller::getInstance();
			foreach ($templates as $template) {
				$installer->refreshManifestCache($template->extension_id);
			}
			
			// Second run - after Manifest Cache has been refreshed.
			$db->setQuery($query);
			$templates = $db->loadObjectList();
			
			if (empty($templates)) {
				return $output;
			}
			
			$http = JHttpFactory::getHttp();
			$response = $http->get($this->updatesURL);
			if (200 != $response->code || empty($response->body))
			{
				$output['error'] = 'HTTP Error ' . $response->code;
				return json_encode($output);
			}
			
			$body = $response->body;
			$cache->store($body, $cacheKey);
		}
		
		try {
			$xml = new SimpleXMLElement($body);
		}
		catch (Exception $e)
		{
			$output['error'] = 'XML is empty';
			return json_encode($output);
		}
		
		if (isset($xml->information) && !empty($xml->information)) {
			$customInfo = trim($xml->information);
		}
		
		if (!isset($xml->templates) || empty($xml->templates)) {
			$output['error'] = 'There are no templates';
			return json_encode($output);
		}
		
		if (!isset($xml->templates->template) || empty($xml->templates->template)) {
			$output['error'] = 'There are no templates';
			return json_encode($output);
		}
		
		$version = new JVersion;
		$joomlaVersion = explode('.', $version->getShortVersion());
		
		foreach($templates as $key => $template) {
			$item = new stdClass();
			
			$item->name = $template->name;
			$item->link = null;
			$item->custom_info = null;
			
			$registry = new JRegistry();
			$registry->loadString($template->manifest_cache, 'JSON');
			
			$item->current_version = $item->version = $registry->get('version', '-');
			
			$found = $xml->xpath('//templates//template[@name="'.$template->element.'"]');
			if ($found) {
				foreach($found as $element) {
					if ($element->joomlaversion == $joomlaVersion[0]) {
						$item->name = $element->title;
						$item->version = $element->version;
						$item->link = isset($element->changelog) ? $element->changelog : null;
						$item->custom_info = isset($element->info) ? $element->info : null;
						if (version_compare($item->current_version, $item->version, '<')) {
							$updatesCount++;
						}
						break;
					}
				}
				// we are not adding templates which do not exist in XML log
				$updates[] = $item;
			}
		}
		
		$html = '';
		
		if ($customInfo) {
			$html .= '<div class="alert alert-info">'.$customInfo.'</div>';
		}
		
		if ($updatesCount > 0) {
			$html .= '<table class="table table-striped">';
			$html .= '<thead><tr>';
			$html .= '<th width="1%" class="center">#</th>';
			$html .= '<th class="nowrap">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_NAME').'</th>';
			$html .= '<th width="5%" class="center nowrap">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_YOUR_VERSION').'</th>';
			$html .= '<th width="5%" class="center nowrap">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_LATEST_VERSION').'</th>';
			$html .= '<th width="5%" class="center nowrap">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_LINK').'</th>';
			$html .= '</tr></thead>';
			$html .= '<tfoot></tfoot>';
			$html .= '<tbody>';
			
			foreach($updates as $k => $update) {
				$html .= '<tr class="row'.($k%2).'">';
				$html .= '<td class="nowrap center">'.($k+1).'</td>';
				$html .= '<td class="nowrap"><strong>'.$update->name.'</strong>';
				if ($update->custom_info) {
					$html .= '<br /><span class="small">'.$update->custom_info.'</span>';
				}
				$html .= '</td>';
				$diffVer = version_compare($update->current_version, $update->version, '<');
				$html .= '<td class="nowrap center"><span class="badge '.($diffVer ? 'badge-warning' : 'badge-success').'">'.$update->current_version.'</span></td>';
				$html .= '<td class="nowrap center"><span class="badge '.($diffVer ? 'badge-important' : 'badge-success').'">'.$update->version.'</span></td>';
				if ($diffVer && $update->link) {
					$html .= '<td class="nowrap center"><a class="btn btn-mini btn-primary" href="'.$update->link.'" target="_blank">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_BUTTON').'</a></td>';
				} else {
					$html .= '<td class="nowrap center"><span class="badge badge-success">'.JText::_('PLG_SYSTEM_EF4_JMFRAMEWORK_UPDATES_TABLE_NOUPDATE').'</span></td>';
				}
				$html .= '</tr>';
			}
			$html .= '</tbody>';
			$html .= '</table>';
		}
		
		$output['updates'] = $updatesCount;
		$output['html'] = $html;
		
		return json_encode($output);
	}
	
	/**
	 * Utility class for quick debugging.
	 */
	private function debug($msg, $data = null) {
		
		if(!$this->debug) return;
		
		$this->_debug[] = array('msg'=>$msg, 'data'=>$data);
	}
	
	/**
	 * Utility class for rendering and displaying debug information.
	 */
	private function renderDebug(&$body) {
		
		if(!$this->debug) return;
		
		$html = '
		<script type="text/javascript">
			function toggleDJQData(name)
			{
				var e = document.getElementById(name);
				e.style.display = (e.style.display == \'none\') ? \'block\' : \'none\';
			}
		</script>
		';
		
		$html .= '<h3>EF4 JOOMLA-MONSTER FRAMEWORK DEBUG INFORMATION</h3>';
		
		foreach($this->_debug as $no => $debug) {
			
			$html .= '<h4>'.($no+1).'. '.$debug['msg'];
			
			if(!empty($debug['data'])) {
				if(is_array($debug['data'])) {
					$html .= ' <a href="#" onclick="toggleDJQData(\'jmfdebug-'.$no.'\'); return false;" class="btn btn-mini">toggle data</a></h4>';
					$html .= ' <pre id="jmfdebug-'.$no.'" style="display: none;">'.print_r($debug['data'], true).'</pre>';
				} else {
					$html .= ' <code>'.$debug['data'].'</code></h4>';
				}
			} else {
				$html .= '</h4>';
			}
		}
		
		$html = '<div class="container well">'.$html.'</div>';
		
		$body = str_replace('</body>', $html . '</body>', $body);
	}
}
