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 = '
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 = ' '; } elseif ( !$this->GetVar('js_enabled') || !$this->GetVar('cookies_enabled') ) { // js/cookies disabled $this->errorMessage = '
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 = '
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 In-Portal support 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, "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 '

Upgrading "' . $module_info['Name'] . '" to "' . $module_info['ToVersion'] . '"

' . "\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 . '".
' . "\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 .= ''; $this->errorMessage .= '
Module "' . $module_name . '" upgrade to "' . $version . '" failed.'; $this->errorMessage .= '
Click Continue button below to skip this query and go further
'; 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:
' . implode('
', $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:
'.htmlspecialchars($sql, ENT_QUOTES, 'UTF-8').'
execution result is error:
['.$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; } }