Преглед на файлове

New implementation of the setup:
- All actions are performed asynchronously at the end of the setup
- Supports upgrading and reinstalling (to add modules)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1157 a333f486-631f-4898-b8df-5754b55c2be0

dflaven преди 14 години
родител
ревизия
f1efd13238
променени са 7 файла, в които са добавени 2179 реда и са изтрити 1400 реда
  1. 10 11
      core/metamodel.class.php
  2. 10 5
      modules/itop-profiles-itil/module.itop-profiles-itil.php
  3. 139 34
      setup/ajax.dataloader.php
  4. 1517 1326
      setup/index.php
  5. 5 1
      setup/moduleinstaller.class.inc.php
  6. 113 19
      setup/setup.js
  7. 385 4
      setup/setuppage.class.inc.php

+ 10 - 11
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);

+ 10 - 5
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)
 		{

+ 139 - 34
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 "<p>An error happened while loading the data</p>\n";
+	echo "<p>An error happened while processing the installation:</p>\n";
 	echo '<p>'.$e."</p>\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());
+	}
 }
 ?>

+ 1517 - 1326
setup/index.php

@@ -1,1326 +1,1517 @@
-<?php
-// Copyright (C) 2010 Combodo SARL
-//
-//   This program is free software; you can redistribute it and/or modify
-//   it under the terms of the GNU General Public License as published by
-//   the Free Software Foundation; version 3 of the License.
-//
-//   This program is distributed in the hope that it will be useful,
-//   but WITHOUT ANY WARRANTY; without even the implied warranty of
-//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-//   GNU General Public License for more details.
-//
-//   You should have received a copy of the GNU General Public License
-//   along with this program; if not, write to the Free Software
-//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
-
-/**
- * Wizard to configure and initialize the iTop application
- *
- * @author      Erwan Taloc <erwan.taloc@combodo.com>
- * @author      Romain Quetiez <romain.quetiez@combodo.com>
- * @author      Denis Flaven <denis.flaven@combodo.com>
- * @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[] = "<a href=\"http://www.php.net/manual/en/book.$sExtension.php\" target=\"_blank\">$sExtension</a>";
-		}
-	}
-	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 "<p>$sDirectory</p>\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 "<p>Loading: $sDirectory/$sFile...</p>\n";
-					require_once($sDirectory.'/'.$sFile);
-					//echo "<p>Done.</p>\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("<script type=\"text/javascript\">\n");
-	$oP->add("function PopulateDataFilesList()\n");
-	$oP->add("{\n");
-	$oP->add("if (aFilesToLoad.length > 0)  return;"); // Populate the list only once...
-
-	$aAvailableModules = GetAvailableModules($oP);
-	$aStructureDataFiles = array();
-	$aSampleDataFiles = array();
-	foreach($aParamValues['module'] as $sModuleId)
-	{
-		$aModuleStruct = $aAvailableModules[$sModuleId]['data.struct'];
-		$aModuleSamples = $aAvailableModules[$sModuleId]['data.sample'];
-		$aStructureDataFiles = array_merge($aStructureDataFiles, $aModuleStruct);
-		$aSampleDataFiles = array_merge($aSampleDataFiles, $aModuleSamples);
-	}
-	// Structure data
-	//
-	foreach($aStructureDataFiles as $sFile)
-	{
-		// Under Windows, it is a must to escape backslashes (not an issue until a folder name starts with t or n, etc...)
-		$sFile = APPROOT.$sFile;
-		$sFile = str_replace('\\', '\\\\', $sFile);
-		$oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n");
-	}
-
-	// Sample data - loaded IIF wished by the user
-	//
-	$oP->add("if (($(\"#sample_data:checked\").length == 1))");
-	$oP->add("{");
-	foreach($aSampleDataFiles as $sFile)
-	{
-		// Under Windows, it is a must to escape backslashes (not an issue until a folder name starts with t or n, etc...)
-		$sFile = APPROOT.$sFile;
-		$sFile = str_replace('\\', '\\\\', $sFile);
-		$oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n");
-	}
-	$oP->add("}\n");
-
-	$oP->add("}\n");
-	$oP->add("</script>\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('<input type="hidden" name="'.$sName.'['.$sKey.']'.'" value="'.$sItem.'">');			
-				}
-			}
-			else
-			{
-				$oP->add('<input type="hidden" name="'.$sName.'" value="'.$value.'">');			
-			}
-		}
-	}
-}
-
-/**
- * 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("<h1>iTop configuration wizard</h1>\n");
-	$sVersionStringShort = GetITopVersion(true);
-	$sVersionStringLong = GetITopVersion(false);
-	$oP->set_title('Welcome to '.$sVersionStringShort);
-	$oP->log($sVersionStringLong);
-	$oP->add("<h2>Checking prerequisites</h2>\n");
-	if (CheckPHPVersion($oP))
-	{
-		$oP->add("<h2 class=\"next\">Next: Licence agreement</h2>\n");
-		$oP->add("<form id=\"theForm\" method=\"post\" onSubmit=\"return DoSubmit('', 0)\">\n");
-		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
-		AddParamsToForm($oP, $aParamValues);
-		$oP->add("<table style=\"width:100%\"><tr>\n");
-		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" type=\"submit\">Next >></button></td>\n");
-		$oP->add("</tr></table>\n");
-		$oP->add("</form>\n");
-	}
-}
-
-function LicenceAcknowledgement($oP, $aParamValues, $iCurrentStep)
-{
-	$sNextOperation = 'step'.($iCurrentStep+1);
-	
-	$oP->set_title('License agreement');
-	$oP->add('<h2>iTop is released by <a href="http://www.combodo.com" target="_blank">Combodo SARL</a> under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.</h2>');
-	$oP->add("<iframe style=\"width: 100%; height: 350px; overflow-y:auto; font-size:0.8em;\" src=\"./licence.html\">Next: Database server selection</iframe>\n");
-	$oP->add("<form id=\"theForm\" method=\"post\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
-	AddParamsToForm($oP, $aParamValues, array('licence_ok'));
-
-	$sChecked = $aParamValues['licence_ok'] == 1 ? 'checked' : '';
-	$oP->add("<h2><input id=\"licence_ok\" type=\"checkbox\" name=\"licence_ok\" value=\"1\" $sChecked><label for=\"licence_ok\">I accept the terms of this licence agreement</label></h2>\n");
-
-	$oP->add("<h2 class=\"next\">Next: Database server selection</h2>\n");
-	$oP->add("<table style=\"width:100%\"><tr>\n");
-	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('', $iCurrentStep)\">Next >></button></td>\n");
-	$oP->add("</tr></table>\n");
-	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
-	AddParamsToForm($oP, $aParamValues, array('db_server', 'db_user', 'db_pwd'));
-	if ($aParamValues['licence_ok'] == 1)
-	{
-		$sRedStar = '<span class="hilite">*</span>';
-		$oP->set_title("Database server selection\n");
-		$oP->add("<h2>Please enter the name of the MySQL database server you want to use for iTop and supply valid credentials to connect to it</h2>\n");
-		// Form goes here
-		$oP->add("<fieldset><legend>Database connection</legend>\n");
-		$aForm = array();
-		$aForm[] = array('label' => "Server name$sRedStar:", 'input' => "<input id=\"db_server\" type=\"text\" name=\"db_server\" value=\"{$aParamValues['db_server']}\">",
-						'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23"');
-		$aForm[] = array('label' => "User name$sRedStar:", 'input' => "<input id=\"db_user\" type=\"text\" name=\"db_user\" value=\"{$aParamValues['db_user']}\">",
-						'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' => "<input id=\"db_pwd\" type=\"password\" name=\"db_pwd\" value=\"{$aParamValues['db_pwd']}\">");
-		$oP->form($aForm);
-		$oP->add("</fieldset>\n");
-		$oP->add("<h2 class=\"next\">Next: Database instance Selection</h2>\n");
-		$oP->add("<table style=\"width:100%\"><tr>\n");
-		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('Connecting to the database...', $iCurrentStep);\">Next >></button></td>\n");
-		$oP->add("</tr></table>\n");
-	}
-	else
-	{
-		$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep);\"><< Back</button>\n");		
-	}
-	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep);\"><< Back</button>\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("<fieldset><legend>Select the database instance to use for iTop<span class=\"hilite\">*</span></legend>\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' => "<input id=\"db_$sDBName\" type=\"radio\" name=\"db_name\" value=\"$sDBName\" $sChecked/><label for=\"db_$sDBName\"> $sDBName</label>");
-			}
-		}
-		else
-		{
-			$aForm[] = array('label' => "<input id=\"current_db\" type=\"radio\" name=\"db_name\" value=\"-1\" /><label for=\"current_db\"> Use the existing database:</label> <input type=\"text\" id=\"current_db_name\" name=\"current_db_name\" value=\"\"  maxlength=\"32\"/>");			
-			$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' => "<input id=\"new_db\" type=\"radio\" name=\"db_name\" value=\"\" $sChecked/><label for=\"new_db\"> Create a new database:</label> <input type=\"text\" id=\"new_db_name\" name=\"new_db_name\" value=\"$sDBName\"  maxlength=\"32\"/>");
-		$oP->form($aForm);
-
-		$oP->add_ready_script("$('#new_db_name').click( function() { $('#new_db').attr('checked', true); })");
-		$oP->add("</fieldset>\n");
-		$aForm = array();
-		$aForm[] = array('label' => "Add a prefix to all the tables: <input id=\"db_prefix\" type=\"text\" name=\"db_prefix\" value=\"{$aParamValues['db_prefix']}\" maxlength=\"32\"/>");
-		$oP->form($aForm);
-
-		$oP->add("<h2 class=\"next\">Next: iTop modules selection</h2>\n");
-		$oP->add("<table style=\"width:100%\"><tr>\n");
-		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('', $iCurrentStep);\">Next >></button></td>\n");
-		$oP->add("</tr></table>\n");
-	}
-	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
-	AddParamsToForm($oP, $aParamValues, array('module'));
-	$sRedStar = '<span class="hilite">*</span>';
-	$oP->set_title("iTop modules selection");
-	$oP->add("<h2>Customize your iTop installation to fit your needs</h2>\n");
-	$aAvailableModules = GetAvailableModules($oP);
-	
-	// Form goes here
-	$oP->add("<fieldset><legend>Select the iTop modules you want to install:</legend>\n");
-	$oP->add("<div style=\"border: 0;width:100%; height: 350px; overflow-y:auto;\">");
-	$sRedStar = '<span class="hilite">*</span>';
-	$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'])) ? "<a href=\"..{$aModule['doc.more_information']}\" target=\"_blank\">more info</a>": '';
-		if ($aModule['category'] == 'authentication')
-		{
-			// For now authentication modules are always on and hidden
-			$oP->add("<input type=\"hidden\" id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\">\n");
-			$index++;
-		}
-		elseif ($aModule['visible'])
-		{
-			$oP->add("<p><input type=\"checkbox\" $sClass $sChecked id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\"><label $sClass for=\"module[$index]\"> {$aModule['label']}</label> $sMoreInfo</p>\n");
-			$index++;
-		}
-		else
-		{
-			// For now hidden modules are always on !
-			$oP->add("<input type=\"hidden\" id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\">\n");
-			$index++;
-		}
-	}	
-	$oP->add("</div>");
-	$oP->add("</fieldset>\n");
-	$oP->add("<h2 class=\"next\">Next: Administrator account creation</h2>\n");
-	$oP->add("<table style=\"width:100%\"><tr>\n");
-	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('Creating the database structure...', $iCurrentStep);\">Next >></button></td>\n");
-	$oP->add("</tr></table>\n");
-	$oP->add("</form>\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("<h2>Creation of the database structure</h2>");
-	$oP->add("<form id=\"theForm\" method=\"post\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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 = "<span class=\"hilite\">*</span>";
-		$oP->add("<h2>Default language for the application:</h2>\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.="<option value=\"{$sLangCode}\">{$aInfo['description']} ({$aInfo['localized_description']})</option>";
-		}
-		
-		$aForm[] = array('label' => "Default Language$sRedStar:", 'input' => "<select id=\"language\" name=\"language\">$sLanguages</option>");
-		$oP->form($aForm);
-		$oP->add("<h2>Definition of the administrator account</h2>\n");
-		// Database created, continue with admin creation		
-		$oP->add("<fieldset><legend>Administrator account</legend>\n");
-		$aForm = array();
-		$aForm[] = array('label' => "Login$sRedStar:", 'input' => "<input id=\"auth_user\" type=\"text\" name=\"auth_user\" value=\"{$aParamValues['auth_user']}\">");
-		$aForm[] = array('label' => "Password$sRedStar:", 'input' => "<input id=\"auth_pwd\" type=\"password\" name=\"auth_pwd\" value=\"{$aParamValues['auth_pwd']}\">");
-		$aForm[] = array('label' => "Retype password$sRedStar:", 'input' => "<input  id=\"auth_pwd2\" type=\"password\" name=\"auth_pwd2\" value=\"{$aParamValues['auth_pwd']}\">");
-		$oP->form($aForm);
-		$oP->add("</fieldset>\n");
-		$oP->add("<h2 class=\"next\">Next: Application initialization</h2>\n");
-		$oP->add("<table style=\"width:100%\"><tr>\n");
-		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('Creating the admin account and profiles...', $iCurrentStep);\">Next >></button></td>\n");
-		$oP->add("</tr></table>\n");
-	}
-	else
-	{
-		$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button>\n");
-	}
-	// Form goes here
-	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\"\">\n");
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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("<h2>Loading of sample data</h2>\n");
-		$oP->p("<fieldset><legend> Do you want to load sample data into the database ? </legend>\n");
-		$oP->p("<input type=\"radio\" id=\"sample_data\" name=\"sample_data\" id=\"sample_data_no\" checked value=\"yes\"><label for=\"sample_data_yes\"> Yes, for testing purposes, populate the database with sample data.</label>\n");
-		$oP->p("<input type=\"radio\" name=\"sample_data\" unchecked id=\"sample_data_no\" value=\"no\"><label for=\"sample_data_no\"> No, this is a production system, load only the data required by the application.</label>\n");
-		$oP->p("</fieldset>\n");	
-		$oP->add("<h2 class=\"next\">Next: Setup complete</h2>\n");
-		$oP->add("<table style=\"width:100%\"><tr>\n");
-		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button></td>\n");
-		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('Finalizing configuration and loading data...', $iCurrentStep)\">Next >></button></td>\n");
-		$oP->add("</tr></table>\n");
-	}
-	else
-	{
-		// Creation failed
-		$oP->error("Internal error: Failed to create the admin account or to setup the user rights");
-		$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep)\"><< Back</button>\n");
-	}
-	// End of visible form
-	$oP->add("</form>\n");
-	// Hidden form submitted when moving on to the next page, once all the data files
-	// have been processed
-	$oP->add("<form id=\"GoToNextStep\" method=\"post\">\n");
-	AddParamsToForm($oP, $aParamValues, array('sample_data'));
-	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
-	$oP->add("</form>\n");
-	$oP->add("<div id=\"log\" style=\"color:#F00;\"></div>\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("<form id=\"theForm\" method=\"get\" action=\"../index.php\">\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("<h2>Manual operations required</h2>");
-				$oP->p("In order to complete the installation, the following manual operations are required:");
-				foreach($aManualSteps as $sModuleLabel => $sUrl)
-				{
-					$oP->p("<a href=\"$sUrl\" target=\"_blank\">Manual instructions for $sModuleLabel</a>");
-				}
-			}
-			else
-			{
-				$oP->add("<h2>Congratulations for installing iTop</h2>");
-				$oP->ok("The initialization completed successfully.");
-			}
-			// Form goes here.. No back button since the job is done !
-			$oP->add('<table style="width:600px;border:0;padding:0;"><tr>');
-			$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Free: Register your iTop version.\" href=\"http://www.combodo.com/register?product=iTop&version=".urlencode(ITOP_VERSION." revision ".ITOP_REVISION)."\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-register.gif\"/></td></a>");
-			$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Support from Combodo\" href=\"http://www.combodo.com/itopsupport\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-support.gif\"/></td></a>");
-			$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Training from Combodo\" href=\"http://www.combodo.com/itoptraining\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-training.gif\"/></td></a>");
-			$oP->add('</tr></table>');
-			$oP->add("<p style=\"text-align:center;width:100%\"><button type=\"submit\">Enter iTop</button></p>\n");
-			$oP->add("</form>\n");
-		}
-		else
-		{
-			$oP->add("<h1>iTop configuration wizard</h1>\n");
-			$oP->add("<h2>Step 5: Configuration completed</h2>\n");
-			
-			@unlink(FINAL_CONFIG_FILE); // remove the aborted config
-			$oP->error("Error: Failed to login for user: '$sAuthUser'\n");
-
-			$oP->add("<form id=\"theForm\" method=\"post\">\n");
-			$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep);\"><< Back</button>\n");
-			AddParamsToForm($oP, $aParamValues);
-			$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step0\">\n");
-			$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
-		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step0\">\n");
-		AddParamsToForm($oP, $aParamValues);
-		$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep);\"><< Back</button>\n");
-		$oP->add("</form>\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("<b>Warning:</b> a configuration file '".FINAL_CONFIG_FILE."' already exists, and will be overwritten.");
-	}
-	else
-	{
-		$oP->add("<h1>iTop configuration wizard</h1>\n");
-		$oP->add("<h2>Fatal error</h2>\n");
-		$oP->error("<b>Error:</b> 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 '<b>".realpath(FINAL_CONFIG_FILE)."</b>' 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("<h1>iTop configuration wizard</h1>\n");
-		$oP->add("<h2>Fatal error</h2>\n");
-		$oP->error("<b>Error:</b> 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 '<b>".realpath(dirname(TMP_CONFIG_FILE))."</b>' is writable for the web server.");
-		$oP->output();
-		exit;
-	}
-	if (!is_writable(dirname(TMP_CONFIG_FILE).'/setup'))
-	{
-		$oP->add("<h1>iTop configuration wizard</h1>\n");
-		$oP->add("<h2>Fatal error</h2>\n");
-		$oP->error("<b>Error:</b> the directory where to store temporary setup files is not writable.");
-		$oP->p("The wizard cannot create operate. Please make sure that the directory '<b>".realpath(dirname(TMP_CONFIG_FILE))."/setup</b>' 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("<button type=\"button\" onClick=\"window.history.back();\"><< Back</button>\n");
-}
-catch(CoreException $e)
-{
-	$oP->error("Error: '".$e->getHtmlDesc()."'");	
-	$oP->add("<button type=\"button\" onClick=\"window.history.back();\"><< Back</button>\n");
-}
-$oP->output();
-?>
+<?php
+// Copyright (C) 2010 Combodo SARL
+//
+//   This program is free software; you can redistribute it and/or modify
+//   it under the terms of the GNU General Public License as published by
+//   the Free Software Foundation; version 3 of the License.
+//
+//   This program is distributed in the hope that it will be useful,
+//   but WITHOUT ANY WARRANTY; without even the implied warranty of
+//   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+//   GNU General Public License for more details.
+//
+//   You should have received a copy of the GNU General Public License
+//   along with this program; if not, write to the Free Software
+//   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+/**
+ * Wizard to configure and initialize the iTop application
+ *
+ * @author      Erwan Taloc <erwan.taloc@combodo.com>
+ * @author      Romain Quetiez <romain.quetiez@combodo.com>
+ * @author      Denis Flaven <denis.flaven@combodo.com>
+ * @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[] = "<a href=\"http://www.php.net/manual/en/book.$sExtension.php\" target=\"_blank\">$sExtension</a>";
+		}
+	}
+	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 = '<img src="../images/messagebox_warning-mid.png" style="vertical-align:middle"> Checking prerequisites: Warning <a href="#" onClick="$(\'#prereq_details\').toggle();">(show details)</a>';
+			$oP->add_ready_script("$('#prereq_details').hide();\n");
+		}
+		else
+		{
+			$sTitle = '<img src="../images/clean-mid.png" style="vertical-align:middle"> Checking prerequisites: Ok <a href="#" onClick="$(\'#prereq_details\').toggle();">(show details)</a>';
+			$oP->add_ready_script("$('#prereq_details').hide();\n");
+		}
+	}
+	$oP->add("<h2>$sTitle</h2>\n");
+	$oP->add("<div id=\"prereq_details\">\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("</div>\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("<script type=\"text/javascript\">\n");
+	$oP->add("function PopulateDataFilesList()\n");
+	$oP->add("{\n");
+	$oP->add("if (aFilesToLoad.length > 0)  return;"); // Populate the list only once...
+
+	$aAvailableModules = AnalyzeInstallation($oConfig);
+
+	$sMode = $aParamValues['mode'];
+	$aStructureDataFiles = array();
+	$aSampleDataFiles = array();
+
+	foreach($aAvailableModules as $sModuleId => $aModule)
+	{
+		if (($sModuleId != 'iTop') && $aModule['visible'])
+		{
+			if (in_array($sModuleId, $aParamValues['module']))
+			{
+				if (empty($aModule['version_db']))
+				{
+					// New installation load the data
+					$aModuleStruct = $aAvailableModules[$sModuleId]['data.struct'];
+					$aModuleSamples = $aAvailableModules[$sModuleId]['data.sample'];
+					$aStructureDataFiles = array_merge($aStructureDataFiles, $aModuleStruct);
+					$aSampleDataFiles = array_merge($aSampleDataFiles, $aModuleSamples);
+				}
+			}
+		}
+	}
+
+	// Structure data
+	//
+	foreach($aStructureDataFiles as $sFile)
+	{
+		// Under Windows, it is a must to escape backslashes (not an issue until a folder name starts with t or n, etc...)
+		$sFile = APPROOT.$sFile;
+		$sFile = str_replace('\\', '\\\\', $sFile);
+		$oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n");
+	}
+
+	// Sample data - loaded IIF wished by the user
+	//
+	if ($aParamValues['sample_data'] != 'no')
+	{
+		foreach($aSampleDataFiles as $sFile)
+		{
+			// Under Windows, it is a must to escape backslashes (not an issue until a folder name starts with t or n, etc...)
+			$sFile = APPROOT.$sFile;
+			$sFile = str_replace('\\', '\\\\', $sFile);
+			$oP->add("aFilesToLoad[aFilesToLoad.length] = '$sFile';\n");
+		}
+	}
+	$oP->add("}\n");
+	$oP->add("</script>\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 <INPUT> 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('<input type="hidden" name="'.$sName.'['.$sKey.']'.'" value="'.$sItem.'">');			
+		}
+	}
+	else
+	{
+		$oP->add('<input type="hidden" name="'.$sName.'" value="'.$value.'">');			
+	}
+}
+
+/**
+ * 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("<h1>iTop configuration wizard</h1>\n");
+	$sVersionStringShort = GetITopVersion(true);
+	$sVersionStringLong = GetITopVersion(false);
+	$oP->set_title('Welcome to '.$sVersionStringShort);
+	$oP->log($sVersionStringLong);
+	$aPreviousParams = array();
+	$oP->add("<form id=\"theForm\" method=\"post\" onSubmit=\"return DoSubmit('', 0)\">\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.<br/>The <b>same version</b> of the application will be reinstalled.");
+			}
+			else
+			{
+				$oP->ok("Version {$aVersion['iTop']['version_db']} of iTop detected.<br/>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("<h2 class=\"next\">Next: Licence agreement</h2>\n");
+				$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
+				AddParamsToForm($oP, $aParamValues, $aPreviousParams);
+				$oP->add("<table style=\"width:100%\"><tr>\n");
+				$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" type=\"submit\">Next >></button></td>\n");
+				$oP->add("</tr></table>\n");
+			}
+			return;
+		}
+		// else, normal install ??
+	}
+	
+	if (CheckPHPVersion($oP))
+	{
+		$oP->add("<h2>What do you want to do?</h2>\n");
+		$sChecked = ($aParamValues['mode'] == 'install') ? 'checked' : '';
+		$oP->p("<input type=\"radio\" value=\"install\" $sChecked name=\"mode\">&nbsp;Install a new iTop\n");
+		$sChecked = ($aParamValues['mode'] == 'upgrade') ? 'checked' : '';
+		$oP->p("<input type=\"radio\" value=\"upgrade\" $sChecked name=\"mode\">&nbsp;Upgrade an existing iTop instance\n");
+		$oP->add("<h2 class=\"next\">Next: Licence agreement</h2>\n");
+		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
+		$aPreviousParams = array('mode');
+		AddParamsToForm($oP, $aParamValues, $aPreviousParams);
+		$oP->add("<table style=\"width:100%\"><tr>\n");
+		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\">Next >></button></td>\n");
+		$oP->add("</tr></table>\n");
+		$oP->add("</form>\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('<h2>iTop is released by <a href="http://www.combodo.com" target="_blank">Combodo SARL</a> under the terms of the GPL V3 license. In order to use iTop you must accept the terms of this license.</h2>');
+	$oP->add("<iframe style=\"width: 100%; height: 350px; overflow-y:auto; font-size:0.8em;\" src=\"./licence.html\">Next: Database server selection</iframe>\n");
+	$oP->add("<form id=\"theForm\" method=\"post\">\n");
+	AddParamsToForm($oP, $aParamValues, array('licence_ok'));
+
+	$sChecked = $aParamValues['licence_ok'] == 1 ? 'checked' : '';
+	$oP->add("<h2><input id=\"licence_ok\" type=\"checkbox\" name=\"licence_ok\" value=\"1\" $sChecked><label for=\"licence_ok\">I accept the terms of this licence agreement</label></h2>\n");
+
+	if (file_exists(FINAL_CONFIG_FILE))
+	{
+		$oP->add("<h2 class=\"next\">Next: Modules selection</h2>\n");		
+		$sNextOperation = 'step4';
+	}
+	else
+	{
+		$oP->add("<h2 class=\"next\">Next: Database server selection</h2>\n");		
+	}
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");	
+	$oP->add("<table style=\"width:100%\"><tr>\n");
+	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('', $iCurrentStep)\">Next >></button></td>\n");
+	$oP->add("</tr></table>\n");
+	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
+	AddParamsToForm($oP, $aParamValues, array('db_server', 'db_user', 'db_pwd'));
+	if ($aParamValues['licence_ok'] == 1)
+	{
+		$sRedStar = '<span class="hilite">*</span>';
+		$oP->set_title("Database server selection\n");
+		$oP->add("<h2>Please enter the name of the MySQL database server you want to use for iTop and supply valid credentials to connect to it</h2>\n");
+		// Form goes here
+		$oP->add("<fieldset><legend>Database connection</legend>\n");
+		$aForm = array();
+		$aForm[] = array('label' => "Server name$sRedStar:", 'input' => "<input id=\"db_server\" type=\"text\" name=\"db_server\" value=\"{$aParamValues['db_server']}\">",
+						'help' => 'E.g. "localhost", "dbserver.mycompany.com" or "192.142.10.23"');
+		$aForm[] = array('label' => "User name$sRedStar:", 'input' => "<input id=\"db_user\" type=\"text\" name=\"db_user\" value=\"{$aParamValues['db_user']}\">",
+						'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' => "<input id=\"db_pwd\" type=\"password\" name=\"db_pwd\" value=\"{$aParamValues['db_pwd']}\">");
+		$oP->form($aForm);
+		$oP->add("</fieldset>\n");
+		$oP->add("<h2 class=\"next\">Next: Database instance Selection</h2>\n");
+		$oP->add("<table style=\"width:100%\"><tr>\n");
+		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('Connecting to the database...', $iCurrentStep);\">Next >></button></td>\n");
+		$oP->add("</tr></table>\n");
+	}
+	else
+	{
+		$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iPrevStep);\"><< Back</button>\n");		
+	}
+	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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("<button type=\"button\" onClick=\"return DoGoBack($iCurrentStep);\"><< Back</button>\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("<fieldset><legend>Select the database instance to use for iTop<span class=\"hilite\">*</span></legend>\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' => "<input id=\"db_$sDBName\" type=\"radio\" name=\"db_name\" value=\"$sDBName\" $sChecked/><label for=\"db_$sDBName\"> $sDBName</label>");
+			}
+		}
+		else
+		{
+			$aForm[] = array('label' => "<input id=\"current_db\" type=\"radio\" name=\"db_name\" value=\"-1\" /><label for=\"current_db\"> Use the existing database:</label> <input type=\"text\" id=\"current_db_name\" name=\"current_db_name\" value=\"\"  maxlength=\"32\"/>");			
+			$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' => "<input id=\"new_db\" type=\"radio\" name=\"db_name\" value=\"\" $sChecked/><label for=\"new_db\"> Create a new database:</label> <input type=\"text\" id=\"new_db_name\" name=\"new_db_name\" value=\"$sDBName\"  maxlength=\"32\"/>");
+		}
+		$oP->add('<div style="height:250px;overflow-y:auto;padding-left:1em;">');
+		$oP->form($aForm);
+		$oP->add('</div>');
+
+		$oP->add_ready_script("$('#new_db_name').click( function() { $('#new_db').attr('checked', true); })");
+		$oP->add("</fieldset>\n");
+		$aForm = array();
+		if ($aParamValues['mode'] == 'install')
+		{
+			$aForm[] = array('label' => "Add a prefix to all the tables: <input id=\"db_prefix\" type=\"text\" name=\"db_prefix\" value=\"{$aParamValues['db_prefix']}\" maxlength=\"32\"/>");
+		}
+		else
+		{
+			$aForm[] = array('label' => "The following prefix is used for all tables: <input id=\"db_prefix\" type=\"text\" name=\"db_prefix\" value=\"{$aParamValues['db_prefix']}\" maxlength=\"32\"/>");
+		}
+		$oP->form($aForm);
+
+		$oP->add("<h2 class=\"next\">Next: iTop modules selection</h2>\n");
+		$oP->add("<table style=\"width:100%\"><tr>\n");
+		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+		$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" onClick=\"return DoSubmit('', $iCurrentStep);\">Next >></button></td>\n");
+		$oP->add("</tr></table>\n");
+	}
+	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
+	AddParamsToForm($oP, $aParamValues, array('module'));
+	$sRedStar = '<span class="hilite">*</span>';
+	$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("<table style=\"width:100%\"><tr>\n");
+			$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+			$oP->add("<td style=\"text-align:right;\">&nbsp;</td>\n");
+			$oP->add("</tr></table>\n");
+			$oP->add("</form>\n");
+			return;
+		}
+		$oP->add("<h2>Customize your iTop installation to fit your needs</h2>\n");
+		$oP->add("<fieldset><legend>Select the iTop modules you want to install or upgrade:</legend>\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("<table style=\"width:100%\"><tr>\n");
+			$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+			$oP->add("<td style=\"text-align:right;\">&nbsp;</td>\n");
+			$oP->add("</tr></table>\n");
+			$oP->add("</form>\n");
+			return;
+		}
+		$oP->add("<h2>Customize your iTop installation to fit your needs</h2>\n");
+		$oP->add("<fieldset><legend>Select the iTop modules you want to install:</legend>\n");
+	}
+	$oP->add("<div style=\"border: 0;width:100%; height: 350px; overflow-y:auto;\">");
+	$sRedStar = '<span class="hilite">*</span>';
+	$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'])) ? "<a href=\"..{$aModule['doc.more_information']}\" target=\"_blank\">more info</a>": '';
+		if ($aModule['category'] == 'authentication')
+		{
+			// For now authentication modules are always on and hidden
+			$oP->add("<input type=\"hidden\" id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\">\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("<p><input type=\"checkbox\" $sChecked $sClass id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\"><label $sClass for=\"module[$index]\"> {$aModule['label']}</label> $sMoreInfo</p>\n");
+				break;
+				
+				case MODULE_ACTION_MANDATORY:
+				$oP->add("<p><input type=\"checkbox\" class=\"read-only\" checked id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\"><label class=\"read-only\" for=\"module[$index]\"> {$aModule['label']}</label> $sMoreInfo</p>\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("<p><input type=\"checkbox\" class=\"read-only\" id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\"><label class=\"read-only\" for=\"module[$index]\"> {$aModule['label']}</label> $sMoreInfo</p>\n");
+				}
+				break;
+				
+			}
+			$index++;
+		}
+		else
+		{
+			// For now hidden modules are always on !
+			$oP->add("<input type=\"hidden\" id=\"module[$index]\" name=\"module[$index]\" value=\"$sModuleId\">\n");
+			$index++;
+		}
+	}	
+	$oP->add("</div>");
+	$oP->add("</fieldset>\n");
+	if ($aParamValues['mode'] == 'upgrade')
+	{
+		$oP->add("<h2 class=\"next\">Next: Upgrade summary</h2>\n");
+		AddHiddenParam($oP, 'operation', 'step6');
+	}
+	else
+	{
+		$oP->add("<h2 class=\"next\">Next: Administrator account definition</h2>\n");
+		AddHiddenParam($oP, 'operation', 'step5');
+	}
+	$oP->add("<table style=\"width:100%\"><tr>\n");
+	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\" \">Next >></button></td>\n");
+	$oP->add("</tr></table>\n");
+	$oP->add("</form>\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("<form id=\"theForm\" onSubmit=\"return DoSubmit('', $iCurrentStep);\" method=\"post\">\n");
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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 = "<span class=\"hilite\">*</span>";
+	$oP->add("<h2>Default language for the application:</h2>\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.="<option value=\"{$sLangCode}\">{$aInfo['description']} ({$aInfo['localized_description']})</option>";
+	}
+	
+	$aForm[] = array('label' => "Default Language$sRedStar:", 'input' => "<select id=\"language\" name=\"language\">$sLanguages</option>");
+	$oP->form($aForm);
+	$oP->add("<h2>Definition of the administrator account</h2>\n");
+	// Database created, continue with admin creation		
+	$oP->add("<fieldset><legend>Administrator account</legend>\n");
+	$aForm = array();
+	$aForm[] = array('label' => "Login$sRedStar:", 'input' => "<input id=\"auth_user\" type=\"text\" name=\"auth_user\" value=\"{$aParamValues['auth_user']}\">");
+	$aForm[] = array('label' => "Password$sRedStar:", 'input' => "<input id=\"auth_pwd\" type=\"password\" name=\"auth_pwd\" value=\"{$aParamValues['auth_pwd']}\">");
+	$aForm[] = array('label' => "Retype password$sRedStar:", 'input' => "<input  id=\"auth_pwd2\" type=\"password\" name=\"auth_pwd2\" value=\"{$aParamValues['auth_pwd']}\">");
+	$oP->form($aForm);
+	$oP->add("</fieldset>\n");
+	$oP->add("<h2 class=\"next\">Next: Sample data selection</h2>\n");
+	$oP->add("<table style=\"width:100%\"><tr>\n");
+	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\">Next >></button></td>\n");
+	$oP->add("</tr></table>\n");
+
+	// Form goes here
+	$oP->add("</form>\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("<form id=\"theForm\" method=\"post\"\">\n");
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\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("<h2>Loading of sample data</h2>\n");
+	$oP->p("<fieldset><legend> Do you want to load sample data into the database ? </legend>\n");
+	$sChecked = ($aParamValues['sample_data'] == 'no') ? '' : 'checked';
+	$oP->p("<input type=\"radio\" id=\"sample_data\" name=\"sample_data\" id=\"sample_data_no\" $sChecked value=\"yes\"><label for=\"sample_data_yes\"> Yes, for testing purposes, populate the database with sample data.</label>\n");
+	$sChecked = ($aParamValues['sample_data'] == 'no') ? 'checked' : '';
+	$oP->p("<input type=\"radio\" name=\"sample_data\" unchecked id=\"sample_data_no\" $sChecked value=\"no\"><label for=\"sample_data_no\"> No, this is a production system, load only the data required by the application.</label>\n");
+	$oP->p("</fieldset>\n");	
+	$oP->add("<h2 class=\"next\">Next: Installation summary</h2>\n");
+	$oP->add("<table style=\"width:100%\"><tr>\n");
+	$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack($iPrevStep)\"><< Back</button></td>\n");
+	$oP->add("<td style=\"text-align:right;\"><button type=\"submit\"> Next >></button></td>\n");
+	$oP->add("</tr></table>\n");
+
+	// End of visible form
+	$oP->add("</form>\n");
+	// Hidden form submitted when moving on to the next page, once all the data files
+	// have been processed
+	$oP->add("<form id=\"GoToNextStep\" method=\"post\">\n");
+	AddParamsToForm($oP, $aParamValues, array('sample_data'));
+	$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
+	$oP->add("</form>\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("<h3>iTop version ".$aAvailableModules['iTop']['version_code']." will be installed in the database: ".$oConfig->GetDBName()." on server: ".$oConfig->GetDBHost().".".'</h3>');
+		$oP->add('<div id="summary_content" style="height:350px;overflow-y:auto;border:1px solid #999;padding-left:1em;">');
+		if (count($aInstall) > 0)
+		{
+			$oP->add('<h3>Modules to install</h3>');
+			foreach($aInstall as $sModuleId => $aModule)
+			{
+				$oP->p('<img src="../images/added.png">&nbsp;'.$aModule['label'].' version '.$aModule['version_code']);
+			}		
+		}
+		$oP->add('<h3>Sample data</h3>');
+		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('<h3>Administrator account</h3>');
+		$oP->p('Login:'.htmlentities($aParamValues['auth_user'], ENT_QUOTES, 'UTF-8'));
+		$oP->add('<h3>Default application language:</h3>');
+		$aAvailableLanguages = Dict::GetLanguages();
+		$oP->p($aAvailableLanguages[$aParamValues['language']]['description']." (".$aAvailableLanguages[$aParamValues['language']]['localized_description'].")");
+		$oP->add('</div>');
+		
+		$oP->add("<form id=\"GoToNextStep\" method=\"post\">\n");
+		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step8\">\n");
+		AddParamsToForm($oP, $aParamValues);
+		$oP->add("<table style=\"width:100%\"><tr>\n");
+		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack(6)\"><< Back</button></td>\n");
+		$oP->add("<td style=\"text-align:right;\"><button type=\"button\" onClick=\"DoSubmit('Installing...', 7)\"> Install ! >></button></td>\n");
+		$oP->add("</tr></table>\n");
+		$oP->add("</form>\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("<h3>iTop instance: database: ".$oConfig->GetDBName()." on server: ".$oConfig->GetDBHost().", version ".$aAvailableModules['iTop']['version_db'].'</h3>');
+		$oP->add('<h3>Will be upgraded to '.$aAvailableModules['iTop']['version_code'].'</h3>');
+		$oP->add('<div id="summary_content" style="height:350px;overflow-y:auto;border:1px solid #999;padding-left:1em;">');
+		if (count($aUpgrade) > 0) 
+		{
+			$oP->add('<h3>Modules to upgrade</h3>');
+			foreach($aUpgrade as $sModuleId => $aModule)
+			{
+				$oP->add('<img src="../images/modified.png">&nbsp;'.$aModule['label'].' version '.$aModule['version_db'].' to version '.$aModule['version_code']);
+			}		
+		}
+		if (count($aInstall) > 0)
+		{
+			$oP->add('<h3>Modules to install</h3>');
+			foreach($aInstall as $sModuleId => $aModule)
+			{
+				$oP->p('<img src="../images/added.png">&nbsp;'.$aModule['label'].' version '.$aModule['version_code']);
+			}		
+		}
+		if (count($aUninstall) > 0)
+		{
+			$oP->add('<h3>Modules to remove</h3>');
+			foreach($aUninstall as $sModuleId => $aModule)
+			{
+				$oP->p('<img src="../images/delete.png">&nbsp;'.$aModule['label'].' '.$sModuleId.' version '.$aModule['version_db']);
+			}		
+		}
+		if (count($aUnchanged) > 0)
+		{
+			$oP->add('<h3>Modules that will remain unchanged</h3>');
+			foreach($aUnchanged as $sModuleId => $aModule)
+			{
+				$oP->p('<img src="../images/unchanged.png">&nbsp;'.$aModule['label'].' version '.$aModule['version_db']);
+			}		
+		}
+		$oP->add('<h3>Sample data</h3>');
+		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('</div>');
+		$oP->add("<form id=\"GoToNextStep\" method=\"post\">\n");
+		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step8\">\n");
+		AddParamsToForm($oP, $aParamValues);
+		$oP->add("<table style=\"width:100%\"><tr>\n");
+		$oP->add("<td style=\"text-align:left;\"><button type=\"button\" onClick=\"return DoGoBack(6)\"><< Back</button></td>\n");
+		$oP->add("<td style=\"text-align:right;\"><button type=\"button\" onClick=\"DoSubmit('Upgrading...', 7)\"> Upgrade ! >></button></td>\n");
+		$oP->add("</tr></table>\n");
+		$oP->add("</form>\n");
+		break;
+		
+		default:
+		$oP->error("Unsupported mode $sMode");
+	}
+	$oP->add("<div id=\"log\" style=\"color:#F00;\"></div>\n");
+	$oP->add_linked_script('./jquery.progression.js');
+	PopulateDataFilesList($oP, $aParamValues, $oConfig);
+	$oP->add_ready_script(
+<<<EOF
+		$('#log').ajaxError(
+				function(e, xhr, settings, exception)
+				{
+					bStopAysncProcess = true;
+					alert('Fatal error detected: '+ xhr.responseText);
+					$('#log').append(xhr.responseText);
+					$('#setup').unblock();
+				} );
+EOF
+);
+}
+
+/** 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'];
+	$iPrevStep = $aParamValues['previous_step'];
+	$aParamValues['previous_step'] = $iCurrentStep; // Come back here	
+
+	try
+	{
+		$sSessionName = $oConfig->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("<h1>iTop configuration wizard</h1>\n");
+				$oP->add("<h2>Step 5: Configuration completed</h2>\n");
+				
+				@unlink(FINAL_CONFIG_FILE); // remove the aborted config
+				$oP->error("Error: Failed to login for user: '$sAuthUser'\n");
+	
+				$oP->add("<form id=\"theForm\" method=\"post\">\n");
+				$oP->add("<button type=\"button\" onClick=\"return DoGoBack($iPrevStep);\"><< Back</button>\n");
+				AddParamsToForm($oP, $aParamValues);
+				$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step0\">\n");
+				$oP->add("</form>\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("<form id=\"theForm\" method=\"get\" action=\"../index.php\">\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("<h2>Manual operations required</h2>");
+			$oP->p("In order to complete the installation, the following manual operations are required:");
+			foreach($aManualSteps as $sModuleLabel => $sUrl)
+			{
+				$oP->p("<a href=\"$sUrl\" target=\"_blank\">Manual instructions for $sModuleLabel</a>");
+			}
+		}
+		else
+		{
+			$oP->add("<h2>Congratulations for installing iTop</h2>");
+			$oP->ok("The initialization completed successfully.");
+		}
+		// Form goes here.. No back button since the job is done !
+		$oP->add('<table style="width:600px;border:0;padding:0;"><tr>');
+		$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Free: Register your iTop version.\" href=\"http://www.combodo.com/register?product=iTop&version=".urlencode(ITOP_VERSION." revision ".ITOP_REVISION)."\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-register.gif\"/></td></a>");
+		$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Support from Combodo\" href=\"http://www.combodo.com/itopsupport\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-support.gif\"/></td></a>");
+		$oP->add("<td><a style=\"background:transparent;padding:0;\" title=\"Get Professional Training from Combodo\" href=\"http://www.combodo.com/itoptraining\" target=\"_blank\"><img style=\"border:0\" src=\"../images/setup-training.gif\"/></td></a>");
+		$oP->add('</tr></table>');
+		$oP->add("<p style=\"text-align:center;width:100%\"><button type=\"submit\">Enter iTop</button></p>\n");
+		$oP->add("</form>\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("<form id=\"theForm\" method=\"post\">\n");
+		$oP->add("<input type=\"hidden\" name=\"operation\" value=\"step7\">\n");
+		AddParamsToForm($oP, $aParamValues, array('previous_step'));
+		$oP->add("<button type=\"button\" onClick=\"return DoGoBack(7);\"><< Back</button>\n");
+		$oP->add("</form>\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("<h1>iTop configuration wizard</h1>\n");
+		$oP->add("<h2>Fatal error</h2>\n");
+		$oP->error("<b>Error:</b> 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 '<b>".realpath(FINAL_CONFIG_FILE)."</b>' 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("<h1>iTop configuration wizard</h1>\n");
+		$oP->add("<h2>Fatal error</h2>\n");
+		$oP->error("<b>Error:</b> 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 '<b>".realpath(dirname(TMP_CONFIG_FILE))."</b>' is writable for the web server.");
+		$oP->output();
+		exit;
+	}
+	if (!is_writable(dirname(TMP_CONFIG_FILE).'/setup'))
+	{
+		$oP->add("<h1>iTop configuration wizard</h1>\n");
+		$oP->add("<h2>Fatal error</h2>\n");
+		$oP->error("<b>Error:</b> the directory where to store temporary setup files is not writable.");
+		$oP->p("The wizard cannot create operate. Please make sure that the directory '<b>".realpath(dirname(TMP_CONFIG_FILE))."/setup</b>' 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("<button type=\"button\" onClick=\"window.history.back();\"><< Back</button>\n");
+}
+catch(CoreException $e)
+{
+	$oP->error("Error: '".$e->getHtmlDesc()."'");	
+	$oP->add("<button type=\"button\" onClick=\"window.history.back();\"><< Back</button>\n");
+}
+$oP->output();
+?>

+ 5 - 1
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)
 	{
 	}
 }

+ 113 - 19
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: '<p>Updating DB schema...<br/><div id=\"progress\">0%</div></p>'});
+		$('#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: '<p>Updating Profiles...<br/><div id=\"progress\">0%</div></p>'});
+		$('#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: '<p>Loading data...<br/><div id=\"progress\">0%</div></p>'});
-		$('#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: '<p>Loading data...<br/><div id=\"progress\">0%</div></p>'});
+			$('#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(',');
+}

+ 385 - 4
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.= "<li>{$aModule['label']} (id: $sId), depends on: ".implode(', ', $aDeps)."</li>";
 			}
 			$sHtml .= "</ul>\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 => ...
+ *     )
+ *     <module_name> => 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(<module1>, <module2>, ...)  
+ *         '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 <module> => array ('installed' => timestamp, 'version' => <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 "<p>$sDirectory</p>\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 "<p>Loading: $sDirectory/$sFile...</p>\n";
+					require_once($sDirectory.'/'.$sFile);
+					//echo "<p>Done.</p>\n";
+				}
+				catch(Exception $e)
+				{
+					// Continue...
+				}
+			}
+		}
+		closedir($hDir);
+	}
+	else
+	{
+		throw new Exception("Data directory (".$sDirectory.") not found or not readable.");
+	}
+}
 ?>