Index: trunk/core/install.php =================================================================== diff -u -N -r6707 -r7702 --- trunk/core/install.php (.../install.php) (revision 6707) +++ trunk/core/install.php (.../install.php) (revision 7702) @@ -3,9 +3,22 @@ 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.sql'); + + /** + * Format of version identificator in upgrade files + * + */ + define('VERSION_MARK', '# ===== v ([\d]+\.[\d]+\.[\d]+) ====='); + // print_pre($_POST); $install_engine = new kInstallator(); @@ -72,26 +85,32 @@ 'fresh_install' => Array ('check_paths', 'db_config', 'root_password', 'choose_modules', 'finish'), 'already_installed' => Array ('install_setup'), - 'upgrade' => Array ('install_setup',/* ..., */ 'finish'), + '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 - 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'); + var $writeableFolders = Array ('/system'); /** * Contains last error message text @@ -121,46 +140,59 @@ // 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) { - // first step of current preset - reset($this->steps[$this->stepsPreset]); - $this->currentStep = current($this->steps[$this->stepsPreset]); + $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 ($preset === false) { - $preset = 'fresh_install'; // default preset - - if (file_exists($this->INIFile)) { - // only at installation first step - $status = $this->CheckDatabase(false); - if ($status && $this->AlreadyInstalled()) { + 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; @@ -172,8 +204,17 @@ */ function InitStep() { - $this->InitApplication(); + $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) { @@ -214,30 +255,43 @@ } break; - case 'install_setup': - if ($this->stepsPreset == 'already_installed') { - // if preset was not choosen, then raise error - $this->errorMessage = 'Please select action to perform'; + case 'upgrade_modules': + // get installed modules from db and compare their versions to upgrade script + $modules = $this->GetUpgradableModules(); + if (!$modules) { + $this->currentStep = $this->GetNextStep(); } - else { - // if preset was choosen, then check root password entered - $user_name = $this->GetVar('user_name'); - $user_password = $this->GetVar('user_password'); - - if ($user_name == 'root') { - $sql = 'SELECT VariableValue - FROM '.$this->systemConfig['Database']['TablePrefix'].' - WHERE VariableName = "RootPass"'; - $root_password = $this->Conn->GetOne($sql); - $user_password = md5( md5($user_password) . 'b38'); - if ($user_password != $root_password) { - $this->errorMessage = 'Invalid User Name or Password. If you don\'t know your username or password, contact Intechnic Support'; + 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 { - $this->errorMessage = 'By now only login using "root" username is supported'; + // 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; } @@ -314,29 +368,100 @@ // 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'); - $sql = 'UPDATE '.TABLE_PREFIX.'ConfigurationValues - SET VariableValue = '.$this->Conn->qstr($password).' - WHERE VariableName = "RootPass"'; - $this->Conn->Query($sql); + $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'); - foreach ($modules as $module) { - $install_file = MODULES_PATH.'/'.$module.'/install.php'; - if (file_exists($install_file)) { - include_once($install_file); + 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 + $themes_helper =& $this->Application->recallObject('ThemesHelper'); + /* @var $themes_helper kThemesHelper */ + + $themes_helper->refreshThemes(); + + $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']); + + $sqls = file_get_contents($upgrades_file); + $version_mark = preg_replace('/(\(.*?\))/', $module_info['FromVersion'], VERSION_MARK); + + $start_pos = strpos($sqls, $version_mark); + $sqls = substr($sqls, $start_pos); + $this->RunSQLText($sqls); + + // 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) { @@ -351,15 +476,55 @@ if ($this->currentStep == -1) { // step after last step -> redirect to admin - $this->Application->Redirect('index', null, '', 'admin/index.php'); + $this->Application->Redirect('index', null, '', 'index.php'); } } - function InitApplication() + /** + * Sets module version to passed + * + * @param string $module_name + * @param string $version + */ + function SetModuleVersion($module_name, $version = false) { - if (!in_array($this->currentStep, $this->skipApplicationSteps) && !isset($this->Application)) { + 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 $debugger; + global $start, $debugger, $dbg_options; include_once(FULL_PATH.'/core/kernel/startup.php'); $this->Application =& kApplication::Instance(); @@ -383,21 +548,42 @@ if (isset($this->Application)) { $this->Application->Done(); - echo 'SID: ['.$this->Application->GetSID().']
'; +// echo 'SID: ['.$this->Application->GetSID().']
'; } exit; } - function GetModuleVersion($module_name) + function GetMaxModuleVersion($module_name) { - return '0.1.1'; + $upgrades_file = sprintf(UPGRADES_FILE, strtolower($module_name).'/'); + 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; @@ -410,7 +596,8 @@ */ function AlreadyInstalled() { - return $this->TableExists('ConfigurationAdmin'); //,Category,Permissions'); + $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) @@ -463,6 +650,7 @@ } else { // was error while connecting + if (!$this->Conn) return false; $this->errorMessage = 'Connection Error: ('.$this->Conn->getErrorCode().') '.$this->Conn->getErrorMsg(); return false; } @@ -493,14 +681,32 @@ 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 @@ -516,13 +722,15 @@ // 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 || substr($sql, 0, 1) == '#') { - continue; // usually last line || comment + if (!$sql) { + continue; // usually last line } $this->Conn->Query($sql); if ($this->Conn->getErrorCode() != 0) { @@ -532,7 +740,7 @@ } } } - + function ImportLanguage($lang_file) { $lang_file = FULL_PATH.$lang_file.'.lang'; @@ -575,6 +783,69 @@ } /** + * 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']); + 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