<?php
/**
* @version	$Id: install.php 16435 2016-11-18 10:46:05Z alex $
* @package	In-Portal
* @copyright	Copyright (C) 1997 - 2009 Intechnic. All rights reserved.
* @license      GNU/GPL
* In-Portal is Open Source software.
* This means that this software may have been modified pursuant
* the GNU General Public License, and as distributed it includes
* or is derivative of works licensed under the GNU General Public License
* or other free or open source software licenses.
* See http://www.in-portal.org/license for copyright notices and details.
*/
	ini_set('display_errors', 1);
	error_reporting(E_ALL & ~E_STRICT);

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

	// run installator
	$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 IDBConnection
		 */
		var $Conn = null;

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

		/**
		 * Step name, that currently being processed
		 *
		 * @var string
		 */
		var $currentStep = '';

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

		/**
		 * Installation steps to be done
		 *
		 * @var Array
		 */
		var $steps = Array (
			'fresh_install'		=>	Array ('sys_requirements', 'check_paths', 'db_config', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'sys_config', 'select_theme', 'security', 'finish'),
			'clean_reinstall'	=>	Array ('install_setup', 'sys_requirements', 'check_paths', 'clean_db', 'db_config', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'sys_config', 'select_theme', 'security', 'finish'),
			'already_installed'	=>	Array ('check_paths', 'install_setup'),

			'upgrade'			=>	Array ('check_paths', 'install_setup', 'sys_config', 'upgrade_modules', 'skin_upgrade', 'security', 'finish'),
			'update_license'	=>	Array ('check_paths', 'install_setup', 'select_license', /*'download_license',*/ 'select_domain', 'security', 'finish'),
			'update_config'		=>	Array ('check_paths', 'install_setup', 'sys_config', 'security', 'finish'),
			'db_reconfig'		=>	Array ('check_paths', 'install_setup', 'db_reconfig', 'security', 'finish'),
			'sys_requirements'	=>	Array ('check_paths', 'install_setup', 'sys_requirements', 'security', 'finish')
		);

		/**
		 * Steps, that doesn't required admin to be logged-in to proceed
		 *
		 * @var Array
		 */
		var $skipLoginSteps = Array ('sys_requirements', 'check_paths', 'select_license', /*'download_license',*/ 'select_domain', 'root_password', 'choose_modules', 'post_config', 'select_theme', 'security', 'finish', -1);

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

		/**
		 * Folders that should be writeable to continue installation. $1 - main writeable folder from config.php ("/system" by default)
		 *
		 * @var Array
		 */
		var $writeableFolders = Array (
			'$1',
			'$1/.restricted',
			'$1/images',
			'$1/images/pending',
			'$1/images/emoticons', // for "In-Bulletin"
			'$1/user_files',
			'$1/cache',
		);

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

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

		/**
		 * Holds number of last executed query in the SQL
		 *
		 * @var int
		 */
		var $LastQueryNum = 0;

		/**
		 * Dependencies, that should be used in upgrade process
		 *
		 * @var Array
		 */
		var $upgradeDepencies = Array ();

		/**
		 * Log of upgrade - list of upgraded modules and their versions
		 *
		 * @var Array
		 */
		var $upgradeLog = Array ();

		/**
		 * Common tools required for installation process
		 *
		 * @var kInstallToolkit
		 */
		var $toolkit = null;

		function Init()
		{
			include_once(FULL_PATH . REL_PATH . '/kernel/kbase.php'); // required by kDBConnection class
			include_once(FULL_PATH . REL_PATH . '/kernel/utility/multibyte.php');	// emulating multi-byte php extension
			include_once(FULL_PATH . REL_PATH . '/kernel/utility/system_config.php');
			require_once(FULL_PATH . REL_PATH . '/install/install_toolkit.php'); // toolkit required for module installations to installator

			$this->toolkit = new kInstallToolkit();
			$this->toolkit->setInstallator($this);
			$this->StepDBFile = FULL_PATH.'/'.REL_PATH.'/install/steps_db.xml';
			$this->baseURL = 'http://' . $_SERVER['HTTP_HOST'] . $this->toolkit->systemConfig->get('WebsitePath', 'Misc') . '/core/install/';
			set_error_handler( Array(&$this, 'ErrorHandler') );

			if ( $this->toolkit->systemConfigFound() ) {
				// if config.php found, then check his write permission too
				$this->writeableFolders[] = $this->toolkit->systemConfig->get('WriteablePath', 'Misc') . '/config.php';
			}

			$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 ($this->toolkit->systemConfigFound()) {
				// 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 ('check_paths', -1);
					if (!$preset) {
						$preset = 'already_installed';
						$this->currentStep = '';
					}
				}
			}
			if ($preset === false) {
				$preset = 'fresh_install'; // default preset
			}

			$this->stepsPreset = $preset;
		}

		/**
		 * Returns variable from request
		 *
		 * @param string $name
		 * @param mixed $default
		 * @return string|bool
		 * @access private
		 */
		private function GetVar($name, $default = false)
		{
			if ( array_key_exists($name, $_COOKIE) ) {
				return $_COOKIE[$name];
			}

			if ( array_key_exists($name, $_POST) ) {
				return $_POST[$name];
			}

			return array_key_exists($name, $_GET) ? $_GET[$name] : $default;
		}

		/**
		 * Sets new value for request variable
		 *
		 * @param string $name
		 * @param mixed $value
		 * @return void
		 * @access private
		 */
		private function SetVar($name, $value)
		{
			$_POST[$name] = $value;
		}

		/**
		 * 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->currentStep = 'install_setup'; // manually set 2nd step, because 'check_paths' step doesn't contain login form
//					$this->SetFirstStep();
				}
			}

			switch ($this->currentStep) {
				case 'sys_requirements':
					$required_checks = Array (
						'php_version', 'composer', 'curl', 'simplexml', 'freetype', 'gd_version',
						'jpeg', 'mysql', 'json', 'date.timezone', 'output_buffering',
					);

					$check_results = $this->toolkit->CallPrerequisitesMethod('core/', 'CheckSystemRequirements');
					$required_checks = array_diff($required_checks, array_keys( array_filter($check_results) ));

					if ( $required_checks ) {
						// php-based checks failed - show error
						$this->errorMessage = '<br/>Installation can not continue until all required environment parameters are set correctly';
					}
					elseif ( $this->GetVar('js_enabled') === false ) {
						// can't check JS without form submit - set some fake error, so user stays on this step
						$this->errorMessage = '&nbsp;';
					}
					elseif ( !$this->GetVar('js_enabled') || !$this->GetVar('cookies_enabled') ) {
						// js/cookies disabled
						$this->errorMessage = '<br/>Installation can not continue until all required environment parameters are set correctly';
					}
					break;

				case 'check_paths':
					$writeable_base = $this->toolkit->systemConfig->get('WriteablePath', 'Misc');
					foreach ($this->writeableFolders as $folder_path) {
						$file_path = FULL_PATH . str_replace('$1', $writeable_base, $folder_path);
						if (file_exists($file_path) && !is_writable($file_path)) {
							$this->errorMessage = '<br/>Installation can not continue until all required permissions are set correctly';
							break;
						}
					}
					break;

				case 'clean_db':
					// don't use Application, because all tables will be erased and it will crash
					$sql = 'SELECT Path
							FROM ' . TABLE_PREFIX . 'Modules';
					$modules = $this->Conn->GetCol($sql);

					foreach ($modules as $module_folder) {
						$remove_file = '/' . $module_folder . 'install/remove_schema.sql';
						if (file_exists(FULL_PATH . $remove_file)) {
							$this->toolkit->RunSQL($remove_file);
						}
					}

					$this->toolkit->deleteEditTables();

					$this->currentStep = $this->GetNextStep();
					break;

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

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

						if ($submit_value !== false) {
							$this->toolkit->systemConfig->set($field_name, 'Database', $submit_value);
						}
						/*else {
							$this->toolkit->systemConfig->set($field_name, 'Database', '');
						}*/
					}
			        break;

				case 'download_license':
					$license_source = $this->GetVar('license_source');
					if ($license_source !== false && $license_source != 1) {
						// previous step was "Select License" and not "Download from Intechnic" option was selected
						$this->currentStep = $this->GetNextStep();
					}
					break;

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

				case 'select_theme':
					// put available theme list in database
					$this->toolkit->rebuildThemes();
					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 'skin_upgrade':
					if ($this->Application->RecallVar('SkinUpgradeLog') === false) {
						// no errors during skin upgrade -> skip this step
						$this->currentStep = $this->GetNextStep();
					}
					break;

				case 'install_setup':
					if ( $this->Application->TableFound(TABLE_PREFIX . 'UserSession', true) ) {
						// update to 5.2.0 -> rename session table before using it
						// don't rename any other table here, since their names could be used in upgrade script
						$this->Conn->Query('RENAME TABLE ' . TABLE_PREFIX . 'UserSession TO ' . TABLE_PREFIX . 'UserSessions');
						$this->Conn->Query('RENAME TABLE ' . TABLE_PREFIX . 'SessionData TO ' . TABLE_PREFIX . 'UserSessionData');
					}

					$next_preset = $this->Application->GetVar('next_preset');
					if ($next_preset !== false) {
						$user_helper = $this->Application->recallObject('UserHelper');
						/* @var $user_helper UserHelper */

						$username = $this->Application->GetVar('login');
						$password = $this->Application->GetVar('password');

						if ($username == 'root') {
							// verify "root" user using configuration settings
							$login_result = $user_helper->loginUser($username, $password);

							if ($login_result != LoginResult::OK) {
								$error_phrase = $login_result == LoginResult::NO_PERMISSION ? 'la_no_permissions' : 'la_invalid_password';
								$this->errorMessage = $this->Application->Phrase($error_phrase) . '. If you don\'t know your username or password, contact Intechnic Support';
							}
						}
						else {
							// non "root" user -> verify using licensing server
							$url_params = Array (
								'login' => md5($username),
								'password' => md5($password),
								'action' => 'check',
								'license_code' => base64_encode( $this->toolkit->systemConfig->get('LicenseCode', 'Intechnic') ),
								'version' => '4.3.0',//$this->toolkit->GetMaxModuleVersion('core/'),
								'domain' => base64_encode($_SERVER['HTTP_HOST']),
							);

							$curl_helper = $this->Application->recallObject('CurlHelper');
							/* @var $curl_helper kCurlHelper */

							$curl_helper->SetRequestData($url_params);
							$file_data = $curl_helper->Send(GET_LICENSE_URL);

							if ( !$curl_helper->isGoodResponseCode() ) {
								$this->errorMessage = 'In-Portal servers temporarily unavailable. Please contact <a href="mailto:support@in-portal.com">In-Portal support</a> personnel directly.';
							}
							elseif (substr($file_data, 0, 5) == 'Error') {
								$this->errorMessage = substr($file_data, 6) . ' If you don\'t know your username or password, contact Intechnic Support';
							}

							if ($this->errorMessage == '') {
								$user_helper->loginUserById(USER_ROOT);
							}
						}

						if ($this->errorMessage == '') {
							// processed with redirect to selected step preset
							if (!isset($this->steps[$next_preset])) {
								$this->errorMessage = 'Preset "'.$next_preset.'" not yet implemented';
							}
							else {
								$this->stepsPreset = $next_preset;
							}
						}
					}
					else {
						// if preset was not choosen, then raise error
						$this->errorMessage = 'Please select action to perform';
					}
					break;

				case 'security':
					// perform write check
					if ($this->Application->GetVar('skip_security_check')) {
						// administrator intensionally skips security checks
						break;
					}

					$write_check = true;
					$check_paths = Array ('/', '/index.php', $this->toolkit->systemConfig->get('WriteablePath', 'Misc') . '/config.php', ADMIN_DIRECTORY . '/index.php');

					foreach ($check_paths as $check_path) {
						$path_check_status = $this->toolkit->checkWritePermissions(FULL_PATH . $check_path);

						if (is_bool($path_check_status) && $path_check_status) {
							$write_check = false;
							break;
						}
					}

					// script execute check
					if (file_exists(WRITEABLE . '/install_check.php')) {
						unlink(WRITEABLE . '/install_check.php');
					}

					$fp = fopen(WRITEABLE . '/install_check.php', 'w');
					fwrite($fp, "<?php\n\techo 'OK';\n");
					fclose($fp);

					$curl_helper = $this->Application->recallObject('CurlHelper');
					/* @var $curl_helper kCurlHelper */

					$output = $curl_helper->Send($this->Application->BaseURL(WRITEBALE_BASE) . 'install_check.php');
					unlink(WRITEABLE . '/install_check.php');
					$execute_check = ($output !== 'OK');

					$directive_check = true;
					$ini_vars = Array ('register_globals' => false, 'open_basedir' => true, 'allow_url_fopen' => false);
					foreach ($ini_vars as $var_name => $var_value) {
						$current_value = ini_get($var_name);

						if (($var_value && !$current_value) || (!$var_value && $current_value)) {
							$directive_check = false;
							break;
						}
					}

					if (!$write_check || !$execute_check || !$directive_check) {
						$this->errorMessage = true;
					}
					/*else {
						$this->currentStep = $this->GetNextStep();
					}*/
					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':
				case 'db_reconfig':
					// 1. check if required fields are filled
					$section_name = 'Database';
					$required_fields = Array ('DBType', 'DBHost', 'DBName', 'DBUser', 'DBCollation');
					foreach ($required_fields as $required_field) {
						if (!$this->toolkit->systemConfig->get($required_field, $section_name)) {
							$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(($this->currentStep == 'db_config') && !$this->GetVar('UseExistingSetup'));
					break;

				case 'select_license':
					$license_source = $this->GetVar('license_source');
					if ($license_source == 2) {
						// license from file -> file must be uploaded
						$upload_error = $_FILES['license_file']['error'];
						if ($upload_error != UPLOAD_ERR_OK) {
							$this->errorMessage = 'Missing License File';
						}
					}
					elseif (!is_numeric($license_source)) {
						$this->errorMessage = 'Please select license';
					}

					$status = $this->errorMessage == '';
					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 (mb_strlen($password) < 4) {
						$this->errorMessage = 'Root Password must be at least 4 characters';
					}

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

				case 'choose_modules':
					break;

				case 'upgrade_modules':
					$modules = $this->Application->GetVar('modules');
					if (!$modules) {
						$modules = Array ();
						$this->errorMessage = 'Please select module(-s) to ' . ($this->currentStep == 'choose_modules' ? 'install' : 'upgrade');
					}

					// check interface module
					$upgrade_data = $this->GetUpgradableModules();

					if (array_key_exists('core', $upgrade_data) && !in_array('core', $modules)) {
						// core can be upgraded, but isn't selected
						$this->errorMessage = 'Please select "Core" as interface module';
					}

					$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':
				case 'db_reconfig':
					// store db configuration
					$sql = 'SHOW COLLATION
							LIKE \''.$this->toolkit->systemConfig->get('DBCollation', 'Database').'\'';
					$collation_info = $this->Conn->Query($sql);
					if ($collation_info) {
						$this->toolkit->systemConfig->set('DBCharset', 'Database', $collation_info[0]['Charset']);

						// database is already connected, that's why set collation on the fly
						$this->Conn->Query('SET NAMES \''.$this->toolkit->systemConfig->get('DBCharset', 'Database').'\' COLLATE \''.$this->toolkit->systemConfig->get('DBCollation', 'Database').'\'');
					}

					$this->toolkit->systemConfig->save();

					if ($this->currentStep == 'db_config') {
						if ($this->GetVar('UseExistingSetup')) {
							// abort clean install and redirect to already_installed
							$this->stepsPreset = 'already_installed';
							break;
						}

						// import base data into new database, not for db_reconfig
						$this->toolkit->RunSQL('/core/install/install_schema.sql');
						$this->toolkit->RunSQL('/core/install/install_data.sql');

						// create category using sql, because Application is not available here
						$table_name = $this->toolkit->systemConfig->get('TablePrefix', 'Database') . 'IdGenerator';
						$this->Conn->Query('UPDATE ' . $table_name . ' SET lastid = lastid + 1');
						$resource_id = $this->Conn->GetOne('SELECT lastid FROM ' . $table_name);
						if ($resource_id === false) {
							$this->Conn->Query('INSERT INTO '.$table_name.' (lastid) VALUES (2)');
							$resource_id = 2;
						}

						// can't use USER_ROOT constant, since Application isn't available here
						$fields_hash = Array (
							'l1_Name' => 'Content', 'l1_MenuTitle' => 'Content', 'Filename' => 'Content',
							'AutomaticFilename' => 0, 'CreatedById' => -1, 'CreatedOn' => time(),
							'ResourceId' => $resource_id - 1, 'l1_Description' => 'Content', 'Status' => 4,
						);

						$this->Conn->doInsert($fields_hash, $this->toolkit->systemConfig->get('TablePrefix', 'Database') . 'Categories');

						$this->toolkit->SetModuleRootCategory('Core', $this->Conn->getInsertID());

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

						// for now we set "In-Portal" module version to "Core" module version (during clean install)
						$this->toolkit->SetModuleVersion('In-Portal', 'core/');
					}
					break;

				case 'select_license':
					// reset memory cache, when application is first available (on fresh install and clean reinstall steps)
					$this->Application->HandleEvent(new kEvent('adm:OnResetMemcache'));

					$license_source = $this->GetVar('license_source');
					switch ($license_source) {
						case 1: // Download from Intechnic

							break;

						case 2: // Upload License File
							$file_data = array_map('trim', file($_FILES['license_file']['tmp_name']));
							if ((count($file_data) == 3) && $file_data[1]) {
								$modules_helper = $this->Application->recallObject('ModulesHelper');
								/* @var $modules_helper kModulesHelper */

								if ($modules_helper->verifyLicense($file_data[1])) {
									$this->toolkit->systemConfig->set('License', 'Intechnic', $file_data[1]);
									$this->toolkit->systemConfig->set('LicenseCode', 'Intechnic', $file_data[2]);
									$this->toolkit->systemConfig->save();
								}
								else {
									$this->errorMessage = 'Invalid License File';
								}
							}
							else {
								$this->errorMessage = 'Invalid License File';
							}
							break;

						case 3: // Use Existing License
							$license_hash = $this->toolkit->systemConfig->get('License', 'Intechnic');
							if ($license_hash) {
								$modules_helper = $this->Application->recallObject('ModulesHelper');
								/* @var $modules_helper kModulesHelper */

								if (!$modules_helper->verifyLicense($license_hash)) {
									$this->errorMessage = 'Invalid or corrupt license detected';
								}
							}
							else {
								// happens, when browser's "Back" button is used
								$this->errorMessage = 'Missing License File';
							}
							break;

						case 4: // Skip License (Local Domain Installation)
							if ($this->toolkit->sectionFound('Intechnic')) {
								// remove any previous license information
								$this->toolkit->systemConfig->set('License', 'Intechnic');
								$this->toolkit->systemConfig->set('LicenseCode', 'Intechnic');
								$this->toolkit->systemConfig->save();
							}
							break;
					}
					break;

				case 'download_license':
					$license_login = $this->GetVar('login');
					$license_password = $this->GetVar('password');
					$license_id = $this->GetVar('licenses');

					$curl_helper = $this->Application->recallObject('CurlHelper');
					/* @var $curl_helper kCurlHelper */

					if (strlen($license_login) && strlen($license_password) && !$license_id) {
						// Here we determine weather login is ok & check available licenses
						$url_params = Array (
							'login' => md5($license_login),
							'password' => md5($license_password),
							'version' => $this->toolkit->GetMaxModuleVersion('core/'),
							'domain' => base64_encode($_SERVER['HTTP_HOST']),
						);

						$curl_helper->SetRequestData($url_params);
						$file_data = $curl_helper->Send(GET_LICENSE_URL);

						if (!$file_data) {
							// error connecting to licensing server
							$this->errorMessage = 'Unable to connect to the Intechnic server! Please try again later!';
						}
						else {
							if (substr($file_data, 0, 5) == 'Error') {
								// after processing data server returned error
								$this->errorMessage = substr($file_data, 6);
							}
							else {
								// license received
								if (substr($file_data, 0, 3) == 'SEL') {
									// we have more, then one license -> let user choose
									$this->SetVar('license_selection', base64_encode( substr($file_data, 4) )); // we received html with radio buttons with names "licenses"
									$this->errorMessage = 'Please select which license to use';
								}
								else {
									// we have one license
									$this->toolkit->processLicense($file_data);
								}
							}
						}
					}
					else if (!$license_id) {
						// licenses were not queried AND user/password missing
						$this->errorMessage = 'Incorrect Username or Password. If you don\'t know your username or password, contact Intechnic Support';
					}
					else {
						// Here we download license
						$url_params = Array (
							'license_id' => md5($license_id),
							'dlog' => md5($license_login),
							'dpass' => md5($license_password),
							'version' => $this->toolkit->GetMaxModuleVersion('core/'),
							'domain' => base64_encode($_SERVER['HTTP_HOST']),
						);

						$curl_helper->SetRequestData($url_params);
						$file_data = $curl_helper->Send(GET_LICENSE_URL);

						if (!$file_data) {
							// error connecting to licensing server
							$this->errorMessage = 'Unable to connect to the Intechnic server! Please try again later!';
						}
						else {
							if (substr($file_data, 0, 5) == 'Error') {
								// after processing data server returned error
								$this->errorMessage = substr($file_data, 6);
							}
							else {
								$this->toolkit->processLicense($file_data);
							}
						}
					}
					break;

				case 'select_domain':
					$modules_helper = $this->Application->recallObject('ModulesHelper');
					/* @var $modules_helper kModulesHelper */

					// get domain name as entered by user on the form
					$domain = $this->GetVar('domain') == 1 ? $_SERVER['HTTP_HOST'] : str_replace(' ', '', $this->GetVar('other'));

					$license_hash = $this->toolkit->systemConfig->get('License', 'Intechnic');
					if ($license_hash) {
						// when license present, then extract domain from it
						$license_hash = base64_decode($license_hash);
						list ( , , $license_keys) = $modules_helper->_ParseLicense($license_hash);
						$license_domain = $license_keys[0]['domain'];
					}
					else {
						// when license missing, then use current domain or domain entered by user
						$license_domain = $domain;
					}

					if ($domain != '') {
						if (strstr($domain, $license_domain) || $modules_helper->_IsLocalSite($domain)) {
							$this->toolkit->systemConfig->set('Domain', 'Misc', $domain);
							$this->toolkit->systemConfig->save();
						}
						else {
							$this->errorMessage = 'Domain name entered does not match domain name in the license!';
						}
					}
					else {
						$this->errorMessage = 'Please enter valid domain!';
					}
					break;

				case 'sys_config':
					$config_data = $this->GetVar('system_config');

					foreach ($config_data as $section => $section_vars) {
						foreach ($section_vars as $var_name => $var_value) {
							$this->toolkit->systemConfig->set($var_name, $section, $var_value);
						}
					}

					$this->toolkit->systemConfig->save();
					break;

				case 'root_password':
					// update root password in database
					$password_formatter = $this->Application->recallObject('kPasswordFormatter');
					/* @var $password_formatter kPasswordFormatter */

					$config_values = Array (
						'RootPass' => $password_formatter->hashPassword($this->Application->GetVar('root_password')),
						'Backup_Path' => FULL_PATH . $this->toolkit->systemConfig->get('WriteablePath', 'Misc') . DIRECTORY_SEPARATOR . 'backupdata',
						'DefaultEmailSender' => 'portal@' . $this->toolkit->systemConfig->get('Domain', 'Misc')
					);

					$site_timezone = date_default_timezone_get();

					if ($site_timezone) {
						$config_values['Config_Site_Time'] = $site_timezone;
					}

					$this->toolkit->saveConfigValues($config_values);

					$user_helper = $this->Application->recallObject('UserHelper');
					/* @var $user_helper UserHelper */

					// login as "root", when no errors on password screen
					$user_helper->loginUser('root', $this->Application->GetVar('root_password'));

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

					// make sure imported language is set as active in session, created during installation
					$this->Application->Session->SetField('Language', 1);

					// 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(true); // for Front-End
					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);
							}
						}
					}

					// update category cache
					$updater = $this->Application->makeClass('kPermCacheUpdater');
					/* @var $updater kPermCacheUpdater */

					$updater->OneStepRun();
					break;

				case 'post_config':
					$this->toolkit->saveConfigValues( $this->GetVar('config') );
					break;

				case 'select_theme':
					// 1. mark theme, that user is selected
					$theme_id = $this->GetVar('theme');
					$theme_table = $this->Application->getUnitOption('theme', 'TableName');
					$theme_idfield = $this->Application->getUnitOption('theme', 'IDField');

					$sql = 'UPDATE ' . $theme_table . '
							SET Enabled = 1, PrimaryTheme = 1
							WHERE ' . $theme_idfield . ' = ' . $theme_id;
					$this->Conn->Query($sql);

					$this->toolkit->rebuildThemes(); // rescan theme to create structure after theme is enabled !!!

					// install theme dependent demo data
					if ($this->Application->GetVar('install_demo_data')) {
						$sql = 'SELECT Name
								FROM ' . $theme_table . '
								WHERE ' . $theme_idfield . ' = ' . $theme_id;
						$theme_name = $this->Conn->GetOne($sql);
						$site_path = $this->toolkit->systemConfig->get('WebsitePath','Misc') . '/';

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

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

							$template_path = '/themes' . '/' . $theme_name . '/' . $module_info['TemplatePath'];
							$this->toolkit->RunSQL( $template_path . '_install/install_data.sql', Array('{ThemeId}', '{SitePath}'), Array($theme_id, $site_path) );

							if ( file_exists(FULL_PATH . $template_path . '_install/images') ) {
								// copy theme demo images into writable path accessible by FCKEditor
								$file_helper->copyFolderRecursive(FULL_PATH . $template_path . '_install/images' . DIRECTORY_SEPARATOR, WRITEABLE . '/user_files/Images');
							}
						}
					}
					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();
						$start_from_query = $this->Application->GetVar('start_from_query');
						$this->upgradeDepencies = $this->getUpgradeDependencies($modules, $upgrade_data);

						if ($start_from_query !== false) {
							$this->upgradeLog = unserialize( $this->Application->RecallVar('UpgradeLog') );
						}
						else {
							$start_from_query = 0;
							$this->upgradeLog = Array ('ModuleVersions' => Array ());

							// remember each module version, before upgrade scripts are executed
							foreach ($modules as $module_name) {
								$module_info = $upgrade_data[$module_name];
								$this->upgradeLog['ModuleVersions'][$module_name] = $module_info['FromVersion'];
							}

							$this->Application->RemoveVar('UpgradeLog');
						}

						// 1. perform "php before", "sql", "php after" upgrades
						foreach ($modules as $module_name) {
							$module_info = $upgrade_data[$module_name];

							/*echo '<h2>Upgrading "' . $module_info['Name'] . '" to "' . $module_info['ToVersion'] . '"</h2>' . "\n";
							flush();*/

							if (!$this->RunUpgrade($module_info['Name'], $module_info['ToVersion'], $upgrade_data, $start_from_query)) {
								$this->Application->StoreVar('UpgradeLog', serialize($this->upgradeLog));
								$this->Done();
							}

							// restore upgradable module version (makes sense after sql error processing)
							$upgrade_data[$module_name]['FromVersion'] = $this->upgradeLog['ModuleVersions'][$module_name];
						}

						// 2. import language pack, perform "languagepack" upgrade for all upgraded versions
						foreach ($modules as $module_name) {
							$module_info = $upgrade_data[$module_name];
							$sqls =& $this->getUpgradeQueriesFromVersion($module_info['Path'], $module_info['FromVersion']);
							preg_match_all('/' . VERSION_MARK . '/s', $sqls, $regs);

							// import module language pack
							$this->toolkit->ImportLanguage('/' . $module_info['Path'] . 'install/english', true);

							// perform advanced language pack upgrade
							foreach ($regs[1] as $version) {
								$this->RunUpgradeScript($module_info['Path'], $version, 'languagepack');
							}
						}

						// 3. update all theme language packs
						$themes_helper = $this->Application->recallObject('ThemesHelper');
						/* @var $themes_helper kThemesHelper */

						$themes_helper->synchronizeModule(false);

						// 4. upgrade admin skin
						if (in_array('core', $modules)) {
							$skin_upgrade_log = $this->toolkit->upgradeSkin($upgrade_data['core']);

							if ($skin_upgrade_log === true) {
								$this->Application->RemoveVar('SkinUpgradeLog');
							}
							else {
								$this->Application->StoreVar('SkinUpgradeLog', serialize($skin_upgrade_log));
							}

							// for now we set "In-Portal" module version to "Core" module version (during upgrade)
							$this->toolkit->SetModuleVersion('In-Portal', false, $upgrade_data['core']['ToVersion']);
						}
					}
					break;

				case 'finish':
					// delete cache
					$this->toolkit->deleteCache();
					$this->toolkit->rebuildThemes();

					// compile admin skin, so it will be available in 3 frames at once
					$skin_helper = $this->Application->recallObject('SkinHelper');
					/* @var $skin_helper SkinHelper */

					$skin = $this->Application->recallObject('skin', null, Array ('skip_autoload' => true));
					/* @var $skin kDBItem */

					$skin->Load(1, 'IsPrimary');
					$skin_helper->compile($skin);

					// set installation finished mark
					if ($this->Application->ConfigValue('InstallFinished') === false) {
						$fields_hash = Array (
							'VariableName'	=>	'InstallFinished',
							'VariableValue'	=>	1,
						);
						$this->Conn->doInsert($fields_hash, TABLE_PREFIX.'SystemSettings');
					}
					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
                $user_helper = $this->Application->recallObject('UserHelper');
                /* @var $user_helper UserHelper */

                $user_helper->logoutUser();

				$this->Application->Redirect($user_helper->event->redirect, $user_helper->event->getRedirectParams(), '', 'index.php');
			}
		}

		function getUpgradeDependencies($modules, &$upgrade_data)
		{
			$dependencies = Array ();

			foreach ($modules as $module_name) {
				$module_info = $upgrade_data[$module_name];
				$upgrade_object =& $this->getUpgradeObject($module_info['Path']);

				if (!is_object($upgrade_object)) {
					continue;
				}

				foreach ($upgrade_object->dependencies as $dependent_version => $version_dependencies) {
					if (!$version_dependencies) {
						// module is independent -> skip
						continue;
					}

					list ($parent_name, $parent_version) = each($version_dependencies);

					if (!array_key_exists($parent_name, $dependencies)) {
						// parent module
						$dependencies[$parent_name] = Array ();
					}

					if (!array_key_exists($parent_version, $dependencies[$parent_name])) {
						// parent module versions, that are required by other module versions
						$dependencies[$parent_name][$parent_version] = Array ();
					}

					$dependencies[$parent_name][$parent_version][] = Array ($module_info['Name'] => $dependent_version);
				}
			}

			return $dependencies;
		}

		/**
		 * Returns database queries, that should be executed to perform upgrade from given to lastest version of given module path
		 *
		 * @param string $module_path
		 * @param string $from_version
		 * @return string
		 */
		function &getUpgradeQueriesFromVersion($module_path, $from_version)
		{
			$upgrades_file = sprintf(UPGRADES_FILE, $module_path, 'sql');

			$sqls = file_get_contents($upgrades_file);
			$version_mark = preg_replace('/(\(.*?\))/', $from_version, 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);

			return $sqls;
		}

		function RunUpgrade($module_name, $to_version, &$upgrade_data, &$start_from_query)
		{
			$module_info = $upgrade_data[ strtolower($module_name) ];

			$sqls =& $this->getUpgradeQueriesFromVersion($module_info['Path'], $module_info['FromVersion']);
			preg_match_all('/(' . VERSION_MARK . ')/s', $sqls, $matches, PREG_SET_ORDER + PREG_OFFSET_CAPTURE);

			foreach ($matches as $index => $match) {
				// upgrade version
				$version = $match[2][0];

				if ($this->toolkit->ConvertModuleVersion($version) > $this->toolkit->ConvertModuleVersion($to_version)) {
					// only upgrade to $to_version, not further
					break;
				}

				if (!in_array($module_name . ':' . $version, $this->upgradeLog)) {
					if ($this->Application->isDebugMode()) {
						$this->Application->Debugger->appendHTML('Upgrading "' . $module_name . '" to "' . $version . '" version: BEGIN.');
					}

					/*echo 'Upgrading "' . $module_name . '" to "' . $version . '".<br/>' . "\n";
					flush();*/

					// don't upgrade same version twice
					$start_pos = $match[0][1] + strlen($match[0][0]);
					$end_pos = array_key_exists($index + 1, $matches) ? $matches[$index + 1][0][1] : strlen($sqls);
					$version_sqls = substr($sqls, $start_pos, $end_pos - $start_pos);

					if ($start_from_query == 0) {
						$this->RunUpgradeScript($module_info['Path'], $version, 'before');
					}

					if (!$this->toolkit->RunSQLText($version_sqls, null, null, $start_from_query)) {
						$this->errorMessage .= '<input type="hidden" name="start_from_query" value="' . $this->LastQueryNum . '">';
						$this->errorMessage .= '<br/>Module "' . $module_name . '" upgrade to "' . $version . '" failed.';
						$this->errorMessage .= '<br/>Click Continue button below to skip this query and go further<br/>';

						return false;
					}
					else {
						// reset query counter, when all queries were processed
						$start_from_query = 0;
					}

					$this->RunUpgradeScript($module_info['Path'], $version, 'after');

					if ($this->Application->isDebugMode()) {
						$this->Application->Debugger->appendHTML('Upgrading "' . $module_name . '" to "' . $version . '" version: END.');
					}

					// remember, that we've already upgraded given version
					$this->upgradeLog[] = $module_name . ':' . $version;
				}

				if (array_key_exists($module_name, $this->upgradeDepencies) && array_key_exists($version, $this->upgradeDepencies[$module_name])) {
					foreach ($this->upgradeDepencies[$module_name][$version] as $dependency_info) {
						list ($dependent_module, $dependent_version) = each($dependency_info);

						if (!$this->RunUpgrade($dependent_module, $dependent_version, $upgrade_data, $start_from_query)) {
							return false;
						}
					}
				}

				// only mark module as updated, when all it's dependent modules are upgraded
				$this->toolkit->SetModuleVersion($module_name, false, $version);
			}

			return true;
		}

		/**
		 * Run upgrade PHP scripts for module with specified path
		 *
		 * @param string $module_path
		 * @param Array $version
		 * @param string $mode upgrade mode = {before,after,languagepack}
		 */
		function RunUpgradeScript($module_path, $version, $mode)
		{
			$upgrade_object =& $this->getUpgradeObject($module_path);

			if (!is_object($upgrade_object)) {
				return ;
			}

			$upgrade_method = 'Upgrade_' . str_replace(Array ('.', '-'), '_', $version);

			if (method_exists($upgrade_object, $upgrade_method)) {
				$upgrade_object->$upgrade_method($mode);
			}
		}

		/**
		 * Returns upgrade class for given module path
		 *
		 * @param string $module_path
		 * @return kUpgradeHelper
		 */
		function &getUpgradeObject($module_path)
		{
			static $upgrade_classes = Array ();
			$upgrades_file = sprintf(UPGRADES_FILE, $module_path, 'php');

			if (!file_exists($upgrades_file)) {
				$false = false;
				return $false;
			}

			if (!isset($upgrade_classes[$module_path])) {
				require_once(FULL_PATH . REL_PATH . '/install/upgrade_helper.php');

				// 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]();
			/* @var $upgrade_object CoreUpgrades */

			$upgrade_object->setToolkit($this->toolkit);

			return $upgrade_object;
		}

		/**
		 * 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->toolkit->Application =& kApplication::Instance();

				$this->includeModuleConstants();
				$this->Application->Init();

				$this->Conn =& $this->Application->GetADODBConnection();
				$this->toolkit->Conn =& $this->Application->GetADODBConnection();
			}
		}

		/**
		 * When no modules installed, then pre-include all modules contants, since they are used in unit configs
		 *
		 */
		function includeModuleConstants()
		{
			$modules = $this->ScanModules();

			foreach ($modules as $module_path) {
				$constants_file = MODULES_PATH . '/' . $module_path . '/constants.php';

				if ( file_exists($constants_file) ) {
					kUtil::includeOnce($constants_file);
				}
			}
		}

		/**
		 * Show next step screen
		 *
		 * @param string $error_message
		 * @return void
		 */
		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();
			}

			exit;
		}

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

			$required_keys = Array ('DBType', 'DBUser', 'DBName');
			foreach ($required_keys as $required_key) {
				if (!$this->toolkit->systemConfig->get($required_key, 'Database')) {
					// one of required db connection settings missing -> abort connection
					return false;
				}
			}

			$this->Conn = new kDBConnection($this->toolkit->systemConfig->get('DBType', 'Database'), Array(&$this, 'DBErrorHandler'));
			$this->Conn->setup($this->toolkit->systemConfig->getData());

			// setup toolkit too
			$this->toolkit->Conn =& $this->Conn;

			return !$this->Conn->hasError();
		}

		/**
		 * Checks if core is already installed
		 *
		 * @return bool
		 */
		function AlreadyInstalled()
		{
			$table_prefix = $this->toolkit->systemConfig->get('TablePrefix', 'Database');
			$settings_table = $this->TableExists('ConfigurationValues') ? 'ConfigurationValues' : 'SystemSettings';

			$sql = 'SELECT VariableValue
					FROM ' . $table_prefix . $settings_table . '
					WHERE VariableName = "InstallFinished"';

			return $this->TableExists($settings_table) && $this->Conn->GetOne($sql);
		}

		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
			// 3. database environment settings met minimum requirements

			if (mb_strlen($this->toolkit->systemConfig->get('TablePrefix', 'Database')) > 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;
					}

					$requirements_error = Array ();
					$db_check_results = $this->toolkit->CallPrerequisitesMethod('core/', 'CheckDBRequirements');

					if ( !$db_check_results['version'] ) {
						$requirements_error[] = '- MySQL Version is below 5.0';
					}

					if ( !$db_check_results['packet_size'] ) {
						$requirements_error[] = '- MySQL Packet Size is below 1 MB';
					}

					if ( $requirements_error ) {
						$this->errorMessage = 'Connection successful, but following system requirements were not met:<br/>' . implode('<br/>', $requirements_error);
						return false;
					}
				}
				else {
					// user has insufficient permissions in database specified
					$this->errorMessage = '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->toolkit->systemConfig->get('TablePrefix', 'Database');

			$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;
		}

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

			if ( !isset($modules) ) {
				// use direct include, because it's called before kApplication::Init, that creates class factory
				kUtil::includeOnce( KERNEL_PATH . kApplication::MODULE_HELPER_PATH );

				$modules_helper = new kModulesHelper();
				$modules = $modules_helper->getModules();
			}

			return $modules;
		}

		/**
		 * Virtually place module under "modules" folder or it won't be recognized during upgrade to 5.1.0 version
		 *
		 * @param string $name
		 * @param string $path
		 * @param string $version
		 * @return string
		 */
		function getModulePath($name, $path, $version)
		{
			if ($name == 'Core') {
				// don't transform path for Core module
				return $path;
			}

			if (!preg_match('/^modules\//', $path)) {
				// upgrade from 5.0.x/1.0.x to 5.1.x/1.1.x
				return 'modules/' . $path;
			}

			return $path;
		}

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

			foreach ($this->Application->ModuleInfo as $module_name => $module_info) {
				if ($module_name == 'In-Portal') {
					// don't show In-Portal, because it shares upgrade scripts with Core module
					continue;
				}

				$module_info['Path'] = $this->getModulePath($module_name, $module_info['Path'], $module_info['Version']);
				$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->toolkit->ConvertModuleVersion($module_info['Version']);
				if ($this->toolkit->ConvertModuleVersion($to_version) > $this_version) {
					// destination version is greather then current
					foreach ($regs[1] as $version) {
						if ($this->toolkit->ConvertModuleVersion($version) > $this_version) {
							$from_version = $version;
							break;
						}
					}

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

					$ret[ strtolower($module_name) ] = array_merge($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;
		}

		/**
		 * 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, ENT_QUOTES, 'UTF-8').'<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|string $errcontext
		 */
		function ErrorHandler($errno, $errstr, $errfile = '', $errline = 0, $errcontext = '')
		{
			if ($errno == E_USER_ERROR) {
				// only react on user fatal errors
				$this->Done($errstr);
			}
		}

		/**
		 * Checks, that given button should be visible on current installation step
		 *
		 * @param string $name
		 * @return bool
		 */
		function buttonVisible($name)
		{
			$button_visibility = Array (
				'continue' => $this->GetNextStep() != -1 || ($this->stepsPreset == 'already_installed'),
				'refresh' => in_array($this->currentStep, Array ('sys_requirements', 'check_paths', 'security')),
				'back' => in_array($this->currentStep, Array (/*'select_license',*/ 'download_license', 'select_domain')),
			);

			if ($name == 'any') {
				foreach ($button_visibility as $button_name => $button_visible) {
					if ($button_visible) {
						return true;
					}
				}

				return false;
			}

			return array_key_exists($name, $button_visibility) ? $button_visibility[$name] : true;
		}
	}