<?php

/**
* Basic class for Kernel3-based Application
*
* This class is a Facade for any other class which needs to deal with Kernel3 framework.<br>
* The class incapsulates the main run-cycle of the script, provide access to all other objects in the framework.<br>
* <br>
* The class is a singleton, which means that there could be only one instance of KernelApplication in the script.<br>
* This could be guranteed by NOT calling the class constuctor directly, but rather calling KernelApplication::Instance() method,
* which returns an instance of the application. The method gurantees that it will return exactly the same instance for any call.<br>
* See singleton pattern by GOF.
* @package kernel4
*/

class kApplication {

	/**
	 * Is true, when Init method was called already, prevents double initialization
	 *
	 * @var bool
	 */
	var $InitDone = false;

	/**
	* Holds internal TemplateParser object
	* @access private
	* @var TemplateParser
	*/
	var $Parser;

	/**
	* Holds parser output buffer
	* @access private
	* @var string
	*/
	var $HTML;

	/**
	 * Prevents request from beeing proceeded twice in case if application init is called mere then one time
	 *
	 * @var bool
	 * @todo This is not good anyway (by Alex)
	 */
	var $RequestProcessed = false;

	/**
	* The main Factory used to create
	* almost any class of kernel and
	* modules
	*
	* @access private
	* @var kFactory
	*/
	var $Factory;

	/**
	 * All ConfigurationValues table content (hash) here
	 *
	 * @var Array
	 * @access private
	 */
	var $ConfigHash = Array();

	/**
	 * Reference to debugger
	 *
	 * @var Debugger
	 */
	var $Debugger = null;

	/**
	 * Holds all phrases used
	 * in code and template
	 *
	 * @var PhrasesCache
	 */
	var $Phrases;

	/**
	 * Modules table content, key - module name
	 *
	 * @var Array
	 */
	var $ModuleInfo = Array();

	/**
	 * Holds DBConnection
	 *
	 * @var kDBConnection
	 */
	var $DB;

	/**
	 * Maintains list of user-defined error handlers
	 *
	 * @var Array
	 */
	var $errorHandlers = Array();

	/**
	* Returns kApplication instance anywhere in the script.
 	*
 	* This method should be used to get single kApplication object instance anywhere in the
 	* Kernel-based application. The method is guranteed to return the SAME instance of kApplication.
 	* Anywhere in the script you could write:
 	* <code>
 	*		$application =& kApplication::Instance();
 	* </code>
 	* or in an object:
 	* <code>
 	*		$this->Application =& kApplication::Instance();
 	* </code>
 	* to get the instance of kApplication. Note that we call the Instance method as STATIC - directly from the class.
 	* To use descendand of standard kApplication class in your project you would need to define APPLICATION_CLASS constant
 	* BEFORE calling kApplication::Instance() for the first time. If APPLICATION_CLASS is not defined the method would
 	* create and return default KernelApplication instance.
 	* @static
 	* @access public
	* @return kApplication
	*/
	function &Instance()
	{
		static $instance = false;

		if(!$instance)
		{
			safeDefine('APPLICATION_CLASS', 'kApplication');
			$class = APPLICATION_CLASS;
			$instance = new $class();
		}
		return $instance;
	}

	/**
	 * Returns module information. Searches module by requested field
	 *
	 * @param string $field
	 * @param mixed $value
	 * @return Array
	 */
	function findModule($field, $value)
	{
		$found = false;
		foreach ($this->ModuleInfo as $module_name => $module_info)
		{
			if ($module_info[$field] == $value)
			{
				$found = true;
				break;
			}
		}
		return $found ? $module_info : false;
	}

	/**
	* Initializes the Application
 	*
 	* @access public
	* @see HTTPQuery
	* @see Session
	* @see TemplatesCache
	* @return bool Was Init actually made now or before
	*/
	function Init()
	{
		if($this->InitDone) return false;

		if( $this->isDebugMode() && dbg_ConstOn('DBG_PROFILE_MEMORY') )
		{
			$this->Debugger->appendMemoryUsage('Application before Init:');
		}

		if( !$this->isDebugMode() && !constOn('DBG_ZEND_PRESENT') )
		{
			error_reporting(0);
			ini_set('display_errors', 0);
		}

		if( !constOn('DBG_ZEND_PRESENT') )
		{
			$error_handler = set_error_handler( Array(&$this,'handleError') );
			if($error_handler) $this->errorHandlers[] = $error_handler;
		}

		$this->DB = new kDBConnection(SQL_TYPE, Array(&$this,'handleSQLError') );
		$this->DB->Connect(SQL_SERVER, SQL_USER, SQL_PASS, SQL_DB);
		$this->DB->debugMode = $this->isDebugMode();

		$this->ModuleInfo = $this->DB->Query('SELECT * FROM '.TABLE_PREFIX.'Modules ORDER BY LoadOrder', 'Name');
		$this->ConfigHash = $this->DB->GetCol('SELECT VariableValue, VariableName FROM '.TABLE_PREFIX.'ConfigurationValues', 'VariableName');

		$rewrite_on = $this->ConfigValue('UseModRewrite');
		$admin_on = getArrayValue($_REQUEST, 'admin') || $this->IsAdmin();
		define('MOD_REWRITE', ($rewrite_on || constOn('CMS') ) && !$admin_on ? 1 : 0);

		$this->Factory = new kFactory();

		$this->registerDefaultClasses();
		$this->SetDefaultConstants();

		// 1. to read configs before doing any recallObject (called from "SetDefaultConstants" anyway)
		$config_reader =& $this->recallObject('kUnitConfigReader');

		// Module items are recalled during url parsing & PhrasesCache is needed already there,
		// because it's used in their build events. That's why phrases cache initialization is
		// called from httpquery in case when mod_rewrite is used
		if( !$this->RewriteURLs() )
		{
			$this->Phrases = new PhrasesCache();
			$this->VerifyLanguageId();
			$this->Phrases->Init('phrases');
			$this->VerifyThemeId();
		}

		$this->SetVar('lang.current_id', $this->GetVar('m_lang') );
		$this->SetVar('theme.current_id', $this->GetVar('m_theme') );
		if( $this->GetVar('m_cat_id') === false ) $this->SetVar('m_cat_id', 0);
		
		if( !$this->RecallVar('UserGroups') )
		{
			$session =& $this->recallObject('Session');
			$user_groups = trim($session->GetField('GroupList'), ',');
			if (!$user_groups) $user_groups = $this->ConfigValue('User_GuestGroup');
			$this->StoreVar('UserGroups', $user_groups);
		}

		if( !$this->RecallVar('curr_iso') ) $this->StoreVar('curr_iso', $this->GetPrimaryCurrency() );

		$this->SetVar('visits_id', $this->RecallVar('visit_id') );
		
		$language =& $this->recallObject( 'lang.current', null, Array('live_table' => true) );

		$this->ValidateLogin(); // TODO: write that method

		if( $this->isDebugMode() )
		{
			$this->Debugger->profileFinish('kernel4_startup');
		}

		$this->InitDone = true;
		return true;
	}

	/**
	 * Checks if passed language id if valid and sets it to primary otherwise
	 *
	 */
	function VerifyLanguageId()
	{
		$language_id = $this->GetVar('m_lang');
		if($language_id)
		{
			$table = $this->getUnitOption('lang', 'TableName');
			$id_field = $this->getUnitOption('lang', 'IDField');
			$language_ids = $this->DB->GetCol('SELECT '.$id_field.' FROM '.$table);
		}

		if ( !$language_id || !in_array($language_id, $language_ids) )
		{
			$this->SetVar('m_lang', $this->GetDefaultLanguageId() );
		}
	}

	/**
	 * Checks if passed theme id if valid and sets it to primary otherwise
	 *
	 */
	function VerifyThemeId()
	{
		$theme_id = $this->GetVar('m_theme');
		if($theme_id)
		{
			$table = $this->getUnitOption('theme', 'TableName');
			$id_field = $this->getUnitOption('theme', 'IDField');
			$theme_ids = $this->DB->GetCol('SELECT '.$id_field.' FROM '.$table);
		}

		if ( !$theme_id || !in_array($theme_id, $theme_ids) )
		{
			$this->SetVar('m_theme', $this->GetDefaultThemeId() );
		}
	}

	function GetDefaultLanguageId()
	{
		static $language_id = 0;

		if ($language_id > 0) return $language_id;

		$table = $this->getUnitOption('lang','TableName');
		$id_field = $this->getUnitOption('lang','IDField');
		$language_id = $this->DB->GetOne('SELECT '.$id_field.' FROM '.$table.' WHERE PrimaryLang = 1');

		return $language_id;
	}

	function GetDefaultThemeId()
	{
		static $theme_id = 0;

		if($theme_id > 0) return $theme_id;

		if ( constOn('DBG_FORCE_THEME') )
		{
			$theme_id = DBG_FORCE_THEME;
		}
		else
		{
			$table = $this->getUnitOption('theme','TableName');
			$id_field = $this->getUnitOption('theme','IDField');
			$theme_id = $this->DB->GetOne('SELECT '.$id_field.' FROM '.$table.' WHERE PrimaryTheme = 1');
		}
		return $theme_id;
	}

	function GetPrimaryCurrency()
	{
		$has_incommerce = getArrayValue($this->ModuleInfo, 'In-Commerce');
		if($has_incommerce  && $has_incommerce['Loaded'] )
		{
			$table = $this->getUnitOption('curr', 'TableName');
			return $this->DB->GetOne('SELECT ISO FROM '.$table.' WHERE IsPrimary = 1');
		}
		else
		{
			return 'USD';
		}
	}

	/**
	* Registers default classes such as ItemController, GridController and LoginController
	*
	* Called automatically while initializing Application
	* @access private
	* @return void
	*/
	function RegisterDefaultClasses()
	{
		$this->registerClass('kArray', KERNEL_PATH.'/utility/params.php');
		$this->registerClass('Params', KERNEL_PATH.'/utility/params.php');

		$this->registerClass('Params', KERNEL_PATH.'/utility/params.php', 'kFilenamesCache');

		$this->registerClass('HTTPQuery', KERNEL_PATH.'/utility/http_query.php', 'HTTPQuery', Array('Params') );

		$this->registerClass('Session', KERNEL_PATH.'/session/session.php');
		$this->registerClass('SessionStorage', KERNEL_PATH.'/session/session.php');

		$this->registerClass('kEventManager', KERNEL_PATH.'/event_manager.php', 'EventManager');
		$this->registerClass('kUnitConfigReader', KERNEL_PATH.'/utility/unit_config_reader.php');

		$this->registerClass('Params', KERNEL_PATH.'/utility/params.php', 'kActions');

		$this->registerClass('kFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kOptionsFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kUploadFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kPictureFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kDateFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kLEFTFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kMultiLanguage', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kPasswordFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kCCDateFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kUnitFormatter', KERNEL_PATH.'/utility/formatters.php');
		$this->registerClass('kFilesizeFormatter', KERNEL_PATH.'/utility/formatters.php');

		$this->registerClass('kTempTablesHandler', KERNEL_PATH.'/utility/temp_handler.php');

		$event_manager =& $this->recallObject('EventManager');
		$event_manager->registerBuildEvent('kTempTablesHandler', 'OnTempHandlerBuild');

		$this->registerClass('TemplatesCache', KERNEL_PATH.'/parser/template.php');
		$this->registerClass('Template', KERNEL_PATH.'/parser/template.php');
		$this->registerClass('TemplateParser', KERNEL_PATH.'/parser/template_parser.php');

		$this->registerClass('kMainTagProcessor', KERNEL_PATH.'/processors/main_processor.php','m_TagProcessor');

		$this->registerClass('kMultipleFilter', KERNEL_PATH.'/utility/filters.php');
		$this->registerClass('kDBList', KERNEL_PATH.'/db/dblist.php');
		$this->registerClass('kDBItem', KERNEL_PATH.'/db/dbitem.php');
		$this->registerClass('kDBEventHandler', KERNEL_PATH.'/db/db_event_handler.php');
		$this->registerClass('kDBTagProcessor', KERNEL_PATH.'/db/db_tag_processor.php');

		$this->registerClass('kTagProcessor', KERNEL_PATH.'/processors/tag_processor.php');
		$this->registerClass('kEmailMessage', KERNEL_PATH.'/utility/email.php');
		$this->registerClass('kSmtpClient', KERNEL_PATH.'/utility/smtp_client.php');

		if (file_exists(MODULES_PATH.'/in-commerce/units/currencies/currency_rates.php')) {
			$this->registerClass('kCurrencyRates', MODULES_PATH.'/in-commerce/units/currencies/currency_rates.php');
		}

		$this->registerClass('FCKeditor', FULL_PATH.'/admin/editor/cmseditor/fckeditor.php'); // need this?
	}

	/**
	 * Returns item's filename that corresponds id passed. If possible, then get it from cache
	 *
	 * @param string $prefix
	 * @param int $id
	 * @return string
	 */
	function getFilename($prefix, $id)
	{
		$field = ($prefix == 'c') ? 'NamedParentPath' : 'Filename';
		$filenames_cache =& $this->recallObject('kFilenamesCache');
		$filename = $filenames_cache->Get($prefix.'_'.$id);
		if($filename === false)
		{
			$table = $this->getUnitOption($prefix, 'TableName');
			$id_field = $this->getUnitOption($prefix, 'IDField');
			$sql = 'SELECT '.$field.' FROM '.$table.' WHERE '.$id_field.' = '.$this->DB->qstr($id);
			$filename = $this->DB->GetOne($sql);
			$filenames_cache->Set($prefix.'_'.$id, $filename);
		}
		return $filename;
	}

	/**
	* Defines default constants if it's not defined before - in config.php
	*
	* @access private
	*/
	function SetDefaultConstants()
	{
		safeDefine('SERVER_NAME', $_SERVER['HTTP_HOST']);

		$admin_dir = $this->ConfigValue('AdminDirectory');
		if(!$admin_dir) $admin_dir = 'admin';
		safeDefine('ADMIN_DIR', $admin_dir);

		$this->registerModuleConstants();
	}

	/**
	 * Registers each module specific constants if any found
	 *
	 */
	function registerModuleConstants()
	{
		if (!$this->ModuleInfo) return false;
		
		foreach($this->ModuleInfo as $module_name => $module_info)
		{
			$module_path = '/'.$module_info['Path'];
			$contants_file = FULL_PATH.$module_path.'constants.php';
			if( file_exists($contants_file) ) k4_include_once($contants_file);
		}
		return true;
	}

	function ProcessRequest()
	{
		$event_manager =& $this->recallObject('EventManager');

		if( $this->isDebugMode() && dbg_ConstOn('DBG_SHOW_HTTPQUERY') )
		{
			global $debugger;
			$http_query =& $this->recallObject('HTTPQuery');
			$debugger->appendHTML('HTTPQuery:');
			$debugger->dumpVars($http_query->_Params);
		}

		$event_manager->ProcessRequest();
		$event_manager->RunRegularEvents(reBEFORE);
		$this->RequestProcessed =  true;
	}

	/**
	* Actually runs the parser against current template and stores parsing result
	*
	* This method gets t variable passed to the script, loads the template given in t variable and
	* parses it. The result is store in {@link $this->HTML} property.
	* @access public
	* @return void
	*/
	function Run()
	{
		if( $this->isDebugMode() && dbg_ConstOn('DBG_PROFILE_MEMORY') )
		{
			$GLOBALS['debugger']->appendMemoryUsage('Application before Run:');
		}

		if (!$this->RequestProcessed) $this->ProcessRequest();

		$this->InitParser();
		$template_cache =& $this->recallObject('TemplatesCache');
		$t = $this->GetVar('t');

		if( constOn('CMS') )
		{
			$cms_handler =& $this->recallObject('cms_EventHandler');
			if( !$template_cache->TemplateExists($t) )
			{
				$t = $cms_handler->GetDesignTemplate();
			}
			else
			{
				$cms_handler->SetCatByTemplate();
			}
		}

		if( $this->isDebugMode() && dbg_ConstOn('DBG_PROFILE_MEMORY') )
		{
			$GLOBALS['debugger']->appendMemoryUsage('Application before Parsing:');
		}

		$this->HTML = $this->Parser->Parse( $template_cache->GetTemplateBody($t), $t );

		if( $this->isDebugMode() && dbg_ConstOn('DBG_PROFILE_MEMORY') )
		{
			$GLOBALS['debugger']->appendMemoryUsage('Application after Parsing:');
		}
	}

	function InitParser()
	{
		if( !is_object($this->Parser) ) $this->Parser =& $this->recallObject('TemplateParser');
	}

	/**
	* Send the parser results to browser
	*
	* Actually send everything stored in {@link $this->HTML}, to the browser by echoing it.
	* @access public
	* @return void
	*/
	function Done()
	{
		if( $this->isDebugMode() && dbg_ConstOn('DBG_PROFILE_MEMORY') )
		{
			$GLOBALS['debugger']->appendMemoryUsage('Application before Done:');
		}

		if( $this->GetVar('admin') )
		{
			$reg = '/('.preg_quote(BASE_PATH, '/').'.*\.html)(#.*){0,1}(")/sU';
			$this->HTML = preg_replace($reg, "$1?admin=1$2$3", $this->HTML);
		}

		//eval("?".">".$this->HTML);

		echo $this->HTML;
		$this->Phrases->UpdateCache();

		flush();

		$event_manager =& $this->recallObject('EventManager');
		$event_manager->RunRegularEvents(reAFTER);

		$session =& $this->recallObject('Session');
		$session->SaveData();
		//$this->SaveBlocksCache();
	}

	function SaveBlocksCache()
	{
		/*if (constOn('EXPERIMENTAL_PRE_PARSE')) {
			$data = serialize($this->PreParsedCache);

			$this->DB->Query('REPLACE '.TABLE_PREFIX.'Cache (VarName, Data, Cached) VALUES ("blocks_cache", '.$this->DB->qstr($data).', '.adodb_mktime().')');
		}*/
	}

	//	Facade

	/**
	* Returns current session id (SID)
	* @access public
	* @return longint
	*/
	function GetSID()
	{
		$session =& $this->recallObject('Session');
		return $session->GetID();
	}

	function DestroySession()
	{
		$session =& $this->recallObject('Session');
		$session->Destroy();
	}

	/**
	* Returns variable passed to the script as GET/POST/COOKIE
	*
	* @access public
	* @param string $var Variable name
	* @return mixed
	*/
	function GetVar($var, $mode = FALSE_ON_NULL)
	{
		$http_query =& $this->recallObject('HTTPQuery');
		return $http_query->Get($var, $mode);
	}

	/**
	* Returns ALL variables passed to the script as GET/POST/COOKIE
	*
	* @access public
	* @return array
	*/
	function GetVars()
	{
		$http_query =& $this->recallObject('HTTPQuery');
		return $http_query->GetParams();
	}

	/**
	* Set the variable 'as it was passed to the script through GET/POST/COOKIE'
	*
	* This could be useful to set the variable when you know that
	* other objects would relay on variable passed from GET/POST/COOKIE
	* or you could use SetVar() / GetVar() pairs to pass the values between different objects.<br>
	*
	* This method is formerly known as $this->Session->SetProperty.
	* @param string $var Variable name to set
	* @param mixed $val Variable value
	* @access public
	* @return void
	*/
	function SetVar($var,$val)
	{
		$http_query =& $this->recallObject('HTTPQuery');
		$http_query->Set($var,$val);
	}

	/**
	 * Deletes Session variable
	 *
	 * @param string $var
	 */
	function RemoveVar($var)
	{
		$session =& $this->recallObject('Session');
		return $session->RemoveVar($var);
	}

	/**
	 * Deletes HTTPQuery variable
	 *
	 * @param string $var
	 * @todo think about method name
	 */
	function DeleteVar($var)
	{
		$http_query =& $this->recallObject('HTTPQuery');
		return $http_query->Remove($var);
	}

	/**
	* Returns session variable value
	*
	* Return value of $var variable stored in Session. An optional default value could be passed as second parameter.
	*
	* @see SimpleSession
	* @access public
	* @param string $var Variable name
	* @param mixed $default Default value to return if no $var variable found in session
	* @return mixed
	*/
	function RecallVar($var,$default=false)
	{
		$session =& $this->recallObject('Session');
		return $session->RecallVar($var,$default);
	}

	/**
	* Stores variable $val in session under name $var
	*
	* Use this method to store variable in session. Later this variable could be recalled.
	* @see RecallVar
	* @access public
	* @param string $var Variable name
	* @param mixed $val Variable value
	*/
	function StoreVar($var, $val)
	{
		$session =& $this->recallObject('Session');
		$session->StoreVar($var, $val);
	}

	function StoreVarDefault($var, $val)
	{
		$session =& $this->recallObject('Session');
		$session->StoreVarDefault($var, $val);
	}

	/**
	* Links HTTP Query variable with session variable
	*
	* If variable $var is passed in HTTP Query it is stored in session for later use. If it's not passed it's recalled from session.
	* This method could be used for making sure that GetVar will return query or session value for given
	* variable, when query variable should overwrite session (and be stored there for later use).<br>
	* This could be used for passing item's ID into popup with multiple tab -
	* in popup script you just need to call LinkVar('id', 'current_id') before first use of GetVar('id').
	* After that you can be sure that GetVar('id') will return passed id or id passed earlier and stored in session
	* @access public
	* @param string $var HTTP Query (GPC) variable name
	* @param mixed $ses_var Session variable name
	* @param mixed $default Default variable value
	*/
	function LinkVar($var, $ses_var=null, $default='')
	{
		if (!isset($ses_var)) $ses_var = $var;
		if ($this->GetVar($var) !== false)
		{
			$this->StoreVar($ses_var, $this->GetVar($var));
		}
		else
		{
			$this->SetVar($var, $this->RecallVar($ses_var, $default));
		}
	}

	/**
	* Returns variable from HTTP Query, or from session if not passed in HTTP Query
	*
	* The same as LinkVar, but also returns the variable value taken from HTTP Query if passed, or from session if not passed.
	* Returns the default value if variable does not exist in session and was not passed in HTTP Query
	*
	* @see LinkVar
	* @access public
	* @param string $var HTTP Query (GPC) variable name
	* @param mixed $ses_var Session variable name
	* @param mixed $default Default variable value
	* @return mixed
	*/
	function GetLinkedVar($var, $ses_var=null, $default='')
	{
		if (!isset($ses_var)) $ses_var = $var;
		$this->LinkVar($var, $ses_var, $default);
		return $this->GetVar($var);
	}

	function AddBlock($name, $tpl)
	{
		$this->cache[$name] = $tpl;
	}

	function SetTemplateBody($title,$body)
	{
		$templates_cache =& $this->recallObject('TemplatesCache');
		$templates_cache->SetTemplateBody($title,$body);
	}

	function ProcessTag($tag_data)
	{
		$a_tag = new Tag($tag_data,$this->Parser);
		return $a_tag->DoProcessTag();
	}

	function ProcessParsedTag($prefix, $tag, $params)
	{
		$a_tag = new Tag('',$this->Parser);
		$a_tag->Tag = $tag;
		$a_tag->Processor = $prefix;
		$a_tag->NamedParams = $params;
		return $a_tag->DoProcessTag();
	}

	/**
	* Return ADODB Connection object
	*
	* Returns ADODB Connection object already connected to the project database, configurable in config.php
	* @access public
	* @return kDBConnection
	*/
	function &GetADODBConnection()
	{
		return $this->DB;
	}

	function ParseBlock($params,$pass_params=0,$as_template=false)
	{
		if (substr($params['name'], 0, 5) == 'html:') return substr($params['name'], 6);
		return $this->Parser->ParseBlock($params, $pass_params, $as_template);
	}

	/**
	* Return href for template
	*
	* @access public
	* @param string $t Template path
	* @var string $prefix index.php prefix - could be blank, 'admin'
	*/
	function HREF($t, $prefix='', $params=null, $index_file=null)
	{
		if(!$t) $t = $this->GetVar('t'); // moved from kMainTagProcessor->T()

		if( substr($t, -4) == '.tpl' ) $t = substr($t, 0, strlen($t) - 4 );

		if ( $this->IsAdmin() && $prefix == '') $prefix = '/admin';
		if ( $this->IsAdmin() && $prefix == '_FRONT_END_') $prefix = '';
		$index_file = isset($index_file) ? $index_file : (defined('INDEX_FILE') ? INDEX_FILE : basename($_SERVER['SCRIPT_NAME']));

		if( isset($params['index_file']) ) $index_file = $params['index_file'];

		$ssl = isset($params['__SSL__']) ? $params['__SSL__'] : null;
		if ($ssl !== null) {
			$session =& $this->recallObject('Session');
			$cookie_url = $session->CookieDomain.$session->CookiePath;
			if ($ssl) {
				$target_url = $this->ConfigValue('SSL_URL');
			}
			else {
				$target_url = 'http://'.DOMAIN.$this->ConfigValue('Site_Path');
			}

			if (!preg_match('#'.preg_quote($cookie_url).'#', $target_url)) {
				$session->SetMode(smGET_ONLY);
			}
		}
		unset($params['__SSL__']);

		if (getArrayValue($params, 'opener') == 'u') {
			$opener_stack=$this->RecallVar('opener_stack');
			if($opener_stack) {
				$opener_stack=unserialize($opener_stack);
				if (count($opener_stack) > 0) {
					list($index_file, $env) = explode('|', $opener_stack[count($opener_stack)-1]);
					$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.ENV_VAR_NAME.'='.$env;
					if( getArrayValue($params,'escape') ) $ret = addslashes($ret);
					return $ret;
				}
				else {
					//define('DBG_REDIRECT', 1);
					$t = $this->GetVar('t');
				}
			}
			else {
				//define('DBG_REDIRECT', 1);
				$t = $this->GetVar('t');
			}
		}

		$pass = isset($params['pass']) ? $params['pass'] : '';
		$pass_events = isset($params['pass_events']) ? $params['pass_events'] : false; // pass events with url

		$map_link = '';
		if( isset($params['anchor']) )
		{
			$map_link = '#'.$params['anchor'];
			unset($params['anchor']);
		}

		if ( $this->RewriteURLs() )
		{
			$session =& $this->recallObject('Session');
			if( $session->NeedQueryString() ) $params['sid'] = $this->GetSID();
			$url = $this->BuildEnv_NEW($t, $params, $pass, $pass_events);
			$ret = $this->BaseURL($prefix, $ssl).$url.$map_link;

		}
		else
		{
			$env = $this->BuildEnv($t, $params, $pass, $pass_events);
			$ret = $this->BaseURL($prefix, $ssl).$index_file.'?'.$env.$map_link;
		}

		return $ret;
	}

	function BuildEnv_NEW($t, $params, $pass = 'all', $pass_events = false)
	{
//		$session =& $this->recallObject('Session');
		$force_admin = getArrayValue($params,'admin') || $this->GetVar('admin');

//		if($force_admin) $sid = $this->GetSID();

		$ret = '';
		$env = '';

		$encode = false;
		if (isset($params['__URLENCODE__']))
		{
			$encode = $params['__URLENCODE__'];
			unset($params['__URLENCODE__']);
		}

		$pass = str_replace('all', trim($this->GetVar('passed'), ','), $pass);

		if(strlen($pass) > 0)
		{
			$pass_info = array_unique( explode(',',$pass) ); // array( prefix[.special], prefix[.special] ...
			$event_params = Array('t' => $t, 'pass_events' => $pass_events);
			foreach($pass_info as $pass_element)
			{
				list($prefix) = explode('.', $pass_element);
				$require_rewrite = $this->findModule('Var', $prefix);
				if($require_rewrite)
				{
					$event_params['url_params'] = $params;
					$event = new kEvent($pass_element.':BuildEnv', $event_params);
					$this->HandleEvent($event);
					$ret .= '/'.trim( $event->getEventParam('env_string'), '/');
					$params = $event->getEventParam('url_params'); // save back unprocessed parameters
				}
				else
				{
					$env .= ':'.$this->BuildModuleEnv($pass_element, $params, $pass_events);
				}
			}

			$ret = trim($ret, '/').'.html';
			if($env) $params[ENV_VAR_NAME] = ltrim($env, ':');
		}

		unset($params['pass'], $params['opener'], $params['m_event']);
		if ($force_admin) $params['admin'] = 1;

		if( getArrayValue($params,'escape') )
		{
			$ret = addslashes($ret);
			unset($params['escape']);
		}

		$params_str = '';
		$join_string = $encode ? '&' : '&amp;';
		foreach ($params as $param => $value)
		{
			$params_str .= $join_string.$param.'='.$value;
		}
		$ret .= preg_replace('/^&amp;(.*)/', '?\\1', $params_str);

		if ($encode) $ret = str_replace('\\', '%5C', $ret);
		return $ret;
	}

	/**
	 * Builds env part that corresponds prefix passed
	 *
	 * @param string $prefix_special item's prefix & [special]
	 * @param Array $params url params
	 * @param bool $pass_events
	 */
	function BuildModuleEnv($prefix_special, &$params, $pass_events = false)
	{
		list($prefix) = explode('.', $prefix_special);
		$query_vars = $this->getUnitOption($prefix, 'QueryString');

		//if pass events is off and event is not implicity passed
		if( !$pass_events && !isset($params[$prefix_special.'_event']) ) {
			$params[$prefix_special.'_event'] = ''; // remove event from url if requested
			//otherwise it will use value from get_var
		}

		if(!$query_vars) return '';

		$tmp_string = Array(0 => $prefix_special);
		foreach($query_vars as $index => $var_name)
		{
			//if value passed in params use it, otherwise use current from application
			$var_name = $prefix_special.'_'.$var_name;
			$tmp_string[$index] =  isset( $params[$var_name] ) ? $params[$var_name] : $this->GetVar($var_name);
			if ( isset($params[$var_name]) ) unset( $params[$var_name] );
		}

		$escaped = array();
		foreach ($tmp_string as $tmp_val) {
			$escaped[] = str_replace(Array('-',':'), Array('\-','\:'), $tmp_val);
		}

		$ret = implode('-', $escaped);
		if ($this->getUnitOption($prefix, 'PortalStyleEnv') == true)
		{
			$ret = preg_replace('/^([a-zA-Z]+)-([0-9]+)-(.*)/','\\1\\2-\\3', $ret);
		}
		return $ret;
	}

	function BuildEnv($t, $params, $pass='all', $pass_events=false, $env_var=true)
	{
		$session =& $this->recallObject('Session');
		$sid = $session->NeedQueryString() && !$this->RewriteURLs() ? $this->GetSID() : '';
		if( getArrayValue($params,'admin') == 1 ) $sid = $this->GetSID();

		$ret = '';
		if ($env_var) {
			$ret = ENV_VAR_NAME.'=';
		}
		$ret .=	constOn('INPORTAL_ENV') ? $sid.'-'.$t : $sid.':'.$t;

		$encode = false;
		if (isset($params['__URLENCODE__'])) {
			$encode = $params['__URLENCODE__'];
			unset($params['__URLENCODE__']);
		}

		$pass = trim( str_replace('all', $this->GetVar('passed'), $pass), ',');

		if(strlen($pass) > 0)
		{
			$pass_info = array_unique( explode(',',$pass) ); // array( prefix[.special], prefix[.special] ...
			foreach($pass_info as $pass_element)
			{
				$ret .= ':'.$this->BuildModuleEnv($pass_element, $params, $pass_events);
			}
		}
		unset($params['pass']);
		unset($params['opener']);
		unset($params['m_event']);

		if ($this->GetVar('admin') && !isset($params['admin'])) {
			$params['admin'] = 1;
		}

		if( getArrayValue($params,'escape') )
		{
			$ret = addslashes($ret);
			unset($params['escape']);
		}

		$join_string = $encode ? '&' : '&amp;';
		foreach ($params as $param => $value)
		{
			$ret .= $join_string.$param.'='.$value;
		}

		if ($encode) $ret = str_replace('\\', '%5C', $ret);
		return $ret;
	}

	function BaseURL($prefix='', $ssl=null)
	{
		if ($ssl === null) {
			return PROTOCOL.SERVER_NAME.(defined('PORT')?':'.PORT : '').rtrim(BASE_PATH, '/').$prefix.'/';
		}
		else {
			if ($ssl) {
				return rtrim( $this->ConfigValue('SSL_URL'), '/').$prefix.'/';
			}
			else {
				return 'http://'.DOMAIN.(defined('PORT')?':'.PORT : '').rtrim( $this->ConfigValue('Site_Path'), '/').$prefix.'/';
			}
		}
	}

	function Redirect($t='', $params=null, $prefix='', $index_file=null)
	{
		if ($t == '' || $t === true) $t = $this->GetVar('t');

		// pass prefixes and special from previous url
		$js_redirect = getArrayValue($params, 'js_redirect');
		if( isset($params['js_redirect']) ) unset($params['js_redirect']);

		if (!isset($params['pass'])) $params['pass'] = 'all';
		$params['__URLENCODE__'] = 1;
		$location = $this->HREF($t, $prefix, $params, $index_file);
		$a_location = $location;
		$location = "Location: $location";
		//echo " location : $location <br>";


		if( $this->isDebugMode() && dbg_ConstOn('DBG_REDIRECT') )
		{
			/*if( function_exists('apache_response_headers') )
			{
				$this->Debugger->appendHTML('Apache Responce Headers');
				$this->Debugger->dumpVars( apache_response_headers() );

				$this->Debugger->appendHTML('Apache Request Headers');
				$this->Debugger->dumpVars( apache_request_headers() );
			}*/
			$this->Debugger->appendTrace();
			echo "<b>Debug output above!!!</b> Proceed to redirect: <a href=\"$a_location\">$a_location</a><br>";
		}
		else
		{
			if($js_redirect)
			{
				$this->SetVar('t', 'redirect');
				$this->SetVar('redirect_to_js', addslashes($a_location) );
				$this->SetVar('redirect_to', $a_location);
				return true;
			}
			else
			{
				if(headers_sent() != '')
				{
					echo '<script language="javascript" type="text/javascript">window.location.href = \''.$a_location.'\';</script>';
				}
				else
				{
					header("$location");
				}
			}
		}
		
		$session =& $this->recallObject('Session');
		$session->SaveData();
		$this->SaveBlocksCache();
		exit;
	}

	function Phrase($label)
	{
		return $this->Phrases->GetPhrase($label);
	}

	/**
	 * Replace language tags in exclamation marks found in text
	 *
	 * @param string $text
	 * @param bool $force_escape force escaping, not escaping of resulting string
	 * @return string
	 * @access public
	 */
	function ReplaceLanguageTags($text, $force_escape=null)
	{
		// !!!!!!!!
//		if( !is_object($this->Phrases) ) $this->Debugger->appendTrace();
		return $this->Phrases->ReplaceLanguageTags($text,$force_escape);
	}

	/**
	 * Checks if user is logged in, and creates
	 * user object if so. User object can be recalled
	 * later using "u" prefix. Also you may
	 * get user id by getting "u_id" variable.
	 *
	 * @access private
	 */
	function ValidateLogin()
	{
		$session =& $this->recallObject('Session');
		$user_id = $session->GetField('PortalUserId');
		if (!$user_id) $user_id = -2;
		$this->SetVar('u_id', $user_id);
		$this->StoreVar('user_id', $user_id);
	}

	/**
	 * Returns configuration option value by name
	 *
	 * @param string $name
	 * @return string
	 */
	function ConfigValue($name)
	{
		return getArrayValue($this->ConfigHash, $name);
//		return $this->DB->GetOne('SELECT VariableValue FROM '.TABLE_PREFIX.'ConfigurationValues WHERE VariableName = '.$this->DB->qstr($name) );
	}

	/**
	 * Allows to process any type of event
	 *
	 * @param kEvent $event
	 * @access public
	 * @author Alex
	 */
	function HandleEvent(&$event, $params=null, $specificParams=null)
	{
		if ( isset($params) ) {
			$event = new kEvent( $params, $specificParams );
		}
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->HandleEvent($event);
	}

	/**
	 * Registers new class in the factory
	 *
	 * @param string $real_class Real name of class as in class declaration
	 * @param string $file Filename in what $real_class is declared
	 * @param string $pseudo_class Name under this class object will be accessed using getObject method
	 * @param Array $dependecies List of classes required for this class functioning
	 * @access public
	 * @author Alex
	 */
	function registerClass($real_class, $file, $pseudo_class = null, $dependecies = Array() )
	{
		$this->Factory->registerClass($real_class, $file, $pseudo_class, $dependecies);
	}

	/**
	 * Add $class_name to required classes list for $depended_class class.
	 * All required class files are included before $depended_class file is included
	 *
	 * @param string $depended_class
	 * @param string $class_name
	 * @author Alex
	 */
	function registerDependency($depended_class, $class_name)
	{
		$this->Factory->registerDependency($depended_class, $class_name);
	}

	/**
	 * Registers Hook from subprefix event to master prefix event
	 *
	 * @param string $hookto_prefix
	 * @param string $hookto_special
	 * @param string $hookto_event
	 * @param string $mode
	 * @param string $do_prefix
	 * @param string $do_special
	 * @param string $do_event
	 * @param string $conditional
	 * @access public
	 * @todo take care of a lot parameters passed
	 * @author Kostja
	 */
	function registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional)
	{
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->registerHook($hookto_prefix, $hookto_special, $hookto_event, $mode, $do_prefix, $do_special, $do_event, $conditional);
	}

	/**
	 * Allows one TagProcessor tag act as other TagProcessor tag
	 *
	 * @param Array $tag_info
	 * @author Kostja
	 */
	function registerAggregateTag($tag_info)
	{
		$aggregator =& $this->recallObject('TagsAggregator', 'kArray');
		$aggregator->SetArrayValue($tag_info['AggregateTo'], $tag_info['AggregatedTagName'], Array($tag_info['LocalPrefix'], $tag_info['LocalTagName'], getArrayValue($tag_info, 'LocalSpecial')));
	}

	/**
	 * Returns object using params specified,
	 * creates it if is required
	 *
	 * @param string $name
	 * @param string $pseudo_class
	 * @param Array $event_params
	 * @return Object
	 * @author Alex
	 */
	function &recallObject($name,$pseudo_class=null,$event_params=Array())
	{
		$func_args = func_get_args();
		$result =& ref_call_user_func_array( Array(&$this->Factory, 'getObject'), $func_args );
		return $result;
	}

	/**
	 * Checks if object with prefix passes was already created in factory
	 *
	 * @param string $name object presudo_class, prefix
	 * @return bool
	 * @author Kostja
	 */
	function hasObject($name)
	{
		return isset($this->Factory->Storage[$name]);
	}

	/**
	 * Removes object from storage by given name
	 *
	 * @param string $name Object's name in the Storage
	 * @author Kostja
	 */
	function removeObject($name)
	{
		$this->Factory->DestroyObject($name);
	}

	/**
	 * Get's real class name for pseudo class,
	 * includes class file and creates class
	 * instance
	 *
	 * @param string $pseudo_class
	 * @return Object
	 * @access public
	 * @author Alex
	 */
	function &makeClass($pseudo_class)
	{
		$func_args = func_get_args();
		$result =& ref_call_user_func_array( Array(&$this->Factory, 'makeClass'), $func_args);

		return $result;
	}

	/**
	 * Checks if application is in debug mode
	 *
	 * @param bool $check_debugger check if kApplication debugger is initialized too, not only for defined DEBUG_MODE constant
	 * @return bool
	 * @author Alex
	 * @access public
	 */
	function isDebugMode($check_debugger = true)
	{
		$debug_mode = constOn('DEBUG_MODE');
		if($check_debugger)
		{
			$debug_mode = $debug_mode && is_object($this->Debugger);
		}
		return $debug_mode;
	}

	/**
	 * Checks if it is admin
	 *
	 * @return bool
	 * @author Alex
	 */
	function IsAdmin()
	{
		return constOn('ADMIN');
	}

	/**
	 * Apply url rewriting used by mod_rewrite or not
	 *
	 * @return bool
	 */
	function RewriteURLs()
	{
		return constOn('MOD_REWRITE');
	}

	/**
	 * Reads unit (specified by $prefix)
	 * option specified by $option
	 *
	 * @param string $prefix
	 * @param string $option
	 * @return string
	 * @access public
	 * @author Alex
	 */
	function getUnitOption($prefix,$option)
	{
		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $unit_config_reader->getUnitOption($prefix,$option);
	}

	/**
	 * Set's new unit option value
	 *
	 * @param string $prefix
	 * @param string $name
	 * @param string $value
	 * @author Alex
	 * @access public
	 */
	function setUnitOption($prefix,$option,$value)
	{
		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $unit_config_reader->setUnitOption($prefix,$option,$value);
	}

	/**
	 * Read all unit with $prefix options
	 *
	 * @param string $prefix
	 * @return Array
	 * @access public
	 * @author Alex
	 */
	function getUnitOptions($prefix)
	{
		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $unit_config_reader->getUnitOptions($prefix);
	}

	/**
	 * Returns true if config exists and is allowed for reading
	 *
	 * @param string $prefix
	 * @return bool
	 */
	function prefixRegistred($prefix)
	{
		$unit_config_reader =& $this->recallObject('kUnitConfigReader');
		return $unit_config_reader->prefixRegistred($prefix);
	}

	/**
	 * Splits any mixing of prefix and
	 * special into correct ones
	 *
	 * @param string $prefix_special
	 * @return Array
	 * @access public
	 * @author Alex
	 */
	function processPrefix($prefix_special)
	{
		return $this->Factory->processPrefix($prefix_special);
	}

	/**
	 * Set's new event for $prefix_special
	 * passed
	 *
	 * @param string $prefix_special
	 * @param string $event_name
	 * @access public
	 */
	function setEvent($prefix_special,$event_name)
	{
		$event_manager =& $this->recallObject('EventManager');
		$event_manager->setEvent($prefix_special,$event_name);
	}


	/**
	 * SQL Error Handler
	 *
	 * @param int $code
	 * @param string $msg
	 * @param string $sql
	 * @return bool
	 * @access private
	 * @author Alex
	 */
	function handleSQLError($code, $msg, $sql)
	{
		if ( isset($this->Debugger) )
		{
			$errorLevel = constOn('DBG_SQL_FAILURE') ? E_USER_ERROR : E_USER_WARNING;
			$this->Debugger->dumpVars($_REQUEST);
			$this->Debugger->appendTrace();

			$error_msg = '<span class="debug_error">'.$msg.' ('.$code.')</span><br><a href="javascript:SetClipboard(\''.htmlspecialchars($sql).'\');"><b>SQL</b></a>: '.$this->Debugger->formatSQL($sql);
			$long_id = $this->Debugger->mapLongError($error_msg);
			trigger_error( substr($msg.' ('.$code.') ['.$sql.']',0,1000).' #'.$long_id, $errorLevel);
			return true;
		}
		else
		{
			//$errorLevel = constOn('IS_INSTALL') ? E_USER_WARNING : E_USER_ERROR;
			$errorLevel = E_USER_WARNING;
			trigger_error('<b>SQL Error</b> in sql: '.$sql.', code <b>'.$code.'</b> ('.$msg.')', $errorLevel);
			/*echo '<b>xProcessing SQL</b>: '.$sql.'<br>';
			echo '<b>Error ('.$code.'):</b> '.$msg.'<br>';*/
			return $errorLevel == E_USER_ERROR ? false : true;
		}
	}

	/**
	 * Default error handler
	 *
	 * @param int $errno
	 * @param string $errstr
	 * @param string $errfile
	 * @param int $errline
	 * @param Array $errcontext
	 */
	function handleError($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
	{
		if( constOn('SILENT_LOG') )
		{
			$fp = fopen(FULL_PATH.'/silent_log.txt','a');
			$time = adodb_date('d/m/Y H:i:s');
			fwrite($fp, '['.$time.'] #'.$errno.': '.strip_tags($errstr).' in ['.$errfile.'] on line '.$errline."\n");
			fclose($fp);
		}

		if( !$this->errorHandlers ) return true;

		$i = 0; // while (not foreach) because it is array of references in some cases
		$eh_count = count($this->errorHandlers);
		while($i < $eh_count)
		{
			if( is_array($this->errorHandlers[$i]) )
			{
				$object =& $this->errorHandlers[$i][0];
				$method = $this->errorHandlers[$i][1];
				$object->$method($errno, $errstr, $errfile, $errline, $errcontext);
			}
			else
			{
				$function = $this->errorHandlers[$i];
				$function($errno, $errstr, $errfile, $errline, $errcontext);
			}
			$i++;
		}
	}

	/**
	 * Returns & blocks next ResourceId available in system
	 *
	 * @return int
	 * @access public
	 * @author Alex
	 */
	function NextResourceId()
	{
		$table_name = TABLE_PREFIX.'IdGenerator';

		$this->DB->Query('LOCK TABLES '.$table_name.' WRITE');
		$this->DB->Query('UPDATE '.$table_name.' SET lastid = lastid + 1');
		$id = $this->DB->GetOne('SELECT lastid FROM '.$table_name);
		if($id === false)
		{
			$this->DB->Query('INSERT INTO '.$table_name.' (lastid) VALUES (2)');
			$id = 2;
		}
		$this->DB->Query('UNLOCK TABLES');
		return $id - 1;
	}

	/**
	 * Returns main prefix for subtable prefix passes
	 *
	 * @param string $current_prefix
	 * @return string
	 * @access public
	 * @author Kostja
	 */
	function GetTopmostPrefix($current_prefix)
	{
		while ( $parent_prefix = $this->getUnitOption($current_prefix, 'ParentPrefix') )
		{
			$current_prefix = $parent_prefix;
		}
		return $current_prefix;
	}

	function EmailEventAdmin($email_event_name, $to_user_id = -1, $send_params = false)
	{
		return $this->EmailEvent($email_event_name, 1, $to_user_id, $send_params);
	}

	function EmailEventUser($email_event_name, $to_user_id = -1, $send_params = false)
	{
		return $this->EmailEvent($email_event_name, 0, $to_user_id, $send_params);
	}

	function EmailEvent($email_event_name, $email_event_type, $to_user_id = -1, $send_params = false)
	{
		$event = new kEvent('emailevents:OnEmailEvent');
		$event->setEventParam('EmailEventName', $email_event_name);
		$event->setEventParam('EmailEventToUserId', $to_user_id);
		$event->setEventParam('EmailEventType', $email_event_type);
		if ($send_params){
			$event->setEventParam('DirectSendParams', $send_params);
		}
		$this->HandleEvent($event);

		return $event;
	}


	function LoggedIn()
	{
		$user =& $this->recallObject('u');
		return ($user->GetDBField('PortalUserId') > 0);
	}

	function CheckPermission($name, $cat_id = null)
	{
		if( !isset($cat_id) )
		{
			$cat_id = $this->GetVar('m_cat_id');
		}
		if( $cat_id == 0 )
		{
			$cat_hierarchy = Array(0);
		}
		else
		{
			$sql = 'SELECT ParentPath FROM '.$this->getUnitOption('c', 'TableName').' WHERE CategoryId = '.$cat_id;
			$cat_hierarchy = $this->DB->GetOne($sql);
			$cat_hierarchy = explode('|', $cat_hierarchy);
			array_shift($cat_hierarchy);
			array_pop($cat_hierarchy);
			$cat_hierarchy = array_reverse($cat_hierarchy);
			array_push($cat_hierarchy, 0);
		}

		$groups = $this->RecallVar('UserGroups');

		foreach($cat_hierarchy as $category_id)
		{
			$sql = 'SELECT PermissionValue FROM '.TABLE_PREFIX.'Permissions
					WHERE Permission = "'.$name.'"
					AND CatId = '.$category_id.'
					AND GroupId IN ('.$groups.')';
			$res = $this->DB->GetOne($sql);
			if($res !== false)
			{
				return $res;
			}
		}

		return 0;
	}

	/**
	 * Set's any field of current visit
	 *
	 * @param string $field
	 * @param mixed $value
	 */
	function setVisitField($field, $value)
	{
		$visit =& $this->recallObject('visits');
		$visit->SetDBField($field, $value);
		$visit->Update();
	}
	
	/**
	 * Allows to check if in-portal is installed
	 *
	 * @return bool
	 */
	function isInstalled()
	{
		return $this->InitDone && (count($this->ModuleInfo) > 0);
	}

}

?>