diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 79afb1620..6c9a4677c 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -532,7 +532,7 @@ class Config 'type' => 'string', 'description' => 'Source directory for the datamodel files. (which gets compiled to env-production).', // examples... not used - 'default' => 'datamodels/latest', + 'default' => '', 'value' => '', 'source_of_value' => '', 'show_in_conf_sample' => true, @@ -1420,7 +1420,7 @@ class Config /** * Helper function to initialize a configuration from the page arguments */ - public function UpdateFromParams($aParamValues, $sModulesDir = null) + public function UpdateFromParams($aParamValues, $sModulesDir = null, $bPreserveModuleSettings = false) { if (isset($aParamValues['application_path'])) { @@ -1469,7 +1469,7 @@ class Config // Merge the values with the ones provided by the modules // Make sure when don't load the same file twice... - $aModules = ModuleDiscovery::GetAvailableModules(APPROOT, $sModulesDir); + $aModules = ModuleDiscovery::GetAvailableModules(array(APPROOT.$sModulesDir)); foreach($aModules as $sModuleId => $aModuleInfo) { list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); @@ -1489,10 +1489,17 @@ class Config } if (isset($aModuleInfo['settings'])) { + list($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId); foreach($aModuleInfo['settings'] as $sProperty => $value) { - list($sName, $sVersion) = ModuleDiscovery::GetModuleName($sModuleId); - $this->SetModuleSetting($sName, $sProperty, $value); + if ($bPreserveModuleSettings && isset($this->m_aModuleSettings[$sName][$sProperty])) + { + // Do nothing keep the original value + } + else + { + $this->SetModuleSetting($sName, $sProperty, $value); + } } } if (isset($aModuleInfo['installer'])) diff --git a/images/extension.png b/images/extension.png new file mode 100644 index 000000000..1460b8d49 Binary files /dev/null and b/images/extension.png differ diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 3ea53ac7c..dcaa60c19 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -136,18 +136,36 @@ try switch($sOperation) { case 'async_action': - require_once(APPROOT.'/setup/wizardcontroller.class.inc.php'); - require_once(APPROOT.'/setup/wizardsteps.class.inc.php'); + 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); - $sClass = utils::ReadParam('step_class', ''); - $sState = utils::ReadParam('step_state', ''); - $sActionCode = utils::ReadParam('code', ''); - $aParams = utils::ReadParam('params', array(), false, 'raw_data'); - $oPage = new ajax_page(''); - $oDummyController = new WizardController(''); - $oStep = new $sClass($oDummyController, $sState); - $oStep->AsyncAction($oPage, $sActionCode, $aParams); - $oPage->output(); + $sConfigFile = utils::GetConfigFilePath(); + if (file_exists($sConfigFile) && !is_writable($sConfigFile)) + { + $oPage->error("Error: the configuration file '".$sConfigFile."' already exists and cannot be overwritten."); + $oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".realpath($sConfigFile)."' can be modified by the web server."); + $oPage->output(); + } + else + { + require_once(APPROOT.'/setup/wizardcontroller.class.inc.php'); + require_once(APPROOT.'/setup/wizardsteps.class.inc.php'); + + $sClass = utils::ReadParam('step_class', ''); + $sState = utils::ReadParam('step_state', ''); + $sActionCode = utils::ReadParam('code', ''); + $aParams = utils::ReadParam('params', array(), false, 'raw_data'); + $oPage = new ajax_page(''); + $oDummyController = new WizardController(''); + if (is_subclass_of($sClass, 'WizardStep')) + { + $oStep = new $sClass($oDummyController, $sState); + $oStep->AsyncAction($oPage, $sActionCode, $aParams); + } + $oPage->output(); + } break; ////////////////////////////// diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index ccb7a7d32..d0268baf2 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -126,7 +126,7 @@ class ApplicationInstaller $aPreinstall = $this->oParams->Get('preinstall'); $aCopies = $aPreinstall['copies']; - // disabled - $sReport = self::DoCopy($aCopies); + $sReport = self::DoCopy($aCopies); $sReport = "copy disabled..."; $aResult = array( @@ -167,7 +167,8 @@ class ApplicationInstaller case 'compile': $aSelectedModules = $this->oParams->Get('selected_modules'); - $sSourceDir = $this->oParams->Get('source_dir', 'datamodel'); + $sSourceDir = $this->oParams->Get('source_dir', 'datamodels/latest'); + $sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions'); $sTargetEnvironment = $this->oParams->Get('target_env', ''); if ($sTargetEnvironment == '') { @@ -190,7 +191,7 @@ class ApplicationInstaller } } - self::DoCompile($aSelectedModules, $sSourceDir, $sTargetDir, $sWorkspaceDir, $bUseSymbolicLinks); + self::DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sWorkspaceDir, $bUseSymbolicLinks); $aResult = array( 'status' => self::OK, @@ -215,8 +216,9 @@ class ApplicationInstaller $sDBPwd = $aDBParams['pwd']; $sDBName = $aDBParams['name']; $sDBPrefix = $aDBParams['prefix']; + $bOldAddon = $this->oParams->Get('old_addon', false); - self::DoUpdateDBSchema($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment); + self::DoUpdateDBSchema($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment, $bOldAddon); $aResult = array( 'status' => self::OK, @@ -247,8 +249,12 @@ class ApplicationInstaller $sAdminLanguage = $aAdminParams['language']; $sLanguage = $this->oParams->Get('language'); $aSelectedModules = $this->oParams->Get('selected_modules', array()); + $sDataModelVersion = $this->oParams->Get('datamodel_version', '0.0.0'); + $bOldAddon = $this->oParams->Get('old_addon', false); + $sSourceDir = $this->oParams->Get('source_dir', ''); - self::AfterDBCreate($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sAdminUser, $sAdminPwd, $sAdminLanguage, $sLanguage, $aSelectedModules, $sTargetEnvironment); + self::AfterDBCreate($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sAdminUser, + $sAdminPwd, $sAdminLanguage, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sDataModelVersion, $sSourceDir); $aResult = array( 'status' => self::OK, @@ -277,8 +283,9 @@ class ApplicationInstaller $sDBName = $aDBParams['name']; $sDBPrefix = $aDBParams['prefix']; $aFiles = $this->oParams->Get('files', array()); + $bOldAddon = $this->oParams->Get('old_addon', false); - self::DoLoadFiles($aSelectedModules, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment); + self::DoLoadFiles($aSelectedModules, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment, $bOldAddon); $aResult = array( 'status' => self::INFO, @@ -306,8 +313,11 @@ class ApplicationInstaller $sUrl = $this->oParams->Get('url', ''); $sLanguage = $this->oParams->Get('language', ''); $aSelectedModules = $this->oParams->Get('selected_modules', array()); + $bOldAddon = $this->oParams->Get('old_addon', false); + $sSourceDir = $this->oParams->Get('source_dir', ''); + $sPreviousConfigFile = $this->oParams->Get('previous_configuration_file', ''); - self::DoCreateConfig($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment); + self::DoCreateConfig($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile); $aResult = array( 'status' => self::INFO, @@ -338,6 +348,9 @@ class ApplicationInstaller 'next-step-label' => '', 'percentage-completed' => 100, ); + + SetupPage::log_error('An exception occurred: '.$e->getMessage()); + SetupPage::log("Stack trace:\n".$e->getTraceAsString()); } return $aResult; } @@ -374,7 +387,7 @@ class ApplicationInstaller } - protected static function DoCompile($aSelectedModules, $sSourceDir, $sTargetDir, $sWorkspaceDir = '', $bUseSymbolicLinks = false) + protected static function DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sWorkspaceDir = '', $bUseSymbolicLinks = false) { SetupPage::log_info("Compiling data model."); @@ -388,6 +401,7 @@ class ApplicationInstaller } $sSourcePath = APPROOT.$sSourceDir; + $sExtensionsPath = APPROOT.$sExtensionDir; $sTargetPath = APPROOT.$sTargetDir; if (!is_dir($sSourcePath)) { @@ -407,7 +421,7 @@ class ApplicationInstaller } } - $oFactory = new ModelFactory($sSourcePath); + $oFactory = new ModelFactory(array($sSourcePath, $sExtensionsPath)); $aModules = $oFactory->FindModules(); foreach($aModules as $foo => $oModule) @@ -441,13 +455,24 @@ class ApplicationInstaller } else { - $oMFCompiler = new MFCompiler($oFactory, $sSourcePath); + $oMFCompiler = new MFCompiler($oFactory); $oMFCompiler->Compile($sTargetPath, null, $bUseSymbolicLinks); SetupPage::log_info("Data model successfully compiled to '$sTargetPath'."); } + + // Special case to patch a ugly patch in itop-config-mgmt + $sFileToPatch = $sTargetPath.'/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php'; + if (file_exists($sFileToPatch)) + { + $sContent = file_get_contents($sFileToPatch); + + $sContent = str_replace("require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", "//\n// The line below is no longer needed in iTop 2.0 -- patched by the setup program\n// require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", $sContent); + + file_put_contents($sFileToPatch, $sContent); + } } - protected static function DoUpdateDBSchema($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '') + protected static function DoUpdateDBSchema($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '', $bOldAddon = false) { SetupPage::log_info("Update Database Schema for environment '$sTargetEnvironment'."); @@ -461,7 +486,14 @@ class ApplicationInstaller 'db_prefix' => $sDBPrefix, ); $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - + if ($bOldAddon) + { + // Old version of the add-on for backward compatibility with pre-2.0 data models + $oConfig->SetAddons(array( + 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', + )); + } + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); $oProductionEnv->InitDataModel($oConfig, true); // load data model only @@ -472,7 +504,7 @@ class ApplicationInstaller SetupPage::log_info("Database Schema Successfully Updated for environment '$sTargetEnvironment'."); } - protected static function AfterDBCreate($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sAdminUser, $sAdminPwd, $sAdminLanguage, $sLanguage, $aSelectedModules, $sTargetEnvironment = '') + protected static function AfterDBCreate($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sAdminUser, $sAdminPwd, $sAdminLanguage, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sDataModelVersion, $sSourceDir) { SetupPage::log_info('After Database Creation'); @@ -487,18 +519,27 @@ class ApplicationInstaller 'db_prefix' => $sDBPrefix, ); $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - + if ($bOldAddon) + { + // Old version of the add-on for backward compatibility with pre-2.0 data models + $oConfig->SetAddons(array( + 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', + )); + } + $oConfig->Set('source_dir', $sSourceDir); // Needed by RecordInstallation below + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); - $oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database + $oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database + self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously // Perform here additional DB setup... profiles, etc... // - $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), $sModulesDir); + $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir); foreach($aAvailableModules as $sModuleId => $aModule) { if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && - isset($aAvailableModules[$sModuleId]['installer']) ) + isset($aAvailableModules[$sModuleId]['installer']) ) { $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; SetupPage::log_info("Calling Module Handler: $sModuleInstallerClass::AfterDatabaseCreation(oConfig, {$aModule['version_db']}, {$aModule['version_code']})"); @@ -555,7 +596,7 @@ class ApplicationInstaller } } - if (!$oProductionEnv->RecordInstallation($oConfig, $aSelectedModules, $sModulesDir)) + if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModules, $sModulesDir)) { throw new Exception("Failed to record the installation information"); } @@ -591,7 +632,7 @@ class ApplicationInstaller } } - protected static function DoLoadFiles($aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '') + protected static function DoLoadFiles($aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '', $bOldAddon = false) { $aParamValues = array( 'db_server' => $sDBServer, @@ -604,7 +645,14 @@ class ApplicationInstaller $oConfig = new Config(); $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - + if ($bOldAddon) + { + // Old version of the add-on for backward compatibility with pre-2.0 data models + $oConfig->SetAddons(array( + 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', + )); + } + //Load the MetaModel if needed (asynchronous mode) if (!self::$bMetaModelStarted) { @@ -624,7 +672,7 @@ class ApplicationInstaller $aFiles = array(); $oProductionEnv = new RunTimeEnvironment(); - $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $sModulesDir); + $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir); foreach($aAvailableModules as $sModuleId => $aModule) { if (($sModuleId != ROOT_MODULE)) @@ -658,7 +706,7 @@ class ApplicationInstaller SetupPage::log_info("ending data load session"); } - protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment = '') + protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment, $bOldAddon, $sSourceDir, $sPreviousConfigFile) { $aParamValues = array( 'db_server' => $sDBServer, @@ -668,20 +716,46 @@ class ApplicationInstaller 'new_db_name' => $sDBName, 'db_prefix' => $sDBPrefix, 'application_path' => $sUrl, - 'mode' => $sMode, 'language' => $sLanguage, 'selected_modules' => implode(',', $aSelectedModules), ); - $oConfig = new Config(); + $bPreserveModuleSettings = false; + if ($sMode == 'upgrade') + { + try + { + $oOldConfig = new Config($sPreviousConfigFile); + $oConfig = clone($oOldConfig); + $bPreserveModuleSettings = true; + } + catch(Exception $e) + { + // In case the previous configuration is corrupted... start with a blank new one + $oConfig = new Config(); + } + } + else + { + $oConfig = new Config(); + } + // Migration: force utf8_unicode_ci as the collation to make the global search // NON case sensitive $oConfig->SetDBCollation('utf8_unicode_ci'); // Final config update: add the modules - $oConfig->UpdateFromParams($aParamValues, $sModulesDir); - + $oConfig->UpdateFromParams($aParamValues, $sModulesDir, $bPreserveModuleSettings); + if ($bOldAddon) + { + // Old version of the add-on for backward compatibility with pre-2.0 data models + $oConfig->SetAddons(array( + 'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php', + )); + } + $oConfig->Set('source_dir', $sSourceDir); + // Make sure the root configuration directory exists if (!file_exists(APPCONF)) { @@ -700,5 +774,7 @@ class ApplicationInstaller // try to make the final config file read-only @chmod($sConfigFile, 0444); // Read-only for owner and group, nothing for others + // Ready to go !! + MetaModel::ResetCache($sTargetEnvironment); } } diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 2f9a88a35..33d3986c2 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -27,15 +27,14 @@ class DOMFormatException extends Exception class MFCompiler { protected $oFactory; - protected $sSourceDir; + protected $aSourceDirs; protected $aRootClasses; protected $aLog; - public function __construct($oModelFactory, $sSourceDir) + public function __construct($oModelFactory) { $this->oFactory = $oModelFactory; - $this->sSourceDir = $sSourceDir; $this->aLog = array(); } @@ -97,7 +96,7 @@ class MFCompiler $sModuleVersion = $oModule->GetVersion(); $sModuleRootDir = realpath($oModule->GetRootDir()); - $sRelativeDir = substr($sModuleRootDir, strlen($this->sSourceDir) + 1); + $sRelativeDir = basename($sModuleRootDir); // Push the other module files SetupUtils::copydir($sModuleRootDir, $sTargetDir.'/'.$sRelativeDir, $bUseSymbolicLinks); @@ -878,11 +877,12 @@ EOF; { $sPHP .= "\nrequire_once('$sIncludeFile'); // Implementation of the class $sParentClass\n"; } - $sFullPath = $this->sSourceDir.'/'.$sModuleRelativeDir.'/'.$sIncludeFile; - if (!file_exists($sFullPath)) - { - throw new Exception("Failed to process class '".$oClass->getAttribute('id')."', from '$sModuleRelativeDir'. The required include file: '$sFullPath' does not exist."); - } +//TODO fix this !!! +// $sFullPath = $this->sSourceDir.'/'.$sModuleRelativeDir.'/'.$sIncludeFile; +// if (!file_exists($sFullPath)) +// { +// throw new Exception("Failed to process class '".$oClass->getAttribute('id')."', from '$sModuleRelativeDir'. The required include file: '$sFullPath' does not exist."); +// } } else { @@ -1151,7 +1151,6 @@ EOF; $sPHP = <<Run(); diff --git a/setup/modelfactory.class.inc.php b/setup/modelfactory.class.inc.php index e4ba50c9b..e3e1cf026 100644 --- a/setup/modelfactory.class.inc.php +++ b/setup/modelfactory.class.inc.php @@ -1076,7 +1076,7 @@ EOF */ public function FindModules($sSubDirectory = '') { - $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, $sSubDirectory); + $aAvailableModules = ModuleDiscovery::GetAvailableModules($this->sRootDir, array($sSubDirectory)); $aResult = array(); foreach($aAvailableModules as $sId => $aModule) { diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index 0f311fa2d..894ee4cd6 100644 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -30,11 +30,8 @@ class ModuleDiscovery ); - // Cache the results and the source directory - // Note that, as class can be declared within the module files, they cannot be loaded twice. - // Then the following assumption is made: within the same execution page, the module - // discovery CANNOT be executed on several different paths - protected static $m_sModulesRoot = null; + // Cache the results and the source directories + protected static $m_aSearchDirs = null; protected static $m_aModules = array(); // All the entries below are list of file paths relative to the module directory @@ -164,45 +161,44 @@ class ModuleDiscovery /** * 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 sRootDir Application root directory - * @param sSearchDir Directory to search (relative to root dir) + * @param aSearchDirs Array of directories to search (absolute paths) * @return Hash A big array moduleID => ModuleData */ - public static function GetAvailableModules($sRootDir, $sSearchDir, $oP = null) + public static function GetAvailableModules($aSearchDirs, $oP = null) { - $sLookupDir = realpath($sRootDir.'/'.$sSearchDir); - - if (self::$m_sModulesRoot != $sLookupDir) + if (self::$m_aSearchDirs != $aSearchDirs) { self::ResetCache(); } - if (is_null(self::$m_sModulesRoot)) + if (is_null(self::$m_aSearchDirs)) { - // First call - // - if ($sLookupDir == '') + self::$m_aSearchDirs = $aSearchDirs; + + // Not in cache, let's scan the disk + foreach($aSearchDirs as $sSearchDir) { - throw new Exception("Invalid directory '$sRootDir/$sSearchDir'"); + $sLookupDir = realpath($sSearchDir); + if ($sLookupDir == '') + { + throw new Exception("Invalid directory '$sSearchDir'"); + } + + clearstatcache(); + self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir)); } - self::$m_sModulesRoot = $sLookupDir; - - clearstatcache(); - self::ListModuleFiles($sSearchDir, $sRootDir); return self::GetModules($oP); } else { // Reuse the previous results - // return self::GetModules($oP); } } public static function ResetCache() { - self::$m_sModulesRoot = null; - self::$m_sModulesRoot = null; + self::$m_aSearchDirs = null; self::$m_aModules = array(); } @@ -233,6 +229,8 @@ class ModuleDiscovery */ protected static function ListModuleFiles($sRelDir, $sRootDir) { + static $iDummyClassIndex = 0; + static $aDefinedClasses = array(); $sDirectory = $sRootDir.'/'.$sRelDir; if ($hDir = opendir($sDirectory)) @@ -253,15 +251,35 @@ class ModuleDiscovery self::SetModulePath($sRelDir); try { - //echo "

Loading: $sDirectory/$sFile...

\n"; - //SetupPage::log_info("Discovered module $sFile"); - require($sDirectory.'/'.$sFile); // WARNING require_once will NOT work IIF doing an unattended installation WITH symbolic links - // since datamodel/xxx/module.xxx.php and env-production/xxx/module.xxx.php are actually the same file (= inode) + $sModuleFileContents = file_get_contents($sDirectory.'/'.$sFile); + $sModuleFileContents = str_replace(array(''), '', $sModuleFileContents); + $sModuleFileContents = str_replace('__FILE__', "'".addslashes($sDirectory.'/'.$sFile)."'", $sModuleFileContents); + preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches); + //print_r($aMatches); + $idx = 0; + foreach($aMatches[1] as $sClassName) + { + if (class_exists($sClassName)) + { + // rename the class inside the code to prevent a "duplicate class" declaration + // and change its parent class as well so that nobody will find it and try to execute it + $sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_'.($iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents); + } + $idx++; + } + $bRet = eval($sModuleFileContents); + + if ($bRet === false) + { + SetupPage::log_warning("Eval of $sRelDir/$sFile returned false"); + } + //echo "

Done.

\n"; } catch(Exception $e) { // Continue... + SetupPage::log_warning("Eval of $sRelDir/$sFile caused an exception: ".$e->getMessage()); } } } @@ -279,6 +297,41 @@ class ModuleDiscovery * the declaration of a module invokes SetupWebPage::AddModule() * whereas the new form is ModuleDiscovery::AddModule() */ -class SetupWebPage extends ModuleDiscovery{} +class SetupWebPage extends ModuleDiscovery +{ + // For backward compatibility with old modules... + public static function log_error($sText) + { + SetupPage::log_error($sText); + } -?> + public static function log_warning($sText) + { + SetupPage::log_warning($sText); + } + + public static function log_info($sText) + { + SetupPage::log_info($sText); + } + + public static function log_ok($sText) + { + SetupPage::log_ok($sText); + } + + public static function log($sText) + { + SetupPage::log($sText); + } +} + +/** Ugly patch !!! + * In order to be able to analyse / load several times + * the same module file, we rename the class (to avoid duplicate class definitions) + * and we make the class extends the dummy class below in order to "deactivate" completely + * the class (in case some piece of code enumerate the classes derived from a well known class) + * Note that this will not work if someone enumerates the classes that implement a given interface + */ +class DummyHandler { +} diff --git a/setup/runtimeenv.class.inc.php b/setup/runtimeenv.class.inc.php index 556ab881e..1fc3ef4dd 100644 --- a/setup/runtimeenv.class.inc.php +++ b/setup/runtimeenv.class.inc.php @@ -32,6 +32,7 @@ define ('MODULE_ACTION_OPTIONAL', 1); define ('MODULE_ACTION_MANDATORY', 2); define ('MODULE_ACTION_IMPOSSIBLE', 3); define ('ROOT_MODULE', '_Root_'); // Convention to store IN MEMORY the name/version of the root module i.e. application +define ('DATAMODEL_MODULE', 'datamodel'); // Convention to store the version of the datamodel class RunTimeEnvironment { @@ -91,7 +92,8 @@ class RunTimeEnvironment /** * Analyzes the current installation and the possibilities * - * @param $oConfig Config Defines the target environment (DB) + * @param Config $oConfig Defines the target environment (DB) + * @param mixed $modulesPath Either a single string or an array of absolute paths * @return hash Array with the following format: * array => * 'iTop' => array( @@ -115,7 +117,7 @@ class RunTimeEnvironment * ) * ) */ - public function AnalyzeInstallation($oConfig, $sModulesRelativePath) + public function AnalyzeInstallation($oConfig, $modulesPath) { $aRes = array( ROOT_MODULE => array( @@ -126,7 +128,8 @@ class RunTimeEnvironment ) ); - $aModules = ModuleDiscovery::GetAvailableModules(APPROOT, $sModulesRelativePath); + $aDirs = is_array($modulesPath) ? $modulesPath : array($modulesPath); + $aModules = ModuleDiscovery::GetAvailableModules($aDirs); foreach($aModules as $sModuleId => $aModuleInfo) { list($sModuleName, $sModuleVersion) = ModuleDiscovery::GetModuleName($sModuleId); @@ -137,7 +140,8 @@ class RunTimeEnvironment if ($sModuleVersion == '') { // The version must not be empty (it will be used as a criteria to determine wether a module has been installed or not) - throw new Exception("Missing version for the module: '$sModuleId'"); + //throw new Exception("Missing version for the module: '$sModuleId'"); + $sModuleVersion = '1.0.0'; } $sModuleAppVersion = $aModuleInfo['itop_version']; @@ -225,7 +229,7 @@ class RunTimeEnvironment // foreach ($aInstallByModule as $sModuleName => $aModuleDB) { - if ($sModuleName == ROOT_MODULE) continue; // Skip the main module + if ($sModuleName == ROOT_MODULE) continue; // Skip the main module if (!array_key_exists($sModuleName, $aRes)) { @@ -284,7 +288,7 @@ class RunTimeEnvironment // $oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE); $oSourceEnv = new RunTimeEnvironment($sSourceEnv); - $aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $sSourceDir); + $aAvailableModules = $oSourceEnv->AnalyzeInstallation($oSourceConfig, $sSourceDir); //TODO: use an absolute PATH // Do load the required modules // @@ -419,8 +423,19 @@ class RunTimeEnvironment return true; } - public function RecordInstallation(Config $oConfig, $aSelectedModules, $sModulesRelativePath) + public function RecordInstallation(Config $oConfig, $sDataModelVersion, $aSelectedModules, $sModulesRelativePath) { + // Record datamodel version + $aData = array( + 'source_dir' => $oConfig->Get('source_dir'), + ); + $oInstallRec = new ModuleInstallation(); + $oInstallRec->Set('name', DATAMODEL_MODULE); + $oInstallRec->Set('version', $sDataModelVersion); + $oInstallRec->Set('comment', json_encode($aData, true)); + $oInstallRec->Set('parent_id', 0); // root module + $iMainItopRecord = $oInstallRec->DBInsertNoReload(); + // Record main installation $oInstallRec = new ModuleInstallation(); $oInstallRec->Set('name', ITOP_APPLICATION); @@ -429,9 +444,10 @@ class RunTimeEnvironment $oInstallRec->Set('parent_id', 0); // root module $iMainItopRecord = $oInstallRec->DBInsertNoReload(); + // Record installed modules // - $aAvailableModules = $this->AnalyzeInstallation($oConfig, $sModulesRelativePath); + $aAvailableModules = $this->AnalyzeInstallation($oConfig, APPROOT.$sModulesRelativePath); foreach($aSelectedModules as $sModuleId) { $aModuleData = $aAvailableModules[$sModuleId]; @@ -471,6 +487,60 @@ class RunTimeEnvironment // Database is created, installation has been tracked into it return true; } + + public function GetApplicationVersion(Config $oConfig) + { + $aResult = false; + try + { + require_once(APPROOT.'/core/cmdbsource.class.inc.php'); + 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 erroneous information + return false; + } + + // Scan the list of installed modules to get the version of the 'ROOT' module which holds the main application version + foreach ($aSelectInstall as $aInstall) + { + $sModuleVersion = $aInstall['version']; + if ($sModuleVersion == '') + { + // Though the version cannot be empty in iTop 2.0, it used to be possible + // therefore we have to put something here or the module will not be considered + // as being installed + $sModuleVersion = '0.0.0'; + } + + if ($aInstall['parent_id'] == 0) + { + if ($aInstall['name'] == DATAMODEL_MODULE) + { + $aResult['datamodel_version'] = $sModuleVersion; + $aComments = json_decode($aInstall['comment'], true); + if (is_array($aComments)) + { + $aResult = array_merge($aResult, $aComments); + } + } + else + { + $aResult['product_name'] = $aInstall['name']; + $aResult['product_version'] = $sModuleVersion; + } + } + } + if (!array_key_exists('datamodel_version', $aResult)) + { + // Versions prior to 2.0 did not record the version of the datamodel + // so assume that the datamodel version is equal to the application version + $aResult['datamodel_version'] = $aResult['product_version']; + } + return $aResult; + } public static function MakeDirSafe($sDir) { @@ -501,6 +571,3 @@ class RunTimeEnvironment SetupPage::log_ok($sText); } } // End of class - - -?> diff --git a/setup/setuppage.class.inc.php b/setup/setuppage.class.inc.php index d1fbbd9b8..69b805a21 100644 --- a/setup/setuppage.class.inc.php +++ b/setup/setuppage.class.inc.php @@ -304,5 +304,3 @@ h3.clickable.open { } } } // End of class - -?> diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index a65a49fba..15d93684b 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -657,7 +657,7 @@ function DoCheckDBConnection() 'db_pwd': $("#db_pwd").val(), 'db_name': $("#db_name").val() } - if (oXHRCheckDB !== null) + if ((oXHRCheckDB != null) && (oXHRCheckDB != undefined)) { oXHRCheckDB.abort(); oXHRCheckDB = null; @@ -937,6 +937,21 @@ EOF { require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); $oConfig = new Config(); + $sSourceDir = $oWizard->GetParameter('source_dir', ''); + + if (strpos($sSourceDir, APPROOT) !== false) + { + $sRelativeSourceDir = str_replace(APPROOT, '', $sSourceDir); + } + else if (strpos($sSourceDir, $oWizard->GetParameter('previous_version_dir')) !== false) + { + $sRelativeSourceDir = str_replace($oWizard->GetParameter('previous_version_dir'), '', $sSourceDir); + } + else + { + throw(new Exception('Internal error: AnalyzeInstallation: source_dir is neither under APPROOT nor under previous_installation_dir ???')); + } + $aParamValues = array( 'db_server' => $oWizard->GetParameter('db_server', ''), @@ -944,16 +959,43 @@ EOF 'db_pwd' => $oWizard->GetParameter('db_pwd', ''), 'db_name' => $oWizard->GetParameter('db_name', ''), 'db_prefix' => $oWizard->GetParameter('db_prefix', ''), - 'source_dir' => APPROOT.'datamodel', + 'source_dir' => $sRelativeSourceDir, ); $oConfig->UpdateFromParams($aParamValues, 'datamodel'); + $aDirsToScan = array($sSourceDir); + if (is_dir($sSourceDir.'/extensions')) + { + $aDirsToScan[] = $sSourceDir.'/extensions'; + } + if (is_dir($oWizard->GetParameter('copy_extensions_from'))) + { + $aDirsToScan[] = $oWizard->GetParameter('copy_extensions_from'); + } $oProductionEnv = new RunTimeEnvironment(); - $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, 'datamodel'); + $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan); return $aAvailableModules; } + public static function GetApplicationVersion($oWizard) + { + require_once(APPROOT.'/setup/moduleinstaller.class.inc.php'); + $oConfig = new Config(); + + $aParamValues = array( + 'db_server' => $oWizard->GetParameter('db_server', ''), + 'db_user' => $oWizard->GetParameter('db_user', ''), + 'db_pwd' => $oWizard->GetParameter('db_pwd', ''), + 'db_name' => $oWizard->GetParameter('db_name', ''), + 'db_prefix' => $oWizard->GetParameter('db_prefix', ''), + 'source_dir' => '', + ); + $oConfig->UpdateFromParams($aParamValues, 'datamodel'); + + $oProductionEnv = new RunTimeEnvironment(); + return $oProductionEnv->GetApplicationVersion($oConfig); + } /** * Checks if the content of a directory matches the given manifest * @param string $sBaseDir Path to the root directory of iTop @@ -964,6 +1006,7 @@ EOF */ public static function CheckDirAgainstManifest($sBaseDir, $sSourceDir, $aManifest, $aExcludeNames = array('.svn'), $aResult = null) { +//echo "CheckDirAgainstManifest($sBaseDir, $sSourceDir ...)\n"; if ($aResult === null) { $aResult = array('added' => array(), 'removed' => array(), 'modified' => array()); @@ -990,10 +1033,13 @@ EOF } } +//echo "The manifest contains ".count($aDirManifest)." files for the directory '$sSourceDir' (and below)\n"; + // Read the content of the directory foreach(glob($sBaseDir.'/'.$sSourceDir .'/*') as $sFilePath) { $sFile = basename($sFilePath); +//echo "Checking $sFile ($sFilePath)\n"; if (in_array(basename($sFile), $aExcludeNames)) continue; @@ -1023,6 +1069,7 @@ EOF if ($sMD5 != $aDirManifest[$sFile]['md5']) { $aResult['modified'][$sSourceDir.'/'.$sFile] = 'Content modified (MD5 checksums differ).'; +//echo $sSourceDir.'/'.$sFile." modified ($sMD5 == {$aDirManifest[$sFile]['md5']})\n"; } //else //{ @@ -1051,9 +1098,10 @@ EOF $aManifest[] = array('path' => (string)$oFileInfo->path, 'size' => (int)$oFileInfo->size, 'md5' => (string)$oFileInfo->md5); } + $sBaseDir = preg_replace('|modules/?$|', '', $sBaseDir); $aResults = self::CheckDirAgainstManifest($sBaseDir, 'modules', $aManifest); -// echo "
Comparison of ".dirname($sBaseDir)."/modules:\n".print_r($aResults, true)."
"; +// echo "
Comparison of ".dirname($sBaseDir)."/modules against $sManifestFile:\n".print_r($aResults, true)."
"; return $aResults; } @@ -1084,7 +1132,7 @@ EOF $aResults = array('added' => array(), 'removed' => array(), 'modified' => array()); foreach(array('addons', 'core', 'dictionaries', 'js', 'application', 'css', 'pages', 'synchro', 'webservices') as $sDir) { - $aTmp = self::CheckDirAgainstManifest($sBaseDir, 'portal', $aManifest); + $aTmp = self::CheckDirAgainstManifest($sBaseDir, $sDir, $aManifest); $aResults['added'] = array_merge($aResults['added'], $aTmp['added']); $aResults['modified'] = array_merge($aResults['modified'], $aTmp['modified']); $aResults['removed'] = array_merge($aResults['removed'], $aTmp['removed']); @@ -1094,4 +1142,99 @@ EOF return $aResults; } + public static function CheckVersion($sInstalledVersion, $sSourceDir) + { + $sManifestFilePath = self::GetVersionManifest($sInstalledVersion); + if ($sSourceDir != '') + { + if (file_exists($sManifestFilePath)) + { + $aDMchanges = self::CheckDataModelFiles($sManifestFilePath, $sSourceDir); + //$aPortalChanges = self::CheckPortalFiles($sManifestFilePath, $sSourceDir); + //$aCodeChanges = self::CheckApplicationFiles($sManifestFilePath, $sSourceDir); + + //echo("Changes detected compared to $sInstalledVersion:
DataModel:
".print_r($aDMchanges, true)."
"); + //echo("Changes detected compared to $sInstalledVersion:
DataModel:
".print_r($aDMchanges, true)."

Portal:
".print_r($aPortalChanges, true)."

Code:
".print_r($aCodeChanges, true)."
"); + return $aDMchanges; + } + else + { + return false; + } + } + else + { + throw(new Exception("Cannot check version '$sInstalledVersion', no source directory provided to check the files.")); + } + } + + public static function GetVersionManifest($sInstalledVersion) + { + if (preg_match('/^([0-9]+)\./', $sInstalledVersion, $aMatches)) + { + return APPROOT.'datamodels/'.$aMatches[1].'.x/manifest-'.$sInstalledVersion.'.xml'; + } + return false; + } + + public static function CheckWritableDirs($aWritableDirs) + { + $aNonWritableDirs = array(); + foreach($aWritableDirs as $sDir) + { + $sFullPath = APPROOT.$sDir; + if (is_dir($sFullPath) && !is_writable($sFullPath)) + { + $aNonWritableDirs[APPROOT.$sDir] = new CheckResult(CheckResult::ERROR, "The directory '".APPROOT.$sDir."' exists but is not writable for the application."); + } + else if (file_exists($sFullPath) && !is_dir($sFullPath)) + { + $aNonWritableDirs[APPROOT.$sDir] = new CheckResult(CheckResult::ERROR, "A file with the same name as '".APPROOT.$sDir."' exists."); + } + else if (!is_dir($sFullPath) && !is_writable(APPROOT)) + { + $aNonWritableDirs[APPROOT] = new CheckResult(CheckResult::ERROR, "The directory '".APPROOT."' is not writable, the application cannot create the directory '$sDir' inside it."); + } + } + return $aNonWritableDirs; + } + + public static function GetLatestDataModelDir() + { + $sBaseDir = APPROOT.'datamodels'; + + $aDirs = glob($sBaseDir.'/*', GLOB_MARK | GLOB_ONLYDIR); + if ($aDirs !== false) + { + sort($aDirs); + + return array_pop($aDirs); + } + return false; + } + + public static function GetCompatibleDataModelDir($sInstalledVersion) + { + if (preg_match('/^([0-9]+)\./', $sInstalledVersion, $aMatches)) + { + $sMajorVersion = $aMatches[1]; + $sDir = APPROOT.'datamodels/'.$sMajorVersion.'.x/'; + if (is_dir($sDir)) + { + return $sDir; + } + } + return false; + } + + static public function GetDataModelVersion($sDatamodelDir) + { + $sVersionFile = $sDatamodelDir.'version.xml'; + if (file_exists($sVersionFile)) + { + $oParams = new XMLParameters($sVersionFile); + return $oParams->Get('version'); + } + return false; + } } \ No newline at end of file diff --git a/setup/wizardcontroller.class.inc.php b/setup/wizardcontroller.class.inc.php index c6603b0f2..276704dee 100644 --- a/setup/wizardcontroller.class.inc.php +++ b/setup/wizardcontroller.class.inc.php @@ -164,6 +164,23 @@ class WizardController protected function DisplayStep(WizardStep $oStep) { $oPage = new SetupPage($oStep->GetTitle()); + if ($oStep->RequiresWritableConfig()) + { + $sConfigFile = utils::GetConfigFilePath(); + if (file_exists($sConfigFile)) + { + // The configuration file already exists + if (!is_writable($sConfigFile)) + { + $oP = new SetupPage('Installation Cannot Continue'); + $oP->add("

Fatal error

\n"); + $oP->error("Error: the configuration file '".$sConfigFile."' already exists and cannot be overwritten."); + $oP->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '".realpath($sConfigFile)."' can be modified by the web server."); + $oP->output(); + return; + } + } + } $oPage->add_linked_script('../setup/setup.js'); $oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n"); $oPage->add_script("function CanMoveBackward()\n{\n".$oStep->JSCanMoveBackward()."\n}\n"); @@ -432,6 +449,15 @@ abstract class WizardStep return 'return true;'; } + /** + * Tells whether this step of the wizard requires that the configuration file be writable + * @return bool True if the wizard will possibly need to modify the configuration at some point + */ + public function RequiresWritableConfig() + { + return true; + } + /** * Overload this function to implement asynchronous action(s) (AJAX) * @param string $sCode The code of the action (if several actions need to be distinguished) diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php index 157c4f985..bee0213dc 100644 --- a/setup/wizardsteps.class.inc.php +++ b/setup/wizardsteps.class.inc.php @@ -157,7 +157,7 @@ class WizStepInstallOrUpgrade extends WizardStep $sNextStep = ''; $sInstallMode = utils::ReadParam('install_mode'); - $this->oWizard->SaveParameter('source_dir', ''); + $this->oWizard->SaveParameter('previous_version_dir', ''); $this->oWizard->SaveParameter('db_server', ''); $this->oWizard->SaveParameter('db_user', ''); $this->oWizard->SaveParameter('db_pwd', ''); @@ -169,6 +169,9 @@ class WizStepInstallOrUpgrade extends WizardStep if ($sInstallMode == 'install') { $this->oWizard->SetParameter('install_mode', 'install'); + $sFullSourceDir = SetupUtils::GetLatestDataModelDir(); + $this->oWizard->SetParameter('source_dir', $sFullSourceDir); + $this->oWizard->SetParameter('datamodel_version', SetupUtils::GetDataModelVersion($sFullSourceDir)); $sNextStep = 'WizStepLicense'; } else @@ -183,7 +186,6 @@ class WizStepInstallOrUpgrade extends WizardStep public function Display(WebPage $oPage) { $sInstallMode = $this->oWizard->GetParameter('install_mode', ''); - $sSourceDir = $this->oWizard->GetParameter('source_dir', ''); $sDBServer = $this->oWizard->GetParameter('db_server', ''); $sDBUser = $this->oWizard->GetParameter('db_user', ''); $sDBPwd = $this->oWizard->GetParameter('db_pwd', ''); @@ -191,6 +193,7 @@ class WizStepInstallOrUpgrade extends WizardStep $sDBPrefix = $this->oWizard->GetParameter('db_prefix', ''); $bDBBackup = $this->oWizard->GetParameter('db_backup', false); $sDBBackupPath = $this->oWizard->GetParameter('db_backup_path', ''); + $sPreviousVersionDir = ''; if ($sInstallMode == '') { $sDBBackupPath = APPROOT.'data/'.ITOP_APPLICATION.strftime('-backup-%Y-%m-%d.zip'); @@ -205,14 +208,15 @@ class WizStepInstallOrUpgrade extends WizardStep $sDBPwd = $aPreviousInstance['db_pwd']; $sDBName = $aPreviousInstance['db_name']; $sDBPrefix = $aPreviousInstance['db_prefix']; - //TODO: check if we can run the backup $sStyle = ''; + $sPreviousVersionDir = APPROOT; } else { $sInstallMode = 'install'; } } + $sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', $sPreviousVersionDir); $sUpgradeInfoStyle = ''; if ($sInstallMode == 'install') @@ -227,7 +231,7 @@ class WizStepInstallOrUpgrade extends WizardStep //$oPage->add('
'); //$oPage->add('Information about the previous instance:'); $oPage->add(''); - $oPage->add(''); + $oPage->add(''); SetupUtils::DisplayDBParameters($oPage, false, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix); $aBackupChecks = SetupUtils::CheckBackupPrerequisites($sDBBackupPath); @@ -263,7 +267,7 @@ class WizStepInstallOrUpgrade extends WizardStep << 'WizStepLicense2', 'state' => ''); - } - - public function Display(WebPage $oPage) - { - $oPage->p('Info about the detected version'); - $sSourceDir = $this->oWizard->GetParameter('source_dir', ''); - $aInstalledModules = SetupUtils::AnalyzeInstallation($this->oWizard); - $sVersion = $aInstalledModules[ROOT_MODULE]['version_db']; - - if (preg_match('/^([0-9]+)\.([0-9]+)\.([0-9]+)\.(.*)$/', $sVersion, $aMatches)) - { - $sVersion = $aMatches[1].'.'.$aMatches[2].'.'.$aMatches[3]; - } - - $sKnownManifestFile = APPROOT.'setup/known-versions/'.$sVersion.'/manifest.xml'; - if ($sSourceDir != '') - { - if (file_exists($sKnownManifestFile)) - { - $aDMchanges = SetupUtils::CheckDataModelFiles($sKnownManifestFile, $sSourceDir); - $aPortalChanges = SetupUtils::CheckPortalFiles($sKnownManifestFile, $sSourceDir); - $aCodeChanges = SetupUtils::CheckApplicationFiles($sKnownManifestFile, $sSourceDir); - - $oPage->add("Changes detected compared to $sVersion:
DataModel:
".print_r($aDMchanges, true)."

Portal:
".print_r($aPortalChanges, true)."

Code:
".print_r($aCodeChanges, true)."
"); - } - else - { - $oPage->p("Unknown version $sVersion. Do you want to upgrade anyway ??? NOT GUARANTEED AT ALL !!!"); - } - } - else - { - $oPage->p("No source dir provided assuming that the installed version '$sVersion' is genuine iTop build..."); - } - - } -} - -/** - * Keep or Upgrade choice - */ -class WizStepUpgradeKeep extends WizardStep -{ - public function GetTitle() - { - return 'Keep or Upgrade'; - } - - public function GetPossibleSteps() - { - return array('WizStepModulesChoice'); - } - - public function ProcessParams($bMoveForward = true) - { - - return array('class' => 'WizStepModulesChoice', 'state' => 'start_upgrade'); - } - - public function Display(WebPage $oPage) - { - $oPage->p('Keep or Upgrade the data model'); - } -} - -/** - * Automatic Upgrade info - */ -class WizStepUpgradeAuto extends WizardStep -{ public function GetTitle() { return 'Upgrade Information'; @@ -449,20 +371,261 @@ class WizStepUpgradeAuto extends WizardStep public function GetPossibleSteps() { - return array('WizStepModulesChoice'); + return array('WizStepUpgradeMiscParams', 'WizStepLicense2'); } public function ProcessParams($bMoveForward = true) { + $sUpgradeType = utils::ReadParam('upgrade_type'); + + $this->oWizard->SetParameter('mode', 'upgrade'); + $this->oWizard->SetParameter('upgrade_type', $sUpgradeType); + $this->oWizard->SaveParameter('copy_extensions_from', ''); + $bDisplayLicense = $this->oWizard->GetParameter('display_license'); - return array('class' => 'WizStepModulesChoice', 'state' => 'start_upgrade'); + switch ($sUpgradeType) + { + case 'keep-previous': + $sSourceDir = utils::ReadParam('relative_source_dir', '', false, 'raw_data'); + $this->oWizard->SetParameter('source_dir', $this->oWizard->GetParameter('previous_version_dir').'/'.$sSourceDir); + $this->oWizard->SetParameter('datamodel_version', utils::ReadParam('datamodel_previous_version', '', false, 'raw_data')); + break; + + case 'use-compatible': + $sDataModelPath = utils::ReadParam('datamodel_path', '', false, 'raw_data'); + $this->oWizard->SetParameter('source_dir', $sDataModelPath); + $this->oWizard->SaveParameter('datamodel_version', ''); + break; + + default: + // Do nothing, maybe the user pressed the Back button + } + if ($bDisplayLicense) + { + $aRet = array('class' => 'WizStepLicense2', 'state' => ''); + } + else + { + $aRet = array('class' => 'WizStepUpgradeMiscParams', 'state' => ''); + } + return $aRet; } public function Display(WebPage $oPage) { - $oPage->p('Automatic Upgrade information'); + $oPage->add_style( +<<bCanMoveForward = true; + $bDisplayLicense = true; + $sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', ''); + $aInstalledInfo = SetupUtils::GetApplicationVersion($this->oWizard); + + if ($aInstalledInfo === false) + { + throw(new Exception('No previous version of '.ITOP_APPLICATION.' found in the supplied database. The upgrade cannot continue.')); + } + else if (strcasecmp($aInstalledInfo['product_name'], ITOP_APPLICATION) != 0) + { + $oPage->p("Warning: The installed products seem different. Are you sure the you want to upgrade {$aInstalledInfo['product_name']} with ".ITOP_APPLICATION."?"); + } + + $sInstalledVersion = $aInstalledInfo['product_version']; + $sInstalledDataModelVersion = $aInstalledInfo['datamodel_version']; + + if ($sInstalledDataModelVersion == '$ITOP_VERSION$.$WCREV$') + { + // Special case for upgrading some development versions (temporary) + $sLatestDMDir = SetupUtils::GetLatestDataModelDir(); + $sInstalledDataModelVersion = SetupUtils::GetDataModelVersion($sLatestDMDir); + } + + $oPage->add("

Information about the upgrade from version $sInstalledVersion to ".ITOP_VERSION.'.'.ITOP_REVISION."

"); + + if ($sInstalledVersion == (ITOP_VERSION.'.'.ITOP_REVISION)) + { + // Reinstalling the same version let's skip the license agreement... + $bDisplayLicense = false; + } + $this->oWizard->SetParameter('license', $bDisplayLicense); // Remember for later + + $sCompatibleDMDir = SetupUtils::GetCompatibleDataModelDir($sInstalledDataModelVersion); + if ($sCompatibleDMDir === false) + { + // No compatible version exists... cannot upgrade. Either it is too old, or too new (downgrade !) + $this->bCanMoveForward = false; + $oPage->p("The current version of ".ITOP_APPLICATION." (".ITOP_VERSION.'.'.ITOP_REVISION.") does not sem to be compatible with the installed version ($sInstalledVersion)."); + $oPage->p("The upgrade cannot continue, sorry."); + } + else + { + $sUpgradeDMVersion = SetupUtils::GetDataModelVersion($sCompatibleDMDir); + $sPreviousSourceDir = isset($aInstalledInfo['source_dir']) ? $aInstalledInfo['source_dir'] : 'modules'; + $aChanges = false; + if (is_dir($sPreviousVersionDir)) + { + // Check if the previous version is a "genuine" one or not... + $aChanges = SetupUtils::CheckVersion($sInstalledDataModelVersion, $sPreviousVersionDir.'/'.$sPreviousSourceDir); + } + if (($aChanges !== false) && (count($aChanges) > 0)) + { + // Some changes were detected, prompt the user to keep or discard them + $oPage->p(" Some modifications were detected between the ".ITOP_APPLICATION." version in '$sPreviousVersionDir' and a genuine $sInstalledVersion version."); + $oPage->p("What do you want to do?"); + + $aWritableDirs = array('modules', 'portal'); + $aErrors = SetupUtils::CheckWritableDirs($aWritableDirs); + $sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'keep-previous') ? ' checked ' : ''; + $sDisabled = (count($aErrors) > 0) ? ' disabled ' : ''; + + $oPage->p(''); + $oPage->add(''); + + $oPage->add(''); + + if (count($aErrors) > 0) + { + $oPage->p("Cannot copy the installed version due to the following access rights issue(s):"); + foreach($aErrors as $sDir => $oCheckResult) + { + $oPage->p(' '.$oCheckResult->sLabel); + } + } + + $sChecked = ($this->oWizard->GetParameter('upgrade_type') == 'use-compatible') ? ' checked ' : ''; + + $oPage->p(''); + + $oPage->add(''); + $oPage->add(''); + + $oPage->add('
Details of the modifications
'); + if (count($aChanges['added']) > 0) + { + $oPage->add('
    New files added:'); + foreach($aChanges['added'] as $sFilePath => $void) + { + $oPage->add('
  • '.$sFilePath.'
  • '); + } + $oPage->add('
'); + } + if (count($aChanges['removed']) > 0) + { + $oPage->add('
    Deleted files:'); + foreach($aChanges['removed'] as $sFilePath => $void) + { + $oPage->add('
  • '.$sFilePath.'
  • '); + } + $oPage->add('
'); + } + if (count($aChanges['modified']) > 0) + { + $oPage->add('
    Modified files:'); + foreach($aChanges['modified'] as $sFilePath => $void) + { + $oPage->add('
  • '.$sFilePath.'
  • '); + } + $oPage->add('
'); + } + $oPage->add('
'); + } + else + { + // No changes detected... or no way to tell because of the lack of a manifest or previous source dir + // Use the "compatible" datamodel as-is. + $oPage->p(" The datamodel will be upgraded from version $sInstalledDataModelVersion to version $sUpgradeDMVersion."); + $oPage->add(''); + $oPage->add(''); + $oPage->add(''); + } + + // Check if there are "extensions" to preserve and if it's possible + if (is_dir($sPreviousVersionDir.'/extensions')) + { + $aExtensions = glob($sPreviousVersionDir.'/extensions/*', GLOB_ONLYDIR); + if (($aExtensions !== false) && (count($aExtensions)>0)) + { + $aWritableDirs = array('extensions'); + $aErrors = SetupUtils::CheckWritableDirs($aWritableDirs); + if (count($aErrors) > 0) + { + $oPage->p("Cannot copy the extensions from '$sPreviousVersionDir/extensions' to '".APPROOT."extensions' due to the following access rights issue(s):"); + foreach($aErrors as $sDir => $oCheckResult) + { + $oPage->p(' '.$oCheckResult->sLabel); + } + } + else + { + $oPage->p("Note: The extensions present in '$sPreviousVersionDir/extensions' will be copied to '".APPROOT."extensions'."); + $oPage->add(''); + } + } + } + $oPage->add_ready_script( +<<bCanMoveForward; + } + + /** + * Tells whether the "Next" button should be enabled interactively + * @return string A piece of javascript code returning either true or false + */ + public function JSCanMoveForward() + { + return +<< 0); + return bRet; +EOF + ; } } + /** * License acceptation screen */ @@ -534,12 +697,12 @@ class WizStepLicense2 extends WizStepLicense { public function GetPossibleSteps() { - return array('WizStepUpgradeKeep', 'WizStepUpgradeAuto'); + return array('WizStepUpgradeMiscParams'); } public function ProcessParams($bMoveForward = true) { - return array('class' => 'WizStepUpgradeAuto', 'state' => ''); + return array('class' => 'WizStepUpgradeMiscParams', 'state' => ''); } } @@ -799,6 +962,67 @@ EOF } } +/** + * Miscellaneous Parameters (URL...) in case of upgrade + */ +class WizStepUpgradeMiscParams extends WizardStep +{ + public function GetTitle() + { + return 'Miscellaneous Parameters'; + } + + public function GetPossibleSteps() + { + return array('WizStepModulesChoice'); + } + + public function ProcessParams($bMoveForward = true) + { + $this->oWizard->SaveParameter('application_url', ''); + return array('class' => 'WizStepModulesChoice', 'state' => 'start_upgrade'); + } + + public function Display(WebPage $oPage) + { + $sApplicationURL = $this->oWizard->GetParameter('application_url', utils::GetDefaultUrlAppRoot()); + $oPage->add('

Additional parameters

'); + $oPage->add('
'); + $oPage->add('Application URL'); + $oPage->add('
Location on the disk:
Location on the disk:
'); + $oPage->add(''); + $oPage->add(''); + $oPage->add('
URL:
Change the value above if the end-users will be accessing the application by another path due to a specific configuration of the web server.
'); + $oPage->add('
'); + $oPage->add_ready_script( +<<'); + } + else + { + $("#v_application_url").html(''); + } + return bRet; +EOF + ; + } +} /** * Choice of the modules to be installed */ @@ -848,6 +1072,10 @@ class WizStepModulesChoice extends WizardStep $sDisplayChoices .= $this->GetSelectedModules($aStepInfo, $aSelectedChoices[$i], $aModules); } $sDisplayChoices .= ''; + if (class_exists('CreateITILProfilesInstaller')) + { + $this->oWizard->SetParameter('old_addon', true); + } $this->oWizard->SetParameter('selected_modules', json_encode(array_keys($aModules))); $this->oWizard->SetParameter('display_choices', $sDisplayChoices); return array('class' => 'WizStepSummary', 'state' => ''); @@ -865,6 +1093,7 @@ class WizStepModulesChoice extends WizardStep { $aStepInfo = $this->GetStepInfo(); $oPage->add_style("div.choice { margin: 0.5em;}"); + $oPage->add_style("div.choice a { text-decoration:none; font-weight: bold; color: #1C94C4 }"); $oPage->add_style("div.description { margin-left: 2em; }"); $oPage->add_style(".choice-disabled { color: #999; }"); $oPage->add(''); @@ -891,7 +1120,8 @@ class WizStepModulesChoice extends WizardStep // Build the default choices $aDefaults = array(); - $this->GetDefaults($aStepInfo, $aDefaults); + $aModules = SetupUtils::AnalyzeInstallation($this->oWizard); + $this->GetDefaults($aStepInfo, $aDefaults, $aModules); $index = $this->GetStepIndex(); // retrieve the saved selection @@ -903,7 +1133,7 @@ class WizStepModulesChoice extends WizardStep } $aSelectedComponents = $aParameters[$index]; - $oPage->add('
'); + $oPage->add('
'); $this->DisplayOptions($oPage, $aStepInfo, $aSelectedComponents); $oPage->add('
'); @@ -964,7 +1194,7 @@ EOF ); } - protected function GetDefaults($aInfo, &$aDefaults, $sParentId = '') + protected function GetDefaults($aInfo, &$aDefaults, $aModules, $sParentId = '') { $aOptions = isset($aInfo['options']) ? $aInfo['options'] : array(); foreach($aOptions as $index => $aChoice) @@ -974,6 +1204,15 @@ EOF { $aDefaults[$sChoiceId] = $sChoiceId; } + foreach($aChoice['modules'] as $sModuleId) + { + if ($aModules[$sModuleId]['version_db'] != '') + { + // A module corresponding to this choice is installed, the whole choice is selected + $aDefaults[$sChoiceId] = $sChoiceId; + break; + } + } if (isset($aChoice['sub_options'])) { $this->GetDefaults($aChoice['sub_options'], $aDefaults, $sChoiceId); @@ -996,10 +1235,30 @@ EOF } if (isset($aChoice['sub_options'])) { - $this->GetDefaults($aChoice['sub_options'], $aDefaults, $sChoiceId); + $this->GetDefaults($aChoice['sub_options'], $aDefaults, $aModules, $sChoiceId); } $index++; } + + // the installed choices have precedence over the 'default' choices + $sChoiceName = null; + foreach($aAlternatives as $index => $aChoice) + { + $sChoiceId = $sParentId.'_'.$index; + if ($sChoiceName == null) + { + $sChoiceName = $sChoiceId; + } + foreach($aChoice['modules'] as $sModuleId) + { + if ($aModules[$sModuleId]['version_db'] != '') + { + // A module corresponding to this choice is installed, the whole choice is selected + $aDefaults[$sChoiceName] = $sChoiceId; + break; + } + } + } } /** @@ -1023,7 +1282,7 @@ EOF { $aModules[$sModuleId] = true; } - } + } } } @@ -1113,65 +1372,102 @@ EOF { $index = $idx; } + + $aSteps = array(); if (@file_exists($this->GetSourceFilePath())) { $aParams = new XMLParameters($this->GetSourceFilePath()); $aSteps = $aParams->Get('steps', array()); - if (array_key_exists($index, $aSteps)) - { - $aStepInfo = $aSteps[$index]; - } + $bAddExtensionsOnly = true; } - else if ($idx == 0) + else { // No wizard configuration provided, build a standard one: - $aAvailableModules = SetupUtils::AnalyzeInstallation($this->oWizard); - $aStepInfo = array( + $bAddExtensionsOnly = false; + $aSteps[] = array( 'title' => 'Modules Selection', 'description' => '

Select the modules to install. You can launch the installation again to install new modules, but you cannot remove already installed modules.

', 'banner' => '/images/modules.png', - 'options' => array(), + 'options' => array() + ); + } + + // Additional step for the extensions + $aSteps[] = array( + 'title' => 'Extensions', + 'description' => '

Select additional extensions to install. You can launch the installation again to install new extensions, but you cannot remove already installed extensions.

', + 'banner' => '/images/extension.png', + 'options' => array() ); - - try - { - $sDefaultAppPath = utils::GetDefaultUrlAppRoot(); - } - catch(Exception $e) - { - $sDefaultAppPath = '..'; - } - foreach($aAvailableModules as $sModuleId => $aModule) + try + { + $sDefaultAppPath = utils::GetDefaultUrlAppRoot(); + } + catch(Exception $e) + { + $sDefaultAppPath = '..'; + } + + $aAvailableModules = SetupUtils::AnalyzeInstallation($this->oWizard); + foreach($aAvailableModules as $sModuleId => $aModule) + { + if ($sModuleId == ROOT_MODULE) continue; // Convention: the version number of the application (and datamodel) are stored as a module named ROOT_MODULE + + $sModuleLabel = $aModule['label']; + $sModuleHelp = $aModule['doc.more_information']; + $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; + if (($aModule['category'] != 'authentication') && ($aModule['visible'])) { - if ($sModuleId == ROOT_MODULE) continue; // Convention: the version number of the application is stored as a module named ROOT_MODULE - - $sModuleLabel = $aModule['label']; - $sModuleHelp = $aModule['doc.more_information']; - $sMoreInfo = (!empty($aModule['doc.more_information'])) ? "more info": ''; - if (($aModule['category'] != 'authentication') && ($aModule['visible'])) + if (($bAddExtensionsOnly) && (!$this->IsExtension($aModule))) continue; + + if ($this->IsExtension($aModule)) { - $aStepInfo['options'][$index] = array( - 'title' => $sModuleLabel, - 'description' => '', - 'more_info' => $sMoreInfo, - 'default' => true, // by default offer to install all modules - 'modules' => array($sModuleId), - ); - - switch($aModule['install']['flag']) - { - case MODULE_ACTION_MANDATORY: - $aStepInfo['options'][$index]['mandatory'] = true; - break; - - } - $index++; + $iStepIndex = count($aSteps) - 1; } + else + { + $iStepIndex = 0; + } + $aSteps[$iStepIndex]['options'][] = array( + 'title' => $sModuleLabel, + 'description' => '', + 'more_info' => $sMoreInfo, + 'default' => true, // by default offer to install all modules + 'modules' => array($sModuleId), + 'mandatory' => ($aModule['install']['flag'] & MODULE_ACTION_MANDATORY) ? true : false, + ); } } + + if (count($aSteps[count($aSteps) - 1]['options']) == 0) + { + // No extensions at all, remove the last step + unset($aSteps[1]); + } + + if (array_key_exists($index, $aSteps)) + { + $aStepInfo = $aSteps[$index]; + } + return $aStepInfo; } + + protected function GetExtensionsStepInfo() + { + // let the user select from the list of modules located in the "extensions" folder + } + + protected function IsExtension($aModule) + { + // root_dir is the directory containing the module, check if its parent is "extensions" + if (basename(dirname($aModule['root_dir'])) == 'extensions') + { + return true; + } + return false; + } protected function DisplayOptions($oPage, $aStepInfo, $aSelectedComponents, $sParentId = '') { @@ -1222,7 +1518,8 @@ EOF protected function DisplayChoice($oPage, $aChoice, $aSelectedComponents, $sChoiceId) { - $oPage->add(''); + $sMoreInfo = isset($aChoice['more_info']) ? $aChoice['more_info'] : ''; + $oPage->add(' '.$sMoreInfo); $sDescription = isset($aChoice['description']) ? htmlentities($aChoice['description'], ENT_QUOTES, 'UTF-8') : ''; $oPage->add('
'.$sDescription.''); if (isset($aChoice['sub_options'])) @@ -1234,7 +1531,8 @@ EOF protected function GetSourceFilePath() { - return APPROOT.'datamodel/installation.xml'; + $sSourceDir = $this->oWizard->GetParameter('source_dir'); + return $sSourceDir.'/installation.xml'; } } @@ -1286,7 +1584,7 @@ class WizStepSummary extends WizardStep overflow: auto; } #params_summary div { - width:100; + width:100%; margin-top:0; padding-top: 0.5em; padding-left: 0; @@ -1300,7 +1598,7 @@ class WizStepSummary extends WizardStep } #params_summary div li { list-style: none; - width: 100; + width: 100%; margin-left:0; padding-left: 0em; } @@ -1323,36 +1621,39 @@ class WizStepSummary extends WizardStep } EOF ); - $sMode = $this->oWizard->GetParameter('mode', 'install'); + + $aInstallParams = $this->BuildConfig(); + + $sMode = $aInstallParams['mode']; $sPreinstallationPhase = ''; $sDestination = ITOP_APPLICATION.(($sMode == 'install') ? ' version '.ITOP_VERSION.' is about to be installed ' : ' is about to be upgraded '); - $sDBDescription = ' existing database '.$this->oWizard->GetParameter('db_name').''; + $sDBDescription = ' existing database '.$aInstallParams['database']['name'].''; if (($sMode == 'install') && ($this->oWizard->GetParameter('create_db') == 'yes')) { - $sDBDescription = ' new database '.$this->oWizard->GetParameter('db_new_name').''; + $sDBDescription = ' new database '.$aInstallParams['database']['name'].''; } - $sDestination .= 'into the '.$sDBDescription.' on the server '.$this->oWizard->GetParameter('db_server').'.'; + $sDestination .= 'into the '.$sDBDescription.' on the server '.$aInstallParams['database']['server'].'.'; $oPage->add('

'.$sDestination.'

'); $oPage->add('
Installation Parameters'); $oPage->add('
'); $oPage->add('
Database Parameters
    '); - $oPage->add('
  • Server Name: '.$this->oWizard->GetParameter('db_server').'
  • '); - $oPage->add('
  • DB User Name: '.$this->oWizard->GetParameter('db_user').'
  • '); - $oPage->add('
  • DB user password: '.$this->oWizard->GetParameter('db_pwd').'
  • '); + $oPage->add('
  • Server Name: '.$aInstallParams['database']['server'].'
  • '); + $oPage->add('
  • DB User Name: '.$aInstallParams['database']['user'].'
  • '); + $oPage->add('
  • DB user password: '.$aInstallParams['database']['pwd'].'
  • '); if (($sMode == 'install') && ($this->oWizard->GetParameter('create_db') == 'yes')) { - $oPage->add('
  • Database Name: '.$this->oWizard->GetParameter('db_new_name').' (will be created)
  • '); + $oPage->add('
  • Database Name: '.$aInstallParams['database']['name'].' (will be created)
  • '); } else { - $oPage->add('
  • Database Name: '.$this->oWizard->GetParameter('db_name').'
  • '); + $oPage->add('
  • Database Name: '.$aInstallParams['database']['name'].'
  • '); } - if ($this->oWizard->GetParameter('db_prefix') != '') + if ($aInstallParams['database']['prefix'] != '') { - $oPage->add('
  • Prefix for the '.ITOP_APPLICATION.' tables: '.$this->oWizard->GetParameter('db_prefix').'
  • '); + $oPage->add('
  • Prefix for the '.ITOP_APPLICATION.' tables: '.$aInstallParams['database']['prefix'].'
  • '); } else { @@ -1365,21 +1666,32 @@ EOF $oPage->add('
'); $oPage->add('
Other Parameters
    '); - $oPage->add('
  • Default language: '.$this->oWizard->GetParameter('default_language').'
  • '); - $oPage->add('
  • URL to access the application: '.$this->oWizard->GetParameter('application_url').'
  • '); - if ($this->oWizard->GetParameter('sample_data') == 'yes') + if ($sMode == 'install') + { + $oPage->add('
  • Default language: '.$aInstallParams['language'].'
  • '); + } + + $oPage->add('
  • URL to access the application: '.$aInstallParams['url'].'
  • '); + if ($aInstallParams['sample_data'] == 'yes') { $oPage->add('
  • Sample data will be loaded into the database.
  • '); } + if ($aInstallParams['old_addon']) + { + $oPage->add('
  • Compatibility mode: Using the version 1.2 of the UserRightsProfiles add-on.
  • '); + } $oPage->add('
'); - $oPage->add('
Admininistrator Account
    '); - $oPage->add('
  • Login: '.$this->oWizard->GetParameter('admin_user').'
  • '); - $oPage->add('
  • Password: '.$this->oWizard->GetParameter('admin_pwd').'
  • '); - $oPage->add('
  • Language: '.$this->oWizard->GetParameter('admin_language').'
  • '); - $oPage->add('
'); + if ($sMode == 'install') + { + $oPage->add('
Admininistrator Account
    '); + $oPage->add('
  • Login: '.$aInstallParams['admin_account']['user'].'
  • '); + $oPage->add('
  • Password: '.$aInstallParams['admin_account']['pwd'].'
  • '); + $oPage->add('
  • Language: '.$aInstallParams['admin_account']['language'].'
  • '); + $oPage->add('
'); + } - $aMiscOptions = json_decode($this->oWizard->GetParameter('misc_options', '[]'), true /* bAssoc */); + $aMiscOptions = $aInstallParams['options']; if (count($aMiscOptions) > 0) { $oPage->add('
Miscellaneous Options
    '); @@ -1391,100 +1703,8 @@ EOF } - $aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true); -/* - $oPage->add('
      Selected modules:'); - sort($aSelectedModules); - foreach($aSelectedModules as $sModuleId) - { - $oPage->add('
    • '.$sModuleId.'
    • '); - - } - $oPage->add('
    '); -*/ - $oPage->add_ready_script( -<<oWizard->GetParameter('db_name'); - if ($sMode == 'upgrade') - { - if ($this->oWizard->GetParameter('db_backup', false)) - { - $sSourceDir = $this->oWizard->GetParameter('source_dir', ''); - if (!empty($sSourceDir)) - { - $aPreviousInstance = SetupUtils::GetPreviousInstance($sSourceDir); - if ($aPreviousInstance['found']) - { - $sConfigurationFile = $aPreviousInstance['configuration_file']; - } - } - } - } - else - { - - $sDBNewName = $this->oWizard->GetParameter('db_new_name', ''); - if ($sDBNewName != '') - { - $sDBName = $sDBNewName; // Database will be created - } - } - - $aInstallParams = array ( - 'mode' => $sMode, - 'preinstall' => array ( - 'copies' => array ( -// 0 => array ( -// 'source' => '', -// 'destination' => '', -// ), - ), - ), - 'source_dir' => 'datamodel', - 'target_env' => 'production', - 'workspace_dir' => '', - 'database' => array ( - 'server' => $this->oWizard->GetParameter('db_server'), - 'user' => $this->oWizard->GetParameter('db_user'), - 'pwd' => $this->oWizard->GetParameter('db_pwd'), - 'name' => $sDBName, - 'prefix' => $this->oWizard->GetParameter('db_prefix'), - ), - 'url' => $this->oWizard->GetParameter('application_url'), - 'admin_account' => array ( - 'user' => $this->oWizard->GetParameter('admin_user'), - 'pwd' => $this->oWizard->GetParameter('admin_pwd'), - 'language' => $this->oWizard->GetParameter('admin_language'), - ), - 'language' => $this->oWizard->GetParameter('default_language'), - 'selected_modules' => $aSelectedModules, - 'sample_data' => ($this->oWizard->GetParameter('sample_data', '') == 'yes') ? true : false , - 'options' => json_decode($this->oWizard->GetParameter('misc_options', '[]'), true), - ); - - if ($sBackupDestination != '') - { - $aInstallParams['backup'] = array ( - 'destination' => $sBackupDestination, - 'configuration_file' => $sConfigurationFile, - ); - } - $sJSONData = json_encode($aInstallParams); if (isset($aMiscOptions['generate_config'])) { $oDoc = new DOMDocument('1.0', 'UTF-8'); @@ -1499,7 +1719,6 @@ EOF $oPage->add(htmlentities($sXML, ENT_QUOTES, 'UTF-8')); $oPage->add('
'); } - $oPage->add('
'); // params_summary $oPage->add('
'); @@ -1511,7 +1730,127 @@ EOF $oPage->add('
'); // progress_content $oPage->add(''); + $sJSONData = json_encode($aInstallParams); $oPage->add(''); + + $oPage->add_ready_script( +<<oWizard->GetParameter('install_mode', 'install'); + $aSelectedModules = json_decode($this->oWizard->GetParameter('selected_modules'), true); + $sBackupDestination = ''; + $sPreviousConfigurationFile = ''; + $sDBName = $this->oWizard->GetParameter('db_name'); + if ($sMode == 'upgrade') + { + $sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir', ''); + if (!empty($sPreviousVersionDir)) + { + $aPreviousInstance = SetupUtils::GetPreviousInstance($sPreviousVersionDir); + if ($aPreviousInstance['found']) + { + $sPreviousConfigurationFile = $aPreviousInstance['configuration_file']; + } + } + + if ($this->oWizard->GetParameter('db_backup', false)) + { + $sBackupDestination = $this->oWizard->GetParameter('db_backup_path', ''); + } + } + else + { + + $sDBNewName = $this->oWizard->GetParameter('db_new_name', ''); + if ($sDBNewName != '') + { + $sDBName = $sDBNewName; // Database will be created + } + } + + $sSourceDir = $this->oWizard->GetParameter('source_dir'); + $aCopies = array(); + if (($sMode == 'upgrade') && ($this->oWizard->GetParameter('upgrade_type') == 'keep-previous')) + { + $sPreviousVersionDir = $this->oWizard->GetParameter('previous_version_dir'); + $aCopies[] = array('source' => $sSourceDir, 'destination' => 'modules'); // Source is an absolute path, destination is relative to APPROOT + $aCopies[] = array('source' => $sPreviousVersionDir.'/portal', 'destination' => 'portal'); // Source is an absolute path, destination is relative to APPROOT + $sSourceDir = APPROOT.'modules'; + } + + if (($sMode == 'upgrade')) + { + // Copy the previous extensions, if any + $sPreviousExtensionsDir = $this->oWizard->GetParameter('copy_extensions_from'); + if (is_dir($sPreviousExtensionsDir)) + { + // Copy the extensions one by one to merge them with the existing extensions in /extensions + $aExtensions = glob($sPreviousExtensionsDir.'/*', GLOB_ONLYDIR); + foreach($aExtensions as $sDirPath) + { + $sExtName = basename($sDirPath); + $aCopies[] = array('source' => $sDirPath, 'destination' => 'extensions/'.$sExtName); // Source is an absolute path, destination is relative to APPROOT + } + } + } + + $aInstallParams = array ( + 'mode' => $sMode, + 'preinstall' => array ( + 'copies' => $aCopies, + // 'backup' => see below + ), + 'source_dir' => str_replace(APPROOT, '', $sSourceDir), + 'datamodel_version' => $this->oWizard->GetParameter('datamodel_version'), //TODO: let the installer compute this automatically... + 'previous_configuration_file' => $sPreviousConfigurationFile, + 'extensions_dir' => 'extensions', + 'target_env' => 'production', + 'workspace_dir' => '', + 'database' => array ( + 'server' => $this->oWizard->GetParameter('db_server'), + 'user' => $this->oWizard->GetParameter('db_user'), + 'pwd' => $this->oWizard->GetParameter('db_pwd'), + 'name' => $sDBName, + 'prefix' => $this->oWizard->GetParameter('db_prefix'), + ), + 'url' => $this->oWizard->GetParameter('application_url'), + 'admin_account' => array ( + 'user' => $this->oWizard->GetParameter('admin_user'), + 'pwd' => $this->oWizard->GetParameter('admin_pwd'), + 'language' => $this->oWizard->GetParameter('admin_language'), + ), + 'language' => $this->oWizard->GetParameter('default_language'), + 'selected_modules' => $aSelectedModules, + 'sample_data' => ($this->oWizard->GetParameter('sample_data', '') == 'yes') ? true : false , + 'old_addon' => $this->oWizard->GetParameter('old_addon', false), // whether or not to use the "old" userrights profile addon + 'options' => json_decode($this->oWizard->GetParameter('misc_options', '[]'), true), + ); + + if ($sBackupDestination != '') + { + $aInstallParams['backup'] = array ( + 'destination' => $sBackupDestination, + 'configuration_file' => $sPreviousConfigurationFile, + ); + } + return $aInstallParams; } public function AsyncAction(WebPage $oPage, $sCode, $aParameters) @@ -1658,4 +1997,14 @@ class WizStepDone extends WizardStep { return false; } + + /** + * Tells whether this step of the wizard requires that the configuration file be writable + * @return bool True if the wizard will possibly need to modify the configuration at some point + */ + public function RequiresWritableConfig() + { + return false; //This step executes once the config was written and secured + } + }