mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-19 00:28:47 +02:00
The new 2.0 setup is under way... (added backup + file copy + sample data + admin language)
SVN:trunk[2187]
This commit is contained in:
@@ -26,7 +26,7 @@
|
||||
|
||||
require_once(APPROOT."/application/webpage.class.inc.php");
|
||||
|
||||
class CLIPage
|
||||
class CLIPage implements Page
|
||||
{
|
||||
function __construct($s_title)
|
||||
{
|
||||
|
||||
@@ -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('<pre>'.$s_html.'</pre>');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a comment
|
||||
*/
|
||||
public function add_comment($sText)
|
||||
{
|
||||
$this->add('<!--'.$sText.'-->');
|
||||
}
|
||||
/**
|
||||
* Add a paragraph to the body of the page
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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))
|
||||
{
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user