Jelajahi Sumber

The new 2.0 setup is under way... (added backup + file copy + sample data + admin language)

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@2187 a333f486-631f-4898-b8df-5754b55c2be0
romainq 12 tahun lalu
induk
melakukan
2b58c46fa8

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

+ 24 - 1
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;
@@ -126,6 +142,13 @@ class WebPage
     }
     
 	/**
+	 * Add a comment
+	 */
+    public function add_comment($sText)
+    {
+        $this->add('<!--'.$sText.'-->');
+    }
+	/**
 	 * Add a paragraph to the body of the page
 	 */
     public function GetP($s_html)

+ 1 - 0
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);

+ 1 - 1
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

+ 100 - 15
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))
 			{

+ 4 - 46
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 = '';
 
@@ -258,51 +261,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
 	 * Returns string PHP flags, based on the OPT_ATT_ constants, or empty (meaning 0, can be omitted)

+ 178 - 11
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 = "<a href=\"http://www.php.net/manual/en/book.zip.php\" target=\"_blank\">zip</a>";
+			$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;
+		}
+	}
 }