<?php
	ini_set('display_errors', 1);
	error_reporting(E_ALL);

	define('IS_INSTALL', 1);
	define('ADMIN', 1);
	define('FULL_PATH', realpath(dirname(__FILE__).'/..') );
	define('REL_PATH', '/core');

	/**
	 * Upgrade sqls are located using this mask
	 *
	 */
	define('UPGRADES_FILE', FULL_PATH.'/%sinstall/upgrades.%s');

	/**
	 * Format of version identificator in upgrade files
	 *
	 */
	define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+) =====');

//	print_pre($_POST);

	$install_engine = new kInstallator();
	$install_engine->Init();
	$install_engine->Run();
	$install_engine->Done();

	class kInstallator {

		/**
		 * Reference to kApplication class object
		 *
		 * @var kApplication
		 */
		var $Application = null;

		/**
		 * Connection to database
		 *
		 * @var kDBConnection
		 */
		var $Conn = null;

		/**
		 * Path to config.php
		 *
		 * @var string
		 */
		var $INIFile = '';

		/**
		 * XML file containing steps information
		 *
		 * @var string
		 */
		var $StepDBFile = '';

		/**
		 * Parsed data from config.php
		 *
		 * @var Array
		 */
		var $systemConfig = Array ();
		/**
		 * Step name, that currently being processed
		 *
		 * @var string
		 */
		var $currentStep = '';

		/**
		 * Steps list (preset) to use for current installation
		 *
		 * @var string
		 */
		var $stepsPreset = '';

		/**
		 * Installtion steps to be done
		 *
		 * @var Array
		 */
		var $steps = 	Array(
								'fresh_install'		=>	Array ('check_paths', 'db_config', 'root_password', 'choose_modules', 'finish'),
								'already_installed'	=>	Array ('install_setup'),

								'upgrade'			=>	Array ('install_setup', 'upgrade_modules', /* ..., */ 'finish'),
								'db_reconfig'		=>	Array ('install_setup',/* ..., */ 'finish'),
								'fix_paths'			=>	Array ('install_setup',/* ..., */ 'finish'),
						);


		/**
		 * Steps, that doesn't required admin to be logged-in to proceed
		 *
		 * @var Array
		 */
		var $skipLoginSteps = Array ('root_password', 'choose_modules', 'finish', -1);

		/**
		 * Steps, on which kApplication should not be initialized, because of missing correct db table structure
		 *
		 * @var Array
		 */
		var $skipApplicationSteps = Array ('check_paths', 'db_config'/*, 'install_setup'*/); // remove install_setup when application will work separately from install

		/**
		 * Folders that should be writeable to continue installation
		 *
		 * @var Array
		 */
		var $writeableFolders = Array ('/system');

		/**
		 * Contains last error message text
		 *
		 * @var string
		 */
		var $errorMessage = '';

		/**
		 * Base path for includes in templates
		 *
		 * @var string
		 */
		var $baseURL = '';

		function Init()
		{
			$this->INIFile = FULL_PATH.'/config.php';
			$this->StepDBFile = FULL_PATH.'/'.REL_PATH.'/install/steps_db.xml';

			$base_path = rtrim(preg_replace('/'.preg_quote(rtrim(REL_PATH, '/'), '/').'$/', '', str_replace('\\', '/', dirname($_SERVER['PHP_SELF']))), '/');
			$this->baseURL = 'http://'.$_SERVER['HTTP_HOST'].$base_path.'/core/install/';

			set_error_handler( Array(&$this, 'ErrorHandler') );

			if (file_exists($this->INIFile)) {
				// if config.php found, then check his write permission too
				$this->writeableFolders[] = '/config.php';
			}
			else {
				$this->writeableFolders[] = '/';
			}

			$this->systemConfig = $this->ParseConfig(true);
			$this->systemConfig['Misc']['WriteablePath'] = '/system'; // for development purposes

			$this->currentStep = $this->GetVar('step');

			// can't check login on steps where no application present anyways :)
			$this->skipLoginSteps = array_unique(array_merge($this->skipLoginSteps, $this->skipApplicationSteps));

			$this->SelectPreset();

			if (!$this->currentStep) {
				$this->SetFirstStep(); // sets first step of current preset
			}

			$this->InitStep();
		}

		function SetFirstStep()
		{
			reset($this->steps[$this->stepsPreset]);
			$this->currentStep = current($this->steps[$this->stepsPreset]);
		}

		/**
		 * Selects preset to proceed based on various criteria
		 *
		 */
		function SelectPreset()
		{
			$preset = $this->GetVar('preset');
			if (file_exists($this->INIFile) && $this->systemConfig) {
				// only at installation first step
				$status = $this->CheckDatabase(false);
				if ($status && $this->AlreadyInstalled()) {
					// if already installed, then all future actions need login to work
					$this->skipLoginSteps = Array (-1);
					if (!$preset) {
						$preset = 'already_installed';
						$this->currentStep = '';
					}
				}
			}
			if ($preset === false) {
				$preset = 'fresh_install'; // default preset
			}

			$this->stepsPreset = $preset;
		}

		function GetVar($name)
		{
			return isset($_REQUEST[$name]) ? $_REQUEST[$name] : false;
		}

		/**
		 * Performs needed intialization of data, that step requires
		 *
		 */
		function InitStep()
		{
			$require_login = !in_array($this->currentStep, $this->skipLoginSteps);
			$this->InitApplication($require_login);

			if ($require_login) {
				// step require login to proceed
				if (!$this->Application->LoggedIn()) {
					$this->stepsPreset = 'already_installed';
					$this->SetFirstStep();
				}
			}

			switch ($this->currentStep) {
				case 'check_paths':
					foreach ($this->writeableFolders as $folder_path) {
						$file_path = FULL_PATH.$folder_path;
						if (!is_writable($file_path)) {
							$this->errorMessage = 'Install cannot write to specified folder in the root directory of your installation';
							break;
						}
					}
					break;

				case 'db_config':
					$section_name = 'Database';
					$fields = Array ('DBType', 'DBHost', 'DBName', 'DBUser', 'DBUserPassword', 'TablePrefix');

					if (!isset($this->systemConfig[$section_name])) {
						$this->systemConfig[$section_name] = Array ();
					}

					// set fields
					foreach ($fields as $field_name) {
						$submit_value = $this->GetVar($field_name);

						if ($submit_value !== false) {
							$this->systemConfig[$section_name][$field_name] = $submit_value;
						}
						elseif (!isset($this->systemConfig[$section_name][$field_name])) {
							$this->systemConfig[$section_name][$field_name] = '';
						}
					}
			        break;

				case 'choose_modules':
					// if no modules found, then proceed to next step
					$modules = $this->ScanModules();
					if (!$modules) {
						$this->currentStep = $this->GetNextStep();
					}
					break;

				case 'upgrade_modules':
					// get installed modules from db and compare their versions to upgrade script
					$modules = $this->GetUpgradableModules();
					if (!$modules) {
						$this->currentStep = $this->GetNextStep();
					}
					break;

				case 'install_setup':
					$next_preset = $this->Application->GetVar('next_preset');
					if ($next_preset !== false && $this->Application->GetVar('login') == 'root') {
						// option was choosen, then verify password & login user
						$login_event = new kEvent('u.current:OnLogin');
						$this->Application->HandleEvent($login_event);

						if ($login_event->status == erSUCCESS) {
							// login succeeded

							if (!isset($this->steps[$next_preset])) {
								$this->errorMessage = 'Preset "'.$next_preset.'" not yet implemented';
							}
							else {
								$this->stepsPreset = $next_preset;
							}
						}
						else {
							// login failed
							$user =& $this->Application->recallObject('u.current');
							/* @var $user UsersItem */

							$this->errorMessage = $user->GetErrorMsg('ValidateLogin').'. If you don\'t know your username or password, contact Intechnic Support';
						}
					}
					else {
						// if preset was not choosen, then raise error
						$this->errorMessage = 'Please select action to perform';
					}
					break;
			}

			$this->PerformValidation(); // returns validation status (just in case)
		}

		/**
		 * Validates data entered by user
		 *
		 * @return bool
		 */
		function PerformValidation()
		{
			if ($this->GetVar('step') != $this->currentStep) {
				// just redirect from previous step, don't validate
				return true;
			}

			$status = true;

			switch ($this->currentStep) {
				case 'db_config':
					// 1. check if required fields are filled
					$section_name = 'Database';
					$required_fields = Array ('DBType', 'DBHost', 'DBName', 'DBUser');
					foreach ($required_fields as $required_field) {
						if (!$this->systemConfig[$section_name][$required_field]) {
							$status = false;
							$this->errorMessage = 'Please fill all required fields';
							break;
						}
					}
					if (!$status) break;

					// 2. check permissions, that use have in this database
					$status = $this->CheckDatabase();
					break;

				case 'root_password':
					// check, that password & verify password match
					$password = $this->Application->GetVar('root_password');
					$password_verify = $this->Application->GetVar('root_password_verify');

					if ($password != $password_verify) {
						$this->errorMessage = 'Passwords does not match';
					}
					elseif (strlen($password) < 4) {
						$this->errorMessage = 'Root Password must be at least 4 characters';
					}

					$status = $this->errorMessage == '';
					break;
			}

			return $status;
		}

		/**
		 * Perform installation step actions
		 *
		 */
		function Run()
		{
			if ($this->errorMessage) {
				// was error during data validation stage
				return ;
			}

			switch ($this->currentStep) {
				case 'db_config':
					// store db configuration
					$this->SaveConfig();

					// import base data into database
					$this->RunSQL('/core/install/install_schema.sql');
					$this->RunSQL('/core/install/install_data.sql');

					// set module "Core" version after install (based on upgrade scripts)
					$this->SetModuleVersion('Core');
					break;

				case 'root_password':
					// update root password in database
					$password = md5( md5($this->Application->GetVar('root_password')) . 'b38');
					$this->SetConfigValue('RootPass', $password);

					// set Site_Path (for SSL & old in-portal code)
					$this->SetConfigValue('Site_Path', BASE_PATH.'/');

					// import base language for core (english)
					$this->ImportLanguage('/core/install/english');

					// set imported language as primary
					$lang =& $this->Application->recallObject('lang.-item', null, Array('skip_autoload' => true));
					/* @var $lang LanguagesItem */

					$lang->Load(1); // fresh install => ID=1
					$lang->setPrimary();
					break;

				case 'choose_modules':
					// run module install scripts
					$modules = $this->Application->GetVar('modules');
					if ($modules) {
						foreach ($modules as $module) {
							$install_file = MODULES_PATH.'/'.$module.'/install.php';
							if (file_exists($install_file)) {
								include_once($install_file);

								// set module version after install (based on upgrade scripts)
								$this->SetModuleVersion($module);
							}
						}
					}
					// scan themes
					$this->Application->HandleEvent($themes_event, 'adm:OnRebuildThemes');

					$this->Conn->Query('UPDATE '.TABLE_PREFIX.'Theme SET Enabled=1, PrimaryTheme =1 LIMIT 1');

					// update categories cache
					$updater =& $this->Application->recallObject('kPermCacheUpdater');
					/* @var $updater kPermCacheUpdater */

					$updater->OneStepRun();
					break;

				case 'upgrade_modules':
					// get installed modules from db and compare their versions to upgrade script
					$modules = $this->Application->GetVar('modules');
					if ($modules) {
						$upgrade_data = $this->GetUpgradableModules();

						foreach ($modules as $module_name) {
							$module_info = $upgrade_data[$module_name];
							$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'sql');

							$sqls = file_get_contents($upgrades_file);
							$version_mark = preg_replace('/(\(.*?\))/', $module_info['FromVersion'], VERSION_MARK);

							// get only sqls from next (relative to current) version to end of file
							$start_pos = strpos($sqls, $version_mark);
							$sqls = substr($sqls, $start_pos);
							
							preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
							
							$this->RunUpgrades($module_info['Path'], $regs[1], 'before');
							$this->RunSQLText($sqls);
							$this->RunUpgrades($module_info['Path'], $regs[1], 'after');
							
							// after upgrade sqls are executed update version
							$this->SetModuleVersion($module_name, $module_info['ToVersion']);
						}
					}
					else {
						$this->errorMessage = 'Please select module(-s) to upgrade';
					}
					break;

				case 'finish':
					// delete cache
					$sql = 'DELETE FROM '.TABLE_PREFIX.'Cache
							WHERE VarName IN ("config_files","configs_parsed","sections_parsed")';
					$this->Conn->Query($sql);

					// set installation finished mark
					if ($this->Application->ConfigValue('InstallFinished') === false) {
						$fields_hash = Array (
							'VariableName'	=>	'InstallFinished',
							'VariableValue'	=>	1,
						);
						$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'ConfigurationValues');
					}
					break;
			}

			if ($this->errorMessage) {
				// was error during run stage
				return ;
			}

			$this->currentStep = $this->GetNextStep();
			$this->InitStep(); // init next step (that will be shown now)

			$this->InitApplication();

			if ($this->currentStep == -1) {
				// step after last step -> redirect to admin
				$this->Application->Redirect('index', null, '', 'index.php');
			}
		}

		/**
		 * Run upgrade PHP scripts for module with specified path
		 *
		 * @param string $module_path
		 * @param Array $versions
		 * @param string $mode upgrade mode = {before,after}
		 */
		function RunUpgrades($module_path, $versions, $mode)
		{
			static $upgrade_classes = Array ();
			
			$upgrades_file = sprintf(UPGRADES_FILE, $module_path, 'php');
			if (!file_exists($upgrades_file) || !$versions) {
				return ;
			}
			
			if (!isset($upgrade_classes[$module_path])) {
				// save class name, because 2nd time
				// (in after call $upgrade_class variable will not be present)
				include_once $upgrades_file;
				$upgrade_classes[$module_path] = $upgrade_class;
			}
			
			$upgrade_object = new $upgrade_classes[$module_path]();
			
			foreach ($versions as $version) {
				$upgrade_method = 'Upgrade_'.str_replace('.', '_', $version);
				if (method_exists($upgrade_object, $upgrade_method)) {
					$upgrade_object->$upgrade_method($mode);
				}
			}
		}
		
		/**
		 * Sets module version to passed
		 *
		 * @param string $module_name
		 * @param string $version
		 */
		function SetModuleVersion($module_name, $version = false)
		{
			if ($version === false) {
				$version = $this->GetMaxModuleVersion($module_name);
			}

			$table_prefix = $this->systemConfig['Database']['TablePrefix'];

			$sql = 'UPDATE '.$table_prefix.'Modules
					SET Version = "'.$version.'"
					WHERE Name = "'.$module_name.'"';
			$this->Conn->Query($sql);
		}


		/**
		 * Sets new configuration variable value
		 *
		 * @param string $name
		 * @param mixed $value
		 */
		function SetConfigValue($name, $value)
		{
			$sql = 'UPDATE '.TABLE_PREFIX.'ConfigurationValues
		      		SET VariableValue = '.$this->Conn->qstr($value).'
		      		WHERE VariableName = '.$this->Conn->qstr($name);
			$this->Conn->Query($sql);
		}

		/**
		 * Initialize kApplication
		 *
		 * @param bool $force initialize in any case
		 */
		function InitApplication($force = false)
		{
			if (($force || !in_array($this->currentStep, $this->skipApplicationSteps)) && !isset($this->Application)) {
				// step is allowed for application usage & it was not initialized in previous step
				global $start, $debugger, $dbg_options;
				include_once(FULL_PATH.'/core/kernel/startup.php');

				$this->Application =& kApplication::Instance();
				$this->Application->Init();
				$this->Conn =& $this->Application->GetADODBConnection();
			}
		}

		/**
		 * Show next step screen
		 *
		 */
		function Done($error_message = null)
		{
			if (isset($error_message)) {
				$this->errorMessage = $error_message;
			}

			include_once (FULL_PATH.'/'.REL_PATH.'/install/incs/install.tpl');

			if (isset($this->Application)) {
				$this->Application->Done();

//				echo 'SID: ['.$this->Application->GetSID().']<br />';
			}

			exit;
		}

		function GetMaxModuleVersion($module_name)
		{
			$upgrades_file = sprintf(UPGRADES_FILE, strtolower($module_name).'/', 'sql');
			if (!file_exists($upgrades_file)) {
				// no upgrade file
				return '4.0.1';
			}

			$sqls = file_get_contents($upgrades_file);
			$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
			if (!$versions_found) {
				// upgrades file doesn't contain version definitions
				return '4.0.1';
			}

			return end($regs[1]);
		}

		function ConnectToDatabase()
		{
			include_once FULL_PATH.'/core/kernel/db/db_connection.php';

			if (!isset($this->systemConfig['Database']['DBType']) ||
					!isset($this->systemConfig['Database']['DBUser']) ||
					!isset($this->systemConfig['Database']['DBName'])
			   ) {
				return false;
			}


			$this->Conn = new kDBConnection($this->systemConfig['Database']['DBType'], Array(&$this, 'DBErrorHandler'));
			$this->Conn->Connect($this->systemConfig['Database']['DBHost'], $this->systemConfig['Database']['DBUser'], $this->systemConfig['Database']['DBUserPassword'], $this->systemConfig['Database']['DBName']);
			return $this->Conn->errorCode == 0;
		}

		/**
		 * Checks if core is already installed
		 *
		 * @return bool
		 */
		function AlreadyInstalled()
		{
			$table_prefix = $this->systemConfig['Database']['TablePrefix'];
			return $this->TableExists('ConfigurationValues') && $this->Conn->GetOne('SELECT VariableValue FROM '.$table_prefix.'ConfigurationValues WHERE VariableName = \'InstallFinished\'');
		}

		function CheckDatabase($check_installed = true)
		{
			// perform various check type to database specified
			// 1. user is allowed to connect to database
			// 2. user has all types of permissions in database


			if (strlen($this->systemConfig['Database']['TablePrefix']) > 7) {
				$this->errorMessage = 'Table prefix should not be longer than 7 characters';
				return false;
			}

			// connect to database
			$status = $this->ConnectToDatabase();
			if ($status) {
				// if connected, then check if all sql statements work
				$sql_tests[] = 'DROP TABLE IF EXISTS test_table';
				$sql_tests[] = 'CREATE TABLE test_table(test_col mediumint(6))';
				$sql_tests[] = 'LOCK TABLES test_table WRITE';
				$sql_tests[] = 'INSERT INTO test_table(test_col) VALUES (5)';
				$sql_tests[] = 'UPDATE test_table SET test_col = 12';
				$sql_tests[] = 'UNLOCK TABLES';
				$sql_tests[] = 'ALTER TABLE test_table ADD COLUMN new_col varchar(10)';
				$sql_tests[] = 'SELECT * FROM test_table';
				$sql_tests[] = 'DELETE FROM test_table';
				$sql_tests[] = 'DROP TABLE IF EXISTS test_table';

				foreach ($sql_tests as $sql_test) {
					$this->Conn->Query($sql_test);
					if ($this->Conn->getErrorCode() != 0) {
						$status = false;
						break;
					}
				}

				if ($status) {
					// if statements work & connection made, then check table existance
					if ($check_installed && $this->AlreadyInstalled()) {
						$this->errorMessage = 'An In-Portal Database already exists at this location';
						return false;
					}
				}
				else {
					// user has insufficient permissions in database specified
					$db_error = 'Permission Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg();
					return false;
				}
			}
			else {
				// was error while connecting
				if (!$this->Conn) return false;
				$this->errorMessage = 'Connection Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg();
				return false;
			}

			return true;
		}

		/**
		 * Checks if all passed tables exists
		 *
		 * @param string $tables comma separated tables list
		 * @return bool
		 */
		function TableExists($tables)
		{
			$prefix = $this->systemConfig['Database']['TablePrefix'];

			$all_found = true;
			$tables = explode(',', $tables);
			foreach ($tables as $table_name) {
				$sql = 'SHOW TABLES LIKE "'.$prefix.$table_name.'"';
				if (count($this->Conn->Query($sql)) == 0) {
					$all_found = false;
					break;
				}
			}

			return $all_found;
		}

		/**
		 * Runs SQLs from file
		 *
		 * @param string $filename
		 * @param mixed $replace_from
		 * @param mixed $replace_to
		 */
		function RunSQL($filename, $replace_from = null, $replace_to = null)
		{
			if (!file_exists(FULL_PATH.$filename)) {
				return ;
			}

			$sqls = file_get_contents(FULL_PATH.$filename);
			$this->RunSQLText($sqls, $replace_from, $replace_to);
		}

		/**
		 * Runs SQLs from string
		 *
		 * @param string $sqls
		 * @param mixed $replace_from
		 * @param mixed $replace_to
		 */
		function RunSQLText(&$sqls, $replace_from = null, $replace_to = null)
		{
			$table_prefix = $this->systemConfig['Database']['TablePrefix'];

			// add prefix to all tables
			if (strlen($table_prefix) > 0) {
				$replacements = Array ('CREATE TABLE ', 'INSERT INTO ', 'UPDATE ', 'ALTER TABLE ');
				foreach ($replacements as $replacement) {
					$sqls = str_replace($replacement, $replacement.$table_prefix, $sqls);
				}
				$sqls = str_replace('DROP TABLE ', 'DROP TABLE IF EXISTS '.$table_prefix, $sqls);
			}

			if (isset($replace_from) && isset($replace_to)) {
				// replace something additionally, e.g. module root category
				$sqls = str_replace($replace_from, $replace_to, $sqls);
			}

			$sqls = str_replace("\r\n", "\n", $sqls);  // convert to linux line endings
			$sqls = preg_replace("/#(.*?)\n/", '', $sqls); // remove all comments
			$sqls = explode(";\n", $sqls);

			foreach ($sqls as $sql) {
				$sql = trim($sql);
				if (!$sql) {
					continue; // usually last line
				}
				$this->Conn->Query($sql);
				if ($this->Conn->getErrorCode() != 0) {
    				$this->errorMessage = 'Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg().'<br /><br />Database Query:<pre>'.htmlspecialchars($sql).'</pre>';
    				$this->Done();
    				break;
    			}
			}
		}

		function ImportLanguage($lang_file)
		{
			$lang_file = FULL_PATH.$lang_file.'.lang';
			if (!file_exists($lang_file)) {
				return ;
			}

			$lang_xml =& $this->Application->recallObjectP('LangXML', null, Array(), false); // false - don't use temp tables
			$lang_xml->Parse($lang_file, '|0|1|2|', '');
		}

		/**
		 * Returns modules list found in modules folder
		 *
		 * @return Array
		 */
		function ScanModules()
		{
			static $modules = null;

			if (!isset($modules)) {
				$modules = Array();
				$fh = opendir(MODULES_PATH);
				while (($sub_folder = readdir($fh))) {
					$folder_path = MODULES_PATH.'/'.$sub_folder;
					if ($sub_folder != '.' && $sub_folder != '..' && is_dir($folder_path)) {
						if ($sub_folder == 'core') {
							// skip modules here
							continue;
						}
						// this is folder in MODULES_PATH directory
						if (file_exists($folder_path.'/install.php') && file_exists($folder_path.'/install/install_schema.sql')) {
							$modules[] = $sub_folder;
						}
					}
				}
			}

			return $modules;
		}

		/**
		 * Converts module version in format X.Y.Z to signle integer
		 *
		 * @param string $version
		 * @return int
		 */
		function ConvertModuleVersion($version)
		{
			$parts = explode('.', $version);

			$bin = '';
			foreach ($parts as $part) {
				$bin .= str_pad(decbin($part), 8, '0', STR_PAD_LEFT);
			}

			return bindec($bin);
		}

		/**
		 * Returns list of modules, that can be upgraded
		 *
		 */
		function GetUpgradableModules()
		{
			$ret = Array ();

			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
				$upgrades_file = sprintf(UPGRADES_FILE, $module_info['Path'], 'sql');
				if (!file_exists($upgrades_file)) {
					// no upgrade file
					continue;
				}

				$sqls = file_get_contents($upgrades_file);
				$versions_found = preg_match_all('/'.VERSION_MARK.'/s', $sqls, $regs);
				if (!$versions_found) {
					// upgrades file doesn't contain version definitions
					continue;
				}

				$to_version = end($regs[1]);
				$this_version = $this->ConvertModuleVersion($module_info['Version']);
				if ($this->ConvertModuleVersion($to_version) > $this_version) {
					// destination version is greather then current
					foreach ($regs[1] as $version) {
						if ($this->ConvertModuleVersion($version) > $this_version) {
							$from_version = $version;
							break;
						}
					}

					$version_info = Array (
						'FromVersion'	=>	$from_version,
						'ToVersion'		=>	$to_version,
					);

					$ret[$module_name] = array_merge_recursive2($module_info, $version_info);
				}
			}

			return $ret;
		}

		/**
		 * Returns content to show for current step
		 *
		 * @return string
		 */
		function GetStepBody()
		{
			$step_template = FULL_PATH.'/core/install/step_templates/'.$this->currentStep.'.tpl';
			if (file_exists($step_template)) {
				ob_start();
				include_once ($step_template);
				return ob_get_clean();
			}

			return '{step template "'.$this->currentStep.'" missing}';
		}

		/**
		 * Parses step information file, cache result for current step ONLY & return it
		 *
		 * @return Array
		 */
		function &_getStepInfo()
		{
			static $info = Array('help_title' => null, 'step_title' => null, 'help_body' => null, 'queried' => false);

			if (!$info['queried']) {
				$fdata = file_get_contents($this->StepDBFile);

				$parser = xml_parser_create();
				xml_parse_into_struct($parser, $fdata, $values, $index);
				xml_parser_free($parser);

				foreach ($index['STEP'] as $section_index) {
					$step_data =& $values[$section_index];

					if ($step_data['attributes']['NAME'] == $this->currentStep) {
						$info['step_title'] = $step_data['attributes']['TITLE'];
						if (isset($step_data['attributes']['HELP_TITLE'])) {
							$info['help_title'] = $step_data['attributes']['HELP_TITLE'];
						}
						else {
							// if help title not set, then use step title
							$info['help_title'] = $step_data['attributes']['TITLE'];
						}
						$info['help_body'] = trim($step_data['value']);
						break;
					}
				}

				$info['queried'] = true;
			}

			return $info;
		}

		/**
		 * Returns particular information abou current step
		 *
		 * @param string $info_type
		 * @return string
		 */
		function GetStepInfo($info_type)
		{
			$step_info =& $this->_getStepInfo();

			if (isset($step_info[$info_type])) {
				return $step_info[$info_type];
			}

			return '{step "'.$this->currentStep.'"; param "'.$info_type.'" missing}';
		}

		/**
		 * Returns passed steps titles
		 *
		 * @param Array $steps
		 * @return Array
		 * @see kInstaller:PrintSteps
		 */
		function _getStepTitles($steps)
		{
			$fdata = file_get_contents($this->StepDBFile);

			$parser = xml_parser_create();
			xml_parse_into_struct($parser, $fdata, $values, $index);
			xml_parser_free($parser);

			$ret = Array ();
			foreach ($index['STEP'] as $section_index) {
				$step_data =& $values[$section_index];
				if (in_array($step_data['attributes']['NAME'], $steps)) {
					$ret[ $step_data['attributes']['NAME'] ] = $step_data['attributes']['TITLE'];
				}
			}

			return $ret;
		}

		/**
		 * Returns current step number in active steps_preset.
		 * Value can't be cached, because same step can have different number in different presets
		 *
		 * @return int
		 */
		function GetStepNumber()
		{
			return array_search($this->currentStep, $this->steps[$this->stepsPreset]) + 1;
		}

		/**
		 * Returns step name to process next
		 *
		 * @return string
		 */
		function GetNextStep()
		{
			$next_index = $this->GetStepNumber();
			if ($next_index > count($this->steps[$this->stepsPreset]) - 1) {
				return -1;
			}

			return $this->steps[$this->stepsPreset][$next_index];
		}

		/**
		 * Returns step name, that was processed before this step
		 *
		 * @return string
		 */
		function GetPreviousStep()
		{
			$next_index = $this->GetStepNumber() - 1;
			if ($next_index < 0) {
				$next_index = 0;
			}

			return $this->steps[$this->stepsPreset][$next_index];
		}

		/**
		 * Prints all steps from active steps preset and highlights current step
		 *
		 * @param string $active_tpl
		 * @param string $passive_tpl
		 * @return string
		 */
		function PrintSteps($active_tpl, $passive_tpl)
		{
			$ret = '';
			$step_titles = $this->_getStepTitles($this->steps[$this->stepsPreset]);

			foreach ($this->steps[$this->stepsPreset] as $step_name) {
				$template = $step_name == $this->currentStep ? $active_tpl : $passive_tpl;
				$ret .= sprintf($template, $step_titles[$step_name]);
			}

			return $ret;
		}

		function ParseConfig($parse_section = false)
		{
			if (!file_exists($this->INIFile)) {
				return Array();
			}

			if( file_exists($this->INIFile) && !is_readable($this->INIFile) ) {
				die('Could Not Open Ini File');
			}

			$contents = file($this->INIFile);

			$retval = Array();
			$section = '';
			$ln = 1;
			$resave = false;
			foreach ($contents as $line) {
				if ($ln == 1 && $line != '<'.'?'.'php die() ?'.">\n") {
					$resave = true;
				}

				$ln++;
				$line = trim($line);
				$line = eregi_replace(';[.]*','',$line);
				if (strlen($line) > 0) {
					//echo $line . " - ";
					if(eregi('^[[a-z]+]$',str_replace(' ', '', $line))) {
						//echo 'section';
						$section = substr($line, 1, (strlen($line) - 2));
						if ($parse_section) {
							$retval[$section] = array();
						}
						continue;
					} elseif (eregi('=',$line)) {
						//echo 'main element';
						list ($key, $val) = explode(' = ', $line);
						if (!$parse_section) {
							$retval[trim($key)] = str_replace('"', '', $val);
						}
						else {
							$retval[$section][trim($key)] = str_replace('"', '', $val);
						}
					}
				}
			}

			if ($resave) {
				$fp = fopen($this->INIFile, 'w');
				reset($contents);
				fwrite($fp,'<'.'?'.'php die() ?'.">\n\n");
				foreach ($contents as $line) {
					fwrite($fp,"$line");
				}
				fclose($fp);
			}

			return $retval;
		}

		function SaveConfig()
		{
			$fp = fopen($this->INIFile, 'w');
			fwrite($fp,'<'.'?'.'php die() ?'.">\n\n");

			foreach ($this->systemConfig as $section_name => $section_data) {
				fwrite($fp, '['.$section_name."]\n");
				foreach ($section_data as $key => $value) {
					fwrite($fp, $key.' = "'.$value.'"'."\n");
				}
				fwrite($fp, "\n");
			}
			fclose($fp);
		}

		/**
		 * Installation error handler for sql errors
		 *
		 * @param int $code
		 * @param string $msg
		 * @param string $sql
		 * @return bool
		 * @access private
		 */
		function DBErrorHandler($code, $msg, $sql)
		{
			$this->errorMessage = 'Query: <br />'.htmlspecialchars($sql).'<br />execution result is error:<br />['.$code.'] '.$msg;
			return true;
		}

		/**
		 * Installation error handler
		 *
		 * @param int $errno
		 * @param string $errstr
		 * @param string $errfile
		 * @param int $errline
		 * @param Array $errcontext
		 */
		function ErrorHandler($errno, $errstr, $errfile = '', $errline = '', $errcontext = '')
		{
			if ($errno == E_USER_ERROR) {
				// only react on user fatal errors
				$this->Done($errstr);
			}
		}
	}

	/*function print_pre($s)
	{
		echo '<pre>', print_r($s, true). '</pre>';
	}*/


?>