From 5eb97ae1336abfb1d82436f4fe5e7b36346f0954 Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 12 Sep 2012 13:31:46 +0000 Subject: [PATCH] The new 2.0 setup is under way... SVN:trunk[2179] --- setup/ajax.dataloader.php | 14 + setup/applicationinstaller.class.inc.php | 578 +++++++++++++++++++++++ setup/modulediscovery.class.inc.php | 16 +- setup/parameters.class.inc.php | 138 ++++++ setup/setup.js | 14 +- setup/setuputils.class.inc.php | 322 +++++++++++++ setup/wizardcontroller.class.inc.php | 533 +++++++++++++++++++++ setup/wizardsteps.class.inc.php | 405 ++++++++++++++++ 8 files changed, 2015 insertions(+), 5 deletions(-) create mode 100644 setup/applicationinstaller.class.inc.php create mode 100644 setup/parameters.class.inc.php create mode 100644 setup/setuputils.class.inc.php create mode 100644 setup/wizardcontroller.class.inc.php create mode 100644 setup/wizardsteps.class.inc.php diff --git a/setup/ajax.dataloader.php b/setup/ajax.dataloader.php index 225081300..7f5beb9b8 100644 --- a/setup/ajax.dataloader.php +++ b/setup/ajax.dataloader.php @@ -120,6 +120,8 @@ 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('./xmldataloader.class.inc.php'); +require_once(APPROOT.'/application/ajaxwebpage.class.inc.php'); +require_once(APPROOT.'/setup/wizardcontroller.class.inc.php'); // Never cache this page @@ -134,6 +136,18 @@ try { switch($sOperation) { + case 'async_action': + $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(); + break; + ////////////////////////////// // case 'compile_data_model': diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php new file mode 100644 index 000000000..29a4abe69 --- /dev/null +++ b/setup/applicationinstaller.class.inc.php @@ -0,0 +1,578 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +class ApplicationInstaller +{ + const OK = 1; + const ERROR = 2; + const WARNING = 3; + const INFO = 4; + + protected $sXMLResponseFile; + + public function __construct($sXMLResponseFile) + { + $this->sXMLResponseFile = $sXMLResponseFile; + } + + /** + * Runs all the installation steps in one go and directly outputs + * some information about the progress and the success of the various + * sequential steps. + * @return boolean True if the installation was successful, false otherwise + */ + public function ExecuteAllSteps() + { + $sStep = ''; + $sStepLabel = ''; + do + { + if($sStep != '') + { + echo "$sStepLabel\n"; + echo "Executing '$sStep'\n"; + } + else + { + echo "Starting the installation...\n"; + } + $aRes = $this->ExecuteStep($sStep); + $sStep = $aRes['next-step']; + $sStepLabel = $aRes['next-step-label']; + + switch($aRes['status']) + { + case self::OK; + echo "Ok. ".$aRes['percentage-completed']." % done.\n"; + break; + + case self::ERROR: + echo "Error: ".$aRes['message']."\n"; + break; + + case self::WARNING: + echo "Warning: ".$aRes['message']."\n"; + echo $aRes['percentage-completed']." % done.\n"; + break; + + case self::INFO: + echo "Info: ".$aRes['message']."\n"; + echo $aRes['percentage-completed']." % done.\n"; + break; + } + } + while(($aRes['status'] != self::ERROR) && ($aRes['next-step'] != '')); + + return ($aRes['status'] == self::OK); + } + + /** + * Executes the next step of the installation and reports about the progress + * and the next step to perform + * @param string $sStep The identifier of the step to execute + * @return hash An array of (status => , message => , percentage-completed => , next-step => , next-step-label => ) + */ + public function ExecuteStep($sStep = '') + { + try + { + switch($sStep) + { + case '': + $aResult = array( + 'status' => self::OK, + 'message' => '', + 'percentage-completed' => 0, + 'next-step' => 'copy', + 'next-step-label' => 'Copying data model files', + ); + break; + + case 'copy': + $aResult = array( + 'status' => self::WARNING, + 'message' => 'Dummy setup - Nothing to copy', + 'next-step' => 'compile', + 'next-step-label' => 'Compiling the data model', + 'percentage-completed' => 20, + ); + break; + + case 'compile': + $oParams = new XMLParameters($this->sXMLResponseFile); + $aSelectedModules = $oParams->Get('selected_modules'); + $sSourceDir = $oParams->Get('source_dir', 'datamodel'); + $sTargetDir = $oParams->Get('target_dir', 'env-setup-test'); + $sWorkspaceDir = $oParams->Get('workspace_dir', 'workspace'); + + self::DoCompile($aSelectedModules, $sSourceDir, $sTargetDir, $sWorkspaceDir); + + $aResult = array( + 'status' => self::OK, + 'message' => '', + 'next-step' => 'db-schema', + 'next-step-label' => 'Updating database schema', + 'percentage-completed' => 40, + ); + break; + + case 'db-schema': + $oParams = new XMLParameters($this->sXMLResponseFile); + $sMode = $oParams->Get('mode'); + $sTargetDir = $oParams->Get('target_dir', 'env-setup-test'); + $sDBServer = $oParams->Get('db_server', ''); + $sDBUser = $oParams->Get('db_user', ''); + $sDBPwd = $oParams->Get('db_pwd', ''); + $sDBName = $oParams->Get('db_name', ''); + $sDBNewName = $oParams->Get('db_new_name', ''); + $sDBPrefix = $oParams->Get('db_prefix', ''); + $sTargetEnvironment = $oParams->Get('target_env', ''); + + self::DoUpdateDBSchema($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBNewName, $sDBPrefix, $sTargetEnvironment); + + $aResult = array( + 'status' => self::OK, + 'message' => '', + 'next-step' => 'after-db-create', + 'next-step-label' => 'Creating Profiles', + 'percentage-completed' => 60, + ); + break; + + case 'after-db-create': + $oParams = new XMLParameters($this->sXMLResponseFile); + $sMode = $oParams->Get('mode'); + $sTargetDir = $oParams->Get('target_dir', 'env-setup-test'); + $sDBServer = $oParams->Get('db_server', ''); + $sDBUser = $oParams->Get('db_user', ''); + $sDBPwd = $oParams->Get('db_pwd', ''); + $sDBName = $oParams->Get('db_name', ''); + $sDBNewName = $oParams->Get('db_new_name', ''); + $sDBPrefix = $oParams->Get('db_prefix', ''); + $sAdminUser = $oParams->Get('admin_user', ''); + $sAdminPwd = $oParams->Get('admin_pwd', ''); + $sLanguage = $oParams->Get('language', ''); + $aSelectedModules = $oParams->Get('selected_modules', array()); + $sTargetEnvironment = $oParams->Get('target_env', ''); + + self::AfterDBCreate($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBNewName, $sDBPrefix, $sAdminUser, $sAdminPwd, $sLanguage, $aSelectedModules, $sTargetEnvironment); + + $aResult = array( + 'status' => self::OK, + 'message' => '', + 'next-step' => 'sample-data', + 'next-step-label' => 'Loading Sample Data', + 'percentage-completed' => 80, + ); + break; + + case 'sample-data': + $oParams = new XMLParameters($this->sXMLResponseFile); + $sMode = $oParams->Get('mode'); + $sTargetDir = $oParams->Get('target_dir', 'env-setup-test'); + $sDBServer = $oParams->Get('db_server', ''); + $sDBUser = $oParams->Get('db_user', ''); + $sDBPwd = $oParams->Get('db_pwd', ''); + $sDBName = $oParams->Get('db_name', ''); + $sDBNewName = $oParams->Get('db_new_name', ''); + $sDBPrefix = $oParams->Get('db_prefix', ''); + $aFiles = $oParams->Get('files', array()); + $sTargetEnvironment = $oParams->Get('target_env', ''); + + self::DoLoadFiles($aFiles, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment); + + $aResult = array( + 'status' => self::INFO, + 'message' => 'All data loaded', + 'next-step' => 'create-config', + 'next-step-label' => 'Creating the Configuration File', + 'percentage-completed' => 99, + ); + break; + + case 'create-config': + $oParams = new XMLParameters($this->sXMLResponseFile); + $sMode = $oParams->Get('mode'); + $sTargetDir = $oParams->Get('target_dir', 'env-setup-test'); + $sDBServer = $oParams->Get('db_server', ''); + $sDBUser = $oParams->Get('db_user', ''); + $sDBPwd = $oParams->Get('db_pwd', ''); + $sDBName = $oParams->Get('db_name', ''); + $sDBNewName = $oParams->Get('db_new_name', ''); + $sDBPrefix = $oParams->Get('db_prefix', ''); + $sUrl = $oParams->Get('url', ''); + $sLanguage = $oParams->Get('language', ''); + $aSelectedModules = $oParams->Get('selected_modules', array()); + $sTargetEnvironment = $oParams->Get('target_env', ''); + + self::DoCreateConfig($sMode, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment); + + $aResult = array( + 'status' => self::INFO, + 'message' => 'Configuration file created', + 'next-step' => '', + 'next-step-label' => 'Completed', + 'percentage-completed' => 100, + ); + break; + + + default: + $aResult = array( + 'status' => self::ERROR, + 'message' => '', + 'next-step' => '', + 'next-step-label' => "Unknown setup step '$sStep'.", + 'percentage-completed' => 100, + ); + } + } + catch(Exception $e) + { + $aResult = array( + 'status' => self::ERROR, + 'message' => $e->getMessage(), + 'next-step' => '', + 'next-step-label' => '', + 'percentage-completed' => 100, + ); + } + return $aResult; + } + + protected static function DoCompile($aSelectedModules, $sSourceDir, $sTargetDir, $sWorkspaceDir = '') + { + SetupPage::log_info("Compiling data model."); + + require_once(APPROOT.'setup/modulediscovery.class.inc.php'); + require_once(APPROOT.'setup/modelfactory.class.inc.php'); + require_once(APPROOT.'setup/compiler.class.inc.php'); + + if (empty($sSourceDir) || empty($sTargetDir)) + { + throw new Exception("missing parameter source_dir and/or target_dir"); + } + + $sSourcePath = APPROOT.$sSourceDir; + $sTargetPath = APPROOT.$sTargetDir; + if (!is_dir($sSourcePath)) + { + throw new Exception("Failed to find the source directory '$sSourcePath', please check the rights of the web server"); + } + if (!is_dir($sTargetPath) && !mkdir($sTargetPath)) + { + throw new Exception("Failed to create directory '$sTargetPath', please check the rights of the web server"); + } + // owner:rwx user/group:rx + chmod($sTargetPath, 0755); + + $oFactory = new ModelFactory($sSourcePath); + $aModules = $oFactory->FindModules(); + + foreach($aModules as $foo => $oModule) + { + $sModule = $oModule->GetName(); + if (in_array($sModule, $aSelectedModules)) + { + $oFactory->LoadModule($oModule); + } + } + if (strlen($sWorkspaceDir) > 0) + { + $oWorkspace = new MFWorkspace(APPROOT.$sWorkspaceDir); + if (file_exists($oWorkspace->GetWorkspacePath())) + { + $oFactory->LoadModule($oWorkspace); + } + } + //$oFactory->Dump(); + if ($oFactory->HasLoadErrors()) + { + foreach($oFactory->GetLoadErrors() as $sModuleId => $aErrors) + { + SetupPage::log_error("Data model source file (xml) could not be loaded - found errors in module: $sModuleId"); + foreach($aErrors as $oXmlError) + { + SetupPage::log_error("Load error: File: ".$oXmlError->file." Line:".$oXmlError->line." Message:".$oXmlError->message); + } + } + throw new Exception("The data model could not be compiled. Please check the setup error log"); + } + else + { + $oMFCompiler = new MFCompiler($oFactory, $sSourcePath); + $oMFCompiler->Compile($sTargetPath); + SetupPage::log_info("Data model successfully compiled to '$sTargetPath'."); + } + } + + protected static function DoUpdateDBSchema($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBNewName, $sDBPrefix, $sTargetEnvironment = '') + { + SetupPage::log_info("Update Database Schema for environment '$sTargetEnvironment'."); + + $oConfig = new Config(); + + $aParamValues = array( + 'db_server' => $sDBServer, + 'db_user' => $sDBUser, + 'db_pwd' => $sDBPwd, + 'db_name' => $sDBName, + 'new_db_name' => $sDBNewName, + 'db_prefix' => $sDBPrefix, + ); + $oConfig->UpdateFromParams($aParamValues, $sModulesDir); + + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); + $oProductionEnv->InitDataModel($oConfig, true); // load data model only + + if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) + { + throw(new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'")); + } + SetupPage::log_info("Database Schema Successfully Updated for environment '$sTargetEnvironment'."); + } + + protected static function AfterDBCreate($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBNewName, $sDBPrefix, $sAdminUser, $sAdminPwd, $sLanguage, $aSelectedModules, $sTargetEnvironment = '') + { + + SetupPage::log_info('After Database Creation'); + + $oConfig = new Config(); + + $aParamValues = array( + 'db_server' => $sDBServer, + 'db_user' => $sDBUser, + 'db_pwd' => $sDBPwd, + 'db_name' => $sDBName, + 'new_db_name' => $sDBNewName, + 'db_prefix' => $sDBPrefix, + ); + $oConfig->UpdateFromParams($aParamValues, $sModulesDir); + + $oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); + $oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database + + // Perform here additional DB setup... profiles, etc... + // + $aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), $sModulesDir); + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != ROOT_MODULE) && in_array($sModuleId, $aSelectedModules) && + isset($aAvailableModules[$sModuleId]['installer']) ) + { + $sModuleInstallerClass = $aAvailableModules[$sModuleId]['installer']; + SetupPage::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'])); + } + } + + // Constant classes (e.g. User profiles) + // + foreach (MetaModel::GetClasses() as $sClass) + { + $aPredefinedObjects = call_user_func(array($sClass, 'GetPredefinedObjects')); + if ($aPredefinedObjects != null) + { + // Temporary... until this get really encapsulated as the default and transparent behavior + $oMyChange = MetaModel::NewObject("CMDBChange"); + $oMyChange->Set("date", time()); + $sUserString = CMDBChange::GetCurrentUserName(); + $oMyChange->Set("userinfo", $sUserString); + $iChangeId = $oMyChange->DBInsert(); + + // Create/Delete/Update objects of this class, + // according to the given constant values + // + $aDBIds = array(); + $oAll = new DBObjectSet(new DBObjectSearch($sClass)); + while ($oObj = $oAll->Fetch()) + { + if (array_key_exists($oObj->GetKey(), $aPredefinedObjects)) + { + $aObjValues = $aPredefinedObjects[$oObj->GetKey()]; + foreach ($aObjValues as $sAttCode => $value) + { + $oObj->Set($sAttCode, $value); + } + $oObj->DBUpdateTracked($oMyChange); + $aDBIds[$oObj->GetKey()] = true; + } + else + { + $oObj->DBDeleteTracked($oMyChange); + } + } + foreach ($aPredefinedObjects as $iRefId => $aObjValues) + { + if (!array_key_exists($iRefId, $aDBIds)) + { + $oNewObj = MetaModel::NewObject($sClass); + $oNewObj->SetKey($iRefId); + foreach ($aObjValues as $sAttCode => $value) + { + $oNewObj->Set($sAttCode, $value); + } + $oNewObj->DBInsertTracked($oMyChange); + } + } + } + } + + if (!$oProductionEnv->RecordInstallation($oConfig, $aSelectedModules, $sModulesDir)) + { + throw(new Exception("Failed to record the installation information")); + } + + if($sMode == 'install') + { + if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sLanguage)) + { + throw(new Exception("Failed to create the administrator account '$sAdminUser'")); + } + else + { + SetupPage::log_info("Administrator account '$sAdminUser' created."); + } + } + } + + /** + * Helper function to create and administrator account for iTop + * @return boolean true on success, false otherwise + */ + protected static function CreateAdminAccount(Config $oConfig, $sAdminUser, $sAdminPwd, $sLanguage) + { + SetupPage::log_info('CreateAdminAccount'); + + if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) + { + return true; + } + else + { + return false; + } + } + + protected static function DoLoadFiles($aFiles, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '') + { + $aParamValues = array( + 'db_server' => $sDBServer, + 'db_user' => $sDBUser, + 'db_pwd' => $sDBPwd, + 'db_name' => $sDBName, + 'new_db_name' => $sDBName, + 'db_prefix' => $sDBPrefix, + ); + $oConfig = new Config(); + + $oConfig->UpdateFromParams($aParamValues, $sModulesDir); + + //TODO: load the MetaModel if needed (async mode) + //$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment); + //$oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database + + $oDataLoader = new XMLDataLoader(); + $oChange = MetaModel::NewObject("CMDBChange"); + $oChange->Set("date", time()); + $oChange->Set("userinfo", "Initialization"); + $iChangeId = $oChange->DBInsert(); + SetupPage::log_info("starting data load session"); + $oDataLoader->StartSession($oChange); + + foreach($aFiles as $sFileRelativePath) + { + $sFileName = APPROOT.'env-'.(($sTargetEnvironment == '') ? 'production' : $sTargetEnvironment).'/'.$sFileRelativePath; + SetupPage::log_info("Loading file: $sFileName"); + if (empty($sFileName) || !file_exists($sFileName)) + { + throw(new Exception("File $sFileName does not exist")); + } + + $oDataLoader->LoadFile($sFileName); + $sResult = sprintf("loading of %s done.", basename($sFileName)); + SetupPage::log_info($sResult); + } + + $oDataLoader->EndSession(); + SetupPage::log_info("ending data load session"); + } + + protected static function DoCreateConfig($sMode, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sUrl, $sLanguage, $aSelectedModules, $sTargetEnvironment = '') + { + $aParamValues = array( + 'db_server' => $sDBServer, + 'db_user' => $sDBUser, + 'db_pwd' => $sDBPwd, + 'db_name' => $sDBName, + 'new_db_name' => $sDBName, + 'db_prefix' => $sDBPrefix, + 'application_path' => $sUrl, + 'mode' => $sMode, + 'language' => $sLanguage, + 'selected_modules' => implode(',', $aSelectedModules), + ); + + $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); + + // Make sure the root configuration directory exists + if (!file_exists(APPCONF)) + { + mkdir(APPCONF); + chmod(APPCONF, 0770); // RWX for owner and group, nothing for others + SetupPage::log_info("Created configuration directory: ".APPCONF); + } + + // Write the final configuration file + $sConfigFile = APPCONF.(($sTargetEnvironment == '') ? 'production' : $sTargetEnvironment).'/'.ITOP_CONFIG_FILE; + $sConfigDir = dirname($sConfigFile); + @mkdir($sConfigDir); + @chmod($sConfigDir, 0770); // RWX for owner and group, nothing for others + + $oConfig->WriteToFile($sConfigFile); + + // try to make the final config file read-only + @chmod($sConfigFile, 0444); // Read-only for owner and group, nothing for others + } +} diff --git a/setup/modulediscovery.class.inc.php b/setup/modulediscovery.class.inc.php index c19663b01..5d5e9e91d 100644 --- a/setup/modulediscovery.class.inc.php +++ b/setup/modulediscovery.class.inc.php @@ -172,6 +172,11 @@ class ModuleDiscovery { $sLookupDir = realpath($sRootDir.'/'.$sSearchDir); + if (self::$m_sModulesRoot != $sLookupDir) + { + self::ResetCache(); + } + if (is_null(self::$m_sModulesRoot)) { // First call @@ -186,10 +191,6 @@ class ModuleDiscovery self::ListModuleFiles($sSearchDir, $sRootDir); return self::GetModules($oP); } - elseif (self::$m_sModulesRoot != $sLookupDir) - { - throw new Exception("Design issue: the discovery of modules cannot be made on two different paths (previous: ".self::$m_sModulesRoot.", new: $sLookupDir)"); - } else { // Reuse the previous results @@ -197,6 +198,13 @@ class ModuleDiscovery return self::GetModules($oP); } } + + public static function ResetCache() + { + self::$m_sModulesRoot = null; + self::$m_sModulesRoot = null; + self::$m_aModules = array(); + } /** * Helper function to interpret the name of a module diff --git a/setup/parameters.class.inc.php b/setup/parameters.class.inc.php new file mode 100644 index 000000000..2bc913d6d --- /dev/null +++ b/setup/parameters.class.inc.php @@ -0,0 +1,138 @@ +aData = null; + $this->sParametersFile = $sParametersFile; + $this->Load($sParametersFile); + } + + abstract public function Load($sParametersFile); + + public function Get($sCode, $default = '') + { + if (array_key_exists($sCode, $this->aData)) + { + return $this->aData[$sCode]; + } + return $default; + } +} + +class PHPParameters extends Parameters +{ + public function Load($sParametersFile) + { + if ($this->aData == null) + { + require_once($sParametersFile); + $this->aData = $ITOP_PARAMS; + } + } +} + +class XMLParameters extends Parameters +{ + protected $aData = null; + + public function Load($sParametersFile) + { + if ($this->aData == null) + { + libxml_use_internal_errors(true); + $oXML = @simplexml_load_file($sParametersFile); + if (!$oXML) + { + $aMessage = array(); + foreach(libxml_get_errors() as $oError) + { + $aMessage[] = "(line: {$oError->line}) ".$oError->message; // Beware: $oError->columns sometimes returns wrong (misleading) value + } + libxml_clear_errors(); + throw new InvalidParameterException("Invalid Parameters file '{$this->sParametersFile}': ".implode(' ', $aMessage)); + } + + $this->aData = array(); + foreach($oXML as $key => $oElement) + { + $this->aData[(string)$key] = $this->ReadElement($oElement); + } + } + } + + protected function ReadElement(SimpleXMLElement $oElement) + { + $sDefaultNodeType = (count($oElement->children()) > 0) ? 'hash' : 'string'; + $sNodeType = $this->GetAttribute('type', $oElement, $sDefaultNodeType); + switch($sNodeType) + { + case 'array': + $value = array(); + // Treat the current element as zero based array, child tag names are NOT meaningful + $sFirstTagName = null; + foreach($oElement->children() as $oChildElement) + { + if ($sFirstTagName == null) + { + $sFirstTagName = $oChildElement->getName(); + } + else if ($sFirstTagName != $oChildElement->getName()) + { + throw new InvalidParameterException("Invalid Parameters file '{$this->sParametersFile}': mixed tags ('$sFirstTagName' and '".$oChildElement->getName()."') inside array '".$oElement->getName()."'"); + } + $val = $this->ReadElement($oChildElement); + $value[] = $val; + } + break; + + case 'hash': + $value = array(); + // Treat the current element as a hash, child tag names are keys + foreach($oElement->children() as $oChildElement) + { + if (array_key_exists($oChildElement->getName(), $value)) + { + throw new InvalidParameterException("Invalid Parameters file '{$this->sParametersFile}': duplicate tags '".$oChildElement->getName()."' inside hash '".$oElement->getName()."'"); + } + $val = $this->ReadElement($oChildElement); + $value[$oChildElement->getName()] = $val; + } + break; + + case 'int': + case 'integer': + $value = (int)$oElement; + break; + + case 'string': + default: + $value = (string)$oElement; + } + return $value; + } + + protected function GetAttribute($sAttName, $oElement, $sDefaultValue) + { + $sRet = $sDefaultValue; + + foreach($oElement->attributes() as $sKey => $oChildElement) + { + if ((string)$sKey == $sAttName) + { + $sRet = (string)$oChildElement; + break; + } + } + return $sRet; + } +} + + diff --git a/setup/setup.js b/setup/setup.js index c167e4a9e..c59c37687 100644 --- a/setup/setup.js +++ b/setup/setup.js @@ -335,4 +335,16 @@ function GetSelectedModules() var aModules = new Array(); $(':input[name^=module]').each(function() { aModules.push($(this).val()); } ); return aModules.join(','); -} \ No newline at end of file +} + +function WizardAsyncAction(sActionCode, oParams) +{ + var sStepClass = $('#_class').val(); + var sStepState = $('#_state').val(); + + var oMap = { operation: 'async_action', step_class: sStepClass, step_state: sStepState, code: sActionCode, params: oParams }; + + $.post(GetAbsoluteUrlAppRoot()+'setup/ajax.dataloader.php', oMap, function(data) { + $('#async_action').html(data); + }); +} diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php new file mode 100644 index 000000000..8a32454b3 --- /dev/null +++ b/setup/setuputils.class.inc.php @@ -0,0 +1,322 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +class CheckResult +{ + // Severity levels + const ERROR = 0; + const WARNING = 1; + const INFO = 2; + + public $iSeverity; + public $sLabel; + public $sDescription; + + public function __construct($iSeverity, $sLabel, $sDescription = '') + { + $this->iSeverity = $iSeverity; + $this->sLabel = $sLabel; + $this->sDescription = $sDescription; + } +} + +/** + * Namespace for storing all the functions/utilities needed by both + * the setup wizard and the installation process + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +class SetupUtils +{ + const PHP_MIN_VERSION = '5.2.0'; + const MYSQL_MIN_VERSION = '5.0.0'; + const MIN_MEMORY_LIMIT = 33554432; // = 32*1024*1024 Beware: Computations are not allowed in defining constants + const SUHOSIN_GET_MAX_VALUE_LENGTH = 2048; + + /** + * Check the version of PHP, the needed PHP extension and a number + * of configuration parameters (memory_limit, max_upload_file_size, etc...) + * @param SetupPage $oP The page used only for its 'log' method + * @return array An array of CheckResults objects + */ + static function CheckPHPVersion(SetupPage $oP) + { + $aResult = array(); + $bResult = true; + $aErrors = array(); + $aWarnings = array(); + $aOk = array(); + + $oP->log('Info - CheckPHPVersion'); + if (version_compare(phpversion(), self::PHP_MIN_VERSION, '>=')) + { + $aResult[] = new CheckResult(CheckResult::INFO, "The current PHP Version (".phpversion().") is greater than the minimum required version (".self::PHP_MIN_VERSION.")"); + } + else + { + $aResult[] = new CheckResult(CheckResult::ERROR, "Error: The current PHP Version (".phpversion().") is lower than the minimum required version (".self::PHP_MIN_VERSION.")"); + } + $aMandatoryExtensions = array('mysqli', 'iconv', 'simplexml', 'soap', 'hash', 'json', 'session', 'pcre', 'dom'); + $aOptionalExtensions = array('mcrypt' => 'Strong encryption will not be used.', + 'ldap' => 'LDAP authentication will be disabled.'); + asort($aMandatoryExtensions); // Sort the list to look clean ! + ksort($aOptionalExtensions); // Sort the list to look clean ! + $aExtensionsOk = array(); + $aMissingExtensions = array(); + $aMissingExtensionsLinks = array(); + // First check the mandatory extensions + foreach($aMandatoryExtensions as $sExtension) + { + if (extension_loaded($sExtension)) + { + $aExtensionsOk[] = $sExtension; + } + else + { + $aMissingExtensions[] = $sExtension; + $aMissingExtensionsLinks[] = "$sExtension"; + } + } + if (count($aExtensionsOk) > 0) + { + $aResult[] = new CheckResult(CheckResult::INFO, "Required PHP extension(s): ".implode(', ', $aExtensionsOk)."."); + } + if (count($aMissingExtensions) > 0) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "Missing PHP extension(s): ".implode(', ', $aMissingExtensionsLinks)."."); + } + // 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) + { + $aResult[] = new CheckResult(CheckResult::INFO, "Optional PHP extension(s): ".implode(', ', $aExtensionsOk)."."); + } + if (count($aMissingExtensions) > 0) + { + foreach($aMissingExtensions as $sExtension => $sMessage) + { + $aResult[] = new CheckResult(CheckResult::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')) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "Files upload is not allowed on this server (file_uploads = ".ini_get('file_uploads').")."); + } + + $sUploadTmpDir = self::GetUploadTmpDir(); + if (empty($sUploadTmpDir)) + { + $sUploadTmpDir = '/tmp'; + $aResult[] = new CheckResult(CheckResult::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)) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "Temporary directory for files upload ($sUploadTmpDir) does not exist or cannot be read by PHP."); + } + else if (!is_writable($sUploadTmpDir)) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "Temporary directory for files upload ($sUploadTmpDir) is not writable."); + } + else + { + $oP->log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); + } + } + + + if (!ini_get('upload_max_filesize')) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "File upload is not allowed on this server (upload_max_filesize = ".ini_get('upload_max_filesize').")."); + } + + $iMaxFileUploads = ini_get('max_file_uploads'); + if (!empty($iMaxFileUploads) && ($iMaxFileUploads < 1)) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "File upload is not allowed on this server (max_file_uploads = ".ini_get('max_file_uploads').")."); + } + + $iMaxUploadSize = utils::ConvertToBytes(ini_get('upload_max_filesize')); + $iMaxPostSize = utils::ConvertToBytes(ini_get('post_max_size')); + + if ($iMaxPostSize <= $iMaxUploadSize) + { + $aResult[] = new CheckResult(CheckResult::WARNING, "post_max_size (".ini_get('post_max_size').") must be bigger than upload_max_filesize (".ini_get('upload_max_filesize')."). You may want to check the PHP configuration file(s): '$sPhpIniFile'. Be aware that this setting can also be overridden in the apache configuration."); + } + + + $oP->log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); + $oP->log("Info - post_max_size: ".ini_get('post_max_size')); + $oP->log("Info - max_file_uploads: ".ini_get('max_file_uploads')); + + // Check some more ini settings here, needed for file upload + if (function_exists('get_magic_quotes_gpc')) + { + if (@get_magic_quotes_gpc()) + { + $aResult[] = new CheckResult(CheckResult::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."); + } + } + if (function_exists('magic_quotes_runtime')) + { + if (@magic_quotes_runtime()) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "'magic_quotes_runtime' 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."); + } + } + + + $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 + $aResult[] = new CheckResult(CheckResult::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 < self::MIN_MEMORY_LIMIT) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "memory_limit ($iMemoryLimit) is too small, the minimum value to run the application is ".self::MIN_MEMORY_LIMIT."."); + } + else + { + $oP->log_info("memory_limit is $iMemoryLimit, ok."); + } + } + + // Special case for APC + if (extension_loaded('apc')) + { + $sAPCVersion = phpversion('apc'); + $aResult[] = new CheckResult(CheckResult::INFO, "APC detected (version $sAPCVersion). The APC cache will be used to speed-up the application."); + } + + // Special case Suhosin extension + if (extension_loaded('suhosin')) + { + $sSuhosinVersion = phpversion('suhosin'); + $aOk[] = "Suhosin extension detected (version $sSuhosinVersion)."; + + $iGetMaxValueLength = ini_get('suhosin.get.max_value_length'); + if ($iGetMaxValueLength < self::SUHOSIN_GET_MAX_VALUE_LENGTH) + { + $aResult[] = new CheckResult(CheckResult::INFO, "suhosin.get.max_value_length ($iGetMaxValueLength) is too small, the minimum value to run the application is ".self::SUHOSIN_GET_MAX_VALUE_LENGTH.". This value is set by the PHP configuration file(s): '$sPhpIniFile'. Be aware that this setting can also be overridden in the apache configuration."); + } + else + { + $oP->log_info("suhosin.get.max_value_length = $iGetMaxValueLength, ok."); + } + } + + return $aResult; + } + + /** + * 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 + */ + static 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 + */ + static function GetUploadTmpDir() + { + $sPath = ini_get('upload_tmp_dir'); + if (empty($sPath)) + { + $sPath = self::GetTmpDir(); + } + return $sPath; + } +} \ No newline at end of file diff --git a/setup/wizardcontroller.class.inc.php b/setup/wizardcontroller.class.inc.php new file mode 100644 index 000000000..45049f115 --- /dev/null +++ b/setup/wizardcontroller.class.inc.php @@ -0,0 +1,533 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +class WizardController +{ + protected $aSteps; + protected $sInitialStepClass; + protected $sInitialState; + protected $aParameters; + + /** + * Initiailization of the wizard controller + * @param string $sInitialStepClass Class of the initial step/page of the wizard + * @param string $sInitialState Initial state of the initial page (if this class manages states) + */ + public function __construct($sInitialStepClass, $sInitialState = '') + { + $this->sInitialStepClass = $sInitialStepClass; + $this->sInitialState = $sInitialState; + $this->aParameters = array(); + $this->aSteps = array(); + } + + /** + * Pushes information about the current step onto the stack + * @param hash $aStepInfo Array('class' => , 'state' => ) + */ + protected function PushStep($aStepInfo) + { + array_push($this->aSteps, $aStepInfo); + } + + /** + * Removes information about the previous step from the stack + * @return hash Array('class' => , 'state' => ) + */ + protected function PopStep() + { + return array_pop($this->aSteps); + } + + /** + * Reads a "persistent" parameter from the wizard's context + * @param string $sParamCode The code identifying this parameter + * @param mixed $defaultValue The default value of the parameter in case it was not set + */ + public function GetParameter($sParamCode, $defaultValue = '') + { + if (array_key_exists($sParamCode, $this->aParameters)) + { + return $this->aParameters[$sParamCode]; + } + return $defaultValue; + } + + /** + * Stores a "persistent" parameter in the wizard's context + * @param string $sParamCode The code identifying this parameter + * @param mixed $value The value to store + */ + public function SetParameter($sParamCode, $value) + { + $this->aParameters[$sParamCode] = $value; + } + + /** + * Starts the wizard by displaying it in its initial state + */ + protected function Start() + { + $sCurrentStepClass = $this->sInitialStepClass; + $oStep = new $sCurrentStepClass($this, $this->sInitialState); + $this->DisplayStep($oStep); + } + /** + * Progress towards the next step of the wizard + * @throws Exception + */ + protected function Next() + { + $sCurrentStepClass = utils::ReadParam('_class', $this->sInitialStepClass); + $sCurrentState = utils::ReadParam('_state', $this->sInitialState); + $oStep = new $sCurrentStepClass($this, $sCurrentState); + if ($oStep->ValidateParams($sCurrentState)) + { + $this->PushStep(array('class' => $sCurrentStepClass, 'state' => $sCurrentState)); + $aPossibleSteps = $oStep->GetPossibleSteps(); + $aNextStepInfo = $oStep->ProcessParams(true); // true => moving forward + if (in_array($aNextStepInfo['class'], $aPossibleSteps)) + { + $oNextStep = new $aNextStepInfo['class']($this, $aNextStepInfo['state']); + $this->DisplayStep($oNextStep); + } + else + { + throw new Exception("Internal error: Unexpected next step '{$aNextStepInfo['class']}'. The possible next steps are: ".implode(', ', $aPossibleSteps)); + } + } + else + { + $this->DisplayStep($oStep); + } + } + /** + * Move one step back + */ + protected function Back() + { + // let the current step save its parameters + $sCurrentStepClass = utils::ReadParam('_class', $this->sInitialStepClass); + $sCurrentState = utils::ReadParam('_state', $this->sInitialState); + $oStep = new $sCurrentStepClass($this, $sCurrentState); + $aNextStepInfo = $oStep->ProcessParams(); + + // Display the previous step + $aCurrentStepInfo = $this->PopStep(); + $oStep = new $aCurrentStepInfo['class']($this, $aCurrentStepInfo['state']); + $this->DisplayStep($oStep); + } + + /** + * Displays the specified 'step' of the wizard + * @param WizardStep $oStep The 'step' to display + */ + protected function DisplayStep(WizardStep $oStep) + { + $oPage = new SetupPage($oStep->GetTitle()); + $oPage->add_linked_script('../setup/setup.js'); + $oPage->add('
'); + $oStep->Display($oPage); + + // Add the back / next buttons and the hidden form + // to store the parameters + $oPage->add(''); + $oPage->add(''); + foreach($this->aParameters as $sCode => $value) + { + $oPage->add(''); + } + + $oPage->add(''); + if (count($this->aSteps) > 0) + { + $oPage->add(''); + } + if ($oStep->CanMoveForward()) + { + $oPage->add(''); + } + $oPage->add("
"); + $oPage->add(''); + $oPage->output(); + } + /** + * Make the wizard run: Start, Next or Back depending on the page's parameters + */ + public function Run() + { + $sOperation = utils::ReadParam('operation'); + $this->aParameters = utils::ReadParam('_params', array(), false, 'raw_data'); + $this->aSteps = json_decode(utils::ReadParam('_steps', '[]', false, 'raw_data'), true /* bAssoc */); + + switch($sOperation) + { + case 'next': + $this->Next(); + break; + + case 'back': + $this->Back(); + break; + + default: + $this->Start(); + } + } + + /** + * Provides information about the structure/workflow of the wizard by listing + * the possible list of 'steps' and their dependencies + * @param string $sStep Name of the class to start from (used for recursion) + * @param hash $aAllSteps List of steps (used for recursion) + */ + public function DumpStructure($sStep = '', $aAllSteps = null) + { + if ($aAllSteps == null) $aAllSteps = array(); + if ($sStep == '') $sStep = $this->sInitialStepClass; + + $oStep = new $sStep($this, ''); + $aAllSteps[$sStep] = $oStep->GetPossibleSteps(); + foreach($aAllSteps[$sStep] as $sNextStep) + { + if (!array_key_exists($sNextStep, $aAllSteps)) + { + $aAllSteps = $this->DumpStructure($sNextStep , $aAllSteps); + } + } + + return $aAllSteps; + } + + /** + * Dump the wizard's structure as a string suitable to produce a chart + * using graphviz's "dot" program + * @return string The 'dot' formatted output + */ + public function DumpStructureAsDot() + { + $aAllSteps = $this->DumpStructure(); + $sOutput = "digraph finite_state_machine {\n"; + //$sOutput .= "\trankdir=LR;"; + $sOutput .= "\tsize=\"10,12\"\n"; + + $aDeadEnds = array($this->sInitialStepClass); + foreach($aAllSteps as $sStep => $aNextSteps) + { + if (count($aNextSteps) == 0) + { + $aDeadEnds[] = $sStep; + } + } + $sOutput .= "\tnode [shape = doublecircle]; ".implode(' ', $aDeadEnds).";\n"; + $sOutput .= "\tnode [shape = box];\n"; + foreach($aAllSteps as $sStep => $aNextSteps) + { + $oStep = new $sStep($this, ''); + $sOutput .= "\t$sStep [ label = \"".$oStep->GetTitle()."\"];\n"; + if (count($aNextSteps) > 0) + { + foreach($aNextSteps as $sNextStep) + { + $sOutput .= "\t$sStep -> $sNextStep;\n"; + } + } + } + $sOutput .= "}\n"; + return $sOutput; + } +} + +/** + * Abstract class to build "steps" for the wizard controller + * If a step needs to maintain an internal "state" (for complex steps) + * then it's up to the derived class to implement the behavior based on + * the internal 'sCurrentState' variable. + * @author Erwan Taloc + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +abstract class WizardStep +{ + /** + * A reference to the WizardController + * @var WizardController + */ + protected $oWizard; + /** + * Current 'state' of the wizard step. Simple 'steps' can ignore it + * @var string + */ + protected $sCurrentState; + + public function __construct(WizardController $oWizard, $sCurrentState) + { + $this->oWizard = $oWizard; + $this->sCurrentState = $sCurrentState; + } + + public function GetState() + { + return $this->sCurrentState; + } + + /** + * Displays the wizard page for the current class/state + * The page can contain any number of "" fields, but no "
...
" tag + * The name of the input fields (and their id if one is supplied) MUST NOT start with "_" + * (this is reserved for the wizard's own parameters) + * @return void + */ + abstract public function Display(WebPage $oPage); + + /** + * Processes the page's parameters and (if moving forward) returns the next step/state to be displayed + * @param bool $bMoveForward True if the wizard is moving forward 'Next >>' button pressed, false otherwise + * @return hash array('class' => $sNextClass, 'state' => $sNextState) + */ + abstract public function ProcessParams($bMoveForward = true); + + /** + * Returns the list of possible steps from this step forward + * @return array Array of strings (step classes) + */ + abstract public function GetPossibleSteps(); + + /** + * Returns title of the current step + * @return string The title of the wizard page for the current step + */ + abstract public function GetTitle(); + + /** + * Tells whether the parameters are Ok to move forward + * @return boolean True to move forward, false to stey on the same step + */ + public function ValidateParams() + { + return true; + } + + /** + * Tells whether this step/state is the last one of the wizard (dead-end) + * @return boolean True if the 'Next >>' button should be displayed + */ + public function CanMoveForward() + { + 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) + * @param hash $aParameters The action's parameters name => value + */ + public function AsyncAction(WebPage $oPage, $sCode, $aParameters) + { + } +} + +/* + * Example of a simple Setup Wizard with some parameters to store + * the installation mode (install | upgrade) and a simple asynchronous + * (AJAX) action. + * + * The setup wizard is executed by the following code: + * + * $oWizard = new WizardController('Step1'); + * $oWizard->Run(); + * +class Step1 extends WizardStep +{ + public function GetTitle() + { + return 'Welcome'; + } + + public function GetPossibleSteps() + { + return array('Step2', 'Step2bis'); + } + + public function ProcessParams($bMoveForward = true) + { + $sNextStep = ''; + $sInstallMode = utils::ReadParam('install_mode'); + if ($sInstallMode == 'install') + { + $this->oWizard->SetParameter('install_mode', 'install'); + $sNextStep = 'Step2'; + } + else + { + $this->oWizard->SetParameter('install_mode', 'upgrade'); + $sNextStep = 'Step2bis'; + + } + return array('class' => $sNextStep, 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('This is Step 1!'); + $sInstallMode = $this->oWizard->GetParameter('install_mode', 'install'); + $sChecked = ($sInstallMode == 'install') ? ' checked ' : ''; + $oPage->p(' Install'); + $sChecked = ($sInstallMode == 'upgrade') ? ' checked ' : ''; + $oPage->p(' Upgrade'); + } +} + +class Step2 extends WizardStep +{ + public function GetTitle() + { + return 'Installation Parameters'; + } + + public function GetPossibleSteps() + { + return array('Step3'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'Step3', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('This is Step 2! (Installation)'); + } +} + +class Step2bis extends WizardStep +{ + public function GetTitle() + { + return 'Upgrade Parameters'; + } + + public function GetPossibleSteps() + { + return array('Step2ter'); + } + + public function ProcessParams($bMoveForward = true) + { + $sUpgradeInfo = utils::ReadParam('upgrade_info'); + $this->oWizard->SetParameter('upgrade_info', $sUpgradeInfo); + $sAdditionalUpgradeInfo = utils::ReadParam('additional_upgrade_info'); + $this->oWizard->SetParameter('additional_upgrade_info', $sAdditionalUpgradeInfo); + return array('class' => 'Step2ter', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('This is Step 2bis! (Upgrade)'); + $sUpgradeInfo = $this->oWizard->GetParameter('upgrade_info', ''); + $oPage->p('Type your name here: '); + $sAdditionalUpgradeInfo = $this->oWizard->GetParameter('additional_upgrade_info', ''); + $oPage->p('The installer replies: '); + + $oPage->add_ready_script("$('#upgrade_info').change(function() { + $('#v_upgrade_info').html(''); + WizardAsyncAction('', { upgrade_info: $('#upgrade_info').val() }); });"); + } + + public function AsyncAction(WebPage $oPage, $sCode, $aParameters) + { + usleep(300000); // 300 ms + $sName = $aParameters['upgrade_info']; + $sReply = addslashes("Hello ".$sName); + + $oPage->add_ready_script( +<< 'Step3', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('This is Step 2ter! (Upgrade)'); + } +} + +class Step3 extends WizardStep +{ + public function GetTitle() + { + return 'Installation Complete'; + } + + public function GetPossibleSteps() + { + return array(); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => '', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('This is the FINAL Step'); + } + + public function CanMoveForward() + { + return false; + } +} + +End of the example */ \ No newline at end of file diff --git a/setup/wizardsteps.class.inc.php b/setup/wizardsteps.class.inc.php new file mode 100644 index 000000000..07468604a --- /dev/null +++ b/setup/wizardsteps.class.inc.php @@ -0,0 +1,405 @@ + + * @author Romain Quetiez + * @author Denis Flaven + * @license http://www.opensource.org/licenses/gpl-3.0.html GPL + */ + +require_once(APPROOT.'setup/setuputils.class.inc.php'); + +/** + * First step of the iTop Installation Wizard: Welcome screen + */ +class WizStepWelcome extends WizardStep +{ + protected $bCanMoveForward; + + public function GetTitle() + { + return 'Welcome'; + } + + public function GetPossibleSteps() + { + return array('WizStepInstallOrUpgrade'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepInstallOrUpgrade', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('First step of the installation: check of the prerequisites'); + $aResults = SetupUtils::CheckPHPVersion($oPage); + $this->bCanMoveForward = true; + foreach($aResults as $oCheckResult) + { + switch($oCheckResult->iSeverity) + { + case CheckResult::ERROR: + $this->bCanMoveForward = false; + $oPage->error($oCheckResult->sLabel); + break; + + case CheckResult::WARNING: + $oPage->warning($oCheckResult->sLabel); + break; + + case CheckResult::INFO: + $oPage->ok($oCheckResult->sLabel); + break; + } + } + } + + public function CanMoveForward() + { + return $this->bCanMoveForward; + } +} + +/** + * Second step of the iTop Installation Wizard: Install or Upgrade + */ +class WizStepInstallOrUpgrade extends WizardStep +{ + public function GetTitle() + { + return 'Install or Upgrade choice'; + } + + public function GetPossibleSteps() + { + return array('WizStepDetectedInfo', 'WizStepLicense'); + } + + public function ProcessParams($bMoveForward = true) + { + $sNextStep = ''; + $sInstallMode = utils::ReadParam('install_mode'); + if ($sInstallMode == 'install') + { + $this->oWizard->SetParameter('install_mode', 'install'); + $sNextStep = 'WizStepLicense'; + } + else + { + $this->oWizard->SetParameter('install_mode', 'upgrade'); + $sNextStep = 'WizStepDetectedInfo'; + + } + return array('class' => $sNextStep, 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('What do you want to do?'); + $sInstallMode = $this->oWizard->GetParameter('install_mode', 'install'); + $sChecked = ($sInstallMode == 'install') ? ' checked ' : ''; + $oPage->p(' Install a new iTop'); + $sChecked = ($sInstallMode == 'upgrade') ? ' checked ' : ''; + $oPage->p(' Upgrade an existing iTop'); + } +} + +/** + * Upgrade information + */ +class WizStepDetectedInfo extends WizardStep +{ + public function GetTitle() + { + return 'Detected Info'; + } + + public function GetPossibleSteps() + { + return array('WizStepUpgradeKeep', 'WizStepUpgradeAuto', 'WizStepLicense2'); + } + + public function ProcessParams($bMoveForward = true) + { + + return array('class' => 'WizStepUpgradeAuto', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Info about the detected version'); + } +} + +/** + * 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'; + } + + 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('Automatic Upgrade information'); + } +} +/** + * License acceptation screen + */ +class WizStepLicense extends WizardStep +{ + public function GetTitle() + { + return 'License Agreement'; + } + + public function GetPossibleSteps() + { + return array('WizStepDBParams'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepDBParams', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Do you accept ALL the licenses?'); + } +} + +/** + * License acceptation screen (when upgrading) + */ +class WizStepLicense2 extends WizStepLicense +{ + public function GetPossibleSteps() + { + return array('WizStepUpgradeKeep', 'WizStepUpgradeAuto'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepUpgradeAuto', 'state' => ''); + } +} + +/** + * Database Connection parameters screen + */ +class WizStepDBParams extends WizardStep +{ + public function GetTitle() + { + return 'Database Configuration'; + } + + public function GetPossibleSteps() + { + return array('WizStepAdminAccount'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepAdminAccount', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Please enter the DB parameters'); + } +} + +/** + * Administrator Account definition screen + */ +class WizStepAdminAccount extends WizardStep +{ + public function GetTitle() + { + return 'Administrator Account'; + } + + public function GetPossibleSteps() + { + return array('WizStepMiscParams'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepMiscParams', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Please enter Admin Account name/pwd'); + } +} + +/** + * Miscellaneous Parameters (URL, Sample Data) + */ +class WizStepMiscParams extends WizardStep +{ + public function GetTitle() + { + return 'Miscellaneous Parameters'; + } + + public function GetPossibleSteps() + { + return array('WizStepModulesChoice'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepModulesChoice', 'state' => 'start_install'); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Additional Parameters (URl, Sample Data)'); + } +} + +/** + * Choice of the modules to be installed + */ +class WizStepModulesChoice extends WizardStep +{ + public function GetTitle() + { + return 'Modules Selection'; + } + + public function GetPossibleSteps() + { + return array('WizStepSummary'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepSummary', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Select the modules to install/upgrade.'); + } +} + +/** + * Summary of the installation tasks + */ +class WizStepSummary extends WizardStep +{ + public function GetTitle() + { + return 'Installation summary'; + } + + public function GetPossibleSteps() + { + return array('WizStepDone'); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => 'WizStepDone', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Summary of the installation.'); + } +} + +/** + * Summary of the installation tasks + */ +class WizStepDone extends WizardStep +{ + public function GetTitle() + { + return 'Done'; + } + + public function GetPossibleSteps() + { + return array(); + } + + public function ProcessParams($bMoveForward = true) + { + return array('class' => '', 'state' => ''); + } + + public function Display(WebPage $oPage) + { + $oPage->p('Installation Completed.'); + } + + public function CanMoveForward() + { + return false; + } +}