From e3ac7067f7bb0484caf2c6204415988ae0e0db0f Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 30 Mar 2011 17:28:01 +0000 Subject: [PATCH] New implementation of the setup: - All actions are performed asynchronously at the end of the setup - Supports upgrading and reinstalling (to add modules) SVN:trunk[1157] --- core/metamodel.class.php | 21 +- .../module.itop-profiles-itil.php | 15 +- setup/ajax.dataloader.php | 173 +- setup/index.php | 2843 +++++++++-------- setup/moduleinstaller.class.inc.php | 6 +- setup/setup.js | 132 +- setup/setuppage.class.inc.php | 389 ++- 7 files changed, 2179 insertions(+), 1400 deletions(-) diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a450d976c..9ff9b836c 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -3549,9 +3549,9 @@ abstract class MetaModel } } - public static function Startup($sConfigFile, $bModelOnly = false) + public static function Startup($sConfigFile, $bModelOnly = false, $bUseCache = true) { - self::LoadConfig($sConfigFile); + self::LoadConfig($sConfigFile, $bUseCache); if ($bModelOnly) return; @@ -3573,10 +3573,10 @@ abstract class MetaModel } } - public static function LoadConfig($sConfigFile) + public static function LoadConfig($sConfigFile, $bUseCache = false) { self::$m_oConfig = new Config($sConfigFile); - + // Set log ASAP if (self::$m_oConfig->GetLogGlobal()) { @@ -3655,7 +3655,7 @@ abstract class MetaModel $sCharacterSet = self::$m_oConfig->GetDBCharacterSet(); $sCollation = self::$m_oConfig->GetDBCollation(); - if (function_exists('apc_fetch')) + if ($bUseCache && function_exists('apc_fetch')) { $oKPI = new ExecutionKPI(); // Note: For versions of APC older than 3.0.17, fetch() accepts only one parameter @@ -3698,7 +3698,7 @@ abstract class MetaModel self::InitClasses($sTablePrefix); $oKPI->ComputeAndReport('Initialization of Data model structures'); - if (function_exists('apc_store')) + if ($bUseCache && function_exists('apc_store')) { $oKPI = new ExecutionKPI(); @@ -4123,12 +4123,11 @@ abstract class MetaModel } } - public static function GetCacheEntries() + public static function GetCacheEntries($sAppIdentity) { if (!function_exists('apc_cache_info')) return array(); $aCacheUserData = apc_cache_info('user'); - $sAppIdentity = MetaModel::GetConfig()->Get('session_name'); $sPrefix = $sAppIdentity.'-'; $aEntries = array(); @@ -4144,14 +4143,14 @@ abstract class MetaModel return $aEntries; } - public static function ResetCache() + public static function ResetCache(Config $oConfig) { if (!function_exists('apc_delete')) return; - $sAppIdentity = MetaModel::GetConfig()->Get('session_name'); + $sAppIdentity = $oConfig->Get('session_name'); Dict::ResetCache($sAppIdentity); - foreach(self::GetCacheEntries() as $sKey => $aAPCInfo) + foreach(self::GetCacheEntries($sAppIdentity) as $sKey => $aAPCInfo) { $sAPCKey = $aAPCInfo['info']; apc_delete($sAPCKey); diff --git a/modules/itop-profiles-itil/module.itop-profiles-itil.php b/modules/itop-profiles-itil/module.itop-profiles-itil.php index 58ae6b098..37ad030ff 100644 --- a/modules/itop-profiles-itil/module.itop-profiles-itil.php +++ b/modules/itop-profiles-itil/module.itop-profiles-itil.php @@ -75,11 +75,12 @@ class CreateITILProfilesInstaller extends ModuleInstallerAPI return $oConfiguration; } - public static function AfterDatabaseCreation(Config $oConfiguration) + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) { self::ComputeITILProfiles(); //self::ComputeBasicProfiles(); - self::DoCreateProfiles(); + $bFirstInstall = empty($sPreviousVersion); + self::DoCreateProfiles($bFirstInstall); UserRights::FlushPrivileges(true /* reset admin cache */); } @@ -298,10 +299,14 @@ class CreateITILProfilesInstaller extends ModuleInstallerAPI DBObject::BulkInsertFlush(); } - public static function DoCreateProfiles() + public static function DoCreateProfiles($bFirstInstall = true) { - URP_Profiles::DoCreateAdminProfile(); - URP_Profiles::DoCreateUserPortalProfile(); + if ($bFirstInstall) + { + // Makae sure we create these special profiles only once + URP_Profiles::DoCreateAdminProfile(); + URP_Profiles::DoCreateUserPortalProfile(); + } foreach(self::$m_aProfiles as $sName => $aProfileData) { diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 7bacd750f..886e8f301 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -24,16 +24,26 @@ */ /** - * This page is called to load "asynchronously" some xml file into the database + * This page is called to perform "asynchronously" the setup actions * parameters - * 'file' string Name of the file to load - * 'session_status' string 'start', 'continue' or 'end' - * 'percent' integer 0..100 the percentage of completion once the file has been loaded + * 'operation': one of 'update_db_schema', 'after_db_creation', 'file' + * + * if 'operation' == 'update_db_schema': + * 'mode': install | upgrade + * + * if 'operation' == 'after_db_creation': + * 'mode': install | upgrade + * + * if 'operation' == 'file': + * 'file': string Name of the file to load + * 'session_status': string 'start', 'continue' or 'end' + * 'percent': integer 0..100 the percentage of completion once the file has been loaded */ define('SAFE_MINIMUM_MEMORY', 32*1024*1024); require_once('../approot.inc.php'); require_once(APPROOT.'/application/utils.inc.php'); require_once(APPROOT.'/setup/setuppage.class.inc.php'); +require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); $sMemoryLimit = trim(ini_get('memory_limit')); if (empty($sMemoryLimit)) @@ -78,7 +88,24 @@ function FatalErrorCatcher($sOutput) } return $sOutput; } - + +/** + * Helper function to create and administrator account for iTop + * @return boolean true on success, false otherwise + */ +function CreateAdminAccount(Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) +{ + SetupWebPage::log_info('CreateAdminAccount'); + + if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) + { + return true; + } + else + { + return false; + } +} //Define some bogus, invalid HTML tags that no sane //person would ever put in an actual document and tell //PHP to delimit fatal error warnings with them. @@ -104,50 +131,128 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past /** * Main program */ -$sFileName = Utils::ReadParam('file', ''); -$sSessionStatus = Utils::ReadParam('session_status', ''); -$iPercent = (integer)Utils::ReadParam('percent', 0); -SetupWebPage::log_info("Loading file: $sFileName"); - +$sOperation = Utils::ReadParam('operation', ''); try { - if (empty($sFileName) || !file_exists($sFileName)) + switch($sOperation) { - throw(new Exception("File $sFileName does not exist")); - } + + case 'update_db_schema': + SetupWebPage::log_info("Update Database Schema."); + InitDataModel(TMP_CONFIG_FILE, true); // load data model and connect to the database + $sMode = Utils::ReadParam('mode', 'install'); + $sSelectedModules = Utils::ReadParam('selected_modules', ''); + $aSelectedModules = explode(',', $sSelectedModules); + if(!CreateDatabaseStructure(MetaModel::GetConfig(), $aSelectedModules, $sMode)) + { + throw(new Exception("Failed to create/upgrade the database structure")); + } + SetupWebPage::log_info("Database Schema Successfully Updated."); + break; + + case 'after_db_create': + SetupWebPage::log_info('After Database Creation'); + $sMode = Utils::ReadParam('mode', 'install'); + $sSelectedModules = Utils::ReadParam('selected_modules', ''); + $aSelectedModules = explode(',', $sSelectedModules); + InitDataModel(TMP_CONFIG_FILE, true); // load data model and connect to the database + + // Perform here additional DB setup... profiles, etc... + + $aAvailableModules = AnalyzeInstallation(MetaModel::GetConfig()); - $oDataLoader = new XMLDataLoader(TMP_CONFIG_FILE); // When called by the wizard, the final config is not yet there - if ($sSessionStatus == 'start') - { - $oChange = MetaModel::NewObject("CMDBChange"); - $oChange->Set("date", time()); - $oChange->Set("userinfo", "Initialization"); - $iChangeId = $oChange->DBInsert(); - SetupWebPage::log_info("starting data load session"); - $oDataLoader->StartSession($oChange); - } + $aStructureDataFiles = array(); + $aSampleDataFiles = array(); + + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != 'iTop') && in_array($sModuleId, $aSelectedModules) && + isset($aAvailableModules[$sModuleId]['installer']) ) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + SetupWebPage::log_info("Calling Module Handler: $sModuleInstallerClass::AfterDatabaseCreation(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); + // The validity of the sModuleInstallerClass has been established in BuildConfig() + $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); + call_user_func_array($aCallSpec, array(MetaModel::GetConfig(), $aModule['version_db'], $aModule['version_code'])); + } + } - $oDataLoader->LoadFile($sFileName); - $sResult = sprintf("loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); - //echo $sResult; - SetupWebPage::log_info($sResult); - if ($sSessionStatus == 'end') - { - $oDataLoader->EndSession(); - SetupWebPage::log_info("ending data load session"); + if (!RecordInstallation(MetaModel::GetConfig(), $aSelectedModules)) + { + throw(new Exception("Failed to record the installation information")); + } + + if($sMode == 'install') + { + // Create the admin user only in case of installation + $sAdminUser = Utils::ReadParam('auth_user', ''); + $sAdminPwd = Utils::ReadParam('auth_pwd', ''); + $sLanguage = Utils::ReadParam('language', ''); + if (!CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sLanguage)) + { + throw(new Exception("Failed to create the administrator account '$sAdminUser'")); + } + else + { + SetupWebPage::log_info("Administrator account '$sAdminUser' created."); + } + } + break; + + case 'load_data': // Load data files + $sFileName = Utils::ReadParam('file', ''); + $sSessionStatus = Utils::ReadParam('session_status', ''); + $iPercent = (integer)Utils::ReadParam('percent', 0); + SetupWebPage::log_info("Loading file: $sFileName"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader = new XMLDataLoader(TMP_CONFIG_FILE); // When called by the wizard, the final config is not yet there + if ($sSessionStatus == 'start') + { + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + SetupWebPage::log_info("starting data load session"); + $oDataLoader->StartSession($oChange); + } + + $oDataLoader->LoadFile($sFileName); + $sResult = sprintf("loading of %s done. (Overall %d %% completed).", basename($sFileName), $iPercent); + SetupWebPage::log_info($sResult); + + if ($sSessionStatus == 'end') + { + $oDataLoader->EndSession(); + SetupWebPage::log_info("ending data load session"); + } + break; + + default: + throw(new Exception("Error unsupported operation '$sOperation'")); } } catch(Exception $e) { header("HTTP/1.0 500 Internal server error."); - echo "

An error happened while loading the data

\n"; + echo "

An error happened while processing the installation:

\n"; echo '

'.$e."

\n"; - SetupWebPage::log_error("An error happened while loading the data. ".$e); + SetupWebPage::log_error("An error happened while processing the installation: ".$e); } if (function_exists('memory_get_peak_usage')) { - SetupWebPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage()); + if ($sOperation == 'file') + { + SetupWebPage::log_info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage()); + } + else + { + SetupWebPage::log_info("operation '$sOperation', peak memory usage. ".memory_get_peak_usage()); + } } ?> diff --git a/setup/index.php b/setup/index.php index d99a8769e..ad6f492f1 100644 --- a/setup/index.php +++ b/setup/index.php @@ -1,1326 +1,1517 @@ - - * @author Romain Quetiez - * @author Denis Flaven - * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL - */ - -require_once('../approot.inc.php'); -require_once(APPROOT.'/application/utils.inc.php'); -require_once(APPROOT.'/core/config.class.inc.php'); -require_once(APPROOT.'/core/log.class.inc.php'); -require_once(APPROOT.'/core/kpi.class.inc.php'); -require_once(APPROOT.'/core/cmdbsource.class.inc.php'); -require_once(APPROOT.'/setup/setuppage.class.inc.php'); -require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); - -define('TMP_CONFIG_FILE', APPROOT.'/tmp-config-itop.php'); -define('FINAL_CONFIG_FILE', APPROOT.'/config-itop.php'); -define('PHP_MIN_VERSION', '5.2.0'); -define('MYSQL_MIN_VERSION', '5.0.0'); -define('MIN_MEMORY_LIMIT', 32*1024*1024); - - -$sOperation = Utils::ReadParam('operation', 'step0'); -$oP = new SetupWebPage('iTop configuration wizard'); - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Various helper function -/////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Get a nicely formatted version string - */ -function GetITopVersion($bShort = true) -{ - $sVersionString = ''; - if ($bShort) - { - $sVersionString = "iTop Version ".ITOP_VERSION; - } - else - { - if (ITOP_REVISION == '$WCREV$') - { - // This is NOT a version built using the buil system, just display the main version - $sVersionString = "iTop Version ".ITOP_VERSION; - } - else - { - // This is a build made from SVN, let display the full information - $sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE; - } - } - return $sVersionString; -} - -/** - * Helper function to retrieve the system's temporary directory - * Emulates sys_get_temp_dir if neeed (PHP < 5.2.1) - * @return string Path to the system's temp directory - */ -function GetTmpDir() -{ - // try to figure out what is the temporary directory - // prior to PHP 5.2.1 the function sys_get_temp_dir - // did not exist - if ( !function_exists('sys_get_temp_dir')) - { - if( $temp=getenv('TMP') ) return realpath($temp); - if( $temp=getenv('TEMP') ) return realpath($temp); - if( $temp=getenv('TMPDIR') ) return realpath($temp); - $temp=tempnam(__FILE__,''); - if (file_exists($temp)) - { - unlink($temp); - return realpath(dirname($temp)); - } - return null; - } - else - { - return realpath(sys_get_temp_dir()); - } -} - -/** - * Check the value of the PHP setting 'memory_limit' - * against the minimum recommended value - * @param SetpWebPage $oP The current web page - * @param integer $iMinMemoryRequired The minimum memory for the test to pass - * @return boolean Whether or not it's Ok to continue - */ -function CheckMemoryLimit(SetupWebPage $oP, $iMinMemoryRequired) -{ - $sMemoryLimit = trim(ini_get('memory_limit')); - $bResult = true; - if (empty($sMemoryLimit)) - { - // On some PHP installations, memory_limit does not exist as a PHP setting! - // (encountered on a 5.2.0 under Windows) - // In that case, ini_set will not work, let's keep track of this and proceed anyway - $oP->warning("No memory limit has been defined in this instance of PHP"); - } - else - { - // Check that the limit will allow us to load the data - // - $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); - if ($iMemoryLimit < $iMinMemoryRequired) - { - $oP->error("memory_limit ($iMemoryLimit) is too small, the minimum value to run iTop is $iMinMemoryRequired."); - $bResult = false; - } - else - { - $oP->log_info("memory_limit is $iMemoryLimit, ok."); - } - } - return $bResult; -} -/** - * Helper function to retrieve the directory where files are to be uploaded - * @return string Path to the temp directory used for uploading files - */ -function GetUploadTmpDir() -{ - $sPath = ini_get('upload_tmp_dir'); - if (empty($sPath)) - { - $sPath = GetTmpDir(); - } - return $sPath; -} - -/** - * Helper function to check if the current version of PHP - * is compatible with the application - * @return boolean true if this is Ok, false otherwise - */ -function CheckPHPVersion(SetupWebPage $oP) -{ - $bResult = true; - $oP->log('Info - CheckPHPVersion'); - if (version_compare(phpversion(), PHP_MIN_VERSION, '>=')) - { - $oP->ok("The current PHP Version (".phpversion().") is greater than the minimum required version (".PHP_MIN_VERSION.")"); - } - else - { - $oP->error("Error: The current PHP Version (".phpversion().") is lower than the minimum required version (".PHP_MIN_VERSION.")"); - return false; - } - $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml', 'soap', 'hash', 'json', 'session', 'pcre', 'dom'); - $aOptionalExtensions = array('mcrypt' => 'Strong encryption will not be used.', - 'ldap' => 'LDAP authentication will be disabled.'); - asort($aMandatoryExtensions); // Sort the list to look clean ! - ksort($aOptionalExtensions); // Sort the list to look clean ! - $aExtensionsOk = array(); - $aMissingExtensions = array(); - $aMissingExtensionsLinks = array(); - // First check the mandatory extensions - foreach($aMandatoryExtensions as $sExtension) - { - if (extension_loaded($sExtension)) - { - $aExtensionsOk[] = $sExtension; - } - else - { - $aMissingExtensions[] = $sExtension; - $aMissingExtensionsLinks[] = "$sExtension"; - } - } - if (count($aExtensionsOk) > 0) - { - $oP->ok("Required PHP extension(s): ".implode(', ', $aExtensionsOk)."."); - } - if (count($aMissingExtensions) > 0) - { - $oP->error("Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."); - $bResult = false; - } - // Next check the optional extensions - $aExtensionsOk = array(); - $aMissingExtensions = array(); - foreach($aOptionalExtensions as $sExtension => $sMessage) - { - if (extension_loaded($sExtension)) - { - $aExtensionsOk[] = $sExtension; - } - else - { - $aMissingExtensions[$sExtension] = $sMessage; - } - } - if (count($aExtensionsOk) > 0) - { - $oP->ok("Optional PHP extension(s): ".implode(', ', $aExtensionsOk)."."); - } - if (count($aMissingExtensions) > 0) - { - foreach($aMissingExtensions as $sExtension => $sMessage) - { - $oP->warning("Missing optional PHP extension: $sExtension. ".$sMessage); - } - } - // Check some ini settings here - if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4 - { - $sPhpIniFile = php_ini_loaded_file(); - // Other included/scanned files - if ($sFileList = php_ini_scanned_files()) - { - if (strlen($sFileList) > 0) - { - $aFiles = explode(',', $sFileList); - - foreach ($aFiles as $sFile) - { - $sPhpIniFile .= ', '.trim($sFile); - } - } - } - $oP->log("Info - php.ini file(s): '$sPhpIniFile'"); - } - else - { - $sPhpIniFile = 'php.ini'; - } - if (!ini_get('file_uploads')) - { - $oP->error("Files upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."); - $bResult = false; - } - - $sUploadTmpDir = GetUploadTmpDir(); - if (empty($sUploadTmpDir)) - { - $sUploadTmpDir = '/tmp'; - $oP->warning("Temporary directory for files upload is not defined (upload_tmp_dir), assuming that $sUploadTmpDir is used."); - } - // check that the upload directory is indeed writable from PHP - if (!empty($sUploadTmpDir)) - { - if (!file_exists($sUploadTmpDir)) - { - $oP->error("Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."); - $bResult = false; - } - else if (!is_writable($sUploadTmpDir)) - { - $oP->error("Temporary directory for files upload ($sUploadTmpDir) is not writable."); - $bResult = false; - } - else - { - $oP->log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); - } - } - - - if (!ini_get('upload_max_filesize')) - { - $oP->error("File upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."); - } - - $iMaxFileUploads = ini_get('max_file_uploads'); - if (!empty($iMaxFileUploads) && ($iMaxFileUploads < 1)) - { - $oP->error("File upload is not allowed on this server (max_file_uploads = ".ini_get('max_file_uploads').")."); - $bResult = false; - } - $oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); - $oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads')); - - // Check some more ini settings here, needed for file upload - if (get_magic_quotes_gpc()) - { - $oP->error("'magic_quotes_gpc' is set to On. Please turn it Off before continuing. You may want to check the PHP configuration file(s): '$sPhpIniFile'. Be aware that this setting can also be overridden in the apache configuration."); - $bResult = false; - } - - $bResult = $bResult & CheckMemoryLimit($oP, MIN_MEMORY_LIMIT); - - return $bResult; -} - -/** - * Helper function check the connection to the database and (if connected) to enumerate - * the existing databases - * @return Array The list of databases found in the server - */ -function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) -{ - $aResult = array(); - $oP->log('Info - CheckServerConnection'); - try - { - $oDBSource = new CMDBSource; - $oDBSource->Init($sDBServer, $sDBUser, $sDBPwd); - $oP->ok("Connection to '$sDBServer' as '$sDBUser' successful."); - - $oP->log("Info - User privileges: ".($oDBSource->GetRawPrivileges())); - - $sDBVersion = $oDBSource->GetDBVersion(); - if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>=')) - { - $oP->ok("Current MySQL version ($sDBVersion), greater than minimum required version (".MYSQL_MIN_VERSION.")"); - // Check some server variables - $iMaxAllowedPacket = $oDBSource->GetServerVariable('max_allowed_packet'); - $iMaxUploadSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); - if ($iMaxAllowedPacket >= (500 + $iMaxUploadSize)) // Allow some space for the query + the file to upload - { - $oP->ok("MySQL server's max_allowed_packet is big enough."); - } - else if($iMaxAllowedPacket < $iMaxUploadSize) - { - $oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough. Please, consider setting it to at least ".(500 + $iMaxUploadSize)."."); - } - $oP->log("Info - MySQL max_allowed_packet: $iMaxAllowedPacket"); - $iMaxConnections = $oDBSource->GetServerVariable('max_connections'); - if ($iMaxConnections < 5) - { - $oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough. Please, consider setting it to at least 5."); - } - $oP->log("Info - MySQL max_connections: ".($oDBSource->GetServerVariable('max_connections'))); - } - else - { - $oP->error("Error: Current MySQL version is ($sDBVersion), minimum required version (".MYSQL_MIN_VERSION.")"); - return false; - } - try - { - $aResult = $oDBSource->ListDB(); - } - catch(Exception $e) - { - $oP->warning("Warning: unable to enumerate the current databases."); - $aResult = true; // Not an array to differentiate with an empty array - } - } - catch(Exception $e) - { - $oP->error("Error: Connection to '$sDBServer' as '$sDBUser' failed."); - $oP->p($e->GetHtmlDesc()); - $aResult = false; - } - return $aResult; -} - -/** - * Helper function to interpret the name of a module - * @param $sModuleId string Identifier of the module, in the form 'name/version' - * @return array(name, version) - */ -function GetModuleName($sModuleId) -{ - if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches)) - { - $sName = $aMatches[1]; - $sVersion = $aMatches[2]; - } - else - { - $sName = $sModuleId; - $sVersion = ""; - } - return array($sName, $sVersion); -} - -/** - * Helper function to initialize the ORM and load the data model - * from the given file - * @param $sConfigFileName string The name of the configuration file to load - * @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB - * @return none - */ -function InitDataModel(SetupWebPage $oP, $sConfigFileName, $bModelOnly = true) -{ - require_once(APPROOT.'/core/log.class.inc.php'); - require_once(APPROOT.'/core/kpi.class.inc.php'); - require_once(APPROOT.'/core/coreexception.class.inc.php'); - require_once(APPROOT.'/core/dict.class.inc.php'); - require_once(APPROOT.'/core/attributedef.class.inc.php'); - require_once(APPROOT.'/core/filterdef.class.inc.php'); - require_once(APPROOT.'/core/stimulus.class.inc.php'); - require_once(APPROOT.'/core/MyHelpers.class.inc.php'); - require_once(APPROOT.'/core/expression.class.inc.php'); - require_once(APPROOT.'/core/cmdbsource.class.inc.php'); - require_once(APPROOT.'/core/sqlquery.class.inc.php'); - require_once(APPROOT.'/core/dbobject.class.php'); - require_once(APPROOT.'/core/dbobjectsearch.class.php'); - require_once(APPROOT.'/core/dbobjectset.class.php'); - require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); - require_once(APPROOT.'/core/userrights.class.inc.php'); - require_once(APPROOT.'/setup/moduleinstallation.class.inc.php'); - $oP->log("Info - MetaModel::Startup from file '$sConfigFileName' (ModelOnly = $bModelOnly)"); - - MetaModel::Startup($sConfigFileName, $bModelOnly); -} -/** - * Helper function to create the database structure - * @return boolean true on success, false otherwise - */ -function CreateDatabaseStructure(SetupWebPage $oP, Config $oConfig, $sDBName, $sDBPrefix, $aSelectedModules) -{ - InitDataModel($oP, TMP_CONFIG_FILE, true); // Allow the DB to NOT exist since we're about to create it ! - $oP->log('Info - CreateDatabaseStructure'); - if (strlen($sDBPrefix) > 0) - { - $oP->info("Creating the structure in '$sDBName' (table names prefixed by '$sDBPrefix')."); - } - else - { - $oP->info("Creating the structure in '$sDBName'."); - } - - //MetaModel::CheckDefinitions(); - if (!MetaModel::DBExists(/* bMustBeComplete */ false)) - { - MetaModel::DBCreate(); - $oP->ok("Database structure successfully created."); - } - else - { - if (strlen($sDBPrefix) > 0) - { - $oP->error("Error: found iTop tables into the database '$sDBName' (prefix: '$sDBPrefix'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names."); - } - else - { - $oP->error("Error: found iTop tables into the database '$sDBName'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names."); - } - return false; - } - - // Record main installation - $oInstallRec = new ModuleInstallation(); - $oInstallRec->Set('name', 'itop'); - $oInstallRec->Set('version', ITOP_VERSION.'.'.ITOP_REVISION); - $oInstallRec->Set('comment', "Done by the setup program\nBuilt on ".ITOP_BUILD_DATE); - $oInstallRec->Set('parent_id', 0); // root module - $iMainItopRecord = $oInstallRec->DBInsertNoReload(); - - // Record installed modules - // - $aAvailableModules = GetAvailableModules($oP); - foreach($aSelectedModules as $sModuleId) - { - $aModuleData = $aAvailableModules[$sModuleId]; - list($sName, $sVersion) = GetModuleName($sModuleId); - $aComments = array(); - $aComments[] = 'Done by the setup program'; - if ($aModuleData['mandatory']) - { - $aComments[] = 'Mandatory'; - } - else - { - $aComments[] = 'Optional'; - } - if ($aModuleData['visible']) - { - $aComments[] = 'Visible (during the setup)'; - } - else - { - $aComments[] = 'Hidden (selected automatically)'; - } - foreach ($aModuleData['dependencies'] as $sDependOn) - { - $aComments[] = "Depends on module: $sDependOn"; - } - $sComment = implode("\n", $aComments); - - $oInstallRec = new ModuleInstallation(); - $oInstallRec->Set('name', $sName); - $oInstallRec->Set('version', $sVersion); - $oInstallRec->Set('comment', $sComment); - $oInstallRec->Set('parent_id', $iMainItopRecord); - $oInstallRec->DBInsertNoReload(); - } - // Database is created, installation has been tracked into it - return true; -} - -/** - * Helper function to create and administrator account for iTop - * @return boolean true on success, false otherwise - */ -function CreateAdminAccount(SetupWebPage $oP, Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) -{ - $oP->log('Info - CreateAdminAccount'); - - if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) - { - $oP->ok("Administrator account '$sAdminUser' created."); - return true; - } - else - { - $oP->error("Failed to create the administrator account '$sAdminUser'."); - return false; - } -} - -function ListModuleFiles($sRelDir, SetupWebPage $oP) -{ - $sDirectory = APPROOT.'/'.$sRelDir; - //echo "

$sDirectory

\n"; - if ($hDir = opendir($sDirectory)) - { - // This is the correct way to loop over the directory. (according to the documentation) - while (($sFile = readdir($hDir)) !== false) - { - $aMatches = array(); - if (is_dir($sDirectory.'/'.$sFile)) - { - if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn')) - { - ListModuleFiles($sRelDir.'/'.$sFile, $oP); - } - } - else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) - { - $oP->SetModulePath($sRelDir); - try - { - //echo "

Loading: $sDirectory/$sFile...

\n"; - require_once($sDirectory.'/'.$sFile); - //echo "

Done.

\n"; - } - catch(Exception $e) - { - // Continue... - } - } - } - closedir($hDir); - } - else - { - $oP->error("Data directory (".$sDirectory.") not found or not readable."); - } -} - - -/** - * Scans the ./data directory for XML files and output them as a Javascript array - */ -function PopulateDataFilesList(SetupWebPage $oP, $aParamValues) -{ - - $oP->add("\n"); -} - -/** - * Add some parameters as hidden inputs into a form - * @param SetupWebpage $oP The page to insert the form elements into - * @param Hash $aParamValues The pairs name/value to be stored in the form - * @param Array $aExcludeParams A list of parameters to exclude from the previous hash - */ -function AddParamsToForm(SetupWebpage $oP, $aParamValues, $aExcludeParams = array()) -{ - foreach($aParamValues as $sName => $value) - { - if(!in_array($sName, $aExcludeParams)) - { - if (is_array($value)) - { - foreach($value as $sKey => $sItem) - { - $oP->add(''); - } - } - else - { - $oP->add(''); - } - } - } -} - -/** - * Search (on the disk) for all defined iTop modules, load them and returns the list (as an array) - * of the possible iTop modules to install - * @param none - * @return Hash A big array moduleID => ModuleData - */ -function GetAvailableModules(SetupWebpage $oP) -{ - clearstatcache(); - ListModuleFiles('modules', $oP); - return $oP->GetModules(); -} - -/** - * Build the config file from the parameters (especially the selected modules) - */ -function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues, $aAvailableModules) -{ - // Initialize the arrays below with default values for the application... - $aAddOns = $oConfig->GetAddOns(); - $aAppModules = $oConfig->GetAppModules(); - $aDataModels = $oConfig->GetDataModels(); - $aWebServiceCategories = $oConfig->GetWebServiceCategories(); - $aDictionaries = $oConfig->GetDictionaries(); - // Merge the values with the ones provided by the modules - // Make sure when don't load the same file twice... - foreach($aParamValues['module'] as $sModuleId) - { - $oP->log('Installed iTop module: '. $sModuleId); - if (isset($aAvailableModules[$sModuleId]['datamodel'])) - { - $aDataModels = array_unique(array_merge($aDataModels, $aAvailableModules[$sModuleId]['datamodel'])); - } - if (isset($aAvailableModules[$sModuleId]['webservice'])) - { - $aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aAvailableModules[$sModuleId]['webservice'])); - } - if (isset($aAvailableModules[$sModuleId]['dictionary'])) - { - $aDictionaries = array_unique(array_merge($aDictionaries, $aAvailableModules[$sModuleId]['dictionary'])); - } - if (isset($aAvailableModules[$sModuleId]['settings'])) - { - foreach($aAvailableModules[$sModuleId]['settings'] as $sProperty => $value) - { - list($sName, $sVersion) = GetModuleName($sModuleId); - $oConfig->SetModuleSetting($sName, $sProperty, $value); - } - } - if (isset($aAvailableModules[$sModuleId]['installer'])) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - if (!class_exists($sModuleInstallerClass)) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aAvailableModules[$sModuleId]['label']); - } - if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) - { - throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aAvailableModules[$sModuleId]['label']); - } - $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); - $oConfig = call_user_func_array($aCallSpec, array($oConfig)); - } - } - $oConfig->SetAddOns($aAddOns); - $oConfig->SetAppModules($aAppModules); - $oConfig->SetDataModels($aDataModels); - $oConfig->SetWebServiceCategories($aWebServiceCategories); - $oConfig->SetDictionaries($aDictionaries); -} - -///////////////////////////////////////////////////////////////////////////////////////////////////////////// -// Handling of the different steps of the setup wizard -///////////////////////////////////////////////////////////////////////////////////////////////////////////// - -/** - * Displays the welcome screen and check some basic prerequisites - */ -function WelcomeAndCheckPrerequisites(SetupWebPage $oP, $aParamValues, $iCurrentStep) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - $oP->add("

iTop configuration wizard

\n"); - $sVersionStringShort = GetITopVersion(true); - $sVersionStringLong = GetITopVersion(false); - $oP->set_title('Welcome to '.$sVersionStringShort); - $oP->log($sVersionStringLong); - $oP->add("

Checking prerequisites

\n"); - if (CheckPHPVersion($oP)) - { - $oP->add("

Next: Licence agreement

\n"); - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("
\n"); - } -} - -function LicenceAcknowledgement($oP, $aParamValues, $iCurrentStep) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - - $oP->set_title('License agreement'); - $oP->add('

iTop is released by Combodo SARL under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.

'); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('licence_ok')); - - $sChecked = $aParamValues['licence_ok'] == 1 ? 'checked' : ''; - $oP->add("

\n"); - - $oP->add("

Next: Database server selection

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("
\n"); -} - -/** - * Display the form for the first step of the configuration wizard - * which consists in the database server selection - */ -function DatabaseServerSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('db_server', 'db_user', 'db_pwd')); - if ($aParamValues['licence_ok'] == 1) - { - $sRedStar = '*'; - $oP->set_title("Database server selection\n"); - $oP->add("

Please enter the name of the MySQL database server you want to use for iTop and supply valid credentials to connect to it

\n"); - // Form goes here - $oP->add("
Database connection\n"); - $aForm = array(); - $aForm[] = array('label' => "Server name$sRedStar:", 'input' => "", - 'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23"'); - $aForm[] = array('label' => "User name$sRedStar:", 'input' => "", - 'help' => 'The account must have the following privileges on the database: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, CREATE VIEW, TRIGGER'); - $aForm[] = array('label' => 'Password:', 'input' => ""); - $oP->form($aForm); - $oP->add("
\n"); - $oP->add("

Next: Database instance Selection

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - } - else - { - $oP->add("\n"); - } - $oP->add("
\n"); -} - -/** - * Display the form for the second step of the configuration wizard - * which consists in - * 1) Validating the parameters by connecting to the database server - * 2) Prompting to select an existing database or to create a new one - */ -function DatabaseInstanceSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title("Database instance selection\n"); - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('db_name', 'db_prefix', 'new_db_name')); - $sDBServer = $aParamValues['db_server']; - $sDBUser = $aParamValues['db_user']; - $sDBPwd = $aParamValues['db_pwd']; - $aDatabases = CheckServerConnection($oP, $sDBServer, $sDBUser, $sDBPwd); - if ($aDatabases === false) - { - // Connection failed, invalid credentials ? Go back - $oP->add("\n"); - } - else - { - // Connection is Ok, save it and continue the setup wizard - $oConfig->SetDBHost($sDBServer); - $oConfig->SetDBUser($sDBUser); - $oConfig->SetDBPwd($sDBPwd); - $oConfig->WriteToFile(); - - $oP->add("
Select the database instance to use for iTop*\n"); - $aForm = array(); - $bExistingChecked = false; - if (is_array($aDatabases)) - { - foreach($aDatabases as $sDBName) - { - $sChecked = ''; - if ($aParamValues['db_name'] == $sDBName) - { - $sChecked = 'checked'; - $bExistingChecked = true; - } - $aForm[] = array('label' => ""); - } - } - else - { - $aForm[] = array('label' => " "); - $oP->add_ready_script("$('#current_db_name').click( function() { $('#current_db').attr('checked', true); });"); - } - $sChecked = ''; - $sDBName = ''; - // If the 'Create Database' option was checked... and the database still does not exist - if (!$bExistingChecked && !empty($aParamValues['new_db_name'])) - { - $sChecked = 'checked'; - $sDBName = $aParamValues['new_db_name']; - } - $aForm[] = array('label' => " "); - $oP->form($aForm); - - $oP->add_ready_script("$('#new_db_name').click( function() { $('#new_db').attr('checked', true); })"); - $oP->add("
\n"); - $aForm = array(); - $aForm[] = array('label' => "Add a prefix to all the tables: "); - $oP->form($aForm); - - $oP->add("

Next: iTop modules selection

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - } - $oP->add("
\n"); -} - -/** - * Display the form to select the iTop modules to be installed - */ -function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - $sPrevOperation = 'step'.($iCurrentStep-1); - - $sDBName = $aParamValues['db_name']; - if ($sDBName == '') - { - $sDBName = $aParamValues['new_db_name']; - } - $sDBPrefix = $aParamValues['db_prefix']; - $oConfig->SetDBName($sDBName); - $oConfig->SetDBSubname($sDBPrefix); - $oConfig->WriteToFile(TMP_CONFIG_FILE); - - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('module')); - $sRedStar = '*'; - $oP->set_title("iTop modules selection"); - $oP->add("

Customize your iTop installation to fit your needs

\n"); - $aAvailableModules = GetAvailableModules($oP); - - // Form goes here - $oP->add("
Select the iTop modules you want to install:\n"); - $oP->add("
"); - $sRedStar = '*'; - $index = 0; - $aSelectedModules = $aParamValues['module']; - if ($aSelectedModules == '') - { - // Make sure it gets initialized as an array, default value: all modules selected ! - $aSelectedModules = array(); - foreach($aAvailableModules as $sModuleId => $aModule) - { - $aSelectedModules[] = $sModuleId; - } - } - foreach($aAvailableModules as $sModuleId => $aModule) - { - $sModuleLabel = $aModule['label']; - $sModuleHelp = $aModule['doc.more_information']; - $sClass = ($aModule['mandatory']) ? 'class="read-only"' : ''; - $sChecked = ($aModule['mandatory'] || in_array($sModuleId, $aSelectedModules) ) ? 'checked' : ''; - $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; - if ($aModule['category'] == 'authentication') - { - // For now authentication modules are always on and hidden - $oP->add("\n"); - $index++; - } - elseif ($aModule['visible']) - { - $oP->add("

$sMoreInfo

\n"); - $index++; - } - else - { - // For now hidden modules are always on ! - $oP->add("\n"); - $index++; - } - } - $oP->add("
"); - $oP->add("
\n"); - $oP->add("

Next: Administrator account creation

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add_ready_script("$('.read-only').click( function() { $(this).attr('checked','checked'); } );"); - -} -/** - * Display the form for the third step of the configuration wizard - * which consists in - * 1) Validating the parameters by connecting to the database server & selecting the database - * 2) Creating the database structure - * 3) Prompting for the admin account to be created - */ -function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - $oP->set_title("Administrator account creation"); - $oP->add("

Creation of the database structure

"); - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('auth_user', 'auth_pwd', 'language')); - - $sDBName = $aParamValues['db_name']; - if ($sDBName == '') - { - $sDBName = $aParamValues['new_db_name']; - } - $sDBPrefix = $aParamValues['db_prefix']; - $oConfig->SetDBName($sDBName); - $oConfig->SetDBSubname($sDBPrefix); - $aAvailableModules = GetAvailableModules($oP); - BuildConfig($oP, $oConfig, $aParamValues, $aAvailableModules); // Load all the includes based on the modules selected - $oConfig->WriteToFile(TMP_CONFIG_FILE); - if (CreateDatabaseStructure($oP, $oConfig, $sDBName, $sDBPrefix, $aParamValues['module'])) - { - $sRedStar = "*"; - $oP->add("

Default language for the application:

\n"); - // Possible languages (depends on the dictionaries loaded in the config) - $aForm = array(); - $aAvailableLanguages = Dict::GetLanguages(); - $sLanguages = ''; - $sDefaultCode = $oConfig->GetDefaultLanguage(); - foreach($aAvailableLanguages as $sLangCode => $aInfo) - { - $sSelected = ($sLangCode == $sDefaultCode ) ? 'selected ' : ''; - $sLanguages.=""; - } - - $aForm[] = array('label' => "Default Language$sRedStar:", 'input' => ""); - $aForm[] = array('label' => "Password$sRedStar:", 'input' => ""); - $aForm[] = array('label' => "Retype password$sRedStar:", 'input' => ""); - $oP->form($aForm); - $oP->add("\n"); - $oP->add("

Next: Application initialization

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - } - else - { - $oP->add("\n"); - } - // Form goes here - $oP->add("
\n"); -} - -/** - * Display the form for the fourth step of the configuration wizard - * which consists in - * 1) Creating the admin user account - * 2) Prompting to load some sample data - */ -function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) -{ - $sNextOperation = 'step'.($iCurrentStep+1); - - $oP->set_title("Application initialization"); - $sAdminUser = $aParamValues['auth_user']; - $sAdminPwd = $aParamValues['auth_pwd']; - $sLanguage = $aParamValues['language']; - $oConfig->SetDefaultLanguage($aParamValues['language']); - $oConfig->WriteToFile(TMP_CONFIG_FILE); - - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues, array('sample_data')); - - InitDataModel($oP, TMP_CONFIG_FILE, false); // load data model and connect to the database - - // Perform here additional DB setup - // Moved here to spread the setup duration between two steps of the wizard (timeouts...) - $aAvailableModules = GetAvailableModules($oP); - foreach($aParamValues['module'] as $sModuleId) - { - if (isset($aAvailableModules[$sModuleId]['installer'])) - { - $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; - // The validity of the sModuleInstallerClass has been established in BuildConfig() - $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); - call_user_func_array($aCallSpec, array($oConfig)); - } - } - - if (CreateAdminAccount($oP, $oConfig, $sAdminUser, $sAdminPwd, $sLanguage)) - { - $oP->add("

Loading of sample data

\n"); - $oP->p("
Do you want to load sample data into the database ? \n"); - $oP->p("\n"); - $oP->p("\n"); - $oP->p("
\n"); - $oP->add("

Next: Setup complete

\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("\n"); - $oP->add("
\n"); - } - else - { - // Creation failed - $oP->error("Internal error: Failed to create the admin account or to setup the user rights"); - $oP->add("\n"); - } - // End of visible form - $oP->add("
\n"); - // Hidden form submitted when moving on to the next page, once all the data files - // have been processed - $oP->add("
\n"); - AddParamsToForm($oP, $aParamValues, array('sample_data')); - $oP->add("\n"); - $oP->add("
\n"); - $oP->add("
\n"); - $oP->add_linked_script('./jquery.progression.js'); - - PopulateDataFilesList($oP, $aParamValues); -} -/** - * Display the form for the fifth (and final) step of the configuration wizard - * which consists in - * 1) Creating the final configuration file - * 2) Prompting the user to make the file read-only - */ -function SetupFinished(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) -{ - $sAuthUser = $aParamValues['auth_user']; - $sAuthPwd = $aParamValues['auth_pwd']; - try - { - $sSessionName = sprintf('iTop-%x', rand()); - $oConfig->Set('session_name', $sSessionName); - session_name($sSessionName); - session_start(); - - // Write the final configuration file - $oConfig->WriteToFile(FINAL_CONFIG_FILE); - - // Start the application - InitDataModel($oP, FINAL_CONFIG_FILE, false); // Load model and startup DB - if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) - { - UserRights::Login($sAuthUser); - $_SESSION['auth_user'] = $sAuthUser; - $_SESSION['login_mode'] = 'form'; // Will enable the "log-off button" - - // remove the tmp config file - @unlink(TMP_CONFIG_FILE); - // try to make the final config file read-only - @chmod(FINAL_CONFIG_FILE, 0440); // Read-only for owner and group, nothing for others - - $oP->set_title("Setup complete"); - $oP->add("
\n"); - - // Check if there are some manual steps required: - $aAvailableModules = GetAvailableModules($oP); - $aManualSteps = array(); - foreach($aParamValues['module'] as $sModuleId) - { - if (!empty($aAvailableModules[$sModuleId]['doc.manual_setup'])) - { - $aManualSteps[$aAvailableModules[$sModuleId]['label']] = $aAvailableModules[$sModuleId]['doc.manual_setup']; - } - } - if (count($aManualSteps) > 0) - { - $oP->add("

Manual operations required

"); - $oP->p("In order to complete the installation, the following manual operations are required:"); - foreach($aManualSteps as $sModuleLabel => $sUrl) - { - $oP->p("Manual instructions for $sModuleLabel"); - } - } - else - { - $oP->add("

Congratulations for installing iTop

"); - $oP->ok("The initialization completed successfully."); - } - // Form goes here.. No back button since the job is done ! - $oP->add(''); - $oP->add(""); - $oP->add(""); - $oP->add(""); - $oP->add('
'); - $oP->add("

\n"); - $oP->add("
\n"); - } - else - { - $oP->add("

iTop configuration wizard

\n"); - $oP->add("

Step 5: Configuration completed

\n"); - - @unlink(FINAL_CONFIG_FILE); // remove the aborted config - $oP->error("Error: Failed to login for user: '$sAuthUser'\n"); - - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues); - $oP->add("\n"); - $oP->add("
\n"); - } - } - catch(Exception $e) - { - $oP->error("Error: unable to create the configuration file."); - $oP->p($e->getHtmlDesc()); - $oP->p("Did you forget to remove the previous (read-only) configuration file ?"); - $oP->add("
\n"); - $oP->add("\n"); - AddParamsToForm($oP, $aParamValues); - $oP->add("\n"); - $oP->add("
\n"); - } -} - -/////////////////////////////////////////////////////////////////////////////////////////////////// -// Main program -/////////////////////////////////////////////////////////////////////////////////////////////////// - -clearstatcache(); // Make sure we know what we are doing ! -if (file_exists(FINAL_CONFIG_FILE)) -{ - // The configuration file already exists - if (is_writable(FINAL_CONFIG_FILE)) - { - $oP->warning("Warning: a configuration file '".FINAL_CONFIG_FILE."' already exists, and will be overwritten."); - } - else - { - $oP->add("

iTop configuration wizard

\n"); - $oP->add("

Fatal error

\n"); - $oP->error("Error: the configuration file '".FINAL_CONFIG_FILE."' already exists and cannot be overwritten."); - $oP->p("The wizard cannot create the configuration file for you. Please remove the file '".realpath(FINAL_CONFIG_FILE)."' or change its access-rights/read-only flag before continuing."); - $oP->output(); - exit; - } -} -else -{ - // No configuration file yet - // Check that the wizard can write into the root dir to create the configuration file - if (!is_writable(dirname(TMP_CONFIG_FILE))) - { - $oP->add("

iTop configuration wizard

\n"); - $oP->add("

Fatal error

\n"); - $oP->error("Error: the directory where to store the configuration file is not writable."); - $oP->p("The wizard cannot create the configuration file for you. Please make sure that the directory '".realpath(dirname(TMP_CONFIG_FILE))."' is writable for the web server."); - $oP->output(); - exit; - } - if (!is_writable(dirname(TMP_CONFIG_FILE).'/setup')) - { - $oP->add("

iTop configuration wizard

\n"); - $oP->add("

Fatal error

\n"); - $oP->error("Error: the directory where to store temporary setup files is not writable."); - $oP->p("The wizard cannot create operate. Please make sure that the directory '".realpath(dirname(TMP_CONFIG_FILE))."/setup' is writable for the web server."); - $oP->output(); - exit; - } - -} -try -{ - $oConfig = new Config(TMP_CONFIG_FILE); -} -catch(Exception $e) -{ - // We'll end here when the tmp config file does not exist. It's normal - $oConfig = new Config(TMP_CONFIG_FILE, false /* Don't try to load it */); -} -try -{ - // Set a long (at least 4 minutes) execution time for the setup to avoid timeouts during this phase - ini_set('max_execution_time', max(240, ini_get('max_execution_time'))); - // While running the setup it is desirable to see any error that may happen - ini_set('display_errors', true); - ini_set('display_startup_errors', true); - - $aParams = array('licence_ok', 'db_server', 'db_user', 'db_pwd','db_name', 'new_db_name', 'db_prefix', 'module', 'sample_data', 'auth_user', 'auth_pwd', 'language'); - foreach($aParams as $sName) - { - $aParamValues[$sName] = utils::ReadParam($sName, ''); - } - - switch($sOperation) - { - case 'step0': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 0 ========"); - WelcomeAndCheckPrerequisites($oP, $aParamValues, 0); - break; - - case 'step1': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 1 ========"); - LicenceAcknowledgement($oP, $aParamValues, 1); - break; - - case 'step2': - $oP->log("Info - ========= Wizard step 2 ========"); - DatabaseServerSelection($oP, $aParamValues, 2); - break; - - case 'step3': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 3 ========"); - DatabaseInstanceSelection($oP, $aParamValues, 3, $oConfig); - break; - - case 'step4': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 4 ========"); - ModulesSelection($oP, $aParamValues, 4, $oConfig); - break; - - - case 'step5': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 5 ========"); - AdminAccountDefinition($oP, $aParamValues, 5, $oConfig); - break; - - case 'step6': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 6 ========"); - SampleDataSelection($oP, $aParamValues, 6, $oConfig); - break; - - case 'step7': - $oP->no_cache(); - $oP->log("Info - ========= Wizard step 7 ========"); - SetupFinished($oP, $aParamValues, 7, $oConfig); - break; - - default: - $oP->error("Error: unsupported operation '$sOperation'"); - - } -} -catch(Exception $e) -{ - $oP->error("Error: '".$e->getMessage()."'"); - $oP->add("\n"); -} -catch(CoreException $e) -{ - $oP->error("Error: '".$e->getHtmlDesc()."'"); - $oP->add("\n"); -} -$oP->output(); -?> + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL + */ + +require_once('../approot.inc.php'); +require_once(APPROOT.'/application/utils.inc.php'); +require_once(APPROOT.'/core/config.class.inc.php'); +require_once(APPROOT.'/core/log.class.inc.php'); +require_once(APPROOT.'/core/kpi.class.inc.php'); +require_once(APPROOT.'/core/cmdbsource.class.inc.php'); +require_once(APPROOT.'/setup/setuppage.class.inc.php'); +require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); + +define('TMP_CONFIG_FILE', APPROOT.'/tmp-config-itop.php'); +define('FINAL_CONFIG_FILE', APPROOT.'/config-itop.php'); +define('PHP_MIN_VERSION', '5.2.0'); +define('MYSQL_MIN_VERSION', '5.0.0'); +define('MIN_MEMORY_LIMIT', 32*1024*1024); + +$sOperation = Utils::ReadParam('operation', 'step0'); +$oP = new SetupWebPage('iTop configuration wizard'); + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Various helper function +/////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Get a nicely formatted version string + */ +function GetITopVersion($bShort = true) +{ + $sVersionString = ''; + if ($bShort) + { + $sVersionString = "iTop Version ".ITOP_VERSION; + } + else + { + if (ITOP_REVISION == '$WCREV$') + { + // This is NOT a version built using the buil system, just display the main version + $sVersionString = "iTop Version ".ITOP_VERSION; + } + else + { + // This is a build made from SVN, let display the full information + $sVersionString = "iTop Version ".ITOP_VERSION." revision ".ITOP_REVISION.", built on: ".ITOP_BUILD_DATE; + } + } + return $sVersionString; +} + +/** + * Helper function to retrieve the system's temporary directory + * Emulates sys_get_temp_dir if neeed (PHP < 5.2.1) + * @return string Path to the system's temp directory + */ +function GetTmpDir() +{ + // try to figure out what is the temporary directory + // prior to PHP 5.2.1 the function sys_get_temp_dir + // did not exist + if ( !function_exists('sys_get_temp_dir')) + { + if( $temp=getenv('TMP') ) return realpath($temp); + if( $temp=getenv('TEMP') ) return realpath($temp); + if( $temp=getenv('TMPDIR') ) return realpath($temp); + $temp=tempnam(__FILE__,''); + if (file_exists($temp)) + { + unlink($temp); + return realpath(dirname($temp)); + } + return null; + } + else + { + return realpath(sys_get_temp_dir()); + } +} + +/** + * Helper function to retrieve the directory where files are to be uploaded + * @return string Path to the temp directory used for uploading files + */ +function GetUploadTmpDir() +{ + $sPath = ini_get('upload_tmp_dir'); + if (empty($sPath)) + { + $sPath = GetTmpDir(); + } + return $sPath; +} + +/** + * Helper function to check if the current version of PHP + * is compatible with the application + * @return boolean true if this is Ok, false otherwise + */ +function CheckPHPVersion(SetupWebPage $oP) +{ + $bResult = true; + $aErrors = array(); + $aWarnings = array(); + $aOk = array(); + + $oP->log('Info - CheckPHPVersion'); + if (version_compare(phpversion(), PHP_MIN_VERSION, '>=')) + { + $aOk [] = "The current PHP Version (".phpversion().") is greater than the minimum required version (".PHP_MIN_VERSION.")"; + } + else + { + $aErrors[] = "Error: The current PHP Version (".phpversion().") is lower than the minimum required version (".PHP_MIN_VERSION.")"; + $bResult = false; + } + $aMandatoryExtensions = array('mysql', 'iconv', 'simplexml', 'soap', 'hash', 'json', 'session', 'pcre', 'dom'); + $aOptionalExtensions = array('mcrypt' => 'Strong encryption will not be used.', + 'ldap' => 'LDAP authentication will be disabled.'); + asort($aMandatoryExtensions); // Sort the list to look clean ! + ksort($aOptionalExtensions); // Sort the list to look clean ! + $aExtensionsOk = array(); + $aMissingExtensions = array(); + $aMissingExtensionsLinks = array(); + // First check the mandatory extensions + foreach($aMandatoryExtensions as $sExtension) + { + if (extension_loaded($sExtension)) + { + $aExtensionsOk[] = $sExtension; + } + else + { + $aMissingExtensions[] = $sExtension; + $aMissingExtensionsLinks[] = "$sExtension"; + } + } + if (count($aExtensionsOk) > 0) + { + $aOk[] = "Required PHP extension(s): ".implode(', ', $aExtensionsOk)."."; + } + if (count($aMissingExtensions) > 0) + { + $aErrors[] = "Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."; + $bResult = false; + } + // Next check the optional extensions + $aExtensionsOk = array(); + $aMissingExtensions = array(); + foreach($aOptionalExtensions as $sExtension => $sMessage) + { + if (extension_loaded($sExtension)) + { + $aExtensionsOk[] = $sExtension; + } + else + { + $aMissingExtensions[$sExtension] = $sMessage; + } + } + if (count($aExtensionsOk) > 0) + { + $aOk[] = "Optional PHP extension(s): ".implode(', ', $aExtensionsOk)."."; + } + if (count($aMissingExtensions) > 0) + { + foreach($aMissingExtensions as $sExtension => $sMessage) + { + $aWarnings[] = "Missing optional PHP extension: $sExtension. ".$sMessage; + } + } + // Check some ini settings here + if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4 + { + $sPhpIniFile = php_ini_loaded_file(); + // Other included/scanned files + if ($sFileList = php_ini_scanned_files()) + { + if (strlen($sFileList) > 0) + { + $aFiles = explode(',', $sFileList); + + foreach ($aFiles as $sFile) + { + $sPhpIniFile .= ', '.trim($sFile); + } + } + } + $oP->log("Info - php.ini file(s): '$sPhpIniFile'"); + } + else + { + $sPhpIniFile = 'php.ini'; + } + if (!ini_get('file_uploads')) + { + $aErrors[] = "Files upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."; + $bResult = false; + } + + $sUploadTmpDir = GetUploadTmpDir(); + if (empty($sUploadTmpDir)) + { + $sUploadTmpDir = '/tmp'; + $aErrors[] = "Temporary directory for files upload is not defined (upload_tmp_dir), assuming that $sUploadTmpDir is used."; + } + // check that the upload directory is indeed writable from PHP + if (!empty($sUploadTmpDir)) + { + if (!file_exists($sUploadTmpDir)) + { + $aErrors[] = "Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."; + $bResult = false; + } + else if (!is_writable($sUploadTmpDir)) + { + $aErrors[] = "Temporary directory for files upload ($sUploadTmpDir) is not writable."; + $bResult = false; + } + else + { + $oP->log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); + } + } + + + if (!ini_get('upload_max_filesize')) + { + $aErrors[] = "File upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."; + } + + $iMaxFileUploads = ini_get('max_file_uploads'); + if (!empty($iMaxFileUploads) && ($iMaxFileUploads < 1)) + { + $aErrors[] = "File upload is not allowed on this server (max_file_uploads = ".ini_get('max_file_uploads').")."; + $bResult = false; + } + $oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); + $oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads')); + + // Check some more ini settings here, needed for file upload + if (get_magic_quotes_gpc()) + { + $aErrors[] = "'magic_quotes_gpc' is set to On. Please turn it Off before continuing. You may want to check the PHP configuration file(s): '$sPhpIniFile'. Be aware that this setting can also be overridden in the apache configuration."; + $bResult = false; + } + + $sMemoryLimit = trim(ini_get('memory_limit')); + if (empty($sMemoryLimit)) + { + // On some PHP installations, memory_limit does not exist as a PHP setting! + // (encountered on a 5.2.0 under Windows) + // In that case, ini_set will not work, let's keep track of this and proceed anyway + $aWarnings[] = "No memory limit has been defined in this instance of PHP"; + } + else + { + // Check that the limit will allow us to load the data + // + $iMemoryLimit = utils::ConvertToBytes($sMemoryLimit); + if ($iMemoryLimit < MIN_MEMORY_LIMIT) + { + $aErrors[] = "memory_limit ($iMemoryLimit) is too small, the minimum value to run iTop is ".MIN_MEMORY_LIMIT."."; + $bResult = false; + } + else + { + $oP->log_info("memory_limit is $iMemoryLimit, ok."); + } + } + + if (!$bResult) + { + $sTitle = 'Checking prerequisites: Failed !'; + } + else + { + if (count($aWarnings) > 0) + { + $sTitle = ' Checking prerequisites: Warning (show details)'; + $oP->add_ready_script("$('#prereq_details').hide();\n"); + } + else + { + $sTitle = ' Checking prerequisites: Ok (show details)'; + $oP->add_ready_script("$('#prereq_details').hide();\n"); + } + } + $oP->add("

$sTitle

\n"); + $oP->add("
\n"); + foreach($aErrors as $sError) + { + $oP->error($sError); + //$oP->add_ready_script("$('#prereq_details').show();"); + } + foreach($aWarnings as $sWarning) + { + $oP->warning($sWarning); + } + foreach($aOk as $sOk) + { + $oP->ok($sOk); + } + $oP->add("
\n"); + return $bResult; +} + +/** + * Helper function check the connection to the database and (if connected) to enumerate + * the existing databases + * @return Array The list of databases found in the server + */ +function CheckServerConnection(SetupWebPage $oP, $sDBServer, $sDBUser, $sDBPwd) +{ + $aResult = array(); + $oP->log('Info - CheckServerConnection'); + try + { + $oDBSource = new CMDBSource; + $oDBSource->Init($sDBServer, $sDBUser, $sDBPwd); + $oP->ok("Connection to '$sDBServer' as '$sDBUser' successful."); + + $oP->log("Info - User privileges: ".($oDBSource->GetRawPrivileges())); + + $sDBVersion = $oDBSource->GetDBVersion(); + if (version_compare($sDBVersion, MYSQL_MIN_VERSION, '>=')) + { + $oP->ok("Current MySQL version ($sDBVersion), greater than minimum required version (".MYSQL_MIN_VERSION.")"); + // Check some server variables + $iMaxAllowedPacket = $oDBSource->GetServerVariable('max_allowed_packet'); + $iMaxUploadSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); + if ($iMaxAllowedPacket >= (500 + $iMaxUploadSize)) // Allow some space for the query + the file to upload + { + $oP->ok("MySQL server's max_allowed_packet is big enough."); + } + else if($iMaxAllowedPacket < $iMaxUploadSize) + { + $oP->warning("MySQL server's max_allowed_packet ($iMaxAllowedPacket) is not big enough. Please, consider setting it to at least ".(500 + $iMaxUploadSize)."."); + } + $oP->log("Info - MySQL max_allowed_packet: $iMaxAllowedPacket"); + $iMaxConnections = $oDBSource->GetServerVariable('max_connections'); + if ($iMaxConnections < 5) + { + $oP->warning("MySQL server's max_connections ($iMaxConnections) is not enough. Please, consider setting it to at least 5."); + } + $oP->log("Info - MySQL max_connections: ".($oDBSource->GetServerVariable('max_connections'))); + } + else + { + $oP->error("Error: Current MySQL version is ($sDBVersion), minimum required version (".MYSQL_MIN_VERSION.")"); + return false; + } + try + { + $aResult = $oDBSource->ListDB(); + } + catch(Exception $e) + { + $oP->warning("Warning: unable to enumerate the current databases."); + $aResult = true; // Not an array to differentiate with an empty array + } + } + catch(Exception $e) + { + $oP->error("Error: Connection to '$sDBServer' as '$sDBUser' failed."); + $oP->p($e->GetHtmlDesc()); + $aResult = false; + } + return $aResult; +} + +/** + * Scans the ./data directory for XML files and output them as a Javascript array + */ +function PopulateDataFilesList(SetupWebPage $oP, $aParamValues, $oConfig) +{ + + $oP->add("\n"); +} + +/** + * Add some parameters as hidden inputs into a form + * @param SetupWebpage $oP The page to insert the form elements into + * @param Hash $aParamValues The pairs name/value to be stored in the form + * @param Array $aExcludeParams A list of parameters to exclude from the previous hash + */ +function AddParamsToForm(SetupWebpage $oP, $aParamValues, $aExcludeParams = array()) +{ + foreach($aParamValues as $sName => $value) + { + if(!in_array($sName, $aExcludeParams)) + { + AddHiddenParam($oP, $sName, $value); + } + } +} + +/** + * Add a hidden field to store the specified parameter + * @param $sName string Name of the parameter + * @param $value mixed Value of the parameter + */ +function AddHiddenParam($oP, $sName, $value) +{ + if (is_array($value)) + { + foreach($value as $sKey => $sItem) + { + $oP->add(''); + } + } + else + { + $oP->add(''); + } +} + +/** + * Build the config file from the parameters (especially the selected modules) + */ +function BuildConfig(SetupWebpage $oP, Config &$oConfig, $aParamValues, $aAvailableModules) +{ + // Initialize the arrays below with default values for the application... + $aAddOns = $oConfig->GetAddOns(); + $aAppModules = $oConfig->GetAppModules(); + $aDataModels = $oConfig->GetDataModels(); + $aWebServiceCategories = $oConfig->GetWebServiceCategories(); + $aDictionaries = $oConfig->GetDictionaries(); + // Merge the values with the ones provided by the modules + // Make sure when don't load the same file twice... + foreach($aParamValues['module'] as $sModuleId) + { + $oP->log('Installed iTop module: '. $sModuleId); + if (isset($aAvailableModules[$sModuleId]['datamodel'])) + { + $aDataModels = array_unique(array_merge($aDataModels, $aAvailableModules[$sModuleId]['datamodel'])); + } + if (isset($aAvailableModules[$sModuleId]['webservice'])) + { + $aWebServiceCategories = array_unique(array_merge($aWebServiceCategories, $aAvailableModules[$sModuleId]['webservice'])); + } + if (isset($aAvailableModules[$sModuleId]['dictionary'])) + { + $aDictionaries = array_unique(array_merge($aDictionaries, $aAvailableModules[$sModuleId]['dictionary'])); + } + if (isset($aAvailableModules[$sModuleId]['settings'])) + { + foreach($aAvailableModules[$sModuleId]['settings'] as $sProperty => $value) + { + list($sName, $sVersion) = GetModuleName($sModuleId); + $oConfig->SetModuleSetting($sName, $sProperty, $value); + } + } + if (isset($aAvailableModules[$sModuleId]['installer'])) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + if (!class_exists($sModuleInstallerClass)) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not a PHP class - Module: ".$aAvailableModules[$sModuleId]['label']); + } + if (!is_subclass_of($sModuleInstallerClass, 'ModuleInstallerAPI')) + { + throw new Exception("Wrong installer class: '$sModuleInstallerClass' is not derived from 'ModuleInstallerAPI' - Module: ".$aAvailableModules[$sModuleId]['label']); + } + $aCallSpec = array($sModuleInstallerClass, 'BeforeWritingConfig'); + $oConfig = call_user_func_array($aCallSpec, array($oConfig)); + } + } + $oConfig->SetAddOns($aAddOns); + $oConfig->SetAppModules($aAppModules); + $oConfig->SetDataModels($aDataModels); + $oConfig->SetWebServiceCategories($aWebServiceCategories); + $oConfig->SetDictionaries($aDictionaries); +} + +///////////////////////////////////////////////////////////////////////////////////////////////////////////// +// Handling of the different steps of the setup wizard +///////////////////////////////////////////////////////////////////////////////////////////////////////////// + +/** + * Displays the welcome screen and check some basic prerequisites + */ +function WelcomeAndCheckPrerequisites(SetupWebPage $oP, $aParamValues, $iCurrentStep) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $aParamValues['previous_step'] = 0; + + $oP->add("

iTop configuration wizard

\n"); + $sVersionStringShort = GetITopVersion(true); + $sVersionStringLong = GetITopVersion(false); + $oP->set_title('Welcome to '.$sVersionStringShort); + $oP->log($sVersionStringLong); + $aPreviousParams = array(); + $oP->add("
\n"); + $sMode = 'install'; // Fresh install + + // Check for a previous version + if (file_exists(FINAL_CONFIG_FILE)) + { + $oConfig = new Config(FINAL_CONFIG_FILE); + $oConfig->WriteToFile(TMP_CONFIG_FILE); + + $aVersion = AnalyzeInstallation($oConfig); + if (!empty($aVersion['iTop']['version_db'])) + { + $aPreviousParams = array('mode', 'db_server', 'db_user', 'db_pwd','db_name', 'new_db_name', 'db_prefix'); + $sMode = 'upgrade'; + if ($aVersion['iTop']['version_db'] == $aVersion['iTop']['version_code']) + { + $oP->ok("Version {$aVersion['iTop']['version_db']} of iTop detected.
The same version of the application will be reinstalled."); + } + else + { + $oP->ok("Version {$aVersion['iTop']['version_db']} of iTop detected.
The application will be upgraded to version {$aVersion['iTop']['version_code']}"); + } + AddHiddenParam($oP, 'db_server', $oConfig->GetDBHost()); + AddHiddenParam($oP, 'db_user', $oConfig->GetDBUser()); + AddHiddenParam($oP, 'db_pwd', $oConfig->GetDBPwd()); + AddHiddenParam($oP, 'db_name', $oConfig->GetDBName()); + AddHiddenParam($oP, 'db_prefix', $oConfig->GetDBSubname()); + AddHiddenParam($oP, 'mode', $sMode); + if (CheckPHPVersion($oP)) + { + $oP->add("

Next: Licence agreement

\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, $aPreviousParams); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + } + return; + } + // else, normal install ?? + } + + if (CheckPHPVersion($oP)) + { + $oP->add("

What do you want to do?

\n"); + $sChecked = ($aParamValues['mode'] == 'install') ? 'checked' : ''; + $oP->p(" Install a new iTop\n"); + $sChecked = ($aParamValues['mode'] == 'upgrade') ? 'checked' : ''; + $oP->p(" Upgrade an existing iTop instance\n"); + $oP->add("

Next: Licence agreement

\n"); + $oP->add("\n"); + $aPreviousParams = array('mode'); + AddParamsToForm($oP, $aParamValues, $aPreviousParams); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("
\n"); + } +} + +function LicenceAcknowledgement($oP, $aParamValues, $iCurrentStep) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $iPrevStep = 0; + $aParamValues['previous_step'] = $iCurrentStep; // Come back here + + $oP->set_title('License agreement'); + $oP->add('

iTop is released by Combodo SARL under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.

'); + $oP->add("\n"); + $oP->add("
\n"); + AddParamsToForm($oP, $aParamValues, array('licence_ok')); + + $sChecked = $aParamValues['licence_ok'] == 1 ? 'checked' : ''; + $oP->add("

\n"); + + if (file_exists(FINAL_CONFIG_FILE)) + { + $oP->add("

Next: Modules selection

\n"); + $sNextOperation = 'step4'; + } + else + { + $oP->add("

Next: Database server selection

\n"); + } + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("
\n"); +} + +/** + * Display the form for the first step of the configuration wizard + * which consists in the database server selection + */ +function DatabaseServerSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $iPrevStep = 1; + $aParamValues['previous_step'] = $iCurrentStep; // Come back here + + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, array('db_server', 'db_user', 'db_pwd')); + if ($aParamValues['licence_ok'] == 1) + { + $sRedStar = '*'; + $oP->set_title("Database server selection\n"); + $oP->add("

Please enter the name of the MySQL database server you want to use for iTop and supply valid credentials to connect to it

\n"); + // Form goes here + $oP->add("
Database connection\n"); + $aForm = array(); + $aForm[] = array('label' => "Server name$sRedStar:", 'input' => "", + 'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23"'); + $aForm[] = array('label' => "User name$sRedStar:", 'input' => "", + 'help' => 'The account must have the following privileges on the database: SELECT, INSERT, UPDATE, DELETE, CREATE, ALTER, CREATE VIEW, TRIGGER'); + $aForm[] = array('label' => 'Password:', 'input' => ""); + $oP->form($aForm); + $oP->add("
\n"); + $oP->add("

Next: Database instance Selection

\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + } + else + { + $oP->add("\n"); + } + $oP->add("
\n"); +} + +/** + * Display the form for the second step of the configuration wizard + * which consists in + * 1) Validating the parameters by connecting to the database server + * 2) Prompting to select an existing database or to create a new one + */ +function DatabaseInstanceSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $iPrevStep = 2; + $aParamValues['previous_step'] = $iCurrentStep; // Come back here + + $oP->set_title("Database instance selection\n"); + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, array('db_name', 'db_prefix', 'new_db_name')); + $sDBServer = $aParamValues['db_server']; + $sDBUser = $aParamValues['db_user']; + $sDBPwd = $aParamValues['db_pwd']; + $aDatabases = CheckServerConnection($oP, $sDBServer, $sDBUser, $sDBPwd); + if ($aDatabases === false) + { + // Connection failed, invalid credentials ? Go back + $oP->add("\n"); + } + else + { + // Connection is Ok, save it and continue the setup wizard + $oConfig->SetDBHost($sDBServer); + $oConfig->SetDBUser($sDBUser); + $oConfig->SetDBPwd($sDBPwd); + $oConfig->WriteToFile(); + + $oP->add("
Select the database instance to use for iTop*\n"); + $aForm = array(); + $bExistingChecked = false; + if (is_array($aDatabases)) + { + foreach($aDatabases as $sDBName) + { + $sChecked = ''; + if ($aParamValues['db_name'] == $sDBName) + { + $sChecked = 'checked'; + $bExistingChecked = true; + } + $aForm[] = array('label' => ""); + } + } + else + { + $aForm[] = array('label' => " "); + $oP->add_ready_script("$('#current_db_name').click( function() { $('#current_db').attr('checked', true); });"); + } + $sChecked = ''; + $sDBName = ''; + // If the 'Create Database' option was checked... and the database still does not exist + if (!$bExistingChecked && !empty($aParamValues['new_db_name'])) + { + $sChecked = 'checked'; + $sDBName = $aParamValues['new_db_name']; + } + if ($aParamValues['mode'] == 'install') + { + $aForm[] = array('label' => " "); + } + $oP->add('
'); + $oP->form($aForm); + $oP->add('
'); + + $oP->add_ready_script("$('#new_db_name').click( function() { $('#new_db').attr('checked', true); })"); + $oP->add("
\n"); + $aForm = array(); + if ($aParamValues['mode'] == 'install') + { + $aForm[] = array('label' => "Add a prefix to all the tables: "); + } + else + { + $aForm[] = array('label' => "The following prefix is used for all tables: "); + } + $oP->form($aForm); + + $oP->add("

Next: iTop modules selection

\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + } + $oP->add("
\n"); +} + +/** + * Display the form to select the iTop modules to be installed + */ +function ModulesSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, $oConfig) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $aParamValues['previous_step'] = $iCurrentStep; // Come back here + + $sDBName = $aParamValues['db_name']; + if ($sDBName == '') + { + $sDBName = $aParamValues['new_db_name']; + } + + $sDBPrefix = $aParamValues['db_prefix']; + $oConfig->SetDBName($sDBName); + $oConfig->SetDBSubname($sDBPrefix); + $oConfig->WriteToFile(TMP_CONFIG_FILE); + + $oP->add("
\n"); + AddParamsToForm($oP, $aParamValues, array('module')); + $sRedStar = '*'; + $oP->set_title("iTop modules selection"); + + $aAvailableModules = AnalyzeInstallation($oConfig); + + // Form goes here + if ($aParamValues['mode'] == 'upgrade') + { + $iPrevStep = 1; // depends on where we came from + if (empty($aAvailableModules['iTop']['version_db'])) + { + $oP->error("Unable to detect the previous installation of iTop. The upgrade cannot continue.\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
 
\n"); + $oP->add("
\n"); + return; + } + $oP->add("

Customize your iTop installation to fit your needs

\n"); + $oP->add("
Select the iTop modules you want to install or upgrade:\n"); + } + else + { + $iPrevStep = 3; // depends on where we came from + if (!empty($aAvailableModules['iTop']['version_db'])) + { + $oP->error("A instance of iTop already exists. Please select the \"Upgrade\" mode to upgrade it.\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
 
\n"); + $oP->add("\n"); + return; + } + $oP->add("

Customize your iTop installation to fit your needs

\n"); + $oP->add("
Select the iTop modules you want to install:\n"); + } + $oP->add("
"); + $sRedStar = '*'; + $index = 0; + $aSelectedModules = $aParamValues['module']; + if ($aSelectedModules == '') + { + // Make sure it gets initialized as an array, default value: all modules selected ! + $aSelectedModules = array(); + foreach($aAvailableModules as $sModuleId => $aModule) + { + $aSelectedModules[] = $sModuleId; + } + } + foreach($aAvailableModules as $sModuleId => $aModule) + { + if ($sModuleId == 'iTop') continue; // Convention: the version number of the application is stored as a module named 'iTop' + $sModuleLabel = $aModule['label']; + $sModuleHelp = $aModule['doc.more_information']; + //$sClass = ($aModule['mandatory']) ? 'class="read-only"' : ''; + $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; + if ($aModule['category'] == 'authentication') + { + // For now authentication modules are always on and hidden + $oP->add("\n"); + $index++; + } + elseif ($aModule['visible']) + { + switch($aModule['install']['flag']) + { + case MODULE_ACTION_OPTIONAL: + $sClass = ''; + if ($aParamValues['mode'] == 'upgrade') + { + if (!empty($aParamValues['module'])) + { + $sChecked = in_array($sModuleId, $aParamValues['module']) ? 'checked' : ''; + } + else + { + $sChecked = ''; + // Default value: modules previously installed are checked + if (!empty($aModule['version_db'])) + { + $sChecked = 'checked'; // Checked if previously installed + // Previously installed, are we allowed to uninstall this module ? + if ($aModule['install']['flag'] == MODULE_ACTION_IMPOSSIBLE) + { + $sClass = 'class="read-only"'; + } + } + } + } + else + { + if (!empty($aParamValues['module'])) + { + $sChecked = in_array($sModuleId, $aParamValues['module']) ? 'checked' : ''; + } + else + { + $sChecked = 'checked'; + } + } + $oP->add("

$sMoreInfo

\n"); + break; + + case MODULE_ACTION_MANDATORY: + $oP->add("

$sMoreInfo

\n"); + break; + + case MODULE_ACTION_IMPOSSIBLE: + if ($aParamValues['mode'] == 'upgrade') + { + if (!empty($aModule['version_db'])) + { + // Previously installed, are we allowed to uninstall this module ? + if ($aModule['uninstall']['flag'] == MODULE_ACTION_IMPOSSIBLE) + { + $oP->error('Error: impossible to uninstall the module: '.$aModule['label']."({$aModule['uninstall']['message']})"); + } + } + } + else + { + $oP->add("

$sMoreInfo

\n"); + } + break; + + } + $index++; + } + else + { + // For now hidden modules are always on ! + $oP->add("\n"); + $index++; + } + } + $oP->add("
"); + $oP->add("
\n"); + if ($aParamValues['mode'] == 'upgrade') + { + $oP->add("

Next: Upgrade summary

\n"); + AddHiddenParam($oP, 'operation', 'step6'); + } + else + { + $oP->add("

Next: Administrator account definition

\n"); + AddHiddenParam($oP, 'operation', 'step5'); + } + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("\n"); + $oP->add_ready_script("$('.read-only').click( function() { $(this).attr('checked','checked'); } );"); + +} +/** + * Display the form for the third step of the configuration wizard + * which consists in + * 1) Validating the parameters by connecting to the database server & selecting the database + * 2) Creating the database structure + * 3) Prompting for the admin account to be created + */ +function AdminAccountDefinition(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) +{ + $sNextOperation = 'step'.($iCurrentStep+1); + $iPrevStep = 4; + $aParamValues['previous_step'] = $iCurrentStep; // Come back here + + $oP->set_title("Administrator account definition"); + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, array('auth_user', 'auth_pwd', 'language')); + + $aAvailableModules = AnalyzeInstallation($oConfig); + BuildConfig($oP, $oConfig, $aParamValues, $aAvailableModules); // Load all the includes based on the modules selected + $oConfig->WriteToFile(TMP_CONFIG_FILE); + InitDataModel(TMP_CONFIG_FILE, true); // Needed to know the available languages + $sRedStar = "*"; + $oP->add("

Default language for the application:

\n"); + // Possible languages (depends on the dictionaries loaded in the config) + $aForm = array(); + $aAvailableLanguages = Dict::GetLanguages(); + $sLanguages = ''; + $sDefaultCode = $oConfig->GetDefaultLanguage(); + foreach($aAvailableLanguages as $sLangCode => $aInfo) + { + $sSelected = ($sLangCode == $sDefaultCode ) ? 'selected ' : ''; + $sLanguages.=""; + } + + $aForm[] = array('label' => "Default Language$sRedStar:", 'input' => ""); + $aForm[] = array('label' => "Password$sRedStar:", 'input' => ""); + $aForm[] = array('label' => "Retype password$sRedStar:", 'input' => ""); + $oP->form($aForm); + $oP->add("
\n"); + $oP->add("

Next: Sample data selection

\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + + // Form goes here + $oP->add("\n"); +} + +/** + * Display the form for the fourth step of the configuration wizard + * which consists in + * 1) Creating the admin user account + * 2) Prompting to load some sample data + */ +function SampleDataSelection(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) +{ + $sNextOperation = 'step7'; + if ($aParamValues['mode'] == 'upgrade') + { + $iPrevStep = 4; + } + else + { + $iPrevStep = 5; + } + + $oP->set_title("Application initialization"); + $sAdminUser = $aParamValues['auth_user']; + $sAdminPwd = $aParamValues['auth_pwd']; + $sLanguage = $aParamValues['language']; + if (($aParamValues['mode'] == 'install') || $oConfig->GetDefaultLanguage() == '') + { + $oConfig->SetDefaultLanguage($aParamValues['language']); + } + $aAvailableModules = AnalyzeInstallation($oConfig); + BuildConfig($oP, $oConfig, $aParamValues, $aAvailableModules); // Load all the includes based on the modules selected + + // in case of upgrade, the value is already present in the config file + $oConfig->WriteToFile(TMP_CONFIG_FILE); + + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, array('sample_data')); + + InitDataModel(TMP_CONFIG_FILE, true); // load data model and connect to the database + $aAvailableModules = GetAvailableModules($oP); + foreach($aParamValues['module'] as $sModuleId) + { + if (isset($aAvailableModules[$sModuleId]['installer'])) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + // The validity of the sModuleInstallerClass has been established in BuildConfig() + $aCallSpec = array($sModuleInstallerClass, 'AfterDatabaseCreation'); + call_user_func_array($aCallSpec, array($oConfig)); + } + } + + $oP->add("

Loading of sample data

\n"); + $oP->p("
Do you want to load sample data into the database ? \n"); + $sChecked = ($aParamValues['sample_data'] == 'no') ? '' : 'checked'; + $oP->p("\n"); + $sChecked = ($aParamValues['sample_data'] == 'no') ? 'checked' : ''; + $oP->p("\n"); + $oP->p("
\n"); + $oP->add("

Next: Installation summary

\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + + // End of visible form + $oP->add("
\n"); + // Hidden form submitted when moving on to the next page, once all the data files + // have been processed + $oP->add("
\n"); + AddParamsToForm($oP, $aParamValues, array('sample_data')); + $oP->add("\n"); + $oP->add("
\n"); +} + +/** + * Displays the summary of the actions to be taken + */ +function DisplaySummary(SetupWebPage $oP, $aParamValues, $iCurrentStep, Config $oConfig) +{ + $sMode = $aParamValues['mode']; + $aAvailableModules = AnalyzeInstallation($oConfig); + BuildConfig($oP, $oConfig, $aParamValues, $aAvailableModules); // Load all the includes based on the modules selected + $oConfig->WriteToFile(TMP_CONFIG_FILE); + InitDataModel(TMP_CONFIG_FILE, true); // Needed to know the available languages + + $aInstall = array(); + $aUpgrade = array(); + $aUninstall = array(); + $aUnchanged = array(); + switch($sMode) + { + case 'install': + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != 'iTop') && $aModule['visible']) + { + if (in_array($sModuleId, $aParamValues['module'])) + { + $aInstall[$sModuleId] = $aModule; + } + } + } + $oP->set_title('Installation Summary'); + $oP->add("

iTop version ".$aAvailableModules['iTop']['version_code']." will be installed in the database: ".$oConfig->GetDBName()." on server: ".$oConfig->GetDBHost().".".'

'); + $oP->add('
'); + if (count($aInstall) > 0) + { + $oP->add('

Modules to install

'); + foreach($aInstall as $sModuleId => $aModule) + { + $oP->p(' '.$aModule['label'].' version '.$aModule['version_code']); + } + } + $oP->add('

Sample data

'); + if ($aParamValues['sample_data'] != 'no') + { + $oP->p('Sample data will be loaded for the new modules installed.'); + } + else + { + $oP->p('No sample data will be loaded.'); + } + $oP->add('

Administrator account

'); + $oP->p('Login:'.htmlentities($aParamValues['auth_user'], ENT_QUOTES, 'UTF-8')); + $oP->add('

Default application language:

'); + $aAvailableLanguages = Dict::GetLanguages(); + $oP->p($aAvailableLanguages[$aParamValues['language']]['description']." (".$aAvailableLanguages[$aParamValues['language']]['localized_description'].")"); + $oP->add('
'); + + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("
\n"); + break; + + case 'upgrade': + + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != 'iTop') && $aModule['visible']) + { + if (in_array($sModuleId, $aParamValues['module'])) + { + if (empty($aModule['version_db'])) + { + $aInstall[$sModuleId] = $aModule; + } + else if ($aModule['version_db'] == $aModule['version_code']) + { + $aUnchanged[$sModuleId] = $aModule; + } + else + { + // Consider it's an upgrade... TO DO: handle downgrades ?? + $aUpgrade[$sModuleId] = $aModule; + } + } + else if (!empty($aModule['version_db'])) + { + $aUninstall[$sModuleId] = $aModule; + } + // Else do nothing: the module was not installed and is not selected + } + } + $oP->set_title('Upgrade Summary'); + $oP->add("

iTop instance: database: ".$oConfig->GetDBName()." on server: ".$oConfig->GetDBHost().", version ".$aAvailableModules['iTop']['version_db'].'

'); + $oP->add('

Will be upgraded to '.$aAvailableModules['iTop']['version_code'].'

'); + $oP->add('
'); + if (count($aUpgrade) > 0) + { + $oP->add('

Modules to upgrade

'); + foreach($aUpgrade as $sModuleId => $aModule) + { + $oP->add(' '.$aModule['label'].' version '.$aModule['version_db'].' to version '.$aModule['version_code']); + } + } + if (count($aInstall) > 0) + { + $oP->add('

Modules to install

'); + foreach($aInstall as $sModuleId => $aModule) + { + $oP->p(' '.$aModule['label'].' version '.$aModule['version_code']); + } + } + if (count($aUninstall) > 0) + { + $oP->add('

Modules to remove

'); + foreach($aUninstall as $sModuleId => $aModule) + { + $oP->p(' '.$aModule['label'].' '.$sModuleId.' version '.$aModule['version_db']); + } + } + if (count($aUnchanged) > 0) + { + $oP->add('

Modules that will remain unchanged

'); + foreach($aUnchanged as $sModuleId => $aModule) + { + $oP->p(' '.$aModule['label'].' version '.$aModule['version_db']); + } + } + $oP->add('

Sample data

'); + if ($aParamValues['sample_data'] != 'no') + { + $oP->p('Sample data will be loaded for the new modules installed.'); + } + else + { + $oP->p('No sample data will be loaded.'); + } + $oP->add('
'); + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("\n"); + $oP->add("
\n"); + $oP->add("
\n"); + break; + + default: + $oP->error("Unsupported mode $sMode"); + } + $oP->add("
\n"); + $oP->add_linked_script('./jquery.progression.js'); + PopulateDataFilesList($oP, $aParamValues, $oConfig); + $oP->add_ready_script( +<<Get('session_name'); + if ($sSessionName != '') + { + $sSessionName = sprintf('iTop-%x', rand()); + $oConfig->Set('session_name', $sSessionName); + } + session_name($sSessionName); + session_start(); + + // Migration: force utf8_unicode_ci as the collation to make the global search + // NON case sensitive + $oConfig->SetDBCollation('utf8_unicode_ci'); + + + // Write the final configuration file + $oConfig->WriteToFile(FINAL_CONFIG_FILE); + + // Start the application + InitDataModel(FINAL_CONFIG_FILE, false, true); // Load model, startup DB and load the cache + if ($aParamValues['mode'] == 'install') + { + if (UserRights::CheckCredentials($sAuthUser, $sAuthPwd)) + { + UserRights::Login($sAuthUser); + $_SESSION['auth_user'] = $sAuthUser; + $_SESSION['login_mode'] = 'form'; // Will enable the "log-off button" + } + else + { + $oP->add("

iTop configuration wizard

\n"); + $oP->add("

Step 5: Configuration completed

\n"); + + @unlink(FINAL_CONFIG_FILE); // remove the aborted config + $oP->error("Error: Failed to login for user: '$sAuthUser'\n"); + + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues); + $oP->add("\n"); + $oP->add("
\n"); + return; + } + } + + // remove the tmp config file + @unlink(TMP_CONFIG_FILE); + // try to make the final config file read-only + @chmod(FINAL_CONFIG_FILE, 0440); // Read-only for owner and group, nothing for others + + $oP->set_title("Setup complete"); + $oP->add("
\n"); + + // Check if there are some manual steps required: + $aAvailableModules = GetAvailableModules($oP); + $aManualSteps = array(); + foreach($aParamValues['module'] as $sModuleId) + { + if (!empty($aAvailableModules[$sModuleId]['doc.manual_setup'])) + { + $aManualSteps[$aAvailableModules[$sModuleId]['label']] = $aAvailableModules[$sModuleId]['doc.manual_setup']; + } + } + if (count($aManualSteps) > 0) + { + $oP->add("

Manual operations required

"); + $oP->p("In order to complete the installation, the following manual operations are required:"); + foreach($aManualSteps as $sModuleLabel => $sUrl) + { + $oP->p("Manual instructions for $sModuleLabel"); + } + } + else + { + $oP->add("

Congratulations for installing iTop

"); + $oP->ok("The initialization completed successfully."); + } + // Form goes here.. No back button since the job is done ! + $oP->add(''); + $oP->add(""); + $oP->add(""); + $oP->add(""); + $oP->add('
'); + $oP->add("

\n"); + $oP->add("
\n"); + } + catch(Exception $e) + { + $oP->error("Error: unable to create the configuration file."); + $oP->p($e->getHtmlDesc()); + $oP->p("Did you forget to remove the previous (read-only) configuration file ?"); + $oP->add("
\n"); + $oP->add("\n"); + AddParamsToForm($oP, $aParamValues, array('previous_step')); + $oP->add("\n"); + $oP->add("
\n"); + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +// Main program +/////////////////////////////////////////////////////////////////////////////////////////////////// + +clearstatcache(); // Make sure we know what we are doing ! +// Set a long (at least 4 minutes) execution time for the setup to avoid timeouts during this phase +ini_set('max_execution_time', max(240, ini_get('max_execution_time'))); +// While running the setup it is desirable to see any error that may happen +ini_set('display_errors', true); +ini_set('display_startup_errors', true); + +$aParams = array('mode', 'previous_step', 'licence_ok', 'db_server', 'db_user', 'db_pwd','db_name', 'new_db_name', 'db_prefix', 'module', 'sample_data', 'auth_user', 'auth_pwd', 'language'); +foreach($aParams as $sName) +{ + $aParamValues[$sName] = utils::ReadParam($sName, ''); +} + +if (file_exists(FINAL_CONFIG_FILE)) +{ + // The configuration file already exists + if (!is_writable(FINAL_CONFIG_FILE)) + { + $oP->add("

iTop configuration wizard

\n"); + $oP->add("

Fatal error

\n"); + $oP->error("Error: the configuration file '".FINAL_CONFIG_FILE."' already exists and cannot be overwritten."); + $oP->p("The wizard cannot modify the configuration file for you. If you want to upgrade iTop, please make sure that the file '".realpath(FINAL_CONFIG_FILE)."' can be modified by the web server."); + $oP->output(); + exit; + } +} +else +{ + // No configuration file yet + // Check that the wizard can write into the root dir to create the configuration file + if (!is_writable(dirname(TMP_CONFIG_FILE))) + { + $oP->add("

iTop configuration wizard

\n"); + $oP->add("

Fatal error

\n"); + $oP->error("Error: the directory where to store the configuration file is not writable."); + $oP->p("The wizard cannot create the configuration file for you. Please make sure that the directory '".realpath(dirname(TMP_CONFIG_FILE))."' is writable for the web server."); + $oP->output(); + exit; + } + if (!is_writable(dirname(TMP_CONFIG_FILE).'/setup')) + { + $oP->add("

iTop configuration wizard

\n"); + $oP->add("

Fatal error

\n"); + $oP->error("Error: the directory where to store temporary setup files is not writable."); + $oP->p("The wizard cannot create operate. Please make sure that the directory '".realpath(dirname(TMP_CONFIG_FILE))."/setup' is writable for the web server."); + $oP->output(); + exit; + } + +} +try +{ + $oConfig = new Config(TMP_CONFIG_FILE); +} +catch(Exception $e) +{ + // We'll end here when the tmp config file does not exist. It's normal + $oConfig = new Config(TMP_CONFIG_FILE, false /* Don't try to load it */); +} +try +{ + switch($sOperation) + { + case 'step0': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 0 ========"); + WelcomeAndCheckPrerequisites($oP, $aParamValues, 0); + break; + + case 'step1': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 1 ========"); + LicenceAcknowledgement($oP, $aParamValues, 1); + break; + + case 'step2': + $oP->log("Info - ========= Wizard step 2 ========"); + DatabaseServerSelection($oP, $aParamValues, 2); + break; + + case 'step3': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 3 ========"); + DatabaseInstanceSelection($oP, $aParamValues, 3, $oConfig); + break; + + case 'step4': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 4 ========"); + ModulesSelection($oP, $aParamValues, 4, $oConfig); + break; + + case 'step5': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 5 ========"); + AdminAccountDefinition($oP, $aParamValues, 5, $oConfig); + break; + + case 'step6': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 6 ========"); + SampleDataSelection($oP, $aParamValues, 6, $oConfig); + break; + + case 'step7': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 7 ========"); + DisplaySummary($oP, $aParamValues, 7, $oConfig); + break; + + case 'step8': + $oP->no_cache(); + $oP->log("Info - ========= Wizard step 8 ========"); + SetupFinished($oP, $aParamValues, 8, $oConfig); + break; + + default: + $oP->error("Error: unsupported operation '$sOperation'"); + + } +} +catch(Exception $e) +{ + $oP->error("Error: '".$e->getMessage()."'"); + $oP->add("\n"); +} +catch(CoreException $e) +{ + $oP->error("Error: '".$e->getHtmlDesc()."'"); + $oP->add("\n"); +} +$oP->output(); +?> \ No newline at end of file diff --git a/setup/moduleinstaller.class.inc.php b/setup/moduleinstaller.class.inc.php index cf77814ad..a2b5b753d 100644 --- a/setup/moduleinstaller.class.inc.php +++ b/setup/moduleinstaller.class.inc.php @@ -31,7 +31,11 @@ abstract class ModuleInstallerAPI return $oConfiguration; } - public static function AfterDatabaseCreation(Config $oConfiguration) + public static function BeforeDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) + { + } + + public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion) { } } diff --git a/setup/setup.js b/setup/setup.js index d32066e51..35988ec1c 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -7,7 +7,7 @@ function NameIsValid(name) function DoGoBack(iStep) { - $('input[name=operation]').val('step'+(iStep-1)); + $('input[name=operation]').val('step'+iStep); $('#theForm').submit(); // Submit the form return true; } @@ -17,6 +17,14 @@ function DoSubmit(sMsg, iStep) var bResult = true; switch(iStep) { + case 0: // Select either install or upgrade or nothing to select... + if ( ($("input:radio").length > 0) && ($("input:radio:checked").length < 1)) + { + alert('Please select either install or upgrade'); + bResult = false; + } + break; + case 1: // Licence agreement if ($('#licence_ok:checked').length < 1) { @@ -94,8 +102,11 @@ function DoSubmit(sMsg, iStep) } break; - case 6: // Asynchronous load of data - bResult = DoLoadDataAsynchronous(); + case 6: // Sample data selection + break; + + case 7: // Display Summary: launch DoUpdateDBSchema to start the asynchronous update + bResult = DoUpdateDBSchema(); break; // Email test page @@ -113,33 +124,108 @@ function DoSubmit(sMsg, iStep) return bResult; } +function DoUpdateDBSchema() +{ + try + { + // Call the asynchronous page that performs the creation/update of the DB Schema + $('#log').html(''); + $('#setup').block({message: '

Updating DB schema...

0%

'}); + $('#progress').progression( {Current:5, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); + $('#log').load( 'ajax.dataloader.php', + { + 'operation': 'update_db_schema', + 'selected_modules': GetSelectedModules(), + 'mode': $(':input[name=mode]').val() + }, + DoUpdateProfiles, 'html'); + } + catch(err) + { + alert('An exception occured: '+err); + } + return false; // Do NOT submit the form yet +} + +function DoUpdateProfiles(response, status, xhr) +{ + if (status == 'error') + { + $('#setup').unblock(); + return; // An error occurred ! + } + try + { + // Call the asynchronous page that performs the creation/update of the DB Schema + $('#log').html(''); + $('#setup').block({message: '

Updating Profiles...

0%

'}); + $('#progress').progression( {Current:40, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); + $('#log').load( 'ajax.dataloader.php', + { + 'operation': 'after_db_create', + 'selected_modules': GetSelectedModules(), + 'mode': $(':input[name=mode]').val(), + 'auth_user': $(':input[name=auth_user]').val(), + 'auth_pwd': $(':input[name=auth_pwd]').val(), + 'language': $(':input[name=language]').val() + }, + DoLoadDataAsynchronous, 'html'); +// $('#log').ajaxError( +// function(e, xhr, settings, exception) +// { +// bStopAysncProcess = true; +// alert('Fatal error detected: '+ xhr.responseText); +// $('#log').append(xhr.responseText); +// $('#setup').unblock(); +// } ); + } + catch(err) + { + alert('An exception occured: '+err); + } + return true; // Continue loading the data +} + var aFilesToLoad = new Array(); var iCounter = 0; -function DoLoadDataAsynchronous() +function DoLoadDataAsynchronous(response, status, xhr) { + if (status == 'error') + { + $('#setup').unblock(); + return; // An error occurred ! + } try { // The array aFilesToLoad is populated by this function dynamically written on the server PopulateDataFilesList(); - iCounter = 0; - $('#log').html(''); - $('#setup').block({message: '

Loading data...

0%

'}); - $('#progress').progression( {Current:0, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); - $('#log').ajaxError( - function(e, xhr, settings, exception) - { - alert('Fatal error detected: '+ xhr.responseText); - $('#log').append(xhr.responseText); - $('#setup').unblock(); - } ); + iCurrent = 60; + if (aFilesToLoad.length == 0) + { + $('#progress').progression( {Current: 100} ); + } + else + { + $('#log').html(''); + $('#setup').block({message: '

Loading data...

0%

'}); + $('#progress').progression( {Current: 60, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000'} ); +// $('#log').ajaxError( +// function(e, xhr, settings, exception) +// { +// bStopAysncProcess = true; +// alert('Fatal error detected: '+ xhr.responseText); +// $('#log').append(xhr.responseText); +// $('#setup').unblock(); +// } ); + } LoadNextDataFile('', '', ''); } catch(err) { alert('An exception occured: '+err); } - return false; // Stop here for now + return true; // Continue } function LoadNextDataFile(response, status, xhr) @@ -168,16 +254,17 @@ function LoadNextDataFile(response, status, xhr) { sSessionStatus = 'continue'; } - iPercent = Math.round((100.0 * (1+iCounter)) / aFilesToLoad.length); + iPercent = 60+Math.round((40.0 * (1+iCounter)) / aFilesToLoad.length); sFileName = aFilesToLoad[iCounter]; //alert('Loading file '+sFileName+' ('+iPercent+' %) - '+sSessionStatus); - $("#progress").progression({ Current: iPercent }); + $("#progress").progression({ Current: iPercent, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000' }); iCounter++; - $('#log').load( 'ajax.dataloader.php', { 'file': sFileName, 'percent': iPercent, 'session_status': sSessionStatus }, LoadNextDataFile, 'html'); + $('#log').load( 'ajax.dataloader.php', { 'operation': 'load_data', 'file': sFileName, 'percent': iPercent, 'session_status': sSessionStatus }, LoadNextDataFile, 'html'); } else { // We're done + $("#progress").progression({ Current: 100, Maximum: 100, aBackgroundImg: 'orange-progress.gif', aTextColor: '#000000' }); $('#setup').unblock(); $('#GoToNextStep').submit(); // Use the hidden form to navigate to the next step } @@ -187,3 +274,10 @@ function LoadNextDataFile(response, status, xhr) alert('An exception occurred: '+err); } } + +function GetSelectedModules() +{ + var aModules = new Array(); + $(':input[name^=module]').each(function() { aModules.push($(this).val()); } ); + return aModules.join(','); +} \ No newline at end of file diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index bf00b0590..061efc871 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -25,6 +25,11 @@ require_once(APPROOT."/application/nicewebpage.class.inc.php"); define('INSTALL_LOG_FILE', APPROOT.'/setup.log'); + +define ('MODULE_ACTION_OPTIONAL', 1); +define ('MODULE_ACTION_MANDATORY', 2); +define ('MODULE_ACTION_IMPOSSIBLE', 3); + date_default_timezone_set('Europe/Paris'); class SetupWebPage extends NiceWebPage { @@ -86,6 +91,11 @@ h2 { color: #000; font-size: 14pt; } +h3 { + color: #1C94C4; + font-size: 12pt; + font-weight: bold; +} .next { width: 100%; text-align: right; @@ -262,7 +272,7 @@ table.formTable { static $m_aFilesList = array('datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample'); static $m_sModulePath = null; - public function SetModulePath($sModulePath) + public static function SetModulePath($sModulePath) { self::$m_sModulePath = $sModulePath; } @@ -297,7 +307,7 @@ table.formTable { } } } - public function GetModules() + public static function GetModules($oP = null) { // Order the modules to take into account their inter-dependencies $aDependencies = array(); @@ -336,7 +346,14 @@ table.formTable { $sHtml.= "
  • {$aModule['label']} (id: $sId), depends on: ".implode(', ', $aDeps)."
  • "; } $sHtml .= "\n"; - $this->warning($sHtml); + if (is_object($oP)) + { + $oP->warning($sHtml); + } + else + { + self::log_warning($sHtml); + } } // Return the ordered list, so that the dependencies are met... $aResult = array(); @@ -346,6 +363,370 @@ table.formTable { } return $aResult; } - } // End of class + +/** + * Helper function to initialize the ORM and load the data model + * from the given file + * @param $sConfigFileName string The name of the configuration file to load + * @param $bModelOnly boolean Whether or not to allow loading a data model with no corresponding DB + * @return none + */ +function InitDataModel($sConfigFileName, $bModelOnly = true, $bUseCache = false) +{ + require_once(APPROOT.'/core/log.class.inc.php'); + require_once(APPROOT.'/core/kpi.class.inc.php'); + require_once(APPROOT.'/core/coreexception.class.inc.php'); + require_once(APPROOT.'/core/dict.class.inc.php'); + require_once(APPROOT.'/core/attributedef.class.inc.php'); + require_once(APPROOT.'/core/filterdef.class.inc.php'); + require_once(APPROOT.'/core/stimulus.class.inc.php'); + require_once(APPROOT.'/core/MyHelpers.class.inc.php'); + require_once(APPROOT.'/core/expression.class.inc.php'); + require_once(APPROOT.'/core/cmdbsource.class.inc.php'); + require_once(APPROOT.'/core/sqlquery.class.inc.php'); + require_once(APPROOT.'/core/dbobject.class.php'); + require_once(APPROOT.'/core/dbobjectsearch.class.php'); + require_once(APPROOT.'/core/dbobjectset.class.php'); + require_once(APPROOT.'/application/cmdbabstract.class.inc.php'); + require_once(APPROOT.'/core/userrights.class.inc.php'); + require_once(APPROOT.'/setup/moduleinstallation.class.inc.php'); + SetupWebPage::log_info("MetaModel::Startup from file '$sConfigFileName' (ModelOnly = $bModelOnly)"); + + if ($bUseCache) + { + // Reset the cache for the first use ! + $oConfig = new Config($sConfigFileName, false); + MetaModel::ResetCache($oConfig); + } + + MetaModel::Startup($sConfigFileName, $bModelOnly, $bUseCache); +} + +/** + * Search (on the disk) for all defined iTop modules, load them and returns the list (as an array) + * of the possible iTop modules to install + * @param none + * @return Hash A big array moduleID => ModuleData + */ +function GetAvailableModules($oP = null) +{ + clearstatcache(); + ListModuleFiles('modules'); + return SetupWebPage::GetModules($oP); +} + +/** + * Analyzes the current installation and the possibilities + * + * @param $oP SetupWebPage For accessing the list of loaded modules + * @param $sDBServer string Name/IP of the DB server + * @param $sDBUser username for the DB server connection + * @param $sDBPwd password for the DB server connection + * @param $sDBName Name of the database instance + * @param $sDBPrefix Prefix for the iTop tables in the DB instance + * @return hash Array with the following format: + * array => + * 'iTop' => array( + * 'version_db' => ... (could be empty in case of a fresh install) + * 'version_code => ... + * ) + * => array( + * 'version_db' => ... + * 'version_code' => ... + * 'install' => array( + * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY + * 'message' => ... + * ) + * 'uninstall' => array( + * 'flag' => SETUP_NEVER | SETUP_OPTIONAL | SETUP_MANDATORY + * 'message' => ... + * ) + * 'label' => ... + * 'dependencies' => array(, , ...) + * 'visible' => true | false + * ) + * ) + */ +function AnalyzeInstallation($oConfig) +{ + $aRes = array( + 'iTop' => array( + 'version_db' => '', + 'version_code' => ITOP_VERSION.'.'.ITOP_REVISION, + ) + ); + + $aModules = GetAvailableModules(); + foreach($aModules as $sModuleId => $aModuleInfo) + { + list($sModuleName, $sModuleVersion) = GetModuleName($sModuleId); + + $sModuleAppVersion = $aModuleInfo['itop_version']; + $aModuleInfo['version_db'] = ''; + $aModuleInfo['version_code'] = $sModuleVersion; + + if (!in_array($sModuleAppVersion, array('1.0.0', '1.0.1', '1.0.2'))) + { + // This module is NOT compatible with the current version + $aModuleInfo['install'] = array( + 'flag' => MODULE_ACTION_IMPOSSIBLE, + 'message' => 'the module is not compatible with the current version of the application' + ); + } + elseif ($aModuleInfo['mandatory']) + { + $aModuleInfo['install'] = array( + 'flag' => MODULE_ACTION_MANDATORY, + 'message' => 'the module is part of the application' + ); + } + else + { + $aModuleInfo['install'] = array( + 'flag' => MODULE_ACTION_OPTIONAL, + 'message' => '' + ); + } + $aRes[$sModuleName] = $aModuleInfo; + } + + try + { + CMDBSource::Init($oConfig->GetDBHost(), $oConfig->GetDBUser(), $oConfig->GetDBPwd(), $oConfig->GetDBName()); + $aSelectInstall = CMDBSource::QueryToArray("SELECT * FROM ".$oConfig->GetDBSubname()."priv_module_install"); + } + catch (MySQLException $e) + { + // No database or eroneous information + $aSelectInstall = array(); + } + + // Build the list of installed module (get the latest installation) + // + $aInstallByModule = array(); // array of => array ('installed' => timestamp, 'version' => ) + foreach ($aSelectInstall as $aInstall) + { + //$aInstall['comment']; // unsused + //$aInstall['parent_id']; // unsused + $iInstalled = strtotime($aInstall['installed']); + $sModuleName = $aInstall['name']; + $sModuleVersion = $aInstall['version']; + + if ($sModuleName == 'itop') + { + $aRes['iTop']['version_db'] = $sModuleVersion; + continue; + } + + if (array_key_exists($sModuleName, $aInstallByModule)) + { + if ($iInstalled < $aInstallByModule[$sModuleName]['installed']) + { + continue; + } + } + $aInstallByModule[$sModuleName]['installed'] = $iInstalled; + $aInstallByModule[$sModuleName]['version'] = $sModuleVersion; + } + + // Adjust the list of proposed modules + // + foreach ($aInstallByModule as $sModuleName => $aModuleDB) + { + if (!array_key_exists($sModuleName, $aRes)) + { + // A module was installed, it is not proposed in the new build... skip + continue; + } + $aRes[$sModuleName]['version_db'] = $aModuleDB['version']; + + if ($aRes[$sModuleName]['install']['flag'] == MODULE_ACTION_MANDATORY) + { + $aRes[$sModuleName]['uninstall'] = array( + 'flag' => MODULE_ACTION_IMPOSSIBLE, + 'message' => 'the module is part of the application' + ); + } + else + { + $aRes[$sModuleName]['uninstall'] = array( + 'flag' => MODULE_ACTION_OPTIONAL, + 'message' => '' + ); + } + } + + return $aRes; +} + + +/** + * Helper function to interpret the name of a module + * @param $sModuleId string Identifier of the module, in the form 'name/version' + * @return array(name, version) + */ +function GetModuleName($sModuleId) +{ + if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches)) + { + $sName = $aMatches[1]; + $sVersion = $aMatches[2]; + } + else + { + $sName = $sModuleId; + $sVersion = ""; + } + return array($sName, $sVersion); +} +/** + * Helper function to create the database structure + * @return boolean true on success, false otherwise + */ +function CreateDatabaseStructure(Config $oConfig, $aSelectedModules, $sMode) +{ + if (strlen($oConfig->GetDBSubname()) > 0) + { + SetupWebPage::log_info("Creating the structure in '".$oConfig->GetDBName()."' (table names prefixed by '".$oConfig->GetDBSubname()."')."); + } + else + { + SetupWebPage::log_info("Creating the structure in '".$oConfig->GetDBSubname()."'."); + } + + //MetaModel::CheckDefinitions(); + if ($sMode == 'install') + { + if (!MetaModel::DBExists(/* bMustBeComplete */ false)) + { + MetaModel::DBCreate(); + SetupWebPage::log_ok("Database structure successfully created."); + } + else + { + if (strlen($oConfig->GetDBSubname()) > 0) + { + throw new Exception("Error: found iTop tables into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance or specify another prefix to prevent conflicting table names."); + } + else + { + throw new Exception("Error: found iTop tables into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance or specify a prefix to prevent conflicting table names."); + } + } + } + else + { + if (MetaModel::DBExists(/* bMustBeComplete */ false)) + { + MetaModel::DBCreate(); + SetupWebPage::log_ok("Database structure successfully created."); + } + else + { + if (strlen($oConfig->GetDBSubname()) > 0) + { + throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."' (prefix: '".$oConfig->GetDBSubname()."'). Please, try selecting another database instance."); + } + else + { + throw new Exception("Error: No previous instance of iTop found into the database '".$oConfig->GetDBName()."'. Please, try selecting another database instance."); + } + } + } + return true; +} + +function RecordInstallation(Config $oConfig, $aSelectedModules) +{ + // Record main installation + $oInstallRec = new ModuleInstallation(); + $oInstallRec->Set('name', 'itop'); + $oInstallRec->Set('version', ITOP_VERSION.'.'.ITOP_REVISION); + $oInstallRec->Set('comment', "Done by the setup program\nBuilt on ".ITOP_BUILD_DATE); + $oInstallRec->Set('parent_id', 0); // root module + $iMainItopRecord = $oInstallRec->DBInsertNoReload(); + + // Record installed modules + // + $aAvailableModules = AnalyzeInstallation($oConfig); + foreach($aSelectedModules as $sModuleId) + { + $aModuleData = $aAvailableModules[$sModuleId]; + $sName = $sModuleId; + $sVersion = $aModuleData['version_code']; + $aComments = array(); + $aComments[] = 'Done by the setup program'; + if ($aModuleData['mandatory']) + { + $aComments[] = 'Mandatory'; + } + else + { + $aComments[] = 'Optional'; + } + if ($aModuleData['visible']) + { + $aComments[] = 'Visible (during the setup)'; + } + else + { + $aComments[] = 'Hidden (selected automatically)'; + } + foreach ($aModuleData['dependencies'] as $sDependOn) + { + $aComments[] = "Depends on module: $sDependOn"; + } + $sComment = implode("\n", $aComments); + + $oInstallRec = new ModuleInstallation(); + $oInstallRec->Set('name', $sName); + $oInstallRec->Set('version', $sVersion); + $oInstallRec->Set('comment', $sComment); + $oInstallRec->Set('parent_id', $iMainItopRecord); + $oInstallRec->DBInsertNoReload(); + } + // Database is created, installation has been tracked into it + return true; +} + +function ListModuleFiles($sRelDir) +{ + $sDirectory = APPROOT.'/'.$sRelDir; + //echo "

    $sDirectory

    \n"; + if ($hDir = opendir($sDirectory)) + { + // This is the correct way to loop over the directory. (according to the documentation) + while (($sFile = readdir($hDir)) !== false) + { + $aMatches = array(); + if (is_dir($sDirectory.'/'.$sFile)) + { + if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn')) + { + ListModuleFiles($sRelDir.'/'.$sFile); + } + } + else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) + { + SetupWebPage::SetModulePath($sRelDir); + try + { + //echo "

    Loading: $sDirectory/$sFile...

    \n"; + require_once($sDirectory.'/'.$sFile); + //echo "

    Done.

    \n"; + } + catch(Exception $e) + { + // Continue... + } + } + } + closedir($hDir); + } + else + { + throw new Exception("Data directory (".$sDirectory.") not found or not readable."); + } +} ?>