From bb404f8a952e5e0ff487e9f1c31b5803f55bbe69 Mon Sep 17 00:00:00 2001 From: Romain Quetiez Date: Tue, 18 Sep 2012 09:59:36 +0000 Subject: [PATCH] The new 2.0 setup is under way... (added backup + file copy + sample data + admin language) SVN:trunk[2187] --- application/clipage.class.inc.php | 2 +- application/webpage.class.inc.php | 25 ++- core/config.class.inc.php | 1 + core/metamodel.class.php | 2 +- setup/applicationinstaller.class.inc.php | 115 ++++++++++++-- setup/compiler.class.inc.php | 50 +----- setup/setuputils.class.inc.php | 189 +++++++++++++++++++++-- 7 files changed, 309 insertions(+), 75 deletions(-) diff --git a/application/clipage.class.inc.php b/application/clipage.class.inc.php index ca9ec5e46..6a074da6b 100644 --- a/application/clipage.class.inc.php +++ b/application/clipage.class.inc.php @@ -26,7 +26,7 @@ require_once(APPROOT."/application/webpage.class.inc.php"); -class CLIPage +class CLIPage implements Page { function __construct($s_title) { diff --git a/application/webpage.class.inc.php b/application/webpage.class.inc.php index 65431aa83..b5f552c7c 100644 --- a/application/webpage.class.inc.php +++ b/application/webpage.class.inc.php @@ -23,6 +23,22 @@ * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ + +/** + * Generic interface common to CLI and Web pages + */ +Interface Page +{ + public function output(); + public function add($sText); + public function p($sText); + public function pre($sText); + public function add_comment($sText); + public function table($aConfig, $aData, $aParams = array()); +} + + + /** * Simple helper class to ease the production of HTML pages * @@ -34,7 +50,7 @@ * $oPage->p("Hello World !"); * $oPage->output(); */ -class WebPage +class WebPage implements Page { protected $s_title; protected $s_content; @@ -125,6 +141,13 @@ class WebPage $this->add('
'.$s_html.'
'); } + /** + * Add a comment + */ + public function add_comment($sText) + { + $this->add(''); + } /** * Add a paragraph to the body of the page */ diff --git a/core/config.class.inc.php b/core/config.class.inc.php index 864e76010..e10a3cd2e 100644 --- a/core/config.class.inc.php +++ b/core/config.class.inc.php @@ -1398,6 +1398,7 @@ class Config $sDBName = $aParamValues['db_name']; if ($sDBName == '') { + // Todo - obsolete after the transition to the new setup (2.0) is complete (WARNING: used by the designer) $sDBName = $aParamValues['new_db_name']; } $this->SetDBName($sDBName); diff --git a/core/metamodel.class.php b/core/metamodel.class.php index a2c30b9db..c8f856c05 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -647,7 +647,7 @@ abstract class MetaModel return self::$m_sTablePrefix."view_".$sClass; } - final static protected function DBEnumTables() + final static public function DBEnumTables() { // This API does not rely on our capability to query the DB and retrieve // the list of existing tables diff --git a/setup/applicationinstaller.class.inc.php b/setup/applicationinstaller.class.inc.php index 4ef45a736..47e23e9dc 100644 --- a/setup/applicationinstaller.class.inc.php +++ b/setup/applicationinstaller.class.inc.php @@ -16,6 +16,7 @@ require_once(APPROOT.'setup/parameters.class.inc.php'); require_once(APPROOT.'setup/xmldataloader.class.inc.php'); +require_once(APPROOT.'setup/backup.class.inc.php'); /** * The base class for the installation process. @@ -121,9 +122,41 @@ class ApplicationInstaller break; case 'copy': + $aPreinstall = $this->oParams->Get('preinstall'); + $aCopies = $aPreinstall['copies']; + + $sReport = self::DoCopy($aCopies); + $aResult = array( - 'status' => self::WARNING, - 'message' => 'Dummy setup - Nothing to copy', + 'status' => self::OK, + 'message' => $sReport, + ); + if (isset($aPreinstall['backup'])) + { + $aResult['next-step'] = 'backup'; + $aResult['next-step-label'] = 'Backuping the database'; + $aResult['percentage-completed'] = 20; + } + else + { + $aResult['next-step'] = 'compile'; + $aResult['next-step-label'] = 'Compiling the data model'; + $aResult['percentage-completed'] = 20; + } + break; + + case 'backup': + $aPreinstall = $this->oParams->Get('preinstall'); + // __DB__-%Y-%m-%d.zip + $sDestination = $aPreinstall['backup']['destination']; + $sSourceConfigFile = $aPreinstall['backup']['configuration_file']; + $aDBParams = $this->oParams->Get('database'); + + self::DoBackup($aDBParams['server'], $aDBParams['user'], $aDBParams['pwd'], $aDBParams['name'], $aDBParams['prefix'], $sDestination, $sSourceConfigFile); + + $aResult = array( + 'status' => self::OK, + 'message' => "Created backup", 'next-step' => 'compile', 'next-step-label' => 'Compiling the data model', 'percentage-completed' => 20, @@ -208,25 +241,27 @@ class ApplicationInstaller 'next-step-label' => 'Loading Sample Data', 'percentage-completed' => 80, ); + + $bLoadData = ($this->oParams->Get('sample_data', 0) == 1); + if (!$bLoadData) + { + $aResult['next-step'] = 'create-config'; + $aResult['next-step-label'] = 'Creating the Configuration File'; + } break; case 'sample-data': - $sMode = $this->oParams->Get('mode'); + $aSelectedModules = $this->oParams->Get('selected_modules'); $sTargetEnvironment = $this->oParams->Get('target_env', ''); - if ($sTargetEnvironment == '') - { - $sTargetEnvironment = 'production'; - } - $sTargetDir = 'env-'.$sTargetEnvironment; + $sTargetDir = 'env-'.(($sTargetEnvironment == '') ? 'production' : $sTargetEnvironment); $aDBParams = $this->oParams->Get('database'); $sDBServer = $aDBParams['server']; $sDBUser = $aDBParams['user']; $sDBPwd = $aDBParams['pwd']; $sDBName = $aDBParams['name']; $sDBPrefix = $aDBParams['prefix']; - $aFiles = $this->oParams->Get('files', array()); - self::DoLoadFiles($aFiles, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment); + self::DoLoadFiles($aSelectedModules, $sTargetDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment); $aResult = array( 'status' => self::INFO, @@ -289,6 +324,38 @@ class ApplicationInstaller } return $aResult; } + + protected static function DoCopy($aCopies) + { + $aReports = array(); + foreach ($aCopies as $aCopy) + { + $sSource = $aCopy['source']; + $sDestination = APPROOT.$aCopy['destination']; + + SetupUtils::builddir($sDestination); + SetupUtils::tidydir($sDestination); + SetupUtils::copydir($sSource, $sDestination); + $aReports[] = "'{$aCopy['source']}' to '{$aCopy['destination']}' (OK)"; + } + if (count($aReports) > 0) + { + $sReport = "Copies: ".count($aReports).': '.implode('; ', $aReports); + } + else + { + $sReport = "No file copy"; + } + return $sReport; + } + + protected static function DoBackup($sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sBackupFile, $sSourceConfigFile) + { + $oBackup = new DBBackup($sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix); + $sZipFile = $oBackup->MakeName($sBackupFile); + $oBackup->CreateZip($sZipFile, $sSourceConfigFile); + } + protected static function DoCompile($aSelectedModules, $sSourceDir, $sTargetDir, $sWorkspaceDir = '') { @@ -376,7 +443,7 @@ class ApplicationInstaller if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) { - throw(new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'")); + throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'"); } SetupPage::log_info("Database Schema Successfully Updated for environment '$sTargetEnvironment'."); } @@ -470,7 +537,7 @@ class ApplicationInstaller if (!$oProductionEnv->RecordInstallation($oConfig, $aSelectedModules, $sModulesDir)) { - throw(new Exception("Failed to record the installation information")); + throw new Exception("Failed to record the installation information"); } if($sMode == 'install') @@ -504,7 +571,7 @@ class ApplicationInstaller } } - protected static function DoLoadFiles($aFiles, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '') + protected static function DoLoadFiles($aSelectedModules, $sModulesDir, $sDBServer, $sDBUser, $sDBPwd, $sDBName, $sDBPrefix, $sTargetEnvironment = '') { $aParamValues = array( 'db_server' => $sDBServer, @@ -528,10 +595,28 @@ class ApplicationInstaller $iChangeId = $oChange->DBInsert(); SetupPage::log_info("starting data load session"); $oDataLoader->StartSession($oChange); - + + $aFiles = array(); + $oProductionEnv = new RunTimeEnvironment(); + $aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $sModulesDir); + foreach($aAvailableModules as $sModuleId => $aModule) + { + if (($sModuleId != ROOT_MODULE)) + { + if (in_array($sModuleId, $aSelectedModules)) + { + $aFiles = array_merge( + $aFiles, + $aAvailableModules[$sModuleId]['data.struct'], + $aAvailableModules[$sModuleId]['data.sample'] + ); + } + } + } + foreach($aFiles as $sFileRelativePath) { - $sFileName = APPROOT.'env-'.(($sTargetEnvironment == '') ? 'production' : $sTargetEnvironment).'/'.$sFileRelativePath; + $sFileName = APPROOT.$sFileRelativePath; SetupPage::log_info("Loading file: $sFileName"); if (empty($sFileName) || !file_exists($sFileName)) { diff --git a/setup/compiler.class.inc.php b/setup/compiler.class.inc.php index 9bcb1a026..45190ce06 100644 --- a/setup/compiler.class.inc.php +++ b/setup/compiler.class.inc.php @@ -14,6 +14,9 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +require_once(APPROOT.'setup/setuputils.class.inc.php'); + class DOMFormatException extends Exception { } @@ -97,7 +100,7 @@ class MFCompiler $sRelativeDir = substr($sModuleRootDir, strlen($this->sSourceDir) + 1); // Push the other module files - $this->CopyDirectory($sModuleRootDir, $sTargetDir.'/'.$sRelativeDir); + SetupUtils::copydir($sModuleRootDir, $sTargetDir.'/'.$sRelativeDir); $sCompiledCode = ''; @@ -257,51 +260,6 @@ EOF; } } - /** - * Helper to copy the module files to the exploitation environment - * Returns true if successfull - */ - protected function CopyDirectory($sSource, $sDest) - { - if (is_dir($sSource)) - { - if (!is_dir($sDest)) - { - mkdir($sDest); - } - $aFiles = scandir($sSource); - if(sizeof($aFiles) > 0 ) - { - foreach($aFiles as $sFile) - { - if ($sFile == '.' || $sFile == '..' || $sFile == '.svn') - { - // Skip - continue; - } - - if (is_dir($sSource.'/'.$sFile)) - { - $this->CopyDirectory($sSource.'/'.$sFile, $sDest.'/'.$sFile); - } - else - { - copy($sSource.'/'.$sFile, $sDest.'/'.$sFile); - } - } - } - return true; - } - elseif (is_file($sSource)) - { - return copy($sSource, $sDest); - } - else - { - return false; - } - } - /** * Helper to format the flags for an attribute, in a given state * @param object $oAttNode DOM node containing the information to build the flags diff --git a/setup/setuputils.class.inc.php b/setup/setuputils.class.inc.php index 8a32454b3..65515f236 100644 --- a/setup/setuputils.class.inc.php +++ b/setup/setuputils.class.inc.php @@ -63,15 +63,14 @@ class SetupUtils * @param SetupPage $oP The page used only for its 'log' method * @return array An array of CheckResults objects */ - static function CheckPHPVersion(SetupPage $oP) + static function CheckPHPVersion() { $aResult = array(); - $bResult = true; $aErrors = array(); $aWarnings = array(); $aOk = array(); - $oP->log('Info - CheckPHPVersion'); + SetupPage::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.")"); @@ -151,7 +150,7 @@ class SetupUtils } } } - $oP->log("Info - php.ini file(s): '$sPhpIniFile'"); + SetupPage::log("Info - php.ini file(s): '$sPhpIniFile'"); } else { @@ -165,7 +164,7 @@ class SetupUtils $sUploadTmpDir = self::GetUploadTmpDir(); if (empty($sUploadTmpDir)) { - $sUploadTmpDir = '/tmp'; + $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 @@ -181,7 +180,7 @@ class SetupUtils } else { - $oP->log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); + SetupPage::log("Info - Temporary directory for files upload ($sUploadTmpDir) is writable."); } } @@ -206,9 +205,9 @@ class SetupUtils } - $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')); + SetupPage::log("Info - upload_max_filesize: ".ini_get('upload_max_filesize')); + SetupPage::log("Info - post_max_size: ".ini_get('post_max_size')); + SetupPage::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')) @@ -246,7 +245,7 @@ class SetupUtils } else { - $oP->log_info("memory_limit is $iMemoryLimit, ok."); + SetupPage::log("Info - memory_limit is $iMemoryLimit, ok."); } } @@ -270,12 +269,81 @@ class SetupUtils } else { - $oP->log_info("suhosin.get.max_value_length = $iGetMaxValueLength, ok."); + SetupPage::log("Info - suhosin.get.max_value_length = $iGetMaxValueLength, ok."); } } return $aResult; } + + /** + * Check that the backup could be executed + * @param Page $oP The page used only for its 'log' method + * @return array An array of CheckResults objects + */ + static function CheckBackupPrerequisites($sDestDir) + { + $aResult = array(); + SetupPage::log('Info - CheckBackupPrerequisites'); + + // zip extension + // + if (!extension_loaded('zip')) + { + $sMissingExtensionLink = "zip"; + $aResult[] = new CheckResult(CheckResult::ERROR, "Missing PHP extension: zip", $sMissingExtensionLink); + } + + // availability of exec() + // + $aDisabled = explode(', ', ini_get('disable_functions')); + SetupPage::log('Info - PHP functions disabled: '.implode(', ', $aDisabled)); + if (in_array('exec', $aDisabled)) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "The PHP exec() function has been disabled on this server"); + } + + // availability of mysqldump + $sMySQLBinDir = utils::ReadParam('mysql_bindir', '', true); + if (empty($sMySQLBinDir)) + { + $sMySQLDump = 'mysqldump'; + } + else + { + SetupPage::log('Info - Found mysql_bindir: '.$sMySQLBinDir); + $sMySQLDump = '"'.$sMySQLBinDir.'/mysqldump"'; + } + $sCommand = "$sMySQLDump -V 2>&1"; + + $aOutput = array(); + $iRetCode = 0; + exec($sCommand, $aOutput, $iRetCode); + if ($iRetCode == 0) + { + $aResult[] = new CheckResult(CheckResult::INFO, "mysqldump is present: ".$aOutput[0]); + } + elseif ($iRetCode == 1) + { + $aResult[] = new CheckResult(CheckResult::ERROR, "mysqldump could not be found: ".implode(' ', $aOutput)." - Please make sure it is installed and in the path."); + } + else + { + $aResult[] = new CheckResult(CheckResult::ERROR, "mysqldump could not be executed (retcode=$iRetCode): Please make sure it is installed and in the path"); + } + foreach($aOutput as $sLine) + { + SetupPage::log('Info - mysqldump -V said: '.$sLine); + } + + // check disk space + // to do... evaluate how we can correlate the DB size with the size of the dump (and the zip!) + // E.g. 2,28 Mb after a full install, giving a zip of 26 Kb (data = 26 Kb) + // Example of query (DB without a suffix) + //$sDBSize = "SELECT SUM(ROUND(DATA_LENGTH/1024/1024, 2)) AS size_mb FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = `$sDBName`"; + + return $aResult; + } /** * Helper function to retrieve the system's temporary directory @@ -319,4 +387,103 @@ class SetupUtils } return $sPath; } + + /** + * Helper to recursively remove a directory + */ + public static function rrmdir($dir) + { + if ((strlen(trim($dir)) == 0) || ($dir == '/') || ($dir == '\\')) + { + throw new Exception("Attempting to delete directory: '$dir'"); + } + self::tidydir($dir); + rmdir($dir); + } + + /** + * Helper to recursively cleanup a directory + */ + public static function tidydir($dir) + { + if ((strlen(trim($dir)) == 0) || ($dir == '/') || ($dir == '\\')) + { + throw new Exception("Attempting to delete directory: '$dir'"); + } + + foreach(glob($dir . '/*') as $file) + { + if(is_dir($file)) + { + self::tidydir($file); + rmdir($file); + } + else + { + unlink($file); + } + } + } + + /** + * Helper to build the full path of a new directory + */ + public static function builddir($dir) + { + $parent = dirname($dir); + if(!is_dir($parent)) + { + self::builddir($parent); + } + if (!is_dir($dir)) + { + mkdir($dir); + } + } + + /** + * Helper to copy a directory to a target directory, skipping .SVN files (for developer's comfort!) + * Returns true if successfull + */ + public static function copydir($sSource, $sDest) + { + if (is_dir($sSource)) + { + if (!is_dir($sDest)) + { + mkdir($sDest); + } + $aFiles = scandir($sSource); + if(sizeof($aFiles) > 0 ) + { + foreach($aFiles as $sFile) + { + if ($sFile == '.' || $sFile == '..' || $sFile == '.svn') + { + // Skip + continue; + } + + if (is_dir($sSource.'/'.$sFile)) + { + // Recurse + self::copydir($sSource.'/'.$sFile, $sDest.'/'.$sFile); + } + else + { + copy($sSource.'/'.$sFile, $sDest.'/'.$sFile); + } + } + } + return true; + } + elseif (is_file($sSource)) + { + return copy($sSource, $sDest); + } + else + { + return false; + } + } } \ No newline at end of file