N°8796 - Add PHP code style validation in iTop and extensions - format whole code base

This commit is contained in:
odain
2025-11-07 15:39:53 +01:00
parent 12f23113f5
commit 890a2568c8
2110 changed files with 53099 additions and 63885 deletions

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -21,23 +22,23 @@
* This page is called to perform "asynchronously" the setup actions
* parameters
* 'operation': one of 'compile_data_model', 'update_db_schema', 'after_db_creation', 'file'
*
* if 'operation' == 'update_db_schema':
*
* if 'operation' == 'update_db_schema':
* 'mode': install | upgrade
*
*
* if 'operation' == 'after_db_creation':
* 'mode': install | upgrade
*
* if 'operation' == 'file':
*
* if 'operation' == 'file':
* 'file': string Name of the file to load
* 'session_status': string 'start', 'continue' or 'end'
* 'percent': integer 0..100 the percentage of completion once the file has been loaded
* 'percent': integer 0..100 the percentage of completion once the file has been loaded
*/
use Combodo\iTop\Application\WebPage\AjaxPage;
$bBypassMaintenance = true; // Reset maintenance mode in case of problem
define('SAFE_MINIMUM_MEMORY', 64*1024*1024);
define('SAFE_MINIMUM_MEMORY', 64 * 1024 * 1024);
require_once('../approot.inc.php');
require_once(APPROOT.'/application/utils.inc.php');
require_once(APPROOT.'/setup/setuppage.class.inc.php');
@@ -47,35 +48,26 @@ ini_set('max_execution_time', max(3600, ini_get('max_execution_time'))); // Unde
date_default_timezone_set('Europe/Paris'); // Just to avoid a warning if the timezone is not set in php.ini
$sMemoryLimit = trim(ini_get('memory_limit'));
if (empty($sMemoryLimit))
{
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 with the data load
SetupLog::Info("No memory limit has been defined in this instance of PHP");
}
else
{
} else {
// Check that the limit will allow us to load the data
//
$iMemoryLimit = utils::ConvertToBytes($sMemoryLimit);
if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY))
{
if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === FALSE)
{
if (!utils::IsMemoryLimitOk($iMemoryLimit, SAFE_MINIMUM_MEMORY)) {
if (ini_set('memory_limit', SAFE_MINIMUM_MEMORY) === false) {
SetupLog::Error("memory_limit is too small: $iMemoryLimit and can not be increased by the script itself.");
}
else
{
} else {
SetupLog::Info("memory_limit increased from $iMemoryLimit to ".SAFE_MINIMUM_MEMORY.".");
}
}
}
define('PHP_FATAL_ERROR_TAG', 'phpfatalerror');
/**
* Handler for register_shutdown_function, to catch PHP errors
*/
@@ -83,8 +75,7 @@ function ShutdownCallback()
{
$error = error_get_last();
$bIsErrorToReport = (($error !== null) && ($error['type'] & (E_ERROR | E_PARSE | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR)));
if (!$bIsErrorToReport)
{
if (!$bIsErrorToReport) {
return;
}
@@ -97,19 +88,16 @@ function ShutdownCallback()
echo '<'.PHP_FATAL_ERROR_TAG.'>'.$sLogMessage.'</'.PHP_FATAL_ERROR_TAG.'>';
}
function FatalErrorCatcher($sOutput)
{
if (preg_match('|<'.PHP_FATAL_ERROR_TAG.'>.*</'.PHP_FATAL_ERROR_TAG.'>|s', $sOutput, $aMatches))
{
if (preg_match('|<'.PHP_FATAL_ERROR_TAG.'>.*</'.PHP_FATAL_ERROR_TAG.'>|s', $sOutput, $aMatches)) {
header("HTTP/1.0 500 Internal server error.");
$errors = '';
foreach ($aMatches as $sMatch)
{
foreach ($aMatches as $sMatch) {
$errors .= strip_tags($sMatch)."\n";
}
$sOutput = "$errors\n";
// Logging to a file does not work if the whole memory is exhausted...
// Logging to a file does not work if the whole memory is exhausted...
// SetupLog::Error("Fatal error - in $__FILE__ , $errors");
}
return $sOutput;
@@ -132,7 +120,6 @@ require_once(APPROOT.'/core/kpi.class.inc.php');
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
require_once('./xmldataloader.class.inc.php');
// Never cache this page
header("Cache-Control: no-cache, must-revalidate"); // HTTP/1.1
header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
@@ -141,41 +128,38 @@ header("Expires: Fri, 17 Jul 1970 05:00:00 GMT"); // Date in the past
* Main program
*/
$sOperation = Utils::ReadParam('operation', '');
try
{
try {
SetupUtils::CheckSetupToken();
switch($sOperation)
{
switch ($sOperation) {
case 'async_action':
ini_set('max_execution_time', max(240, ini_get('max_execution_time')));
// While running the setup it is desirable to see any error that may happen
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
$sClass = utils::ReadParam('step_class', '');
$sState = utils::ReadParam('step_state', '');
$sActionCode = utils::ReadParam('code', '');
$aParams = utils::ReadParam('params', array(), false, 'raw_data');
$oPage = new AjaxPage('');
$oDummyController = new WizardController('');
if (is_subclass_of($sClass, 'WizardStep'))
{
/** @var WizardStep $oStep */
$oStep = new $sClass($oDummyController, $sState);
$sConfigFile = utils::GetConfigFilePath();
if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig()) {
$sRelativePath = utils::GetConfigFilePathRelative();
$oPage->error("<b>Error:</b> the configuration file '".$sRelativePath."' already exists and cannot be overwritten.");
$oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '<b>".$sRelativePath."</b>' can be modified by the web server.");
$oPage->output();
} else {
$oStep->AsyncAction($oPage, $sActionCode, $aParams);
ini_set('max_execution_time', max(240, ini_get('max_execution_time')));
// While running the setup it is desirable to see any error that may happen
ini_set('display_errors', true);
ini_set('display_startup_errors', true);
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
$sClass = utils::ReadParam('step_class', '');
$sState = utils::ReadParam('step_state', '');
$sActionCode = utils::ReadParam('code', '');
$aParams = utils::ReadParam('params', [], false, 'raw_data');
$oPage = new AjaxPage('');
$oDummyController = new WizardController('');
if (is_subclass_of($sClass, 'WizardStep')) {
/** @var WizardStep $oStep */
$oStep = new $sClass($oDummyController, $sState);
$sConfigFile = utils::GetConfigFilePath();
if (file_exists($sConfigFile) && !is_writable($sConfigFile) && $oStep->RequiresWritableConfig()) {
$sRelativePath = utils::GetConfigFilePathRelative();
$oPage->error("<b>Error:</b> the configuration file '".$sRelativePath."' already exists and cannot be overwritten.");
$oPage->p("The wizard cannot modify the configuration file for you. If you want to upgrade ".ITOP_APPLICATION.", make sure that the file '<b>".$sRelativePath."</b>' can be modified by the web server.");
$oPage->output();
} else {
$oStep->AsyncAction($oPage, $sActionCode, $aParams);
}
}
}
$oPage->output();
break;
@@ -189,23 +173,17 @@ try
default:
throw(new Exception("Error unsupported operation '$sOperation'"));
}
}
catch(Exception $e)
{
} catch (Exception $e) {
header("HTTP/1.0 500 Internal server error.");
echo "<p>An error happened while processing the installation:</p>\n";
echo '<p>'.$e."</p>\n";
SetupLog::Error("An error happened while processing the installation: ".$e);
}
if (function_exists('memory_get_peak_usage'))
{
if ($sOperation == 'file')
{
if (function_exists('memory_get_peak_usage')) {
if ($sOperation == 'file') {
SetupLog::Info("loading file '$sFileName', peak memory usage. ".memory_get_peak_usage());
}
else
{
} else {
SetupLog::Info("operation '$sOperation', peak memory usage. ".memory_get_peak_usage());
}
}
}

View File

@@ -1,4 +1,5 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
@@ -36,10 +37,10 @@ require_once(APPROOT.'setup/backup.class.inc.php');
class ApplicationInstaller
{
const OK = 1;
const ERROR = 2;
const WARNING = 3;
const INFO = 4;
public const OK = 1;
public const ERROR = 2;
public const WARNING = 3;
public const INFO = 4;
/** @var \Parameters */
protected $oParams;
@@ -67,8 +68,7 @@ class ApplicationInstaller
private function GetTargetEnv()
{
$sTargetEnvironment = $this->oParams->Get('target_env', '');
if ($sTargetEnvironment !== '')
{
if ($sTargetEnvironment !== '') {
return $sTargetEnvironment;
}
@@ -100,17 +100,12 @@ class ApplicationInstaller
$sStep = '';
$sStepLabel = '';
$iOverallStatus = self::OK;
do
{
if ($bVerbose)
{
if ($sStep != '')
{
do {
if ($bVerbose) {
if ($sStep != '') {
echo "$sStepLabel\n";
echo "Executing '$sStep'\n";
}
else
{
} else {
echo "Starting the installation...\n";
}
}
@@ -118,11 +113,9 @@ class ApplicationInstaller
$sStep = $aRes['next-step'];
$sStepLabel = $aRes['next-step-label'];
$sMessage = $aRes['message'];
if ($bVerbose)
{
switch ($aRes['status'])
{
case self::OK;
if ($bVerbose) {
switch ($aRes['status']) {
case self::OK:
echo "Ok. ".$aRes['percentage-completed']." % done.\n";
break;
@@ -142,11 +135,8 @@ class ApplicationInstaller
echo $aRes['percentage-completed']." % done.\n";
break;
}
}
else
{
switch ($aRes['status'])
{
} else {
switch ($aRes['status']) {
case self::ERROR:
$iOverallStatus = self::ERROR;
break;
@@ -155,8 +145,7 @@ class ApplicationInstaller
break;
}
}
}
while(($aRes['status'] != self::ERROR) && ($aRes['next-step'] != ''));
} while (($aRes['status'] != self::ERROR) && ($aRes['next-step'] != ''));
return ($iOverallStatus == self::OK);
}
@@ -167,8 +156,7 @@ class ApplicationInstaller
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
try {
$oConfig = new Config($sConfigFile);
}
catch (Exception $e) {
} catch (Exception $e) {
return null;
}
@@ -189,21 +177,19 @@ class ApplicationInstaller
*/
public function ExecuteStep($sStep = '', $sInstallComment = null)
{
try
{
try {
$fStart = microtime(true);
SetupLog::Info("##### STEP {$sStep} start");
$this->EnterReadOnlyMode();
switch ($sStep)
{
switch ($sStep) {
case '':
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => '',
'percentage-completed' => 0,
'next-step' => 'copy',
'next-step-label' => 'Copying data model files',
);
];
// Log the parameters...
$oDoc = new DOMDocument('1.0', 'UTF-8');
@@ -217,8 +203,7 @@ class ApplicationInstaller
// Save the response file as a stand-alone file as well
$sFileName = 'install-'.date('Y-m-d');
$index = 0;
while (file_exists(APPROOT.'log/'.$sFileName.'.xml'))
{
while (file_exists(APPROOT.'log/'.$sFileName.'.xml')) {
$index++;
$sFileName = 'install-'.date('Y-m-d').'-'.$index;
}
@@ -233,18 +218,15 @@ class ApplicationInstaller
self::DoCopy($aCopies);
$sReport = "Copying...";
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => $sReport,
);
if (isset($aPreinstall['backup']))
{
];
if (isset($aPreinstall['backup'])) {
$aResult['next-step'] = 'backup';
$aResult['next-step-label'] = 'Performing a backup of the database';
$aResult['percentage-completed'] = 20;
}
else
{
} else {
$aResult['next-step'] = 'compile';
$aResult['next-step-label'] = 'Compiling the data model';
$aResult['percentage-completed'] = 20;
@@ -262,13 +244,13 @@ class ApplicationInstaller
$sMySQLBinDir = $this->oParams->Get('mysql_bindir', null);
self::DoBackup($oTempConfig, $sDestination, $sSourceConfigFile, $sMySQLBinDir);
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => "Created backup",
'next-step' => 'compile',
'next-step-label' => 'Compiling the data model',
'percentage-completed' => 20,
);
];
break;
case 'compile':
@@ -277,7 +259,7 @@ class ApplicationInstaller
$sExtensionDir = $this->oParams->Get('extensions_dir', 'extensions');
$sTargetEnvironment = $this->GetTargetEnv();
$sTargetDir = $this->GetTargetDir();
$aMiscOptions = $this->oParams->Get('options', array());
$aMiscOptions = $this->oParams->Get('options', []);
$bUseSymbolicLinks = null;
if ((isset($aMiscOptions['symlinks']) && $aMiscOptions['symlinks'])) {
@@ -290,36 +272,49 @@ class ApplicationInstaller
}
$aParamValues = $this->oParams->GetParamForConfigArray();
self::DoCompile($aSelectedModules, $sSourceDir, $sExtensionDir, $sTargetDir, $sTargetEnvironment,
$bUseSymbolicLinks, $aParamValues);
self::DoCompile(
$aSelectedModules,
$sSourceDir,
$sExtensionDir,
$sTargetDir,
$sTargetEnvironment,
$bUseSymbolicLinks,
$aParamValues
);
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => '',
'next-step' => 'db-schema',
'next-step-label' => 'Updating database schema',
'percentage-completed' => 40,
);
];
break;
case 'db-schema':
$aSelectedModules = $this->oParams->Get('selected_modules', array());
$aSelectedModules = $this->oParams->Get('selected_modules', []);
$sTargetEnvironment = $this->GetTargetEnv();
$sTargetDir = $this->GetTargetDir();
$aParamValues = $this->oParams->GetParamForConfigArray();
$bOldAddon = $this->oParams->Get('old_addon', false);
$sUrl = $this->oParams->Get('url', '');
self::DoUpdateDBSchema($aSelectedModules, $sTargetDir, $aParamValues, $sTargetEnvironment,
$bOldAddon, $sUrl);
self::DoUpdateDBSchema(
$aSelectedModules,
$sTargetDir,
$aParamValues,
$sTargetEnvironment,
$bOldAddon,
$sUrl
);
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => '',
'next-step' => 'after-db-create',
'next-step-label' => 'Creating profiles',
'percentage-completed' => 60,
);
];
break;
case 'after-db-create':
@@ -330,19 +325,27 @@ class ApplicationInstaller
$sAdminUser = $aAdminParams['user'];
$sAdminPwd = $aAdminParams['pwd'];
$sAdminLanguage = $aAdminParams['language'];
$aSelectedModules = $this->oParams->Get('selected_modules', array());
$aSelectedModules = $this->oParams->Get('selected_modules', []);
$bOldAddon = $this->oParams->Get('old_addon', false);
self::AfterDBCreate($sTargetDir, $aParamValues, $sAdminUser, $sAdminPwd, $sAdminLanguage,
$aSelectedModules, $sTargetEnvironment, $bOldAddon);
self::AfterDBCreate(
$sTargetDir,
$aParamValues,
$sAdminUser,
$sAdminPwd,
$sAdminLanguage,
$aSelectedModules,
$sTargetEnvironment,
$bOldAddon
);
$aResult = array(
$aResult = [
'status' => self::OK,
'message' => '',
'next-step' => 'load-data',
'next-step-label' => 'Loading data',
'percentage-completed' => 80,
);
];
break;
case 'load-data':
@@ -353,16 +356,22 @@ class ApplicationInstaller
$bOldAddon = $this->oParams->Get('old_addon', false);
$bSampleData = ($this->oParams->Get('sample_data', 0) == 1);
self::DoLoadFiles($aSelectedModules, $sTargetDir, $aParamValues, $sTargetEnvironment, $bOldAddon,
$bSampleData);
self::DoLoadFiles(
$aSelectedModules,
$sTargetDir,
$aParamValues,
$sTargetEnvironment,
$bOldAddon,
$bSampleData
);
$aResult = array(
$aResult = [
'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':
@@ -371,51 +380,56 @@ class ApplicationInstaller
$sPreviousConfigFile = $this->oParams->Get('previous_configuration_file', '');
$sDataModelVersion = $this->oParams->Get('datamodel_version', '0.0.0');
$bOldAddon = $this->oParams->Get('old_addon', false);
$aSelectedModuleCodes = $this->oParams->Get('selected_modules', array());
$aSelectedExtensionCodes = $this->oParams->Get('selected_extensions', array());
$aSelectedModuleCodes = $this->oParams->Get('selected_modules', []);
$aSelectedExtensionCodes = $this->oParams->Get('selected_extensions', []);
$aParamValues = $this->oParams->GetParamForConfigArray();
self::DoCreateConfig($sTargetDir, $sPreviousConfigFile, $sTargetEnvironment, $sDataModelVersion,
$bOldAddon, $aSelectedModuleCodes, $aSelectedExtensionCodes, $aParamValues, $sInstallComment);
self::DoCreateConfig(
$sTargetDir,
$sPreviousConfigFile,
$sTargetEnvironment,
$sDataModelVersion,
$bOldAddon,
$aSelectedModuleCodes,
$aSelectedExtensionCodes,
$aParamValues,
$sInstallComment
);
$aResult = array(
$aResult = [
'status' => self::INFO,
'message' => 'Configuration file created',
'next-step' => '',
'next-step-label' => 'Completed',
'percentage-completed' => 100,
);
];
$this->ExitReadOnlyMode();
break;
default:
$aResult = array(
$aResult = [
'status' => self::ERROR,
'message' => '',
'next-step' => '',
'next-step-label' => "Unknown setup step '$sStep'.",
'percentage-completed' => 100,
);
];
break;
}
}
catch (Exception $e)
{
$aResult = array(
} catch (Exception $e) {
$aResult = [
'status' => self::ERROR,
'message' => $e->getMessage(),
'next-step' => '',
'next-step-label' => '',
'percentage-completed' => 100,
);
];
SetupLog::Error('An exception occurred: '.$e->getMessage().' at line '.$e->getLine().' in file '.$e->getFile());
$idx = 0;
// Log the call stack, but not the parameters since they may contain passwords or other sensitive data
SetupLog::Ok("Call stack:");
foreach ($e->getTrace() as $aTrace)
{
foreach ($e->getTrace() as $aTrace) {
$sLine = empty($aTrace['line']) ? "" : $aTrace['line'];
$sFile = empty($aTrace['file']) ? "" : $aTrace['file'];
$sClass = empty($aTrace['class']) ? "" : $aTrace['class'];
@@ -425,9 +439,7 @@ class ApplicationInstaller
SetupLog::Ok("#$idx $sFile($sLine): $sVerb(...)");
$idx++;
}
}
finally
{
} finally {
$fDuration = round(microtime(true) - $fStart, 2);
SetupLog::Info("##### STEP {$sStep} duration: {$fDuration}s");
}
@@ -437,13 +449,11 @@ class ApplicationInstaller
private function EnterReadOnlyMode()
{
if ($this->GetTargetEnv() != 'production')
{
if ($this->GetTargetEnv() != 'production') {
return;
}
if (SetupUtils::IsInReadOnlyMode())
{
if (SetupUtils::IsInReadOnlyMode()) {
return;
}
@@ -452,25 +462,21 @@ class ApplicationInstaller
private function ExitReadOnlyMode()
{
if ($this->GetTargetEnv() != 'production')
{
if ($this->GetTargetEnv() != 'production') {
return;
}
if (!SetupUtils::IsInReadOnlyMode())
{
if (!SetupUtils::IsInReadOnlyMode()) {
return;
}
SetupUtils::ExitReadOnlyMode();
}
protected static function DoCopy($aCopies)
{
$aReports = array();
foreach ($aCopies as $aCopy)
{
$aReports = [];
foreach ($aCopies as $aCopy) {
$sSource = $aCopy['source'];
$sDestination = APPROOT.$aCopy['destination'];
@@ -479,12 +485,9 @@ class ApplicationInstaller
SetupUtils::copydir($sSource, $sDestination);
$aReports[] = "'{$aCopy['source']}' to '{$aCopy['destination']}' (OK)";
}
if (count($aReports) > 0)
{
if (count($aReports) > 0) {
$sReport = "Copies: ".count($aReports).': '.implode('; ', $aReports);
}
else
{
} else {
$sReport = "No file copy";
}
return $sReport;
@@ -513,7 +516,6 @@ class ApplicationInstaller
$oBackup->CreateCompressedBackup($sTargetFile, $sSourceConfigFile);
}
/**
* @param array $aSelectedModules
* @param string $sSourceDir
@@ -542,28 +544,24 @@ class ApplicationInstaller
}
$sSourcePath = APPROOT.$sSourceDir;
$aDirsToScan = array($sSourcePath);
$aDirsToScan = [$sSourcePath];
$sExtensionsPath = APPROOT.$sExtensionDir;
if (is_dir($sExtensionsPath))
{
if (is_dir($sExtensionsPath)) {
// if the extensions dir exists, scan it for additional modules as well
$aDirsToScan[] = $sExtensionsPath;
}
$sExtraPath = APPROOT.'/data/'.$sEnvironment.'-modules/';
if (is_dir($sExtraPath))
{
if (is_dir($sExtraPath)) {
// if the extra dir exists, scan it for additional modules as well
$aDirsToScan[] = $sExtraPath;
}
$sTargetPath = APPROOT.$sTargetDir;
if (!is_dir($sSourcePath))
{
if (!is_dir($sSourcePath)) {
throw new Exception("Failed to find the source directory '$sSourcePath', please check the rights of the web server");
}
$bIsAlreadyInMaintenanceMode = SetupUtils::IsInMaintenanceMode();
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode)
{
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
$sConfigFilePath = utils::GetConfigFilePath($sEnvironment);
if (is_file($sConfigFilePath)) {
$oConfig = new Config($sConfigFilePath);
@@ -578,21 +576,15 @@ class ApplicationInstaller
SetupUtils::EnterMaintenanceMode($oConfig);
}
if (!is_dir($sTargetPath))
{
if (!mkdir($sTargetPath))
{
if (!is_dir($sTargetPath)) {
if (!mkdir($sTargetPath)) {
throw new Exception("Failed to create directory '$sTargetPath', please check the rights of the web server");
}
else
{
} else {
// adjust the rights if and only if the directory was just created
// owner:rwx user/group:rx
chmod($sTargetPath, 0755);
}
}
else if (substr($sTargetPath, 0, strlen(APPROOT)) == APPROOT)
{
} elseif (substr($sTargetPath, 0, strlen(APPROOT)) == APPROOT) {
// If the directory is under the root folder - as expected - let's clean-it before compiling
SetupUtils::tidydir($sTargetPath);
}
@@ -603,25 +595,21 @@ class ApplicationInstaller
$oFactory->LoadModule($oDictModule);
$sDeltaFile = APPROOT.'core/datamodel.core.xml';
if (file_exists($sDeltaFile))
{
if (file_exists($sDeltaFile)) {
$oCoreModule = new MFCoreModule('core', 'Core Module', $sDeltaFile);
$oFactory->LoadModule($oCoreModule);
}
$sDeltaFile = APPROOT.'application/datamodel.application.xml';
if (file_exists($sDeltaFile))
{
if (file_exists($sDeltaFile)) {
$oApplicationModule = new MFCoreModule('application', 'Application Module', $sDeltaFile);
$oFactory->LoadModule($oApplicationModule);
}
$aModules = $oFactory->FindModules();
foreach($aModules as $oModule)
{
foreach ($aModules as $oModule) {
$sModule = $oModule->GetName();
if (in_array($sModule, $aSelectedModules))
{
if (in_array($sModule, $aSelectedModules)) {
$oFactory->LoadModule($oModule);
}
}
@@ -629,8 +617,7 @@ class ApplicationInstaller
$oFactory->SaveToFile(APPROOT.'data/datamodel-'.$sEnvironment.'.xml');
$sDeltaFile = APPROOT.'data/'.$sEnvironment.'.delta.xml';
if (file_exists($sDeltaFile))
{
if (file_exists($sDeltaFile)) {
$oDelta = new MFDeltaModule($sDeltaFile);
$oFactory->LoadModule($oDelta);
$oFactory->SaveToFile(APPROOT.'data/datamodel-'.$sEnvironment.'-with-delta.xml');
@@ -648,8 +635,7 @@ class ApplicationInstaller
// Special case to patch a ugly patch in itop-config-mgmt
$sFileToPatch = $sTargetPath.'/itop-config-mgmt-1.0.0/model.itop-config-mgmt.php';
if (file_exists($sFileToPatch))
{
if (file_exists($sFileToPatch)) {
$sContent = file_get_contents($sFileToPatch);
$sContent = str_replace("require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", "//\n// The line below is no longer needed in iTop 2.0 -- patched by the setup program\n// require_once(APPROOT.'modules/itop-welcome-itil/model.itop-welcome-itil.php');", $sContent);
@@ -660,13 +646,11 @@ class ApplicationInstaller
// Set an "Instance UUID" identifying this machine based on a file located in the data directory
$sInstanceUUIDFile = APPROOT.'data/instance.txt';
SetupUtils::builddir(APPROOT.'data');
if (!file_exists($sInstanceUUIDFile))
{
if (!file_exists($sInstanceUUIDFile)) {
$sIntanceUUID = utils::CreateUUID('filesystem');
file_put_contents($sInstanceUUIDFile, $sIntanceUUID);
}
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode)
{
if (($sEnvironment == 'production') && !$bIsAlreadyInMaintenanceMode) {
SetupUtils::ExitMaintenanceMode();
}
}
@@ -698,12 +682,11 @@ class ApplicationInstaller
$oConfig = new Config();
$oConfig->UpdateFromParams($aParamValues, $sModulesDir);
if ($bOldAddon)
{
if ($bOldAddon) {
// Old version of the add-on for backward compatibility with pre-2.0 data models
$oConfig->SetAddons(array(
$oConfig->SetAddons([
'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php',
));
]);
}
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
@@ -717,39 +700,32 @@ class ApplicationInstaller
// priv_internalUser caused troubles because MySQL transforms table names to lower case under Windows
// This becomes an issue when moving your installation data to/from Windows
// Starting 2.0, all table names must be lowercase
if ($sMode != 'install')
{
if ($sMode != 'install') {
SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' into '{$sDBPrefix}priv_internaluser' (lowercase)");
// This command will have no effect under Windows...
// and it has been written in two steps so as to make it work under windows!
CMDBSource::SelectDB($sDBName);
try
{
try {
$sRepair = "RENAME TABLE `{$sDBPrefix}priv_internalUser` TO `{$sDBPrefix}priv_internaluser_other`, `{$sDBPrefix}priv_internaluser_other` TO `{$sDBPrefix}priv_internaluser`";
CMDBSource::Query($sRepair);
}
catch (Exception $e)
{
} catch (Exception $e) {
SetupLog::Info("Renaming '{$sDBPrefix}priv_internalUser' failed (already done in a previous upgrade?)");
}
// let's remove the records in priv_change which have no counterpart in priv_changeop
SetupLog::Info("Cleanup of '{$sDBPrefix}priv_change' to remove orphan records");
CMDBSource::SelectDB($sDBName);
try
{
try {
$sTotalCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change`";
$iTotalCount = (int)CMDBSource::QueryToScalar($sTotalCount);
SetupLog::Info("There is a total of $iTotalCount records in {$sDBPrefix}priv_change.");
$sOrphanCount = "SELECT COUNT(c.id) FROM `{$sDBPrefix}priv_change` AS c left join `{$sDBPrefix}priv_changeop` AS o ON c.id = o.changeid WHERE o.id IS NULL";
$iOrphanCount = (int)CMDBSource::QueryToScalar($sOrphanCount);
SetupLog::Info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0*$iOrphanCount)/$iTotalCount))."%)");
if ($iOrphanCount > 0)
{
SetupLog::Info("There are $iOrphanCount useless records in {$sDBPrefix}priv_change (".sprintf('%.2f', ((100.0 * $iOrphanCount) / $iTotalCount))."%)");
if ($iOrphanCount > 0) {
//N°3793
if ($iOrphanCount > 100000)
{
if ($iOrphanCount > 100000) {
SetupLog::Info("There are too much useless records ($iOrphanCount) in {$sDBPrefix}priv_change. Cleanup cannot be done during setup.");
} else {
SetupLog::Info("Removing the orphan records...");
@@ -757,14 +733,10 @@ class ApplicationInstaller
CMDBSource::Query($sCleanup);
SetupLog::Info("Cleanup completed successfully.");
}
}
else
{
} else {
SetupLog::Info("Ok, nothing to cleanup.");
}
}
catch (Exception $e)
{
} catch (Exception $e) {
SetupLog::Info("Cleanup of orphan records in `{$sDBPrefix}priv_change` failed: ".$e->getMessage());
}
@@ -775,15 +747,13 @@ class ApplicationInstaller
$aAvailableModules = $oProductionEnv->AnalyzeInstallation(MetaModel::GetConfig(), APPROOT.$sModulesDir);
$oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'BeforeDatabaseCreation');
if(!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode))
{
if (!$oProductionEnv->CreateDatabaseStructure(MetaModel::GetConfig(), $sMode)) {
throw new Exception("Failed to create/upgrade the database structure for environment '$sTargetEnvironment'");
}
// Set a DBProperty with a unique ID to identify this instance of iTop
$sUUID = DBProperty::GetProperty('database_uuid', '');
if ($sUUID === '')
{
if ($sUUID === '') {
$sUUID = utils::CreateUUID('database');
DBProperty::SetProperty('database_uuid', $sUUID, 'Installation/upgrade of '.ITOP_APPLICATION, 'Unique ID of this '.ITOP_APPLICATION.' Database');
}
@@ -792,12 +762,10 @@ class ApplicationInstaller
// Let's initialize the field with 'interactive' for all records were it's null
// Then check if some records should hold a different value, based on a pattern matching in the userinfo field
CMDBSource::SelectDB($sDBName);
try
{
try {
$sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_change` WHERE `origin` IS NULL";
$iCount = (int)CMDBSource::QueryToScalar($sCount);
if ($iCount > 0)
{
if ($iCount > 0) {
SetupLog::Info("Initializing '{$sDBPrefix}priv_change.origin' ($iCount records to update)");
// By default all uninitialized values are considered as 'interactive'
@@ -812,13 +780,11 @@ class ApplicationInstaller
$sInit = "UPDATE `{$sDBPrefix}priv_change` SET `origin` = 'csv-interactive' WHERE `userinfo` LIKE '%(CSV)' AND origin = 'interactive'";
CMDBSource::Query($sInit);
// Syncho data sources were identified by the comment at the end
// Unfortunately the comment is localized, so we have to search for all possible patterns
$sCurrentLanguage = Dict::GetUserLanguage();
$aSuffixes = array();
foreach(array_keys(Dict::GetLanguages()) as $sLangCode)
{
$aSuffixes = [];
foreach (array_keys(Dict::GetLanguages()) as $sLangCode) {
Dict::SetUserLanguage($sLangCode);
$sSuffix = CMDBSource::Quote('%'.Dict::S('Core:SyncDataExchangeComment'));
$aSuffixes[$sSuffix] = true;
@@ -830,26 +796,20 @@ class ApplicationInstaller
CMDBSource::Query($sInit);
SetupLog::Info("Initialization of '{$sDBPrefix}priv_change.origin' completed.");
}
else
{
} else {
SetupLog::Info("'{$sDBPrefix}priv_change.origin' already initialized, nothing to do.");
}
}
catch (Exception $e)
{
} catch (Exception $e) {
SetupLog::Error("Initializing '{$sDBPrefix}priv_change.origin' failed: ".$e->getMessage());
}
// priv_async_task now has a 'status' field to distinguish between the various statuses rather than just relying on the date columns
// Let's initialize the field with 'planned' or 'error' for all records were it's null
CMDBSource::SelectDB($sDBName);
try
{
try {
$sCount = "SELECT COUNT(*) FROM `{$sDBPrefix}priv_async_task` WHERE `status` IS NULL";
$iCount = (int)CMDBSource::QueryToScalar($sCount);
if ($iCount > 0)
{
if ($iCount > 0) {
SetupLog::Info("Initializing '{$sDBPrefix}priv_async_task.status' ($iCount records to update)");
$sInit = "UPDATE `{$sDBPrefix}priv_async_task` SET `status` = 'planned' WHERE (`status` IS NULL) AND (`started` IS NULL)";
@@ -859,14 +819,10 @@ class ApplicationInstaller
CMDBSource::Query($sInit);
SetupLog::Info("Initialization of '{$sDBPrefix}priv_async_task.status' completed.");
}
else
{
} else {
SetupLog::Info("'{$sDBPrefix}priv_async_task.status' already initialized, nothing to do.");
}
}
catch (Exception $e)
{
} catch (Exception $e) {
SetupLog::Error("Initializing '{$sDBPrefix}priv_async_task.status' failed: ".$e->getMessage());
}
@@ -886,10 +842,15 @@ class ApplicationInstaller
}
protected static function AfterDBCreate(
$sModulesDir, $aParamValues, $sAdminUser, $sAdminPwd, $sAdminLanguage, $aSelectedModules, $sTargetEnvironment,
$sModulesDir,
$aParamValues,
$sAdminUser,
$sAdminPwd,
$sAdminLanguage,
$aSelectedModules,
$sTargetEnvironment,
$bOldAddon
)
{
) {
/**
* @since 3.2.0 move the ContextTag init at the very beginning of the method
* @noinspection PhpUnusedLocalVariableInspection
@@ -901,12 +862,11 @@ class ApplicationInstaller
$oConfig = new Config();
$oConfig->UpdateFromParams($aParamValues, $sModulesDir);
if ($bOldAddon)
{
if ($bOldAddon) {
// Old version of the add-on for backward compatibility with pre-2.0 data models
$oConfig->SetAddons(array(
$oConfig->SetAddons([
'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php',
));
]);
}
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
@@ -921,14 +881,10 @@ class ApplicationInstaller
$oProductionEnv->UpdatePredefinedObjects();
if($sMode == 'install')
{
if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sAdminLanguage))
{
if ($sMode == 'install') {
if (!self::CreateAdminAccount(MetaModel::GetConfig(), $sAdminUser, $sAdminPwd, $sAdminLanguage)) {
throw(new Exception("Failed to create the administrator account '$sAdminUser'"));
}
else
{
} else {
SetupLog::Info("Administrator account '$sAdminUser' created.");
}
}
@@ -946,21 +902,21 @@ class ApplicationInstaller
{
SetupLog::Info('CreateAdminAccount');
if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage))
{
if (UserRights::CreateAdministrator($sAdminUser, $sAdminPwd, $sLanguage)) {
return true;
}
else
{
} else {
return false;
}
}
protected static function DoLoadFiles(
$aSelectedModules, $sModulesDir, $aParamValues, $sTargetEnvironment = 'production', $bOldAddon = false,
$aSelectedModules,
$sModulesDir,
$aParamValues,
$sTargetEnvironment = 'production',
$bOldAddon = false,
$bSampleData = false
)
{
) {
/**
* @since 3.2.0 move the ContextTag init at the very beginning of the method
* @noinspection PhpUnusedLocalVariableInspection
@@ -970,19 +926,17 @@ class ApplicationInstaller
$oConfig = new Config();
$oConfig->UpdateFromParams($aParamValues, $sModulesDir);
if ($bOldAddon)
{
if ($bOldAddon) {
// Old version of the add-on for backward compatibility with pre-2.0 data models
$oConfig->SetAddons(array(
$oConfig->SetAddons([
'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php',
));
]);
}
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
//Load the MetaModel if needed (asynchronous mode)
if (!self::$bMetaModelStarted)
{
if (!self::$bMetaModelStarted) {
$oProductionEnv->InitDataModel($oConfig, false); // load data model and connect to the database
self::$bMetaModelStarted = true; // No need to reload the final MetaModel in case the installer runs synchronously
@@ -991,7 +945,7 @@ class ApplicationInstaller
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, APPROOT.$sModulesDir);
$oProductionEnv->LoadData($aAvailableModules, $aSelectedModules, $bSampleData);
// Perform after dbload setup tasks here
// Perform after dbload setup tasks here
//
$oProductionEnv->CallInstallerHandlers($aAvailableModules, $aSelectedModules, 'AfterDataLoad');
}
@@ -1013,8 +967,15 @@ class ApplicationInstaller
* @throws \Exception
*/
protected static function DoCreateConfig(
$sModulesDir, $sPreviousConfigFile, $sTargetEnvironment, $sDataModelVersion, $bOldAddon, $aSelectedModuleCodes,
$aSelectedExtensionCodes, $aParamValues, $sInstallComment = null
$sModulesDir,
$sPreviousConfigFile,
$sTargetEnvironment,
$sDataModelVersion,
$bOldAddon,
$aSelectedModuleCodes,
$aSelectedExtensionCodes,
$aParamValues,
$sInstallComment = null
) {
/**
* @since 3.2.0 move the ContextTag init at the very beginning of the method
@@ -1026,22 +987,16 @@ class ApplicationInstaller
$sMode = $aParamValues['mode'];
$bPreserveModuleSettings = false;
if ($sMode == 'upgrade')
{
try
{
if ($sMode == 'upgrade') {
try {
$oOldConfig = new Config($sPreviousConfigFile);
$oConfig = clone($oOldConfig);
$bPreserveModuleSettings = true;
}
catch(Exception $e)
{
} catch (Exception $e) {
// In case the previous configuration is corrupted... start with a blank new one
$oConfig = new Config();
}
}
else
{
} else {
$oConfig = new Config();
// To preserve backward compatibility while upgrading to 2.0.3 (when tracking_level_linked_set_default has been introduced)
// the default value on upgrade differs from the default value at first install
@@ -1051,26 +1006,23 @@ class ApplicationInstaller
$oConfig->Set('access_mode', ACCESS_FULL);
// Final config update: add the modules
$oConfig->UpdateFromParams($aParamValues, $sModulesDir, $bPreserveModuleSettings);
if ($bOldAddon)
{
if ($bOldAddon) {
// Old version of the add-on for backward compatibility with pre-2.0 data models
$oConfig->SetAddons(array(
$oConfig->SetAddons([
'user rights' => 'addons/userrights/userrightsprofile.db.class.inc.php',
));
]);
}
// Record which modules are installed...
$oProductionEnv = new RunTimeEnvironment($sTargetEnvironment);
$oProductionEnv->InitDataModel($oConfig, true); // load data model and connect to the database
if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sInstallComment))
{
if (!$oProductionEnv->RecordInstallation($oConfig, $sDataModelVersion, $aSelectedModuleCodes, $aSelectedExtensionCodes, $sInstallComment)) {
throw new Exception("Failed to record the installation information");
}
// Make sure the root configuration directory exists
if (!file_exists(APPCONF))
{
if (!file_exists(APPCONF)) {
mkdir(APPCONF);
chmod(APPCONF, 0770); // RWX for owner and group, nothing for others
SetupLog::Info("Created configuration directory: ".APPCONF);

View File

@@ -15,8 +15,7 @@ function AppUpgradeCopyFiles($sSourceDir)
CoreUpdater::CopyDir($sSourceDir, APPROOT);
// Update Core update files
$sSource = realpath($sSourceDir.'/datamodels/2.x/itop-core-update');
if ($sSource !== false)
{
if ($sSource !== false) {
CoreUpdater::CopyDir($sSource, APPROOT.'env-production/itop-core-update');
}
}
}

View File

@@ -1,9 +1,10 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -71,12 +72,10 @@ interface BackupArchive
public function getFromName($name, $length = 0, $flags = null);
}
class BackupException extends Exception
{
}
class DBBackup
{
/**
@@ -84,7 +83,7 @@ class DBBackup
*
* @since 2.5.0 see N°1001
*/
const MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS = '5.5.33';
public const MYSQL_VERSION_WITH_UTF8MB4_IN_PROGRAMS = '5.5.33';
// To be overriden depending on the expected usages
protected function LogInfo($sMsg)
@@ -121,8 +120,7 @@ class DBBackup
*/
public function __construct($oConfig = null)
{
if (is_null($oConfig))
{
if (is_null($oConfig)) {
// Defaulting to the current config
$oConfig = MetaModel::GetConfig();
}
@@ -135,7 +133,6 @@ class DBBackup
$this->sDBSubName = $oConfig->get('db_subname');
}
/**
* Create a normalized backup name, depending on the current date/time and Database
*
@@ -225,26 +222,23 @@ class DBBackup
*
* @param string $sSourceConfigFile
* @param string $sTmpFolder
* @param bool $bSkipSQLDumpForTesting
* @param bool $bSkipSQLDumpForTesting
*
* @return array list of files to archive
* @throws \Exception
*/
protected function PrepareFilesToBackup($sSourceConfigFile, $sTmpFolder, $bSkipSQLDumpForTesting = false)
{
$aRet = array();
if (is_dir($sTmpFolder))
{
$aRet = [];
if (is_dir($sTmpFolder)) {
SetupUtils::rrmdir($sTmpFolder);
}
$this->LogInfo("backup: creating tmp dir '$sTmpFolder'");
@mkdir($sTmpFolder, 0777, true);
if (is_null($sSourceConfigFile))
{
if (is_null($sSourceConfigFile)) {
$sSourceConfigFile = MetaModel::GetConfig()->GetLoadedFile();
}
if (!empty($sSourceConfigFile))
{
if (!empty($sSourceConfigFile)) {
$sFile = $sTmpFolder.'/config-itop.php';
$this->LogInfo("backup: adding resource '$sSourceConfigFile'");
@copy($sSourceConfigFile, $sFile); // During unattended install config file may be absent
@@ -252,16 +246,14 @@ class DBBackup
}
$sDeltaFile = APPROOT.'data/'.utils::GetCurrentEnvironment().'.delta.xml';
if (file_exists($sDeltaFile))
{
if (file_exists($sDeltaFile)) {
$sFile = $sTmpFolder.'/delta.xml';
$this->LogInfo("backup: adding resource '$sDeltaFile'");
copy($sDeltaFile, $sFile);
$aRet[] = $sFile;
}
$sExtraDir = APPROOT.'data/'.utils::GetCurrentEnvironment().'-modules/';
if (is_dir($sExtraDir))
{
if (is_dir($sExtraDir)) {
$sModules = utils::GetCurrentEnvironment().'-modules';
$sFile = $sTmpFolder.'/'.$sModules;
$this->LogInfo("backup: adding resource '$sExtraDir'");
@@ -270,38 +262,31 @@ class DBBackup
}
$aExtraFiles = [];
if (MetaModel::GetConfig() !== null) // During unattended install config file may be absent
{
if (MetaModel::GetConfig() !== null) { // During unattended install config file may be absent
$aExtraFiles = MetaModel::GetModuleSetting('itop-backup', 'extra_files', []);
}
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iBackupExtraFilesExtension::class) as $sExtensionClass)
{
foreach (InterfaceDiscovery::GetInstance()->FindItopClasses(iBackupExtraFilesExtension::class) as $sExtensionClass) {
/** @var iBackupExtraFilesExtension $oExtensionInstance */
$oExtensionInstance = new $sExtensionClass();
$aExtraFiles = array_merge($aExtraFiles, $oExtensionInstance->GetExtraFilesRelPaths());
}
foreach($aExtraFiles as $sExtraFileOrDir)
{
if(!file_exists(APPROOT.'/'.$sExtraFileOrDir)) {
foreach ($aExtraFiles as $sExtraFileOrDir) {
if (!file_exists(APPROOT.'/'.$sExtraFileOrDir)) {
continue; // Ignore non-existing files
}
$sExtraFullPath = utils::RealPath(APPROOT.'/'.$sExtraFileOrDir, APPROOT);
if ($sExtraFullPath === false)
{
if ($sExtraFullPath === false) {
throw new Exception("Backup: Aborting, resource '$sExtraFileOrDir'. Considered as UNSAFE because not inside the iTop directory.");
}
if (is_dir($sExtraFullPath))
{
if (is_dir($sExtraFullPath)) {
$sFile = $sTmpFolder.'/'.$sExtraFileOrDir;
$this->LogInfo("backup: adding directory '$sExtraFileOrDir'");
SetupUtils::copydir($sExtraFullPath, $sFile);
$aRet[] = $sFile;
}
elseif (file_exists($sExtraFullPath))
{
} elseif (file_exists($sExtraFullPath)) {
$sFile = $sTmpFolder.'/'.$sExtraFileOrDir;
$this->LogInfo("backup: adding file '$sExtraFileOrDir'");
@mkdir(dirname($sFile), 0755, true);
@@ -309,8 +294,7 @@ class DBBackup
$aRet[] = $sFile;
}
}
if (!$bSkipSQLDumpForTesting)
{
if (!$bSkipSQLDumpForTesting) {
$sDataFile = $sTmpFolder.'/itop-dump.sql';
$this->DoBackup($sDataFile);
$aRet[] = $sDataFile;
@@ -344,19 +328,16 @@ class DBBackup
$this->DBConnect();
$sTables = '';
if ($this->sDBSubName != '')
{
if ($this->sDBSubName != '') {
// This instance of iTop uses a prefix for the tables, so there may be other tables in the database
// Let's explicitely list all the tables and views to dump
$aTables = $this->EnumerateTables();
if (count($aTables) == 0)
{
if (count($aTables) == 0) {
// No table has been found with the given prefix
throw new BackupException("No table has been found with the given prefix");
}
$aEscapedTables = array();
foreach ($aTables as $sTable)
{
$aEscapedTables = [];
foreach ($aTables as $sTable) {
$aEscapedTables[] = self::EscapeShellArg($sTable);
}
$sTables = implode(' ', $aEscapedTables);
@@ -391,39 +372,32 @@ EOF;
file_put_contents($sMySQLDumpCnfFile, $sMySQLDumpCnf, LOCK_EX);
// Note: opt implicitly sets lock-tables... which cancels the benefit of single-transaction!
// skip-lock-tables compensates and allows for writes during a backup
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=" . $sMysqldumpCharset . " --add-drop-database --single-transaction --host=$sHost $sPortAndTransportOptions --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=" . $sMysqldumpCharset . " --add-drop-database --single-transaction --host=$sHost $sPortAndTransportOptions --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
// skip-lock-tables compensates and allows for writes during a backup
$sCommand = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortAndTransportOptions --user=$sUser $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables 2>&1";
$sCommandDisplay = "$sMySQLDump --defaults-extra-file=\"$sMySQLDumpCnfFile\" --opt --skip-lock-tables --default-character-set=".$sMysqldumpCharset." --add-drop-database --single-transaction --host=$sHost $sPortAndTransportOptions --user=xxxxx $sTlsOptions --result-file=$sTmpFileName $sDBName $sTables";
// Now run the command for real
$this->LogInfo("backup: generate data file with command: $sCommandDisplay");
$aOutput = array();
$aOutput = [];
$iRetCode = 0;
exec($sCommand, $aOutput, $iRetCode);
@unlink($sMySQLDumpCnfFile);
foreach ($aOutput as $sLine)
{
foreach ($aOutput as $sLine) {
$this->LogInfo("mysqldump said: $sLine");
}
if ($iRetCode != 0)
{
if ($iRetCode != 0) {
// Cleanup residual output (Happens with Error 2020: Got packet bigger than 'maxallowedpacket' bytes...)
if (file_exists($sBackupFileName))
{
if (file_exists($sBackupFileName)) {
unlink($sBackupFileName);
}
$this->LogError("Failed to execute: $sCommandDisplay. The command returned:$iRetCode");
foreach ($aOutput as $sLine)
{
foreach ($aOutput as $sLine) {
$this->LogError("mysqldump said: $sLine");
}
if (count($aOutput) == 1)
{
if (count($aOutput) == 1) {
$sMoreInfo = trim($aOutput[0]);
}
else
{
} else {
$sMoreInfo = "Check the log files 'log/setup.log' or 'log/error.log' for more information.";
}
throw new BackupException("Failed to execute mysqldump: ".$sMoreInfo);
@@ -439,8 +413,7 @@ EOF;
*/
public function DownloadBackup($sFile)
{
if (!file_exists($sFile))
{
if (!file_exists($sFile)) {
throw new InvalidParameterException('Invalid file path');
}
@@ -473,25 +446,27 @@ EOF;
$sTlsEnabled = $oConfig->Get('db_tls.enabled');
$sTlsCA = $oConfig->Get('db_tls.ca');
try
{
$oMysqli = CMDBSource::GetMysqliInstance($sServer, $sUser, $sPwd, $sSource, $sTlsEnabled, $sTlsCA,
false);
try {
$oMysqli = CMDBSource::GetMysqliInstance(
$sServer,
$sUser,
$sPwd,
$sSource,
$sTlsEnabled,
$sTlsCA,
false
);
if ($oMysqli->connect_errno)
{
if ($oMysqli->connect_errno) {
$sHost = is_null($this->iDBPort) ? $this->sDBHost : $this->sDBHost.' on port '.$this->iDBPort;
throw new MySQLException('Could not connect to the DB server '.$oMysqli->connect_errno.' (mysql errno: '.$oMysqli->connect_error, array('host' => $sHost, 'user' => $sUser));
}
if (!$oMysqli->select_db($this->sDBName))
{
throw new MySQLException('Could not connect to the DB server '.$oMysqli->connect_errno.' (mysql errno: '.$oMysqli->connect_error, ['host' => $sHost, 'user' => $sUser]);
}
if (!$oMysqli->select_db($this->sDBName)) {
throw new BackupException("The database '$this->sDBName' does not seem to exist");
}
return $oMysqli;
}
catch (MySQLException $e)
{
} catch (MySQLException $e) {
throw new BackupException($e->getMessage());
}
}
@@ -504,28 +479,22 @@ EOF;
protected function EnumerateTables()
{
$oMysqli = $this->DBConnect();
if ($this->sDBSubName != '')
{
if ($this->sDBSubName != '') {
$oResult = $oMysqli->query("SHOW TABLES LIKE '{$this->sDBSubName}%'");
}
else
{
} else {
$oResult = $oMysqli->query("SHOW TABLES");
}
if (!$oResult)
{
if (!$oResult) {
throw new BackupException("Failed to execute the SHOW TABLES query: ".$oMysqli->error);
}
$aTables = array();
while ($aRow = $oResult->fetch_row())
{
$aTables = [];
while ($aRow = $oResult->fetch_row()) {
$aTables[] = $aRow[0];
}
return $aTables;
}
/**
* @param Config $oConfig
*
@@ -541,25 +510,18 @@ EOF;
public static function GetMysqlCliTlsOptions($oConfig)
{
$bDbTlsEnabled = $oConfig->Get('db_tls.enabled');
if (!$bDbTlsEnabled)
{
if (!$bDbTlsEnabled) {
return '';
}
$sTlsOptions = '';
// Mysql 5.7.11 and upper deprecated --ssl and uses --ssl-mode instead
if (CMDBSource::IsSslModeDBVersion())
{
if(empty($oConfig->Get('db_tls.ca')))
{
if (CMDBSource::IsSslModeDBVersion()) {
if (empty($oConfig->Get('db_tls.ca'))) {
$sTlsOptions .= ' --ssl-mode=REQUIRED';
}
else
{
} else {
$sTlsOptions .= ' --ssl-mode=VERIFY_CA';
}
}
else
{
} else {
$sTlsOptions .= ' --ssl';
}
@@ -582,8 +544,7 @@ EOF;
*/
private static function GetMysqliCliSingleOption($sCliArgName, $sData)
{
if (empty($sData))
{
if (empty($sData)) {
return '';
}
@@ -613,7 +574,7 @@ EOF;
$sPortOption = self::GetMysqliCliSingleOption('port', $iPort);
$sTransportOptions = ' --protocol=tcp';
return $sPortOption . $sTransportOptions;
return $sPortOption.$sTransportOptions;
}
if (is_null($iPort)) {
@@ -632,8 +593,7 @@ EOF;
{
if (empty($sMySQLBinDir)) {
$sMySQLCommand = $sCmd;
}
else {
} else {
$sMySQLBinDir = escapeshellcmd($sMySQLBinDir);
$sMySQLCommand = '"'.$sMySQLBinDir.'/$sCmd"';
if (!file_exists($sMySQLCommand)) {
@@ -666,15 +626,12 @@ class TarGzArchive implements BackupArchive
{
// remove leading and tailing /
$sFile = trim($sFile, "/ \t\n\r\0\x0B");
if ($this->aFiles === null)
{
if ($this->aFiles === null) {
// Initial load
$this->buildFileList();
}
foreach ($this->aFiles as $aArchFile)
{
if ($aArchFile['filename'] == $sFile)
{
foreach ($this->aFiles as $aArchFile) {
if ($aArchFile['filename'] == $sFile) {
return true;
}
}
@@ -691,15 +648,12 @@ class TarGzArchive implements BackupArchive
{
// remove leading and tailing /
$sDirectory = trim($sDirectory, "/ \t\n\r\0\x0B");
if ($this->aFiles === null)
{
if ($this->aFiles === null) {
// Initial load
$this->buildFileList();
}
foreach ($this->aFiles as $aArchFile)
{
if (($aArchFile['typeflag'] == 5) && ($aArchFile['filename'] == $sDirectory))
{
foreach ($this->aFiles as $aArchFile) {
if (($aArchFile['typeflag'] == 5) && ($aArchFile['filename'] == $sDirectory)) {
return true;
}
}
@@ -764,4 +718,3 @@ class TarGzArchive implements BackupArchive
$this->aFiles = $this->oArchive->listContent();
}
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (c) 2010-2024 Combodo SAS
*
@@ -19,30 +20,34 @@
*
*/
/**
* Allow the setup page to load and perform its checks (including the check about the required extensions)
*/
if (!class_exists('DOMDocument'))
{
if (!class_exists('DOMDocument')) {
/**
* Class DOMDocument
*/
class DOMDocument {
function __construct(){throw new Exception('The dom extension is not enabled');}
class DOMDocument
{
public function __construct()
{
throw new Exception('The dom extension is not enabled');
}
}
}
/**
* Allow the setup page to load and perform its checks (including the check about the required extensions)
*/
if (!class_exists('DOMElement'))
{
if (!class_exists('DOMElement')) {
/**
* Class DOMElement
*/
class DOMElement {
function __construct(){throw new Exception('The dom extension is not enabled');}
class DOMElement
{
public function __construct()
{
throw new Exception('The dom extension is not enabled');
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -40,7 +41,6 @@ $oP = new SetupPage('iTop email test utility');
// So we're adding this http header to reduce CSRF exposure...
$oP->add_http_headers('DENY');
/**
* Helper to check server setting required to send an email
*/
@@ -48,85 +48,64 @@ function CheckEmailSetting($oP)
{
$bRet = true;
if (function_exists('php_ini_loaded_file')) // PHP >= 5.2.4
{
if (function_exists('php_ini_loaded_file')) { // PHP >= 5.2.4
$sPhpIniFile = php_ini_loaded_file();
}
else
{
} else {
$sPhpIniFile = 'php.ini';
}
$sTransport = MetaModel::GetConfig()->Get('email_transport');
switch($sTransport)
{
switch ($sTransport) {
case 'PHPMail':
$oP->info("iTop is configured to use PHP's <a style=\"background:transparent;padding:0;color:#000;text-decoration:underline;\" href=\"http://www.php.net/manual/en/function.mail.php\" target=\"_blank\">mail</a> function to send emails.");
$bIsWindows = (array_key_exists('WINDIR', $_SERVER) || array_key_exists('windir', $_SERVER));
if ($bIsWindows)
{
$sSmtpServer = ini_get('SMTP');
if (empty($sSmtpServer))
{
$oP->error("The SMTP server is not defined. Please add the 'SMTP' directive into $sPhpIniFile");
$bRet = false;
$oP->info("iTop is configured to use PHP's <a style=\"background:transparent;padding:0;color:#000;text-decoration:underline;\" href=\"http://www.php.net/manual/en/function.mail.php\" target=\"_blank\">mail</a> function to send emails.");
$bIsWindows = (array_key_exists('WINDIR', $_SERVER) || array_key_exists('windir', $_SERVER));
if ($bIsWindows) {
$sSmtpServer = ini_get('SMTP');
if (empty($sSmtpServer)) {
$oP->error("The SMTP server is not defined. Please add the 'SMTP' directive into $sPhpIniFile");
$bRet = false;
} elseif (strcasecmp($sSmtpServer, 'localhost') == 0) {
$oP->warning("Your SMTP server is configured to 'localhost'. You might want to set or change the 'SMTP' directive into $sPhpIniFile");
} else {
$oP->info("Your SMTP server: <strong>$sSmtpServer</strong>. To change this value, modify the 'SMTP' directive into $sPhpIniFile");
}
$iSmtpPort = (int) ini_get('smtp_port');
if (empty($iSmtpPort)) {
$oP->info("The SMTP port is not defined. Please add the 'smtp_port' directive into $sPhpIniFile");
$bRet = false;
} elseif ($iSmtpPort == 25) {
$oP->info("Your SMTP port is configured to the default value: 25. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
} else {
$oP->info("Your SMTP port is configured to $iSmtpPort. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
}
} else {
// Not a windows system
$sSendMail = ini_get('sendmail_path');
if (empty($sSendMail)) {
$oP->error("The command to send mail is not defined. Please add the 'sendmail_path' directive into $sPhpIniFile. A recommended setting is <em>sendmail_path=sendmail -t -i</em>");
$bRet = false;
} else {
$oP->info("The command to send mail is: <strong>$sSendMail</strong>. To change this value, modify the 'sendmail_path' directive into $sPhpIniFile");
}
}
else if (strcasecmp($sSmtpServer, 'localhost') == 0)
{
$oP->warning("Your SMTP server is configured to 'localhost'. You might want to set or change the 'SMTP' directive into $sPhpIniFile");
}
else
{
$oP->info("Your SMTP server: <strong>$sSmtpServer</strong>. To change this value, modify the 'SMTP' directive into $sPhpIniFile");
}
$iSmtpPort = (int) ini_get('smtp_port');
if (empty($iSmtpPort))
{
$oP->info("The SMTP port is not defined. Please add the 'smtp_port' directive into $sPhpIniFile");
$bRet = false;
}
else if ($iSmtpPort == 25)
{
$oP->info("Your SMTP port is configured to the default value: 25. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
}
else
{
$oP->info("Your SMTP port is configured to $iSmtpPort. You might want to set or change the 'smtp_port' directive into $sPhpIniFile");
}
}
else
{
// Not a windows system
$sSendMail = ini_get('sendmail_path');
if (empty($sSendMail))
{
$oP->error("The command to send mail is not defined. Please add the 'sendmail_path' directive into $sPhpIniFile. A recommended setting is <em>sendmail_path=sendmail -t -i</em>");
$bRet = false;
}
else
{
$oP->info("The command to send mail is: <strong>$sSendMail</strong>. To change this value, modify the 'sendmail_path' directive into $sPhpIniFile");
}
}
break;
break;
case 'SMTP':
$oP->info("iTop is configured to use the <b>$sTransport</b> transport.");
$sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host');
$sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port');
$sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption');
$sDisplayEncryption = empty($sEncryption) ? '<em>no encryption</em> ' : $sEncryption;
$sUserName = MetaModel::GetConfig()->Get('email_transport_smtp.username');
$sDisplayUserName = empty($sUserName) ? '<em>no user</em> ' : $sUserName;
$sPassword = MetaModel::GetConfig()->Get('email_transport_smtp.password');
$sDisplayPassword = empty($sPassword) ? '<em>no password</em> ' : str_repeat('*', strlen($sPassword));
$oP->info("SMTP configuration (from config-itop.php): host: $sHost, port: $sPort, user: $sDisplayUserName, password: $sDisplayPassword, encryption: $sDisplayEncryption.");
if (($sHost == 'localhost') && ($sPort == '25') && ($sUserName == '') && ($sPassword == '') )
{
$oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().").");
}
break;
$oP->info("iTop is configured to use the <b>$sTransport</b> transport.");
$sHost = MetaModel::GetConfig()->Get('email_transport_smtp.host');
$sPort = MetaModel::GetConfig()->Get('email_transport_smtp.port');
$sEncryption = MetaModel::GetConfig()->Get('email_transport_smtp.encryption');
$sDisplayEncryption = empty($sEncryption) ? '<em>no encryption</em> ' : $sEncryption;
$sUserName = MetaModel::GetConfig()->Get('email_transport_smtp.username');
$sDisplayUserName = empty($sUserName) ? '<em>no user</em> ' : $sUserName;
$sPassword = MetaModel::GetConfig()->Get('email_transport_smtp.password');
$sDisplayPassword = empty($sPassword) ? '<em>no password</em> ' : str_repeat('*', strlen($sPassword));
$oP->info("SMTP configuration (from config-itop.php): host: $sHost, port: $sPort, user: $sDisplayUserName, password: $sDisplayPassword, encryption: $sDisplayEncryption.");
if (($sHost == 'localhost') && ($sPort == '25') && ($sUserName == '') && ($sPassword == '')) {
$oP->warning("The default settings may not be suitable for your environment. You may want to adjust these values by editing iTop's configuration file (".utils::GetConfigFilePathRelative().").");
}
break;
case 'SMTP_OAuth':
$oP->info("iTop is configured to use the <b>$sTransport</b> transport.");
@@ -153,63 +132,58 @@ function CheckEmailSetting($oP)
}
break;
case 'Null':
$oP->warning("iTop is configured to use the <b>Null</b> transport: emails sending will have no effect.");
$bRet = false;
break;
$oP->warning("iTop is configured to use the <b>Null</b> transport: emails sending will have no effect.");
$bRet = false;
break;
case 'LogFile':
$oP->warning("iTop is configured to use the <b>LogFile</b> transport: emails will <em>not</em> be sent but logged to the file: 'log/mail.log'.");
$bRet = true;
break;
$oP->warning("iTop is configured to use the <b>LogFile</b> transport: emails will <em>not</em> be sent but logged to the file: 'log/mail.log'.");
$bRet = true;
break;
default:
$oP->error("Unknown transport '$sTransport' configured.");
$bRet = false;
$oP->error("Unknown transport '$sTransport' configured.");
$bRet = false;
}
if ($bRet)
{
if ($bRet) {
$oP->ok("PHP settings are ok to proceed with a test of the email");
}
$bConfigAsync = MetaModel::GetConfig()->Get('email_asynchronous');
if ($bConfigAsync)
{
if ($bConfigAsync) {
$oP->warning("iTop is configured to send emails <em>asynchronously</em>. Make sure that cron.php is scheduled to run in the background, otherwise regular emails will <em>not</em> be sent. For the purpose of this test, the email will be sent <em>synchronously</em>.");
}
return $bRet;
}
/**
* Display the form for the first step of the test wizard
* which consists in a basic check of the configuration and display of a form for testing
*/
*/
function DisplayStep1(SetupPage $oP)
{
$sNextOperation = 'step2';
$oP->add("<h1>iTop email test</h1>\n");
$oP->add("<h2>Checking prerequisites</h2>\n");
if (CheckEmailSetting($oP))
{
if (CheckEmailSetting($oP)) {
$sRedStar = '<span class="hilite">*</span>';
$oP->add("<h2>Try to send an email</h2>\n");
$oP->add("<form method=\"post\" onSubmit=\"return DoSubmit('Sending an email...', 10)\">\n");
// Form goes here
$oP->add("<fieldset><legend>Test recipient</legend>\n");
$aForm = array();
$aForm[] = array(
$aForm = [];
$aForm[] = [
'label' => "To$sRedStar:",
'input' => "<input id=\"to\" type=\"text\" name=\"to\" value=\"\">",
'help' => ' email address (e.g. john.foo@worldcompany.com)',
);
];
$sDefaultFrom = MetaModel::GetConfig()->Get('email_transport_smtp.username');
$aForm[] = array(
$aForm[] = [
'label' => "From:",
'input' => "<input id=\"from\" type=\"text\" name=\"from\" value=\"$sDefaultFrom\">",
'help' => ' defaults to the configuration param "email_default_sender_address" or "To" field.',
);
];
$oP->form($aForm);
$oP->add("</fieldset>\n");
$oP->add("<input type=\"hidden\" name=\"operation\" value=\"$sNextOperation\">\n");
@@ -225,7 +199,7 @@ function DisplayStep1(SetupPage $oP)
/**
* Display the form for the second step of the configuration wizard
* which consists in sending an email, which maybe a problem under Windows
*/
*/
function DisplayStep2(SetupPage $oP, $sFrom, $sTo)
{
//$sNextOperation = 'step3';
@@ -240,16 +214,12 @@ function DisplayStep2(SetupPage $oP, $sFrom, $sTo)
$oEmail->SetSubject("Test iTop");
$oEmail->SetBody("<p>Hello,</p><p>The email function is now working fine.</p><p>You may now be able to use the notification function.</p><p>iTop</p>");
$iRes = $oEmail->Send($aIssues, true /* force synchronous exec */);
switch ($iRes)
{
switch ($iRes) {
case EMAIL_SEND_OK:
$sTransport = MetaModel::GetConfig()->Get('email_transport');
if ($sTransport == 'LogFile')
{
if ($sTransport == 'LogFile') {
$oP->ok("The email has been logged into the file ".APPROOT."/log/mail.log.");
}
else
{
} else {
$oP->ok("The email has been sent, check your inbox for the incoming mail...");
}
$oP->add("<button onClick=\"window.history.back();\"><< Back</button>\n");
@@ -261,8 +231,7 @@ function DisplayStep2(SetupPage $oP, $sFrom, $sTo)
break;
case EMAIL_SEND_ERROR:
foreach ($aIssues as $sError)
{
foreach ($aIssues as $sError) {
$oP->error(htmlentities($sError, ENT_QUOTES, 'utf-8'));
}
$oP->add("<button onClick=\"window.history.back();\"><< Back</button>\n");
@@ -279,15 +248,12 @@ function DisplayStep2(SetupPage $oP, $sFrom, $sTo)
// generate a notice
date_default_timezone_set('Europe/Paris');
try
{
switch($sOperation)
{
try {
switch ($sOperation) {
case 'step1':
DisplayStep1($oP);
break;
DisplayStep1($oP);
break;
case 'step2':
$oP->no_cache();
$sTo = Utils::ReadParam('to', '', false, 'raw_data');
@@ -296,17 +262,12 @@ try
break;
default:
$oP->error("Error: unsupported operation '$sOperation'");
$oP->error("Error: unsupported operation '$sOperation'");
}
}
catch(Exception $e)
{
} catch (Exception $e) {
$oP->error("Error: '".htmlentities($e->getMessage(), ENT_QUOTES, 'utf-8')."'");
}
catch(CoreException $e)
{
$oP->error("Error: '".$e->getHtmlDesc()."'");
} catch (CoreException $e) {
$oP->error("Error: '".$e->getHtmlDesc()."'");
}
$oP->output();

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<?php
/*
* Copyright (C) 2010-2024 Combodo SAS
*
@@ -17,7 +18,6 @@
* You should have received a copy of the GNU Affero General Public License
*/
/**
* Simple redirection page to check PHP requirements
*

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -17,7 +18,6 @@
* You should have received a copy of the GNU Affero General Public License
*/
/**
* Utility to upgrade the format of a given XML datamodel to the latest version
* The datamodel is supplied as a loaded DOMDocument and modified in-place.
@@ -49,76 +49,76 @@ class iTopDesignFormat
* next: string,
* go_to_next: string
* }
* }
* }
*/
public static $aVersions = array(
'1.0' => array(
public static $aVersions = [
'1.0' => [
'previous' => null,
'go_to_previous' => null,
'next' => '1.1',
'go_to_next' => 'From10To11',
),
'1.1' => array(
],
'1.1' => [
'previous' => '1.0',
'go_to_previous' => 'From11To10',
'next' => '1.2',
'go_to_next' => 'From11To12',
),
'1.2' => array(
],
'1.2' => [
'previous' => '1.1',
'go_to_previous' => 'From12To11',
'next' => '1.3',
'go_to_next' => 'From12To13',
),
'1.3' => array( // iTop >= 2.2.0
],
'1.3' => [ // iTop >= 2.2.0
'previous' => '1.2',
'go_to_previous' => 'From13To12',
'next' => '1.4',
'go_to_next' => 'From13To14',
),
'1.4' => array( // iTop >= 2.4.0
],
'1.4' => [ // iTop >= 2.4.0
'previous' => '1.3',
'go_to_previous' => 'From14To13',
'next' => '1.5',
'go_to_next' => 'From14To15',
),
'1.5' => array( // iTop >= 2.5.0
],
'1.5' => [ // iTop >= 2.5.0
'previous' => '1.4',
'go_to_previous' => 'From15To14',
'next' => '1.6',
'go_to_next' => 'From15To16',
),
'1.6' => array( // iTop >= 2.6.0
],
'1.6' => [ // iTop >= 2.6.0
'previous' => '1.5',
'go_to_previous' => 'From16To15',
'next' => '1.7',
'go_to_next' => 'From16To17',
),
'1.7' => array( // iTop >= 2.7.0
],
'1.7' => [ // iTop >= 2.7.0
'previous' => '1.6',
'go_to_previous' => 'From17To16',
'next' => '3.0',
'go_to_next' => 'From17To30',
),
'3.0' => array(
],
'3.0' => [
'previous' => '1.7',
'go_to_previous' => 'From30To17',
'next' => '3.1',
'go_to_next' => 'From30To31',
),
'3.1' => array(
],
'3.1' => [
'previous' => '3.0',
'go_to_previous' => 'From31To30',
'next' => '3.2',
'go_to_next' => 'From31To32',
),
'3.2' => array(
],
'3.2' => [
'previous' => '3.1',
'go_to_previous' => 'From32To31',
'next' => null,
'go_to_next' => null,
),
);
],
];
/**
* The Document to work on
@@ -148,10 +148,10 @@ class iTopDesignFormat
*/
protected function LogError($sMessage)
{
$this->aLog[] = array(
$this->aLog[] = [
'severity' => 'Error',
'msg' => $sMessage,
);
];
$this->bStatus = false;
}
@@ -161,10 +161,10 @@ class iTopDesignFormat
*/
protected function LogWarning($sMessage)
{
$this->aLog[] = array(
$this->aLog[] = [
'severity' => 'Warning',
'msg' => $sMessage,
);
];
}
/**
@@ -173,10 +173,10 @@ class iTopDesignFormat
*/
protected function LogInfo($sMessage)
{
$this->aLog[] = array(
$this->aLog[] = [
'severity' => 'Info',
'msg' => $sMessage,
);
];
}
/**
@@ -207,11 +207,9 @@ class iTopDesignFormat
*/
public function GetErrors()
{
$aErrors = array();
foreach ($this->aLog as $aLogEntry)
{
if ($aLogEntry['severity'] == 'Error')
{
$aErrors = [];
foreach ($this->aLog as $aLogEntry) {
if ($aLogEntry['severity'] == 'Error') {
$aErrors[] = $aLogEntry['msg'];
}
}
@@ -223,11 +221,9 @@ class iTopDesignFormat
*/
public function GetWarnings()
{
$aErrors = array();
foreach ($this->aLog as $aLogEntry)
{
if ($aLogEntry['severity'] == 'Warning')
{
$aErrors = [];
foreach ($this->aLog as $aLogEntry) {
if ($aLogEntry['severity'] == 'Warning') {
$aErrors[] = $aLogEntry['msg'];
}
}
@@ -284,33 +280,35 @@ class iTopDesignFormat
*/
public static function GetItopNodePath($oNode)
{
if ($oNode instanceof DOMDocument) return '';
if ($oNode instanceof DOMDocument) {
return '';
}
$sId = $oNode->getAttribute('id');
$sNodeDesc = ($sId != '') ? $oNode->nodeName.'['.$sId.']' : $oNode->nodeName;
return self::GetItopNodePath($oNode->parentNode).'/'.$sNodeDesc;
}
}
/**
* Test the conversion without altering the DOM
*
*
* @param string $sTargetVersion The desired version (or the latest possible version if not specified)
* @param object $oFactory Full data model (not yet used, aimed at allowing conversion that could not be performed without knowing the
* whole data model)
*
* @return bool True on success
* @return bool True on success
*/
public function CheckConvert($sTargetVersion = ITOP_DESIGN_LATEST_VERSION, $oFactory = null)
{
// Clone the document
$this->oDocument = $this->oDocument->cloneNode(true);
$this->oDocument = $this->oDocument->cloneNode(true);
return $this->Convert($sTargetVersion, $oFactory);
}
/**
* Make adjustements to the DOM to migrate it to the specified version (default is latest)
* For now only the conversion from version 1.0 to 1.1 is supported.
*
*
* @param string $sTargetVersion The desired version (or the latest possible version if not specified)
* @param object $oFactory Full data model (not yet used, aimed at allowing conversion that could not be performed without knowing the
* whole data model)
@@ -319,13 +317,12 @@ class iTopDesignFormat
*/
public function Convert($sTargetVersion = ITOP_DESIGN_LATEST_VERSION, $oFactory = null)
{
$this->aLog = array();
$this->aLog = [];
$this->bStatus = true;
try {
$sVersion = $this->GetVersion();
}
catch (iTopXmlException $e) {
} catch (iTopXmlException $e) {
$this->LogError($e->getMessage());
return $this->bStatus;
@@ -334,8 +331,7 @@ class iTopDesignFormat
$this->LogInfo("Converting from $sVersion to $sTargetVersion");
try {
$this->DoConvert($sVersion, $sTargetVersion, $oFactory);
}
catch (Exception|Error $e) {
} catch (Exception|Error $e) {
$this->LogError($e->getMessage());
return false;
@@ -353,7 +349,7 @@ class iTopDesignFormat
/**
* Does the conversion, eventually in a recursive manner
*
*
* @param string $sFrom The source format version
* @param string $sTo The desired format version
* @param object $oFactory Full data model (not yet used, aimed at allowing conversion that could not be performed without knowing the
@@ -361,47 +357,40 @@ class iTopDesignFormat
*/
protected function DoConvert($sFrom, $sTo, $oFactory = null)
{
if ($sFrom == $sTo)
{
if ($sFrom == $sTo) {
return;
}
if (!array_key_exists($sFrom, self::$aVersions))
{
if (!array_key_exists($sFrom, self::$aVersions)) {
$this->LogError("Unknown source format version: $sFrom");
return;
}
if (!array_key_exists($sTo, self::$aVersions))
{
if (!array_key_exists($sTo, self::$aVersions)) {
$this->LogError("Unknown target format version: $sTo");
return; // unknown versions are not supported
}
$aVersionIds = array_keys(self::$aVersions);
$iFrom = array_search($sFrom, $aVersionIds);
$iTo = array_search($sTo, $aVersionIds);
if ($iFrom < $iTo)
{
if ($iFrom < $iTo) {
// This is an upgrade
$sIntermediate = self::$aVersions[$sFrom]['next'];
$sTransform = self::$aVersions[$sFrom]['go_to_next'];
$this->LogInfo("Upgrading from $sFrom to $sIntermediate ($sTransform)");
}
else
{
} else {
// This is a downgrade
$sIntermediate = self::$aVersions[$sFrom]['previous'];
$sTransform = self::$aVersions[$sFrom]['go_to_previous'];
$this->LogInfo("Downgrading from $sFrom to $sIntermediate ($sTransform)");
}
// Transform to the intermediate format
$aCallSpec = array($this, $sTransform);
$aCallSpec = [$this, $sTransform];
try {
call_user_func($aCallSpec, $oFactory);
// Recurse
$this->DoConvert($sIntermediate, $sTo, $oFactory);
}
catch (Exception $e) {
} catch (Exception $e) {
$this->LogError($e->getMessage());
}
}
@@ -436,45 +425,37 @@ class iTopDesignFormat
// which don't already have one
$oXPath = new DOMXPath($this->oDocument);
$oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition/stimulus');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$oNode->parentNode->SetAttribute('id', $oNode->textContent);
$this->DeleteNode($oNode);
}
// Adjust the XML to transparently add an id (=percent) on all thresholds of stopwatches
// which don't already have one
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeStopWatch']/thresholds/threshold/percent");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$oNode->parentNode->SetAttribute('id', $oNode->textContent);
$this->DeleteNode($oNode);
}
// Adjust the XML to transparently add an id (=action:<type>) on all allowed actions (profiles)
// which don't already have one
$oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action');
foreach ($oNodeList as $oNode)
{
if ($oNode->getAttribute('id') == '')
{
$oNode->SetAttribute('id', 'action:' . $oNode->getAttribute('xsi:type'));
foreach ($oNodeList as $oNode) {
if ($oNode->getAttribute('id') == '') {
$oNode->SetAttribute('id', 'action:'.$oNode->getAttribute('xsi:type'));
$oNode->removeAttribute('xsi:type');
}
elseif ($oNode->getAttribute('xsi:type') == 'stimulus')
{
$oNode->SetAttribute('id', 'stimulus:' . $oNode->getAttribute('id'));
} elseif ($oNode->getAttribute('xsi:type') == 'stimulus') {
$oNode->SetAttribute('id', 'stimulus:'.$oNode->getAttribute('id'));
$oNode->removeAttribute('xsi:type');
}
}
// Adjust the XML to transparently add an id (=value) on all values of an enum which don't already have one.
// This enables altering an enum for just adding/removing one value, intead of redefining the whole list of values.
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value");
foreach ($oNodeList as $oNode)
{
if ($oNode->getAttribute('id') == '')
{
foreach ($oNodeList as $oNode) {
if ($oNode->getAttribute('id') == '') {
$oNode->SetAttribute('id', $oNode->textContent);
}
}
@@ -490,60 +471,49 @@ class iTopDesignFormat
// Move the id down to a stimulus node on all life-cycle transitions
$oXPath = new DOMXPath($this->oDocument);
$oNodeList = $oXPath->query('/itop_design/classes//class/lifecycle/states/state/transitions/transition[@id]');
foreach ($oNodeList as $oNode)
{
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0)
{
foreach ($oNodeList as $oNode) {
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0) {
$this->LogError('Alterations have been defined under the node: '.self::GetItopNodePath($oNode));
}
$oStimulus = $oNode->ownerDocument->createElement('stimulus', $oNode->getAttribute('id'));
$oNode->appendChild($oStimulus);
$oNode->removeAttribute('id');
}
// Move the id down to a percent node on all thresholds
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeStopWatch']/thresholds/threshold[@id]");
foreach ($oNodeList as $oNode)
{
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0)
{
foreach ($oNodeList as $oNode) {
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0) {
$this->LogError('Alterations have been defined under the node: '.self::GetItopNodePath($oNode));
}
$oStimulus = $oNode->ownerDocument->createElement('percent', $oNode->getAttribute('id'));
$oNode->appendChild($oStimulus);
$oNode->removeAttribute('id');
}
// Restore the type and id on profile/actions
// Restore the type and id on profile/actions
$oNodeList = $oXPath->query('/itop_design/user_rights/profiles/profile/groups/group/actions/action');
foreach ($oNodeList as $oNode)
{
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0)
{
foreach ($oNodeList as $oNode) {
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0) {
$this->LogError('Alterations have been defined under the node: '.self::GetItopNodePath($oNode));
}
if (substr($oNode->getAttribute('id'), 0, strlen('action')) == 'action')
{
if (substr($oNode->getAttribute('id'), 0, strlen('action')) == 'action') {
// The id has the form 'action:<action_code>'
$sActionCode = substr($oNode->getAttribute('id'), strlen('action:'));
$oNode->removeAttribute('id');
$oNode->setAttribute('xsi:type', $sActionCode);
}
else
{
} else {
// The id has the form 'stimulus:<stimulus_code>'
$sStimulusCode = substr($oNode->getAttribute('id'), strlen('stimulus:'));
$oNode->setAttribute('id', $sStimulusCode);
$oNode->setAttribute('xsi:type', 'stimulus');
}
}
// Remove the id on all enum values
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeEnum']/values/value[@id]");
foreach ($oNodeList as $oNode)
{
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0)
{
foreach ($oNodeList as $oNode) {
if ($oXPath->query('descendant-or-self::*[@_delta or @_rename_from]', $oNode)->length > 0) {
$this->LogError('Alterations have been defined under the node: '.self::GetItopNodePath($oNode));
}
$oNode->removeAttribute('id');
@@ -571,8 +541,7 @@ class iTopDesignFormat
// Transform ObjectKey attributes into Integer
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeObjectKey']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$oNode->setAttribute('xsi:type', 'AttributeInteger');
// The property class_attcode is left there (doing no harm)
$this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' has been degraded into an integer attribute. Any OQL query using this attribute will fail.');
@@ -581,37 +550,32 @@ class iTopDesignFormat
// Remove Redundancy settings attributes (no redundancy could be defined in the previous format)
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeRedundancySettings']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' is of no use and must be removed.');
$this->DeleteNode($oNode);
}
// Later: transform the relations into code (iif defined as an SQL query)
$oNodeList = $oXPath->query('/itop_design/classes//class/relations');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The relations defined in '.self::GetItopNodePath($oNode).' will be lost.');
$this->DeleteNode($oNode);
}
$oNodeList = $oXPath->query('/itop_design/portal');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('Portal definition will be lost.');
$this->DeleteNode($oNode);
}
$oNodeList = $oXPath->query('/itop_design/module_parameters');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('Module parameters will be lost.');
$this->DeleteNode($oNode);
}
$oNodeList = $oXPath->query('/itop_design/snippets');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('Code snippets will be lost.');
$this->DeleteNode($oNode);
}
@@ -636,8 +600,7 @@ class iTopDesignFormat
$oXPath = new DOMXPath($this->oDocument);
$oNodeList = $oXPath->query('/itop_design/module_designs/module_design');
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The module design defined in '.self::GetItopNodePath($oNode).' will be lost.');
$this->DeleteNode($oNode);
}
@@ -645,8 +608,7 @@ class iTopDesignFormat
// Remove MetaEnum attributes
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeMetaEnum']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' is irrelevant and must be removed.');
$this->DeleteNode($oNode);
}
@@ -654,8 +616,7 @@ class iTopDesignFormat
// Remove CustomFields attributes
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeCustomFields']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' is irrelevant and must be removed.');
$this->DeleteNode($oNode);
}
@@ -663,8 +624,7 @@ class iTopDesignFormat
// Remove Image attributes
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@xsi:type='AttributeImage']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The attribute '.self::GetItopNodePath($oNode).' is irrelevant and must be removed.');
$this->DeleteNode($oNode);
}
@@ -672,8 +632,7 @@ class iTopDesignFormat
// Discard _delta="if_exists"
//
$oNodeList = $oXPath->query("//*[@_delta='if_exists']");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('The flag _delta="if_exists" on '.self::GetItopNodePath($oNode).' is irrelevant and must be replaced by _delta="must_exist".');
$oNode->setAttribute('_delta', 'must_exist');
}
@@ -696,30 +655,27 @@ class iTopDesignFormat
protected function From14To13($oFactory)
{
$oXPath = new DOMXPath($this->oDocument);
// Transform _delta="force" into _delta="define"
//
$oNodeList = $oXPath->query("/itop_design/classes//class/fields/field[@_delta='force']");
$iCount = 0;
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$oNode->setAttribute('_delta', 'define');
$iCount++;
}
if ($iCount > 0)
{
if ($iCount > 0) {
$this->LogWarning('The attribute _delta="force" is not supported, converted to _delta="define" ('.$iCount.' instances processed).');
}
// Remove attribute flags on transitions
//
$oNodeList = $oXPath->query("/itop_design/classes//class/lifecycle/states/state/transitions/transition/flags");
$this->LogWarning('Before removing flags nodes');
foreach ($oNodeList as $oNode)
{
$this->LogWarning('Attribute flags '.self::GetItopNodePath($oNode).' is irrelevant on transition and must be removed.');
$this->DeleteNode($oNode);
}
// Remove attribute flags on transitions
//
$oNodeList = $oXPath->query("/itop_design/classes//class/lifecycle/states/state/transitions/transition/flags");
$this->LogWarning('Before removing flags nodes');
foreach ($oNodeList as $oNode) {
$this->LogWarning('Attribute flags '.self::GetItopNodePath($oNode).' is irrelevant on transition and must be removed.');
$this->DeleteNode($oNode);
}
}
/**
@@ -735,8 +691,7 @@ class iTopDesignFormat
//
$sPath = "/itop_design/menus/menu[@xsi:type!='MenuGroup' and @xsi:type!='TemplateMenuNode']";
$oNodeList = $oXPath->query("$sPath/enable_class | $sPath/enable_action | $sPath/enable_permission | $sPath/enable_stimulus");
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be ignored. Use enable_admin_only instead.');
}
}
@@ -786,14 +741,16 @@ class iTopDesignFormat
protected function From16To17($oFactory)
{
// N°2275 Clean branding node (move "define" from collection to logos or theme, noe that any other nodes will be seen as "merge")
$this->CleanDefineOnCollectionNode('/itop_design/branding',
'*[self::main_logo or self::login_logo or self::portal_logo]|themes/theme');
$this->CleanDefineOnCollectionNode(
'/itop_design/branding',
'*[self::main_logo or self::login_logo or self::portal_logo]|themes/theme'
);
// N°2275 Clean portal form properties node (move "define" from collection to each property)
$this->CleanDefineOnCollectionNode('/itop_design/module_designs/module_design[@id="itop-portal"]/forms/form/properties', '*');
// N°2806 Clean legacy portal constants
$aConstantsIDsToRemove = array(
$aConstantsIDsToRemove = [
'PORTAL_POWER_USER_PROFILE',
'PORTAL_SERVICECATEGORY_QUERY',
'PORTAL_SERVICE_SUBCATEGORY_QUERY',
@@ -823,9 +780,8 @@ class iTopDesignFormat
'PORTAL_USERREQUEST_DETAILS_ZLIST',
'PORTAL_USERREQUEST_DISPLAY_QUERY',
'PORTAL_USERREQUEST_DISPLAY_POWERUSER_QUERY',
);
foreach($aConstantsIDsToRemove as $sConstantIDToRemove)
{
];
foreach ($aConstantsIDsToRemove as $sConstantIDToRemove) {
$sXPath = '/itop_design/constants/constant[@id="'.$sConstantIDToRemove.'"]';
$this->RemoveNodeFromXPath($sXPath);
}
@@ -914,12 +870,12 @@ class iTopDesignFormat
$oNode->setAttribute('id', 'ibo-page-banner--background-color');
}
$oNodeList = $oXPath->query( '/itop_design/branding/themes/theme[@id="test-red"]/variables/variable[@id="backoffice-environment-banner-text-color"]');
$oNodeList = $oXPath->query('/itop_design/branding/themes/theme[@id="test-red"]/variables/variable[@id="backoffice-environment-banner-text-color"]');
foreach ($oNodeList as $oNode) {
$oNode->setAttribute('id', 'ibo-page-banner--text-color');
}
$oNodeList = $oXPath->query( '/itop_design/branding/themes/theme[@id="test-red"]/variables/variable[@id="backoffice-environment-banner-text-content"]');
$oNodeList = $oXPath->query('/itop_design/branding/themes/theme[@id="test-red"]/variables/variable[@id="backoffice-environment-banner-text-content"]');
foreach ($oNodeList as $oNode) {
$oNode->setAttribute('id', 'ibo-page-banner--text-content');
}
@@ -1032,12 +988,12 @@ class iTopDesignFormat
}
// Add new attribute to theme import nodes
$oNodeList = $oXPath->query('/itop_design/branding/themes/theme/imports/import');
foreach ($oNodeList as $oNode) {
$oNode->removeAttribute('xsi:type');
}
// Remove class style
$oNodeList = $oXPath->query("/itop_design/classes//class/properties");
foreach ($oNodeList as $oNode) {
@@ -1126,12 +1082,11 @@ class iTopDesignFormat
$oXPath = new DOMXPath($this->oDocument);
$oNodeList = $oXPath->query($sPath);
foreach ($oNodeList as $oNode)
{
foreach ($oNodeList as $oNode) {
$this->LogWarning('Node '.self::GetItopNodePath($oNode).' is irrelevant in this version, it will be removed.');
$this->DeleteNode($oNode);
}
$this->DeleteNode($oNode);
}
}
/**
* Clean a collection node by removing the _delta="define" on it and moving it to the item nodes.
@@ -1149,17 +1104,14 @@ class iTopDesignFormat
// Iterate over collections
$oCollectionNodeList = $oXPath->query($sCollectionXPath);
/** @var \DOMElement $oCollectionNode */
foreach ($oCollectionNodeList as $oCollectionNode)
{
foreach ($oCollectionNodeList as $oCollectionNode) {
// Move _delta="define" from collection to items
if (($oCollectionNode->hasAttribute('_delta')) && ($oCollectionNode->getAttribute('_delta') === "define"))
{
if (($oCollectionNode->hasAttribute('_delta')) && ($oCollectionNode->getAttribute('_delta') === "define")) {
$oCollectionNode->removeAttribute('_delta');
$oItemNodeList = $oXPath->query($sItemsRelativeXPath, $oCollectionNode);
/** @var \DOMElement $oItemNode */
foreach ($oItemNodeList as $oItemNode)
{
foreach ($oItemNodeList as $oItemNode) {
$oItemNode->setAttribute('_delta', 'define');
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (c) 2010-2024 Combodo SAS
*
@@ -66,7 +67,7 @@ HTML;
class ModuleDiscovery
{
static $m_aModuleArgs = array(
public static $m_aModuleArgs = [
'label' => 'One line description shown during the interactive setup',
'dependencies' => 'array of module ids',
'mandatory' => 'boolean',
@@ -77,17 +78,15 @@ class ModuleDiscovery
'data.sample' => 'array of sample data files',
'doc.manual_setup' => 'url',
'doc.more_information' => 'url',
);
];
// Cache the results and the source directories
protected static $m_aSearchDirs = null;
protected static $m_aModules = array();
protected static $m_aModuleVersionByName = array();
protected static $m_aModules = [];
protected static $m_aModuleVersionByName = [];
// All the entries below are list of file paths relative to the module directory
protected static $m_aFilesList = array('datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample');
protected static $m_aFilesList = ['datamodel', 'webservice', 'dictionary', 'data.struct', 'data.sample'];
// ModulePath is used by AddModule to get the path of the module being included (in ListModuleFiles)
protected static $m_sModulePath = null;
@@ -105,47 +104,37 @@ class ModuleDiscovery
*/
public static function AddModule($sFilePath, $sId, $aArgs)
{
if (!array_key_exists('itop_version', $aArgs))
{
if (!array_key_exists('itop_version', $aArgs)) {
// Assume 1.0.2
$aArgs['itop_version'] = '1.0.2';
}
foreach (array_keys(self::$m_aModuleArgs) as $sArgName)
{
if (!array_key_exists($sArgName, $aArgs))
{
foreach (array_keys(self::$m_aModuleArgs) as $sArgName) {
if (!array_key_exists($sArgName, $aArgs)) {
throw new Exception("Module '$sId': missing argument '$sArgName'");
}
}
}
$aArgs['root_dir'] = dirname($sFilePath);
$aArgs['module_file'] = $sFilePath;
list($sModuleName, $sModuleVersion) = static::GetModuleName($sId);
if ($sModuleVersion == '')
{
if ($sModuleVersion == '') {
$sModuleVersion = '1.0.0';
}
if (array_key_exists($sModuleName, self::$m_aModuleVersionByName))
{
if (version_compare($sModuleVersion, self::$m_aModuleVersionByName[$sModuleName]['version'], '>'))
{
if (array_key_exists($sModuleName, self::$m_aModuleVersionByName)) {
if (version_compare($sModuleVersion, self::$m_aModuleVersionByName[$sModuleName]['version'], '>')) {
// Newer version, let's upgrade
$sIdToRemove = self::$m_aModuleVersionByName[$sModuleName]['id'];
unset(self::$m_aModules[$sIdToRemove]);
self::$m_aModuleVersionByName[$sModuleName]['version'] = $sModuleVersion;
self::$m_aModuleVersionByName[$sModuleName]['id'] = $sId;
}
else
{
} else {
// Older (or equal) version, let's ignore it
return;
}
}
else
{
} else {
// First version to be loaded for this module, remember it
self::$m_aModuleVersionByName[$sModuleName]['version'] = $sModuleVersion;
self::$m_aModuleVersionByName[$sModuleName]['id'] = $sId;
@@ -169,24 +158,19 @@ class ModuleDiscovery
}
*/
// Populate automatically the list of dictionary files
$aMatches = array();
if(preg_match('|^([^/]+)|', $sId, $aMatches)) // ModuleName = everything before the first forward slash
{
$aMatches = [];
if (preg_match('|^([^/]+)|', $sId, $aMatches)) { // ModuleName = everything before the first forward slash
$sModuleName = $aMatches[1];
$sDir = dirname($sFilePath);
$aDirs = [
$sDir => self::$m_sModulePath,
$sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries'
$sDir.'/dictionaries' => self::$m_sModulePath.'/dictionaries',
];
foreach ($aDirs as $sRootDir => $sPath)
{
if ($hDir = @opendir($sRootDir))
{
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (preg_match("/^[^\\.]+.dict.$sModuleName.php$/i", $sFile, $aMatches)) // Dictionary files named like <Lang>.dict.<ModuleName>.php are loaded automatically
{
foreach ($aDirs as $sRootDir => $sPath) {
if ($hDir = @opendir($sRootDir)) {
while (($sFile = readdir($hDir)) !== false) {
$aMatches = [];
if (preg_match("/^[^\\.]+.dict.$sModuleName.php$/i", $sFile, $aMatches)) { // Dictionary files named like <Lang>.dict.<ModuleName>.php are loaded automatically
self::$m_aModules[$sId]['dictionary'][] = $sPath.'/'.$sFile;
}
}
@@ -224,11 +208,9 @@ class ModuleDiscovery
// Order the modules to take into account their inter-dependencies
$aDependencies = [];
$aSelectedModules = [];
foreach($aModules as $sId => $aModule)
{
foreach ($aModules as $sId => $aModule) {
list($sModuleName, ) = self::GetModuleName($sId);
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad))
{
if (is_null($aModulesToLoad) || in_array($sModuleName, $aModulesToLoad)) {
$aDependencies[$sId] = $aModule['dependencies'];
$aSelectedModules[$sModuleName] = true;
}
@@ -236,46 +218,36 @@ class ModuleDiscovery
ksort($aDependencies);
$aOrderedModules = [];
$iLoopCount = 1;
while(($iLoopCount < count($aModules)) && (count($aDependencies) > 0) )
{
foreach($aDependencies as $sId => $aRemainingDeps)
{
while (($iLoopCount < count($aModules)) && (count($aDependencies) > 0)) {
foreach ($aDependencies as $sId => $aRemainingDeps) {
$bDependenciesSolved = true;
foreach($aRemainingDeps as $sDepId)
{
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules))
{
foreach ($aRemainingDeps as $sDepId) {
if (!self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
$bDependenciesSolved = false;
}
}
if ($bDependenciesSolved)
{
if ($bDependenciesSolved) {
$aOrderedModules[] = $sId;
unset($aDependencies[$sId]);
}
}
$iLoopCount++;
}
if ($bAbortOnMissingDependency && count($aDependencies) > 0)
{
if ($bAbortOnMissingDependency && count($aDependencies) > 0) {
$aModulesInfo = [];
$aModuleDeps = [];
foreach($aDependencies as $sId => $aDeps)
{
foreach ($aDependencies as $sId => $aDeps) {
$aModule = $aModules[$sId];
$aDepsWithIcons = [];
foreach($aDeps as $sIndex => $sDepId)
{
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules))
{
$aDepsWithIcons[$sIndex] = ' ' . $sDepId;
} else
{
$aDepsWithIcons[$sIndex] = '❌ ' . $sDepId;
foreach ($aDeps as $sIndex => $sDepId) {
if (self::DependencyIsResolved($sDepId, $aOrderedModules, $aSelectedModules)) {
$aDepsWithIcons[$sIndex] = '✅ '.$sDepId;
} else {
$aDepsWithIcons[$sIndex] = ' '.$sDepId;
}
}
$aModuleDeps[] = "{$aModule['label']} (id: $sId) depends on: ".implode(' + ', $aDepsWithIcons);
$aModulesInfo[$sId] = array('module' => $aModule, 'dependencies' => $aDepsWithIcons);
$aModulesInfo[$sId] = ['module' => $aModule, 'dependencies' => $aDepsWithIcons];
}
$sMessage = "The following modules have unmet dependencies:\n".implode(",\n", $aModuleDeps);
$oException = new MissingDependencyException($sMessage);
@@ -283,9 +255,8 @@ class ModuleDiscovery
throw $oException;
}
// Return the ordered list, so that the dependencies are met...
$aResult = array();
foreach($aOrderedModules as $sId)
{
$aResult = [];
foreach ($aOrderedModules as $sId) {
$aResult[$sId] = $aModules[$sId];
}
return $aResult;
@@ -306,88 +277,66 @@ class ModuleDiscovery
protected static function DependencyIsResolved($sDepString, $aOrderedModules, $aSelectedModules)
{
$bResult = false;
$aModuleVersions = array();
$aModuleVersions = [];
// Separate the module names from their version for an easier comparison later
foreach($aOrderedModules as $sModuleId)
{
$aMatches = array();
if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches))
{
foreach ($aOrderedModules as $sModuleId) {
$aMatches = [];
if (preg_match('|^([^/]+)/(.*)$|', $sModuleId, $aMatches)) {
$aModuleVersions[$aMatches[1]] = $aMatches[2];
}
else
{
} else {
// No version number found, assume 1.0.0
$aModuleVersions[$sModuleId] = '1.0.0';
}
}
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches))
{
$aReplacements = array();
$aPotentialPrerequisites = array();
foreach($aMatches as $aMatch)
{
foreach($aMatch as $sModuleId)
{
if (preg_match_all('/([^\(\)&| ]+)/', $sDepString, $aMatches)) {
$aReplacements = [];
$aPotentialPrerequisites = [];
foreach ($aMatches as $aMatch) {
foreach ($aMatch as $sModuleId) {
// $sModuleId in the dependency string is made of a <name>/<optional_operator><version>
// where the operator is < <= = > >= (by default >=)
$aModuleMatches = array();
if(preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches))
{
$aModuleMatches = [];
if (preg_match('|^([^/]+)/(<?>?=?)([^><=]+)$|', $sModuleId, $aModuleMatches)) {
$sModuleName = $aModuleMatches[1];
$aPotentialPrerequisites[$sModuleName] = true;
$sOperator = $aModuleMatches[2];
if ($sOperator == '')
{
if ($sOperator == '') {
$sOperator = '>=';
}
$sExpectedVersion = $aModuleMatches[3];
if (array_key_exists($sModuleName, $aModuleVersions))
{
if (array_key_exists($sModuleName, $aModuleVersions)) {
// module is present, check the version
$sCurrentVersion = $aModuleVersions[$sModuleName];
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator))
{
if (version_compare($sCurrentVersion, $sExpectedVersion, $sOperator)) {
$aReplacements[$sModuleId] = '(true)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
}
else
{
// a function call that results in a runtime fatal error
} else {
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
// a function call that results in a runtime fatal error
}
}
else
{
} else {
// module is not present
$aReplacements[$sModuleId] = '(false)'; // Add parentheses to protect against invalid condition causing
// a function call that results in a runtime fatal error
// a function call that results in a runtime fatal error
}
}
}
}
$bMissingPrerequisite = false;
foreach (array_keys($aPotentialPrerequisites) as $sModuleName)
{
if (array_key_exists($sModuleName, $aSelectedModules))
{
foreach (array_keys($aPotentialPrerequisites) as $sModuleName) {
if (array_key_exists($sModuleName, $aSelectedModules)) {
// This module is actually a prerequisite
if (!array_key_exists($sModuleName, $aModuleVersions))
{
if (!array_key_exists($sModuleName, $aModuleVersions)) {
$bMissingPrerequisite = true;
}
}
}
if ($bMissingPrerequisite)
{
if ($bMissingPrerequisite) {
$bResult = false;
}
else
{
} else {
$sBooleanExpr = str_replace(array_keys($aReplacements), array_values($aReplacements), $sDepString);
$bOk = @eval('$bResult = '.$sBooleanExpr.'; return true;');
if ($bOk == false)
{
if ($bOk == false) {
SetupLog::Warning("Eval of '$sBooleanExpr' returned false");
echo "Failed to parse the boolean Expression = '$sBooleanExpr'<br/>";
}
@@ -409,21 +358,17 @@ class ModuleDiscovery
*/
public static function GetAvailableModules($aSearchDirs, $bAbortOnMissingDependency = false, $aModulesToLoad = null)
{
if (self::$m_aSearchDirs != $aSearchDirs)
{
if (self::$m_aSearchDirs != $aSearchDirs) {
self::ResetCache();
}
if (is_null(self::$m_aSearchDirs))
{
if (is_null(self::$m_aSearchDirs)) {
self::$m_aSearchDirs = $aSearchDirs;
// Not in cache, let's scan the disk
foreach($aSearchDirs as $sSearchDir)
{
foreach ($aSearchDirs as $sSearchDir) {
$sLookupDir = realpath($sSearchDir);
if ($sLookupDir == '')
{
if ($sLookupDir == '') {
throw new Exception("Invalid directory '$sSearchDir'");
}
@@ -431,9 +376,7 @@ class ModuleDiscovery
self::ListModuleFiles(basename($sSearchDir), dirname($sSearchDir));
}
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
}
else
{
} else {
// Reuse the previous results
return self::GetModules($bAbortOnMissingDependency, $aModulesToLoad);
}
@@ -442,8 +385,8 @@ class ModuleDiscovery
public static function ResetCache()
{
self::$m_aSearchDirs = null;
self::$m_aModules = array();
self::$m_aModuleVersionByName = array();
self::$m_aModules = [];
self::$m_aModuleVersionByName = [];
}
/**
@@ -453,18 +396,15 @@ class ModuleDiscovery
*/
public static function GetModuleName($sModuleId)
{
$aMatches = array();
if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches))
{
$aMatches = [];
if (preg_match('!^(.*)/(.*)$!', $sModuleId, $aMatches)) {
$sName = $aMatches[1];
$sVersion = $aMatches[2];
}
else
{
} else {
$sName = $sModuleId;
$sVersion = "";
}
return array($sName, $sVersion);
return [$sName, $sVersion];
}
/**
@@ -480,34 +420,25 @@ class ModuleDiscovery
static $iDummyClassIndex = 0;
$sDirectory = $sRootDir.'/'.$sRelDir;
if ($hDir = opendir($sDirectory))
{
if ($hDir = opendir($sDirectory)) {
// This is the correct way to loop over the directory. (according to the documentation)
while (($sFile = readdir($hDir)) !== false)
{
$aMatches = array();
if (is_dir($sDirectory.'/'.$sFile))
{
if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn') && ($sFile != 'vendor'))
{
while (($sFile = readdir($hDir)) !== false) {
$aMatches = [];
if (is_dir($sDirectory.'/'.$sFile)) {
if (($sFile != '.') && ($sFile != '..') && ($sFile != '.svn') && ($sFile != 'vendor')) {
self::ListModuleFiles($sRelDir.'/'.$sFile, $sRootDir);
}
}
else if (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches))
{
} elseif (preg_match('/^module\.(.*).php$/i', $sFile, $aMatches)) {
self::SetModulePath($sRelDir);
try
{
try {
$sModuleFileContents = file_get_contents($sDirectory.'/'.$sFile);
$sModuleFileContents = str_replace(array('<?php', '?>'), '', $sModuleFileContents);
$sModuleFileContents = str_replace(['<?php', '?>'], '', $sModuleFileContents);
$sModuleFileContents = str_replace('__FILE__', "'".addslashes($sDirectory.'/'.$sFile)."'", $sModuleFileContents);
preg_match_all('/class ([A-Za-z0-9_]+) extends ([A-Za-z0-9_]+)/', $sModuleFileContents, $aMatches);
//print_r($aMatches);
$idx = 0;
foreach($aMatches[1] as $sClassName)
{
if (class_exists($sClassName))
{
foreach ($aMatches[1] as $sClassName) {
if (class_exists($sClassName)) {
// rename the class inside the code to prevent a "duplicate class" declaration
// and change its parent class as well so that nobody will find it and try to execute it
$sModuleFileContents = str_replace($sClassName.' extends '.$aMatches[2][$idx], $sClassName.'_'.($iDummyClassIndex++).' extends DummyHandler', $sModuleFileContents);
@@ -516,35 +447,27 @@ class ModuleDiscovery
}
$bRet = eval($sModuleFileContents);
if ($bRet === false)
{
if ($bRet === false) {
SetupLog::Warning("Eval of $sRelDir/$sFile returned false");
}
//echo "<p>Done.</p>\n";
}
catch(ParseError $e)
{
// PHP 7
} catch (ParseError $e) {
// PHP 7
SetupLog::Warning("Eval of $sRelDir/$sFile caused an exception: ".$e->getMessage()." at line ".$e->getLine());
}
catch(Exception $e)
{
} catch (Exception $e) {
// Continue...
SetupLog::Warning("Eval of $sRelDir/$sFile caused an exception: ".$e->getMessage());
}
}
}
closedir($hDir);
}
else
{
} else {
throw new Exception("Data directory (".$sDirectory.") not found or not readable.");
}
}
} // End of class
/** Alias for backward compatibility with old module files in which
* the declaration of a module invokes SetupWebPage::AddModule()
* whereas the new form is ModuleDiscovery::AddModule()
@@ -585,6 +508,6 @@ class SetupWebPage extends ModuleDiscovery
* the class (in case some piece of code enumerate the classes derived from a well known class)
* Note that this will not work if someone enumerates the classes that implement a given interface
*/
class DummyHandler {
class DummyHandler
{
}

View File

@@ -1,9 +1,10 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -16,7 +17,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Persistent class ModuleInstallation to record the installed modules
* Log of module installations
@@ -29,33 +29,32 @@ class ModuleInstallation extends DBObject
{
public static function Init()
{
$aParams = array
(
$aParams =
[
"category" => "core,view_in_gui",
"key_type" => "autoincrement",
'name_attcode' => array('name', 'version'),
'name_attcode' => ['name', 'version'],
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => [],
"db_table" => "priv_module_install",
"db_key_field" => "id",
"db_finalclass_field" => "",
'order_by_default' => array('installed' => false),
);
'order_by_default' => ['installed' => false],
];
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("name", array("allowed_values" => null, "sql" => "name", "default_value" => null, "is_null_allowed" => false, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values" => null, "sql" => "version", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values" => null, "sql" => "installed", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeText("comment", array("allowed_values" => null, "sql" => "comment", "default_value" => null, "is_null_allowed" => true, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", array("targetclass" => "ModuleInstallation", "jointype" => "", "allowed_values" => null, "sql" => "parent_id", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => array())));
MetaModel::Init_AddAttribute(new AttributeString("name", ["allowed_values" => null, "sql" => "name", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeString("version", ["allowed_values" => null, "sql" => "version", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", ["allowed_values" => null, "sql" => "installed", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeText("comment", ["allowed_values" => null, "sql" => "comment", "default_value" => null, "is_null_allowed" => true, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeExternalKey("parent_id", ["targetclass" => "ModuleInstallation", "jointype" => "", "allowed_values" => null, "sql" => "parent_id", "is_null_allowed" => true, "on_target_delete" => DEL_MANUAL, "depends_on" => []]));
// Display lists
MetaModel::Init_SetZListItems('details', array('name', 'version', 'installed', 'comment', 'parent_id')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('installed', 'comment')); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('details', ['name', 'version', 'installed', 'comment', 'parent_id']); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['installed', 'comment']); // Attributes to be displayed for a list
// Search criteria
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
// MetaModel::Init_SetZListItems('standard_search', array('name')); // Criteria of the std search form
// MetaModel::Init_SetZListItems('advanced_search', array('name')); // Criteria of the advanced search form
}
}
@@ -70,31 +69,28 @@ class ExtensionInstallation extends cmdbAbstractObject
{
public static function Init()
{
$aParams = array
(
$aParams =
[
"category" => "core,view_in_gui",
"key_type" => "autoincrement",
"name_attcode" => "",
"state_attcode" => "",
"reconc_keys" => array(),
"reconc_keys" => [],
"db_table" => "priv_extension_install",
"db_key_field" => "id",
"db_finalclass_field" => "",
);
];
MetaModel::Init_Params($aParams);
//MetaModel::Init_InheritAttributes();
MetaModel::Init_AddAttribute(new AttributeString("code", array("allowed_values"=>null, "sql"=>"code", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("label", array("allowed_values"=>null, "sql"=>"label", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("version", array("allowed_values"=>null, "sql"=>"version", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("source", array("allowed_values"=>null, "sql"=>"source", "default_value"=>null, "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", array("allowed_values"=>null, "sql"=>"installed", "default_value"=>'NOW()', "is_null_allowed"=>false, "depends_on"=>array())));
MetaModel::Init_AddAttribute(new AttributeString("code", ["allowed_values" => null, "sql" => "code", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeString("label", ["allowed_values" => null, "sql" => "label", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeString("version", ["allowed_values" => null, "sql" => "version", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeString("source", ["allowed_values" => null, "sql" => "source", "default_value" => null, "is_null_allowed" => false, "depends_on" => []]));
MetaModel::Init_AddAttribute(new AttributeDateTime("installed", ["allowed_values" => null, "sql" => "installed", "default_value" => 'NOW()', "is_null_allowed" => false, "depends_on" => []]));
// Display lists
MetaModel::Init_SetZListItems('details', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('standard_search', array('code', 'label', 'version', 'installed', 'source')); // Attributes to be displayed in the search form
MetaModel::Init_SetZListItems('details', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed for the complete details
MetaModel::Init_SetZListItems('list', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed for a list
MetaModel::Init_SetZListItems('standard_search', ['code', 'label', 'version', 'installed', 'source']); // Attributes to be displayed in the search form
}
}

View File

@@ -1,9 +1,10 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -16,12 +17,11 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
require_once(APPROOT.'setup/setuppage.class.inc.php');
/**
* Class ModuleInstaller
* Defines the API to implement module specific actions during the setup
* Defines the API to implement module specific actions during the setup
*
* @copyright Copyright (C) 2010-2024 Combodo SAS
* @license http://opensource.org/licenses/AGPL-3.0
@@ -43,7 +43,7 @@ abstract class ModuleInstallerAPI
public static function BeforeDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
/**
* Handler called after the creation/update of the database schema
* @param $oConfiguration Config The new configuration of the application
@@ -54,41 +54,39 @@ abstract class ModuleInstallerAPI
{
}
/**
* Handler called at the end of the setup of the database (profiles and admin accounts created), but before the data load
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*/
public static function AfterDatabaseSetup(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
/**
* Handler called at the end of the setup of the database (profiles and admin accounts created), but before the data load
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*/
public static function AfterDatabaseSetup(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
/**
* Handler called at the end of the data load
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*/
public static function AfterDataLoad(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
/**
* Handler called at the end of the data load
* @param $oConfiguration Config The new configuration of the application
* @param $sPreviousVersion string Previous version number of the module (empty string in case of first install)
* @param $sCurrentVersion string Current version number of the module
*/
public static function AfterDataLoad(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
/**
* Helper to complete the renaming of a class
* The renaming is made in the datamodel definition, but the name has to be changed in the DB as well
* The renaming is made in the datamodel definition, but the name has to be changed in the DB as well
* Must be called after DB update, i.e within an implementation of AfterDatabaseCreation()
*
* @param string $sFrom Original name (already INVALID in the current datamodel)
*
* @param string $sFrom Original name (already INVALID in the current datamodel)
* @param string $sTo New name (valid in the current datamodel)
* @return void
* @return void
*/
public static function RenameClassInDB($sFrom, $sTo)
{
try
{
if (!MetaModel::IsStandaloneClass($sTo))
{
try {
if (!MetaModel::IsStandaloneClass($sTo)) {
$sRootClass = MetaModel::GetRootClass($sTo);
$sTableName = MetaModel::DBGetTable($sRootClass);
$sFinalClassCol = MetaModel::DBGetClassField($sRootClass);
@@ -97,77 +95,61 @@ abstract class ModuleInstallerAPI
$iAffectedRows = CMDBSource::AffectedRows();
SetupLog::Info("Renaming class in DB - final class from '$sFrom' to '$sTo': $iAffectedRows rows affected");
}
}
catch(Exception $e)
{
} catch (Exception $e) {
SetupLog::Warning("Failed to rename class in DB - final class from '$sFrom' to '$sTo'. Reason: ".$e->getMessage());
}
}
}
/**
* Helper to modify an enum value
* The change is made in the datamodel definition, but the value has to be changed in the DB as well
* Helper to modify an enum value
* The change is made in the datamodel definition, but the value has to be changed in the DB as well
* Must be called BEFORE DB update, i.e within an implementation of BeforeDatabaseCreation()
* This helper does change ONE value at a time
*
* This helper does change ONE value at a time
*
* @param string $sClass A valid class name
* @param string $sAttCode The enum attribute code
* @param string $sFrom Original value (already INVALID in the current datamodel)
* @param string $sFrom Original value (already INVALID in the current datamodel)
* @param string $sTo New value (valid in the current datamodel)
* @return void
* @return void
*/
public static function RenameEnumValueInDB($sClass, $sAttCode, $sFrom, $sTo)
{
try
{
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
try {
if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) {
SetupLog::Warning("Changing enum in DB - $sClass::$sAttCode - from '$sFrom' to '$sTo' failed. Reason '$sAttCode' is not a valid attribute of the class '$sClass'.");
return;
}
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sAttCode);
$sTableName = MetaModel::DBGetTable($sOriginClass);
$oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sAttCode);
if ($oAttDef instanceof AttributeEnum)
{
if ($oAttDef instanceof AttributeEnum) {
$oValDef = $oAttDef->GetValuesDef();
if ($oValDef)
{
$aNewValues = array_keys($oValDef->GetValues(array(), ""));
if (in_array($sTo, $aNewValues))
{
if ($oValDef) {
$aNewValues = array_keys($oValDef->GetValues([], ""));
if (in_array($sTo, $aNewValues)) {
$sEnumCol = $oAttDef->Get("sql");
$aFields = CMDBSource::QueryToArray("SHOW COLUMNS FROM `$sTableName` WHERE Field = '$sEnumCol'");
if (isset($aFields[0]['Type']))
{
if (isset($aFields[0]['Type'])) {
$sColType = $aFields[0]['Type'];
// Note: the parsing should rely on str_getcsv (requires PHP 5.3) to cope with escaped string
if (preg_match("/^enum\('(.*)'\)$/", $sColType, $aMatches))
{
if (preg_match("/^enum\('(.*)'\)$/", $sColType, $aMatches)) {
$aCurrentValues = explode("','", $aMatches[1]);
}
else
{
} else {
// not an enum currently : return !
// we could update values, but a clear error message will be displayed when altering the column
return;
}
}
if (!in_array($sFrom, $aNewValues))
{
if (!in_array($sTo, $aCurrentValues)) // if not already transformed!
{
if (!in_array($sFrom, $aNewValues)) {
if (!in_array($sTo, $aCurrentValues)) { // if not already transformed!
$sNullSpec = $oAttDef->IsNullAllowed() ? 'NULL' : 'NOT NULL';
if (strtolower($sTo) == strtolower($sFrom))
{
if (strtolower($sTo) == strtolower($sFrom)) {
SetupLog::Info("Changing enum in DB - $sClass::$sAttCode from '$sFrom' to '$sTo' (just a change in the case)");
$aTargetValues = array();
foreach ($aCurrentValues as $sValue)
{
if ($sValue == $sFrom)
{
$aTargetValues = [];
foreach ($aCurrentValues as $sValue) {
if ($sValue == $sFrom) {
$sValue = $sTo;
}
$aTargetValues[] = $sValue;
@@ -175,9 +157,7 @@ abstract class ModuleInstallerAPI
$sColumnDefinition = "ENUM(".implode(",", CMDBSource::Quote($aTargetValues)).") $sNullSpec";
$sRepair = "ALTER TABLE `$sTableName` MODIFY `$sEnumCol` $sColumnDefinition";
CMDBSource::Query($sRepair);
}
else
{
} else {
// 1st - Allow both values in the column definition
//
SetupLog::Info("Changing enum in DB - $sClass::$sAttCode from '$sFrom' to '$sTo'");
@@ -186,21 +166,19 @@ abstract class ModuleInstallerAPI
$sColumnDefinition = "ENUM(".implode(",", CMDBSource::Quote($aAllValues)).") $sNullSpec";
$sRepair = "ALTER TABLE `$sTableName` MODIFY `$sEnumCol` $sColumnDefinition";
CMDBSource::Query($sRepair);
// 2nd - Change the old value into the new value
//
$sRepair = "UPDATE `$sTableName` SET `$sEnumCol` = '$sTo' WHERE `$sEnumCol` = BINARY '$sFrom'";
CMDBSource::Query($sRepair);
$iAffectedRows = CMDBSource::AffectedRows();
SetupLog::Info("Changing enum in DB - $iAffectedRows rows updated");
// 3rd - Remove the useless value from the column definition
//
$aTargetValues = array();
foreach ($aCurrentValues as $sValue)
{
if ($sValue == $sFrom)
{
$aTargetValues = [];
foreach ($aCurrentValues as $sValue) {
if ($sValue == $sFrom) {
$sValue = $sTo;
}
$aTargetValues[] = $sValue;
@@ -211,21 +189,15 @@ abstract class ModuleInstallerAPI
SetupLog::Info("Changing enum in DB - removed useless value '$sFrom'");
}
}
}
else
{
} else {
SetupLog::Warning("Changing enum in DB - $sClass::$sAttCode - '$sFrom' is still a valid value (".implode(', ', $aNewValues).")");
}
}
else
{
} else {
SetupLog::Warning("Changing enum in DB - $sClass::$sAttCode - '$sTo' is not a known value (".implode(', ', $aNewValues).")");
}
}
}
}
catch(Exception $e)
{
} catch (Exception $e) {
SetupLog::Warning("Changing enum in DB - $sClass::$sAttCode - '$sTo' failed. Reason ".$e->getMessage());
}
}
@@ -253,30 +225,27 @@ abstract class ModuleInstallerAPI
*/
public static function MoveColumnInDB($sOrigTable, $sOrigColumn, $sDstTable, $sDstColumn, bool $bIgnoreExistingDstColumn = false)
{
if (!MetaModel::DBExists(false))
{
if (!MetaModel::DBExists(false)) {
// Install from scratch, no migration
return;
}
if (!CMDBSource::IsTable($sOrigTable) || !CMDBSource::IsField($sOrigTable, $sOrigColumn))
{
if (!CMDBSource::IsTable($sOrigTable) || !CMDBSource::IsField($sOrigTable, $sOrigColumn)) {
// Original field is not present
return;
}
$bDstTableFieldExists = CMDBSource::IsField($sDstTable, $sDstColumn);
if (!CMDBSource::IsTable($sDstTable) || ($bDstTableFieldExists && !$bIgnoreExistingDstColumn))
{
if (!CMDBSource::IsTable($sDstTable) || ($bDstTableFieldExists && !$bIgnoreExistingDstColumn)) {
// Destination field is already created, and we are not ignoring it
return;
}
// Create the destination field if necessary
if($bDstTableFieldExists === false){
if ($bDstTableFieldExists === false) {
$sSpec = CMDBSource::GetFieldSpec($sOrigTable, $sOrigColumn);
$sQueryAdd = "ALTER TABLE `{$sDstTable}` ADD `{$sDstColumn}` {$sSpec}";
CMDBSource::Query($sQueryAdd);
CMDBSource::Query($sQueryAdd);
}
// Copy the data
@@ -306,25 +275,21 @@ abstract class ModuleInstallerAPI
*/
public static function RenameTableInDB(string $sOrigTable, string $sDstTable)
{
if ($sOrigTable == $sDstTable)
{
if ($sOrigTable == $sDstTable) {
throw new CoreUnexpectedValue("Origin table and destination table are the same");
}
if (!MetaModel::DBExists(false))
{
if (!MetaModel::DBExists(false)) {
// Install from scratch, no migration
return;
}
if (!CMDBSource::IsTable($sOrigTable))
{
if (!CMDBSource::IsTable($sOrigTable)) {
SetupLog::Warning(sprintf('Rename table in DB - Origin table %s doesn\'t exist', $sOrigTable));
return;
}
if (CMDBSource::IsTable($sDstTable))
{
if (CMDBSource::IsTable($sDstTable)) {
SetupLog::Warning(sprintf('Rename table in DB - Destination table %s already exists', $sDstTable));
return;
}

View File

@@ -1,4 +1,5 @@
<?php
class InvalidParameterException extends Exception
{
}
@@ -6,7 +7,7 @@ class InvalidParameterException extends Exception
abstract class Parameters
{
public $aData = null;
public function __construct()
{
$this->aData = null;
@@ -14,8 +15,7 @@ abstract class Parameters
public function Get($sCode, $default = '')
{
if (array_key_exists($sCode, $this->aData))
{
if (array_key_exists($sCode, $this->aData)) {
return $this->aData[$sCode];
}
return $default;
@@ -27,7 +27,7 @@ abstract class Parameters
public function GetParamForConfigArray()
{
$aDBParams = $this->Get('database');
$aParamValues = array(
$aParamValues = [
'mode' => $this->Get('mode'),
'db_server' => $aDBParams['server'],
'db_user' => $aDBParams['user'],
@@ -41,7 +41,7 @@ abstract class Parameters
'language' => $this->Get('language', ''),
'graphviz_path' => $this->Get('graphviz_path', ''),
'source_dir' => $this->Get('source_dir', ''),
);
];
return $aParamValues;
}
@@ -53,51 +53,37 @@ abstract class Parameters
public function ToXML(DOMNode $oRoot, $data = null, $sNodeName = null)
{
if ($data === null)
{
if ($data === null) {
$data = $this->aData;
}
if (is_array($data))
{
if ($oRoot instanceof DOMDocument)
{
if (is_array($data)) {
if ($oRoot instanceof DOMDocument) {
$oNode = $oRoot->createElement($sNodeName);
}
else
{
} else {
$oNode = $oRoot->ownerDocument->createElement($sNodeName);
}
$oRoot->appendChild($oNode);
$aKeys = array_keys($data);
$bNumericKeys = true;
foreach($aKeys as $subkey)
{
if(((int)$subkey) !== $subkey)
{
foreach ($aKeys as $subkey) {
if (((int)$subkey) !== $subkey) {
$bNumericKeys = false;
break;
}
}
if ($bNumericKeys)
{
if ($bNumericKeys) {
$oNode->setAttribute("type", "array");
foreach($data as $key => $value)
{
$this->ToXML($oNode, $value , 'item');
foreach ($data as $key => $value) {
$this->ToXML($oNode, $value, 'item');
}
} else {
foreach ($data as $key => $value) {
$this->ToXML($oNode, $value, $key);
}
}
else
{
foreach($data as $key => $value)
{
$this->ToXML($oNode, $value , $key);
}
}
}
else
{
} else {
$oNode = $oRoot->ownerDocument->createElement($sNodeName);
$oRoot->appendChild($oNode);
$oTextNode = $oRoot->ownerDocument->createTextNode($data);
@@ -113,11 +99,10 @@ class PHPParameters extends Parameters
{
$this->aData = $aData;
}
public function LoadFromFile($sParametersFile)
{
if ($this->aData == null)
{
if ($this->aData == null) {
require_once($sParametersFile);
$this->aData = $ITOP_PARAMS; // Defined in the file loaded just above
}
@@ -139,88 +124,75 @@ class XMLParameters extends Parameters
public function LoadFromFile($sParametersFile)
{
$this->sParametersFile = $sParametersFile;
if ($this->aData == null)
{
if ($this->aData == null) {
libxml_use_internal_errors(true);
$oXML = @simplexml_load_file($this->sParametersFile);
if (!$oXML)
{
$aMessage = array();
foreach(libxml_get_errors() as $oError)
{
if (!$oXML) {
$aMessage = [];
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 = [];
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)
{
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();
$value = [];
// 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();
} elseif ($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;
}
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;
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()."'");
$value = [];
// 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;
}
$val = $this->ReadElement($oChildElement);
$value[$oChildElement->getName()] = $val;
}
break;
break;
case 'int':
case 'integer':
$value = (int)$oElement;
break;
$value = (int)$oElement;
break;
case 'string':
default:
$value = (string)$oElement;
$value = (string)$oElement;
}
return $value;
}
protected function GetAttribute($sAttName, $oElement, $sDefaultValue)
{
$sRet = $sDefaultValue;
foreach($oElement->attributes() as $sKey => $oChildElement)
{
if ((string)$sKey == $sAttName)
{
foreach ($oElement->attributes() as $sKey => $oChildElement) {
if ((string)$sKey == $sAttName) {
$sRet = (string)$oChildElement;
break;
}

View File

@@ -1,2 +1,3 @@
<?php
echo '<h1>PHP works!</h1>';
echo '<h1>PHP works!</h1>';

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -40,4 +41,3 @@ LoginWebPage::DoLogin(true); // Check user rights and prompt if needed (N°1934)
/** @noinspection ForgottenDebugOutputInspection */
phpinfo();
?>

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -28,13 +29,12 @@ require_once(APPROOT.'core/log.class.inc.php');
SetupLog::Enable(APPROOT.'/log/setup.log');
/**
* @uses SetupLog
*/
class SetupPage extends NiceWebPage
{
const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/setuppage/layout';
public const DEFAULT_PAGE_TEMPLATE_REL_PATH = 'pages/backoffice/setuppage/layout';
public function __construct($sTitle)
{
@@ -43,7 +43,7 @@ class SetupPage extends NiceWebPage
$this->LinkScriptFromAppRoot('node_modules/@popperjs/core/dist/umd/popper.js');
$this->LinkScriptFromAppRoot('node_modules/tippy.js/dist/tippy-bundle.umd.js');
$this->LinkScriptFromAppRoot("setup/setup.js");
$this->LinkScriptFromAppRoot("setup/csp-detection.js?itop_version_wiki_syntax=" . utils::GetItopVersionWikiSyntax());
$this->LinkScriptFromAppRoot("setup/csp-detection.js?itop_version_wiki_syntax=".utils::GetItopVersionWikiSyntax());
$this->LinkStylesheetFromAppRoot('css/font-awesome/css/all.min.css');
$this->LinkStylesheetFromAppRoot('css/font-combodo/font-combodo.css');
$this->LinkStylesheetFromAppRoot('node_modules/tippy.js/dist/tippy.css');
@@ -72,7 +72,7 @@ class SetupPage extends NiceWebPage
/**
* Overriden because the application is not fully loaded when the setup is being run
*/
function GetApplicationContext()
public function GetApplicationContext()
{
return '';
}
@@ -104,33 +104,22 @@ class SetupPage extends NiceWebPage
public function form($aData)
{
$this->add("<table class=\"formTable\">\n");
foreach ($aData as $aRow)
{
foreach ($aData as $aRow) {
$this->add("<tr>\n");
if (isset($aRow['label']) && isset($aRow['input']) && isset($aRow['help']))
{
if (isset($aRow['label']) && isset($aRow['input']) && isset($aRow['help'])) {
$this->add("<td class=\"wizlabel\">{$aRow['label']}</td>\n");
$this->add("<td class=\"wizinput\">{$aRow['input']}</td>\n");
$this->add("<td class=\"wizhelp\">{$aRow['help']}</td>\n");
}
else
{
if (isset($aRow['label']) && isset($aRow['help']))
{
} else {
if (isset($aRow['label']) && isset($aRow['help'])) {
$this->add("<td colspan=\"2\" class=\"wizlabel\">{$aRow['label']}</td>\n");
$this->add("<td class=\"wizhelp\">{$aRow['help']}</td>\n");
}
else
{
if (isset($aRow['label']) && isset($aRow['input']))
{
} else {
if (isset($aRow['label']) && isset($aRow['input'])) {
$this->add("<td class=\"wizlabel\">{$aRow['label']}</td>\n");
$this->add("<td colspan=\"2\" class=\"wizinput\">{$aRow['input']}</td>\n");
}
else
{
if (isset($aRow['label']))
{
} else {
if (isset($aRow['label'])) {
$this->add("<td colspan=\"3\" class=\"wizlabel\">{$aRow['label']}</td>\n");
}
}
@@ -145,14 +134,12 @@ class SetupPage extends NiceWebPage
{
$this->add("<h3 class=\"clickable open\" id=\"{$sId}\">$sTitle</h3>");
$this->p('<ul id="'.$sId.'_list">');
foreach ($aItems as $sItem)
{
foreach ($aItems as $sItem) {
$this->p("<li>$sItem</li>\n");
}
$this->p('</ul>');
$this->add_ready_script("$('#{$sId}').on('click', function() { $(this).toggleClass('open'); $('#{$sId}_list').toggle();} );\n");
if (!$bOpen)
{
if (!$bOpen) {
$this->add_ready_script("$('#{$sId}').toggleClass('open'); $('#{$sId}_list').toggle();\n");
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,8 @@ require_once(APPROOT.'/setup/setuppage.class.inc.php');
require_once(APPROOT.'/setup/wizardcontroller.class.inc.php');
require_once(APPROOT.'/setup/wizardsteps.class.inc.php');
class InstallationFileService {
class InstallationFileService
{
/** @var \RunTimeEnvironment $oProductionEnv */
private $oProductionEnv;
@@ -28,17 +29,19 @@ class InstallationFileService {
* @param bool $bInstallationOptionalChoicesChecked : this option is used only when no extensions are selected (ie empty
* $aSelectedExtensions)
*/
public function __construct(string $sInstallationPath, string $sTargetEnvironment='production', array $aSelectedExtensions = [], bool $bInstallationOptionalChoicesChecked=true) {
public function __construct(string $sInstallationPath, string $sTargetEnvironment = 'production', array $aSelectedExtensions = [], bool $bInstallationOptionalChoicesChecked = true)
{
$this->sInstallationPath = $sInstallationPath;
$this->aSelectedModules = [];
$this->aUnSelectedModules = [];
$this->sTargetEnvironment = $sTargetEnvironment;
$this->aSelectedExtensions = $aSelectedExtensions;
$this->aAfterComputationSelectedExtensions = (count($aSelectedExtensions)==0) ? [] : $aSelectedExtensions;
$this->aAfterComputationSelectedExtensions = (count($aSelectedExtensions) == 0) ? [] : $aSelectedExtensions;
$this->bInstallationOptionalChoicesChecked = $bInstallationOptionalChoicesChecked;
}
public function Init(): void {
public function Init(): void
{
clearstatcache();
$this->ProcessDefaultModules();
@@ -47,45 +50,54 @@ class InstallationFileService {
$this->ProcessAutoSelectModules();
}
public function GetProductionEnv(): RunTimeEnvironment {
if (is_null($this->oProductionEnv)){
public function GetProductionEnv(): RunTimeEnvironment
{
if (is_null($this->oProductionEnv)) {
$this->oProductionEnv = new RunTimeEnvironment();
}
return $this->oProductionEnv;
}
public function SetProductionEnv(RunTimeEnvironment $oProductionEnv): void {
public function SetProductionEnv(RunTimeEnvironment $oProductionEnv): void
{
$this->oProductionEnv = $oProductionEnv;
}
public function GetAfterComputationSelectedExtensions(): array {
public function GetAfterComputationSelectedExtensions(): array
{
return $this->aAfterComputationSelectedExtensions;
}
public function SetItopExtensionsMap(ItopExtensionsMap $oItopExtensionsMap): void {
public function SetItopExtensionsMap(ItopExtensionsMap $oItopExtensionsMap): void
{
$this->oItopExtensionsMap = $oItopExtensionsMap;
}
public function GetItopExtensionsMap(): ItopExtensionsMap {
if (is_null($this->oItopExtensionsMap)){
public function GetItopExtensionsMap(): ItopExtensionsMap
{
if (is_null($this->oItopExtensionsMap)) {
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment, true);
}
return $this->oItopExtensionsMap;
}
public function GetAutoSelectModules(): array {
public function GetAutoSelectModules(): array
{
return $this->aAutoSelectModules;
}
public function GetSelectedModules(): array {
public function GetSelectedModules(): array
{
return $this->aSelectedModules;
}
public function GetUnSelectedModules(): array {
public function GetUnSelectedModules(): array
{
return $this->aUnSelectedModules;
}
public function ProcessInstallationChoices(): void {
public function ProcessInstallationChoices(): void
{
$oXMLParameters = new XMLParameters($this->sInstallationPath);
$aSteps = $oXMLParameters->Get('steps', []);
if (! is_array($aSteps)) {
@@ -107,20 +119,21 @@ class InstallationFileService {
}
}
foreach ($this->aSelectedModules as $sModuleId => $sVal){
if (array_key_exists($sModuleId, $this->aUnSelectedModules)){
foreach ($this->aSelectedModules as $sModuleId => $sVal) {
if (array_key_exists($sModuleId, $this->aUnSelectedModules)) {
unset($this->aUnSelectedModules[$sModuleId]);
}
}
}
private function ProcessUnSelectedChoice($aChoiceInfo) {
private function ProcessUnSelectedChoice($aChoiceInfo)
{
if (!is_array($aChoiceInfo)) {
return;
}
$aCurrentModules = $aChoiceInfo["modules"] ?? [];
foreach ($aCurrentModules as $sModuleId){
foreach ($aCurrentModules as $sModuleId) {
$this->aUnSelectedModules[$sModuleId] = true;
}
@@ -151,7 +164,8 @@ class InstallationFileService {
}
}
private function ProcessSelectedChoice($aChoiceInfo, bool $bAllChecked) {
private function ProcessSelectedChoice($aChoiceInfo, bool $bAllChecked)
{
if (!is_array($aChoiceInfo)) {
return;
}
@@ -161,17 +175,17 @@ class InstallationFileService {
$aCurrentModules = $aChoiceInfo["modules"] ?? [];
$sExtensionCode = $aChoiceInfo["extension_code"] ?? null;
if (0 === count($this->aSelectedExtensions)){
if (0 === count($this->aSelectedExtensions)) {
$bSelected = $bAllChecked || $sDefault === "true" || $sMandatory === "true";
if ($bSelected){
$this->aAfterComputationSelectedExtensions[]= $sExtensionCode;
if ($bSelected) {
$this->aAfterComputationSelectedExtensions[] = $sExtensionCode;
}
} else {
$bSelected = $sMandatory === "true" ||
(null !== $sExtensionCode && in_array($sExtensionCode, $this->aSelectedExtensions));
}
foreach ($aCurrentModules as $sModuleId){
foreach ($aCurrentModules as $sModuleId) {
if ($bSelected) {
$this->aSelectedModules[$sModuleId] = true;
} else {
@@ -218,19 +232,19 @@ class InstallationFileService {
}
}
private function GetExtraDirs() : array {
private function GetExtraDirs(): array
{
$aSearchDirs = [];
$aDirs = [
'/datamodels/1.x',
'/datamodels/2.x',
'data/' . $this->sTargetEnvironment . '-modules',
'data/'.$this->sTargetEnvironment.'-modules',
'extensions',
];
foreach ($aDirs as $sRelativeDir){
foreach ($aDirs as $sRelativeDir) {
$sDirPath = APPROOT.$sRelativeDir;
if (is_dir($sDirPath))
{
if (is_dir($sDirPath)) {
$aSearchDirs[] = $sDirPath;
}
}
@@ -238,8 +252,9 @@ class InstallationFileService {
return $aSearchDirs;
}
public function ProcessDefaultModules() : void {
$sProductionModuleDir = APPROOT.'data/' . $this->sTargetEnvironment . '-modules/';
public function ProcessDefaultModules(): void
{
$sProductionModuleDir = APPROOT.'data/'.$this->sTargetEnvironment.'-modules/';
$aAvailableModules = $this->GetProductionEnv()->AnalyzeInstallation(MetaModel::GetConfig(), $this->GetExtraDirs(), false, null);
@@ -255,8 +270,10 @@ class InstallationFileService {
$this->aSelectedModules[$sModuleId] = true;
continue;
}
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'],
$sProductionModuleDir) !== false)); // Some modules (root, datamodel) have no 'root_dir'
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos(
$aModule['root_dir'],
$sProductionModuleDir
) !== false)); // Some modules (root, datamodel) have no 'root_dir'
if ($bIsExtra) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$this->aSelectedModules[$sModuleId] = true;
@@ -265,32 +282,31 @@ class InstallationFileService {
}
}
public function ProcessAutoSelectModules() : void {
foreach($this->GetAutoSelectModules() as $sModuleId => $aModule)
{
public function ProcessAutoSelectModules(): void
{
foreach ($this->GetAutoSelectModules() as $sModuleId => $aModule) {
try {
$bSelected = false;
SetupInfo::SetSelectedModules($this->aSelectedModules);
eval('$bSelected = ('.$aModule['auto_select'].');');
if ($bSelected)
{
if ($bSelected) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$this->aSelectedModules[$sModuleId] = true;
}
}
catch (Exception $e) {
} catch (Exception $e) {
}
}
}
public function CanChooseUnpackageExtension(iTopExtension $oExtension) : bool {
if ($oExtension->sSource === iTopExtension::SOURCE_REMOTE){
public function CanChooseUnpackageExtension(iTopExtension $oExtension): bool
{
if ($oExtension->sSource === iTopExtension::SOURCE_REMOTE) {
SetupLog::Info("Data Extension can be selected", null, ['extension' => $oExtension->sCode]);
return true;
}
$bSelectable = $this->bInstallationOptionalChoicesChecked && ($oExtension->sSource === iTopExtension::SOURCE_MANUAL);
if ($bSelectable){
if ($bSelectable) {
SetupLog::Info("Manual Extension can be selected", null, ['extension' => $oExtension->sCode]);
} else {
SetupLog::Debug("Manual Extension can NOT be selected", null, ['extension' => $oExtension->sCode]);
@@ -299,18 +315,22 @@ class InstallationFileService {
return $bSelectable;
}
public function ProcessExtensionModulesNotSpecifiedInChoices() {
public function ProcessExtensionModulesNotSpecifiedInChoices()
{
/** @var \iTopExtension $oExtension */
foreach($this->GetItopExtensionsMap()->GetAllExtensions() as $oExtension) {
if (in_array($oExtension->sCode, $this->aAfterComputationSelectedExtensions)){
foreach ($this->GetItopExtensionsMap()->GetAllExtensions() as $oExtension) {
if (in_array($oExtension->sCode, $this->aAfterComputationSelectedExtensions)) {
//extension already processed in installation.xml
SetupLog::Info("Extension already processed via installation choices", null,
SetupLog::Info(
"Extension already processed via installation choices",
null,
[
'extension' => $oExtension->sCode,
]) ;
]
) ;
continue;
}
if ($this->CanChooseUnpackageExtension($oExtension)){
if ($this->CanChooseUnpackageExtension($oExtension)) {
if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) === 0)) {
$aCurrentModules = [];
$aUnselectableModules = [];
@@ -332,23 +352,28 @@ class InstallationFileService {
}
if ($bIsExtensionSelectable) {
SetupLog::Debug("Add modules from unpackaged extension", null,
SetupLog::Debug(
"Add modules from unpackaged extension",
null,
[
'extension' => $oExtension->sCode,
'source' => $oExtension->sSource,
'modules to add' => array_keys($aCurrentModules),
]);
]
);
$this->aSelectedModules = array_merge($this->aSelectedModules, $aCurrentModules);
$this->aAfterComputationSelectedExtensions[] = $oExtension->sCode;
} else {
SetupLog::Warning("Unpackaged extension can not be selected due to modules incompatible with installation choices",
SetupLog::Warning(
"Unpackaged extension can not be selected due to modules incompatible with installation choices",
null,
[
'extension' => $oExtension->sCode,
'source' => $oExtension->sSource,
'modules' => array_keys($aCurrentModules),
'unselectable modules' => $aUnselectableModules,
]);
]
);
}
}
}

View File

@@ -1,11 +1,11 @@
<?php
require_once(dirname(__FILE__, 3) . '/approot.inc.php');
require_once(__DIR__ . '/InstallationFileService.php');
require_once(dirname(__FILE__, 3).'/approot.inc.php');
require_once(__DIR__.'/InstallationFileService.php');
function PrintUsageAndExit()
{
echo <<<EOF
echo <<<EOF
Usage: php unattended-install.php --param-file=<path_to_response_file> [--installation_xml=<path_to_installation_xml>] [--use_itop_config]
Options:
@@ -22,27 +22,26 @@ Advanced options:
--clean=1 In case of a first installation, cleanup the environment before proceeding: delete the configuration file, the cache directory, the target directory, the database (default: 0)
--install=0 Set to 0 to perform a dry-run (default: 1)
EOF;
exit(-1);
exit(-1);
}
/////////////////////////////////////////////////
$oCtx = new ContextTag(ContextTag::TAG_SETUP);
$sCleanName = strtolower(trim(PHP_SAPI));
if ($sCleanName !== 'cli')
{
if ($sCleanName !== 'cli') {
echo "Mode CLI only";
exit(-1);
}
if (in_array('--help', $argv)) {
PrintUsageAndExit();
PrintUsageAndExit();
}
$sParamFile = utils::ReadParam('param-file', null, true /* CLI allowed */, 'raw_data') ?? utils::ReadParam('response_file', null, true /* CLI allowed */, 'raw_data');
if (is_null($sParamFile)) {
echo "Missing mandatory argument `--param-file`.\n";
PrintUsageAndExit();
PrintUsageAndExit();
}
$bCheckConsistency = (utils::ReadParam('check-consistency', '0', true /* CLI allowed */) == '1');
@@ -56,8 +55,7 @@ $oParams = new XMLParameters($sParamFile);
$sMode = $oParams->Get('mode');
$sTargetEnvironment = $oParams->Get('target_env', '');
if ($sTargetEnvironment == '')
{
if ($sTargetEnvironment == '') {
$sTargetEnvironment = 'production';
}
@@ -106,12 +104,12 @@ SetupLog::Info($sMsg, null, $aSelectedModules);
// Configuration file
$sConfigFile = APPCONF.$sTargetEnvironment.'/'.ITOP_CONFIG_FILE;
$bUseItopConfig = in_array('--use_itop_config', $argv);
if ($bUseItopConfig && file_exists($sConfigFile)){
if ($bUseItopConfig && file_exists($sConfigFile)) {
//unattended run based on db settings coming from itop configuration
copy($sConfigFile, "$sConfigFile.backup");
$oConfig = new Config($sConfigFile);
$aDBXmlSettings = $oParams->Get('database', array());
$aDBXmlSettings = $oParams->Get('database', []);
$aDBXmlSettings ['server'] = $oConfig->Get('db_host');
$aDBXmlSettings ['user'] = $oConfig->Get('db_user');
$aDBXmlSettings ['pwd'] = $oConfig->Get('db_pwd');
@@ -127,7 +125,7 @@ if ($bUseItopConfig && file_exists($sConfigFile)){
'source_dir' => 'source_dir',
'graphviz_path' => 'graphviz_path',
];
foreach($aFields as $sSetupField => $sConfField){
foreach ($aFields as $sSetupField => $sConfField) {
$oParams->Set($sSetupField, $oConfig->Get($sConfField));
}
@@ -135,7 +133,7 @@ if ($bUseItopConfig && file_exists($sConfigFile)){
$oParams->Set('language', $oConfig->GetDefaultLanguage());
} else {
//unattended run based on db settings coming from response_file (XML file)
$aDBXmlSettings = $oParams->Get('database', array());
$aDBXmlSettings = $oParams->Get('database', []);
}
$sDBServer = $aDBXmlSettings['server'];
@@ -146,36 +144,29 @@ $sDBPrefix = $aDBXmlSettings['prefix'];
$bDBTlsEnabled = $aDBXmlSettings['db_tls_enabled'];
$sDBTlsCa = $aDBXmlSettings['db_tls_ca'];
if ($sMode == 'install')
{
if ($sMode == 'install') {
echo "Installation mode detected.\n";
$bClean = utils::ReadParam('clean', false, true /* CLI allowed */);
if ($bClean)
{
if ($bClean) {
echo "Cleanup mode detected.\n";
if (file_exists($sConfigFile))
{
if (file_exists($sConfigFile)) {
echo "Trying to delete the configuration file: '$sConfigFile'.\n";
@chmod($sConfigFile, 0770); // RWX for owner and group, nothing for others
unlink($sConfigFile);
}
else
{
} else {
echo "No config file to delete ($sConfigFile does not exist).\n";
}
// Starting with iTop 2.7.0, a failed setup leaves some lock files, let's remove them
$aLockFiles = array(
$aLockFiles = [
'data/.readonly' => 'read-only lock file',
'data/.maintenance' => 'maintenance mode lock file',
);
foreach($aLockFiles as $sFile => $sDescription)
{
$sLockFile = APPROOT.$sFile;
if (file_exists($sLockFile))
{
];
foreach ($aLockFiles as $sFile => $sDescription) {
$sLockFile = APPROOT.$sFile;
if (file_exists($sLockFile)) {
echo "Trying to delete the $sDescription: '$sLockFile'.\n";
unlink($sLockFile);
}
@@ -184,69 +175,49 @@ if ($sMode == 'install')
// Starting with iTop 2.6.0, let's remove the cache directory as well
// Can cause some strange issues in the setup (apparently due to the Dict class being automatically loaded ??)
$sCacheDir = APPROOT.'data/cache-'.$sTargetEnvironment;
if (file_exists($sCacheDir))
{
if (is_dir($sCacheDir))
{
echo "Emptying the cache directory '$sCacheDir'.\n";
SetupUtils::tidydir($sCacheDir);
}
else
{
if (file_exists($sCacheDir)) {
if (is_dir($sCacheDir)) {
echo "Emptying the cache directory '$sCacheDir'.\n";
SetupUtils::tidydir($sCacheDir);
} else {
die("ERROR the cache directory '$sCacheDir' exists, but is NOT a directory !!!\nExiting.\n");
}
}
// env-xxx directory
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
if (file_exists($sTargetDir))
{
if (is_dir($sTargetDir))
{
echo "Emptying the target directory '$sTargetDir'.\n";
SetupUtils::tidydir($sTargetDir);
}
else
{
if (file_exists($sTargetDir)) {
if (is_dir($sTargetDir)) {
echo "Emptying the target directory '$sTargetDir'.\n";
SetupUtils::tidydir($sTargetDir);
} else {
die("ERROR the target dir '$sTargetDir' exists, but is NOT a directory !!!\nExiting.\n");
}
}
else
{
} else {
echo "No target directory to delete ($sTargetDir does not exist).\n";
}
if ($sDBPrefix != '')
{
if ($sDBPrefix != '') {
die("Cleanup not implemented for a partial database (prefix= '$sDBPrefix')\nExiting.");
}
try
{
try {
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
if ($oMysqli->select_db($sDBName))
{
if ($oMysqli->select_db($sDBName)) {
echo "Deleting database '$sDBName'\n";
$oMysqli->query("DROP DATABASE `$sDBName`");
}
else
{
} else {
echo "The database '$sDBName' does not seem to exist. Nothing to cleanup.\n";
}
}
catch (MySQLException $e)
{
die($e->getMessage()."\nExiting");
} catch (MySQLException $e) {
die($e->getMessage()."\nExiting");
}
}
}
else
{
} else {
//use settings from itop conf
$sTargetEnvironment = $oParams->Get('target_env', '');
if ($sTargetEnvironment == '')
{
if ($sTargetEnvironment == '') {
$sTargetEnvironment = 'production';
}
$sTargetDir = APPROOT.'env-'.$sTargetEnvironment;
@@ -260,10 +231,8 @@ $sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
foreach($aChecks as $oCheckResult)
{
switch ($oCheckResult->iSeverity)
{
foreach ($aChecks as $oCheckResult) {
switch ($oCheckResult->iSeverity) {
case CheckResult::ERROR:
$bHasErrors = true;
$sHeader = "Error";
@@ -284,15 +253,13 @@ foreach($aChecks as $oCheckResult)
break;
}
echo $sHeader.": ".$oCheckResult->sLabel;
if (strlen($oCheckResult->sDescription))
{
if (strlen($oCheckResult->sDescription)) {
echo ' - '.$oCheckResult->sDescription;
}
echo "\n";
}
if ($bHasErrors)
{
if ($bHasErrors) {
echo "Encountered stopper issues. Aborting...\n";
$sLogMsg = "Encountered stopper issues. Aborting...";
echo "$sLogMsg\n";
@@ -303,103 +270,76 @@ if ($bHasErrors)
$bFoundIssues = false;
$bInstall = utils::ReadParam('install', true, true /* CLI allowed */);
if ($bInstall)
{
if ($bInstall) {
echo "Starting the unattended installation...\n";
$oWizard = new ApplicationInstaller($oParams);
$bRes = $oWizard->ExecuteAllSteps();
if (!$bRes)
{
if (!$bRes) {
echo "\nencountered installation issues!";
$bFoundIssues = true;
}
else
{
try
{
} else {
try {
$oMysqli = CMDBSource::GetMysqliInstance($sDBServer, $sDBUser, $sDBPwd, null, $bDBTlsEnabled, $sDBTlsCa, true);
if ($oMysqli->select_db($sDBName))
{
if ($oMysqli->select_db($sDBName)) {
// Check the presence of a table to record information about the MTP (from the Designer)
$sDesignerUpdatesTable = $sDBPrefix.'priv_designer_update';
$sSQL = "SELECT id FROM `$sDesignerUpdatesTable`";
if ($oMysqli->query($sSQL) !== false)
{
if ($oMysqli->query($sSQL) !== false) {
// Record the Designer Udpates in the priv_designer_update table
$sDeltaFile = APPROOT.'data/'.$sTargetEnvironment.'.delta.xml';
if (is_readable($sDeltaFile))
{
if (is_readable($sDeltaFile)) {
// Retrieve the revision
$oDoc = new DOMDocument();
$oDoc->load($sDeltaFile);
$iRevision = 0;
$iRevision = $oDoc->firstChild->getAttribute('revision_id');
if ($iRevision > 0) // Safety net, just in case...
{
if ($iRevision > 0) { // Safety net, just in case...
$sDate = date('Y-m-d H:i:s');
$sSQL = "INSERT INTO `$sDesignerUpdatesTable` (revision_id, compilation_date, comment) VALUES ($iRevision, '$sDate', 'Deployed using unattended.php.')";
if ($oMysqli->query($sSQL) !== false)
{
if ($oMysqli->query($sSQL) !== false) {
echo "\nDesigner update (MTP at revision $iRevision) successfully recorded.\n";
}
else
{
} else {
echo "\nFailed to record designer updates(".$oMysqli->error.").\n";
}
}
else
{
} else {
echo "\nFailed to read the revision from $sDeltaFile file. No designer update information will be recorded.\n";
}
}
else
{
} else {
echo "\nNo $sDeltaFile file (or the file is not accessible). No designer update information to record.\n";
}
}
}
}
catch (MySQLException $e)
{
// Continue anyway
} catch (MySQLException $e) {
// Continue anyway
}
}
}
else
{
} else {
echo "No installation requested.\n";
}
if (!$bFoundIssues && $bCheckConsistency)
{
if (!$bFoundIssues && $bCheckConsistency) {
echo "Checking data model consistency.\n";
ob_start();
$sCheckRes = '';
try
{
try {
MetaModel::CheckDefinitions(false);
$sCheckRes = ob_get_clean();
}
catch(Exception $e)
{
} catch (Exception $e) {
$sCheckRes = ob_get_clean()."\nException: ".$e->getMessage();
}
if (strlen($sCheckRes) > 0)
{
if (strlen($sCheckRes) > 0) {
echo $sCheckRes;
echo "\nfound consistency issues!";
$bFoundIssues = true;
}
}
if (! $bFoundIssues)
{
if (! $bFoundIssues) {
// last line: used to check the install
// the only way to track issues in case of Fatal error or even parsing error!
$sLogMsg = "installed!";
if ($bUseItopConfig && is_file("$sConfigFile.backup"))
{
if ($bUseItopConfig && is_file("$sConfigFile.backup")) {
echo "\nuse config file provided by backup in $sConfigFile.";
copy("$sConfigFile.backup", $sConfigFile);
}

View File

@@ -1,4 +1,5 @@
<?php
/**
* Copyright (C) 2013-2024 Combodo SAS
*
@@ -42,12 +43,10 @@ ini_set('display_errors', true);
ini_set('display_startup_errors', true);
date_default_timezone_set('Europe/Paris'); // Just to avoid a warning if the timezone is not set in php.ini
/////////////////////////////////////////////////////////////////////
// Fake functions to protect the first run of the installer
// in case the PHP JSON module is not installed...
if (!function_exists('json_encode'))
{
if (!function_exists('json_encode')) {
function json_encode($value, $options = null)
{
return '[]';
@@ -56,7 +55,7 @@ if (!function_exists('json_encode'))
if (!function_exists('json_decode')) {
function json_decode($json, $assoc = null)
{
return array();
return [];
}
}
/////////////////////////////////////////////////////////////////////

View File

@@ -1,9 +1,10 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -37,7 +38,7 @@ class WizardController
protected $sInitialStepClass;
protected $sInitialState;
protected $aParameters;
/**
* Initiailization of the wizard controller
* @param string $sInitialStepClass Class of the initial step/page of the wizard
@@ -47,10 +48,10 @@ class WizardController
{
$this->sInitialStepClass = $sInitialStepClass;
$this->sInitialState = $sInitialState;
$this->aParameters = array();
$this->aSteps = array();
$this->aParameters = [];
$this->aSteps = [];
}
/**
* Pushes information about the current step onto the stack
* @param hash $aStepInfo Array('class' => , 'state' => )
@@ -59,7 +60,7 @@ class WizardController
{
array_push($this->aSteps, $aStepInfo);
}
/**
* Removes information about the previous step from the stack
* @return hash Array('class' => , 'state' => )
@@ -68,7 +69,7 @@ class WizardController
{
return array_pop($this->aSteps);
}
/**
* Reads a "persistent" parameter from the wizard's context
* @param string $sParamCode The code identifying this parameter
@@ -91,7 +92,7 @@ class WizardController
public function GetParamForConfigArray(): array
{
/** @noinspection PhpUnnecessaryLocalVariableInspection */
$aParamValues = array(
$aParamValues = [
'db_server' => $this->GetParameter('db_server', ''),
'db_user' => $this->GetParameter('db_user', ''),
'db_pwd' => $this->GetParameter('db_pwd', ''),
@@ -99,7 +100,7 @@ class WizardController
'db_prefix' => $this->GetParameter('db_prefix', ''),
'db_tls_enabled' => $this->GetParameter('db_tls_enabled', false),
'db_tls_ca' => $this->GetParameter('db_tls_ca', ''),
);
];
return $aParamValues;
}
@@ -114,7 +115,7 @@ class WizardController
{
$this->aParameters[$sParamCode] = $value;
}
/**
* Stores the value of the page's parameter in a "persistent" parameter in the wizard's context
* @param string $sParamCode The code identifying this parameter
@@ -126,7 +127,7 @@ class WizardController
$value = utils::ReadParam($sParamCode, $defaultValue, false, $sSanitizationFilter);
$this->aParameters[$sParamCode] = $value;
}
/**
* Starts the wizard by displaying it in its initial state
*/
@@ -146,23 +147,17 @@ class WizardController
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
/** @var \WizardStep $oStep */
$oStep = new $sCurrentStepClass($this, $sCurrentState);
if ($oStep->ValidateParams())
{
$this->PushStep(array('class' => $sCurrentStepClass, 'state' => $sCurrentState));
if ($oStep->ValidateParams()) {
$this->PushStep(['class' => $sCurrentStepClass, 'state' => $sCurrentState]);
$aPossibleSteps = $oStep->GetPossibleSteps();
$aNextStepInfo = $oStep->ProcessParams(true); // true => moving forward
if (in_array($aNextStepInfo['class'], $aPossibleSteps))
{
if (in_array($aNextStepInfo['class'], $aPossibleSteps)) {
$oNextStep = new $aNextStepInfo['class']($this, $aNextStepInfo['state']);
$this->DisplayStep($oNextStep);
}
else
{
} else {
throw new Exception("Internal error: Unexpected next step '{$aNextStepInfo['class']}'. The possible next steps are: ".implode(', ', $aPossibleSteps));
}
}
else
{
} else {
$this->DisplayStep($oStep);
}
}
@@ -176,13 +171,13 @@ class WizardController
$sCurrentState = utils::ReadParam('_state', $this->sInitialState);
$oStep = new $sCurrentStepClass($this, $sCurrentState);
$aNextStepInfo = $oStep->ProcessParams(false); // false => Moving backwards
// 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
@@ -190,14 +185,11 @@ class WizardController
protected function DisplayStep(WizardStep $oStep)
{
$oPage = new SetupPage($oStep->GetTitle());
if ($oStep->RequiresWritableConfig())
{
if ($oStep->RequiresWritableConfig()) {
$sConfigFile = utils::GetConfigFilePath();
if (file_exists($sConfigFile))
{
if (file_exists($sConfigFile)) {
// The configuration file already exists
if (!is_writable($sConfigFile))
{
if (!is_writable($sConfigFile)) {
SetupUtils::ExitReadOnlyMode(false); // Reset readonly mode in case of problem
SetupUtils::EraseSetupToken();
$sRelativePath = utils::GetConfigFilePathRelative();
@@ -215,7 +207,7 @@ HTML;
// Prevent token creation
exit;
}
}
}
}
$oPage->LinkScriptFromAppRoot('setup/setup.js');
$oPage->add_script("function CanMoveForward()\n{\n".$oStep->JSCanMoveForward()."\n}\n");
@@ -247,7 +239,7 @@ HTML;
// Hack to have the "Next >>" button, be the default button, since the first submit button in the form is the default one
$oPage->add_ready_script(
<<<EOF
<<<EOF
$('form').each(function () {
var thisform = $(this);
@@ -292,24 +284,23 @@ on the page's parameters
$oContextTag = new ContextTag(ContextTag::TAG_SETUP);
$sOperation = utils::ReadParam('operation');
$this->aParameters = utils::ReadParam('_params', array(), false, 'raw_data');
$this->aParameters = utils::ReadParam('_params', [], false, 'raw_data');
$this->aSteps = json_decode(utils::ReadParam('_steps', '[]', false, 'raw_data'), true /* bAssoc */);
switch($sOperation)
{
switch ($sOperation) {
case 'next':
$this->Next();
break;
$this->Next();
break;
case 'back':
$this->Back();
break;
$this->Back();
break;
default:
$this->Start();
$this->Start();
}
}
/**
* Provides information about the structure/workflow of the wizard by listing
* the possible list of 'steps' and their dependencies
@@ -318,22 +309,24 @@ on the page's parameters
*/
public function DumpStructure($sStep = '', $aAllSteps = null)
{
if ($aAllSteps == null) $aAllSteps = array();
if ($sStep == '') $sStep = $this->sInitialStepClass;
if ($aAllSteps == null) {
$aAllSteps = [];
}
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);
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
@@ -345,25 +338,20 @@ on the page's parameters
$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 = [$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)
{
foreach ($aAllSteps as $sStep => $aNextSteps) {
$oStep = new $sStep($this, '');
$sOutput .= "\t$sStep [ label = \"".$oStep->GetTitle()."\"];\n";
if (count($aNextSteps) > 0)
{
foreach($aNextSteps as $sNextStep)
{
if (count($aNextSteps) > 0) {
foreach ($aNextSteps as $sNextStep) {
$sOutput .= "\t$sStep -> $sNextStep;\n";
}
}
@@ -394,18 +382,18 @@ abstract class WizardStep
* @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 "<input/>" fields, but no "<form>...</form>" tag
@@ -445,7 +433,7 @@ abstract class WizardStep
* @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
@@ -454,7 +442,7 @@ abstract class WizardStep
{
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
@@ -463,7 +451,7 @@ abstract class WizardStep
{
return true;
}
/**
* Tells whether the "Next" button should be enabled interactively
* @return string A piece of javascript code returning either true or false
@@ -472,7 +460,7 @@ abstract class WizardStep
{
return 'return true;';
}
/**
* Returns the label for the " Next >> " button
* @return string The label for the button
@@ -481,7 +469,7 @@ abstract class WizardStep
{
return 'Next';
}
/**
* Tells whether this step/state allows to go back or not
* @return boolean True if the '<< Back' button should be displayed
@@ -490,7 +478,7 @@ abstract class WizardStep
{
return true;
}
/**
* Tells whether the "Back" button should be enabled interactively
* @return string A piece of javascript code returning either true or false
@@ -523,9 +511,9 @@ abstract class WizardStep
* 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();
*
@@ -535,12 +523,12 @@ class Step1 extends WizardStep
{
return 'Welcome';
}
public function GetPossibleSteps()
{
return array('Step2', 'Step2bis');
}
public function ProcessParams($bMoveForward = true)
{
$sNextStep = '';
@@ -554,11 +542,11 @@ class Step1 extends WizardStep
{
$this->oWizard->SetParameter('install_mode', 'upgrade');
$sNextStep = 'Step2bis';
}
return array('class' => $sNextStep, 'state' => '');
}
public function Display(WebPage $oPage)
{
$oPage->p('This is Step 1!');
@@ -576,17 +564,17 @@ class Step2 extends WizardStep
{
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)');
@@ -599,12 +587,12 @@ class Step2bis extends WizardStep
{
return 'Upgrade Parameters';
}
public function GetPossibleSteps()
{
return array('Step2ter');
}
public function ProcessParams($bMoveForward = true)
{
$sUpgradeInfo = utils::ReadParam('upgrade_info');
@@ -613,7 +601,7 @@ class Step2bis extends WizardStep
$this->oWizard->SetParameter('additional_upgrade_info', $sAdditionalUpgradeInfo);
return array('class' => 'Step2ter', 'state' => '');
}
public function Display(WebPage $oPage)
{
$oPage->p('This is Step 2bis! (Upgrade)');
@@ -621,18 +609,18 @@ class Step2bis extends WizardStep
$oPage->p('Type your name here: <input type="text" id="upgrade_info" name="upgrade_info" value="'.$sUpgradeInfo.'" size="20"/><span id="v_upgrade_info"></span>');
$sAdditionalUpgradeInfo = $this->oWizard->GetParameter('additional_upgrade_info', '');
$oPage->p('The installer replies: <input type="text" name="additional_upgrade_info" value="'.$sAdditionalUpgradeInfo.'" size="20"/>');
$oPage->add_ready_script("$('#upgrade_info').change(function() {
$('#v_upgrade_info').html('<img src=\"../images/indicator.gif\"/>');
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(
<<<EOF
$("#v_upgrade_info").html('');
@@ -648,17 +636,17 @@ class Step2ter extends WizardStep
{
return 'Additional Upgrade Info';
}
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 2ter! (Upgrade)');
@@ -671,22 +659,22 @@ class Step3 extends WizardStep
{
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;

File diff suppressed because it is too large Load Diff

View File

@@ -1,9 +1,10 @@
<?php
// Copyright (C) 2010-2024 Combodo SAS
//
// This file is part of iTop.
//
// iTop is free software; you can redistribute it and/or modify
// iTop is free software; you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
@@ -16,7 +17,6 @@
// You should have received a copy of the GNU Affero General Public License
// along with iTop. If not, see <http://www.gnu.org/licenses/>
/**
* Load XML data from a set of files
*
@@ -24,7 +24,7 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
define ('KEYS_CACHE_FILE', APPROOT.'data/keyscache.tmp');
define('KEYS_CACHE_FILE', APPROOT.'data/keyscache.tmp');
/**
* Class to load sets of objects from XML files into the database
* XML files can be produced by the 'export' web service or by any other means
@@ -49,17 +49,17 @@ class XMLDataLoader
public function __construct()
{
$this->m_aKeys = array();
$this->m_aObjectsCache = array();
$this->m_aKeys = [];
$this->m_aObjectsCache = [];
$this->m_oChange = null;
$this->m_sCacheFileName = KEYS_CACHE_FILE;
$this->LoadKeysCache();
$this->m_bSessionActive = true;
$this->m_aErrors = array();
$this->m_aWarnings = array();
$this->m_aErrors = [];
$this->m_aWarnings = [];
$this->m_iCountCreated = 0;
}
public function StartSession($oChange)
{
// Do cleanup any existing cache file (shall not be necessary unless a setup was interrupted abruptely)
@@ -68,22 +68,17 @@ class XMLDataLoader
$this->m_oChange = $oChange;
$this->m_bSessionActive = true;
}
public function EndSession($bStrict = false)
{
$this->ResolveExternalKeys();
$this->m_bSessionActive = false;
if (count($this->m_aErrors) > 0)
{
if (count($this->m_aErrors) > 0) {
return false;
}
elseif ($bStrict && count($this->m_aWarnings) > 0)
{
} elseif ($bStrict && count($this->m_aWarnings) > 0) {
return false;
}
else
{
} else {
return true;
}
}
@@ -102,81 +97,70 @@ class XMLDataLoader
{
return $this->m_iCountCreated;
}
public function __destruct()
{
// Stopping in the middle of a session, let's save the context information
if ($this->m_bSessionActive)
{
if ($this->m_bSessionActive) {
$this->SaveKeysCache();
}
else
{
} else {
$this->ClearKeysCache();
}
}
/**
* Stores the keys & object cache in a file
*/
protected function SaveKeysCache()
{
if (!is_dir(APPROOT.'data'))
{
if (!is_dir(APPROOT.'data')) {
mkdir(APPROOT.'data');
}
$hFile = @fopen($this->m_sCacheFileName, 'w');
if ($hFile !== false)
{
$sData = serialize( array('keys' => $this->m_aKeys,
if ($hFile !== false) {
$sData = serialize(['keys' => $this->m_aKeys,
'objects' => $this->m_aObjectsCache,
'change' => $this->m_oChange,
'errors' => $this->m_aErrors,
'warnings' => $this->m_aWarnings,
));
]);
fwrite($hFile, $sData);
fclose($hFile);
}
else
{
} else {
throw new Exception("Cannot write to file: '{$this->m_sCacheFileName}'");
}
}
/**
* Loads the keys & object cache from the tmp file
*/
protected function LoadKeysCache()
{
$sFileContent = @file_get_contents($this->m_sCacheFileName);
if (!empty($sFileContent))
{
if (!empty($sFileContent)) {
$aCache = unserialize($sFileContent);
$this->m_aKeys = $aCache['keys'];
$this->m_aObjectsCache = $aCache['objects'];
$this->m_aObjectsCache = $aCache['objects'];
$this->m_oChange = $aCache['change'];
$this->m_aErrors = $aCache['errors'];
$this->m_aWarnings = $aCache['warnings'];
}
}
}
/**
* Remove the tmp file used to store the keys cache
*/
protected function ClearKeysCache()
{
if(is_file($this->m_sCacheFileName))
{
if (is_file($this->m_sCacheFileName)) {
unlink($this->m_sCacheFileName);
}
else
{
} else {
//echo "<p>Hm, it looks like the file does not exist!!!</p>";
}
$this->m_aKeys = array();
$this->m_aObjectsCache = array();
}
$this->m_aKeys = [];
$this->m_aObjectsCache = [];
}
/**
* Helper function to load the objects from a standard XML file into the database
*
@@ -186,7 +170,7 @@ class XMLDataLoader
*
* @since 3.0.0 Added $bSearch parameter
*/
function LoadFile($sFilePath, $bUpdateKeyCacheOnly = false, bool $bSearch = false)
public function LoadFile($sFilePath, $bUpdateKeyCacheOnly = false, bool $bSearch = false)
{
global $aKeys;
@@ -197,15 +181,15 @@ class XMLDataLoader
throw(new Exception("Unable to load xml file - $sFilePath"));
}
$aReplicas = array();
$aReplicas = [];
foreach ($oXml as $sClass => $oXmlObj) {
if (!MetaModel::IsValidClass($sClass)) {
SetupLog::Error("Unknown class - $sClass");
throw(new Exception("Unknown class - $sClass"));
}
$iSrcId = (integer)$oXmlObj['id']; // Mandatory to cast
$iSrcId = (int)$oXmlObj['id']; // Mandatory to cast
// Import algorithm
// Here enumerate all the attributes of the object
// for all attribute that is neither an external field
@@ -216,52 +200,38 @@ class XMLDataLoader
// Once all the objects have been created re-assign all the external keys to
// their actual Ids
$iExistingId = $this->GetObjectKey($sClass, $iSrcId);
if ($iExistingId != 0)
{
if ($iExistingId != 0) {
$oTargetObj = MetaModel::GetObject($sClass, $iExistingId);
}
else
{
} else {
$oTargetObj = MetaModel::NewObject($sClass);
}
foreach($oXmlObj as $sAttCode => $oSubNode)
{
if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
{
foreach ($oXmlObj as $sAttCode => $oSubNode) {
if (!MetaModel::IsValidAttCode($sClass, $sAttCode)) {
$sMsg = "Unknown attribute code - $sClass/$sAttCode";
continue; // ignore silently...
}
$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar()))
{
if ($oAttDef->IsExternalKey())
{
if (substr(trim($oSubNode), 0, 6) == 'SELECT')
{
if (($oAttDef->IsWritable()) && ($oAttDef->IsScalar())) {
if ($oAttDef->IsExternalKey()) {
if (substr(trim($oSubNode), 0, 6) == 'SELECT') {
$sQuery = trim($oSubNode);
$oSet = new DBObjectSet(DBObjectSearch::FromOQL($sQuery));
$iMatches = $oSet->Count();
if ($iMatches == 1)
{
if ($iMatches == 1) {
$oFoundObject = $oSet->Fetch();
$iExtKey = $oFoundObject->GetKey();
}
else
{
} else {
$sMsg = "Ext key not reconcilied - $sClass/$iSrcId - $sAttCode: '".$sQuery."' - found $iMatches matche(s)";
SetupLog::Error($sMsg);
$this->m_aErrors[] = $sMsg;
$iExtKey = 0;
}
}
else
{
$iDstObj = (integer)($oSubNode);
} else {
$iDstObj = (int)($oSubNode);
// Attempt to find the object in the list of loaded objects
$iExtKey = $this->GetObjectKey($oAttDef->GetTargetClass(), $iDstObj);
if ($iExtKey == 0)
{
if ($iExtKey == 0) {
$iExtKey = -$iDstObj; // Convention: Unresolved keys are stored as negative !
$oTargetObj->RegisterAsDirty();
}
@@ -269,31 +239,23 @@ class XMLDataLoader
}
//$oTargetObj->CheckValue($sAttCode, $iExtKey);
$oTargetObj->Set($sAttCode, $iExtKey);
}
elseif ($oAttDef instanceof AttributeBlob)
{
} elseif ($oAttDef instanceof AttributeBlob) {
$sMimeType = (string) $oSubNode->mimetype;
$sFileName = (string) $oSubNode->filename;
$data = base64_decode((string) $oSubNode->data);
$oDoc = new ormDocument($data, $sMimeType, $sFileName);
$oTargetObj->Set($sAttCode, $oDoc);
}
elseif ($oAttDef instanceof AttributeTagSet)
{
} elseif ($oAttDef instanceof AttributeTagSet) {
// TODO
}
else
{
} else {
$value = (string)$oSubNode;
if ($value == '')
{
if ($value == '') {
$value = $oAttDef->GetNullValue();
}
$res = $oTargetObj->CheckValue($sAttCode, $value);
if ($res !== true)
{
if ($res !== true) {
// $res contains the error description
$sMsg = "Value not allowed - $sClass/$iSrcId - $sAttCode: '".$oSubNode."' ; $res";
SetupLog::Error($sMsg);
@@ -307,153 +269,123 @@ class XMLDataLoader
}
return true;
}
/**
* Get the new ID of an object in the database given its original ID
* This may fail (return 0) if the object has not yet been created in the database
* This is why the order of the import may be important
*/
* This is why the order of the import may be important
*/
protected function GetObjectKey($sClass, $iSrcId)
{
if (isset($this->m_aKeys[$sClass]) && isset($this->m_aKeys[$sClass][$iSrcId]))
{
if (isset($this->m_aKeys[$sClass]) && isset($this->m_aKeys[$sClass][$iSrcId])) {
return $this->m_aKeys[$sClass][$iSrcId];
}
return 0;
}
/**
* Store an object in the database and remember the mapping
* between its original ID and the newly created ID in the database
*/
*/
protected function StoreObject($sClass, $oTargetObj, $iSrcId, $bSearch = false, $bUpdateKeyCacheOnly = false)
{
$iObjId = 0;
try
{
if ($bSearch)
{
try {
if ($bSearch) {
// Check if the object does not already exist, based on its usual reconciliation keys...
$aReconciliationKeys = MetaModel::GetReconcKeys($sClass);
if (count($aReconciliationKeys) > 0)
{
if (count($aReconciliationKeys) > 0) {
// Some reconciliation keys have been defined, use them to search for the object
$oSearch = new DBObjectSearch($sClass);
$iConditionsCount = 0;
foreach($aReconciliationKeys as $sAttCode)
{
if ($oTargetObj->Get($sAttCode) != '')
{
foreach ($aReconciliationKeys as $sAttCode) {
if ($oTargetObj->Get($sAttCode) != '') {
$oSearch->AddCondition($sAttCode, $oTargetObj->Get($sAttCode), '=');
$iConditionsCount++;
}
}
if ($iConditionsCount > 0) // Search only if there are some valid conditions...
{
if ($iConditionsCount > 0) { // Search only if there are some valid conditions...
$oSet = new DBObjectSet($oSearch);
if ($oSet->count() == 1)
{
if ($oSet->count() == 1) {
// The object already exists, reuse it
$oExistingObject = $oSet->Fetch();
$iObjId = $oExistingObject->GetKey();
}
}
}
}
if ($iObjId == 0)
{
if($oTargetObj->IsNew())
{
if (!$bUpdateKeyCacheOnly)
{
$iObjId = $oTargetObj->DBInsertNoReload();
}
if ($iObjId == 0) {
if ($oTargetObj->IsNew()) {
if (!$bUpdateKeyCacheOnly) {
$iObjId = $oTargetObj->DBInsertNoReload();
$this->m_iCountCreated++;
}
}
else
{
} else {
$iObjId = $oTargetObj->GetKey();
if (!$bUpdateKeyCacheOnly)
{
if (!$bUpdateKeyCacheOnly) {
$oTargetObj->DBUpdate();
}
}
}
}
catch(Exception $e)
{
}
} catch (Exception $e) {
SetupLog::Error("An object could not be recorded - $sClass/$iSrcId - ".$e->getMessage());
$this->m_aErrors[] = "An object could not be recorded - $sClass/$iSrcId - ".$e->getMessage();
}
$aParentClasses = MetaModel::EnumParentClasses($sClass);
$aParentClasses[] = $sClass;
foreach($aParentClasses as $sObjClass)
{
foreach ($aParentClasses as $sObjClass) {
$this->m_aKeys[$sObjClass][$iSrcId] = $iObjId;
}
$this->m_aObjectsCache[$sClass][$iObjId] = $oTargetObj;
}
/**
* Maps an external key to its (newly created) value
*/
protected function ResolveExternalKeys()
{
/**
* @var string $sClass
* @var \CMDBObject[] $oObjList
*/
foreach($this->m_aObjectsCache as $sClass => $oObjList)
{
foreach($oObjList as $oTargetObj)
{
foreach ($this->m_aObjectsCache as $sClass => $oObjList) {
foreach ($oObjList as $oTargetObj) {
$bChanged = false;
$sClass = get_class($oTargetObj);
foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode=>$oAttDef)
{
if ( ($oAttDef->IsExternalKey()) && ($oTargetObj->Get($sAttCode) < 0) ) // Convention unresolved key = negative
{
foreach (MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) {
if (($oAttDef->IsExternalKey()) && ($oTargetObj->Get($sAttCode) < 0)) { // Convention unresolved key = negative
$sTargetClass = $oAttDef->GetTargetClass();
$iTempKey = $oTargetObj->Get($sAttCode);
$iExtKey = $this->GetObjectKey($sTargetClass, -$iTempKey);
if ($iExtKey == 0)
{
if ($iExtKey == 0) {
$sMsg = "unresolved extkey in $sClass::".$oTargetObj->GetKey()."(".$oTargetObj->GetName().")::$sAttCode=$sTargetClass::$iTempKey";
SetupLog::Warning($sMsg);
$this->m_aWarnings[] = $sMsg;
//echo "<pre>aKeys[".$sTargetClass."]:\n";
//print_r($this->m_aKeys[$sTargetClass]);
//echo "</pre>\n";
}
else
{
} else {
$bChanged = true;
$oTargetObj->Set($sAttCode, $iExtKey);
}
}
}
if ($bChanged)
{
try
{
if (is_subclass_of($oTargetObj, 'CMDBObject'))
{
if ($bChanged) {
try {
if (is_subclass_of($oTargetObj, 'CMDBObject')) {
$oTargetObj::SetCurrentChange($this->m_oChange);
}
$oTargetObj->DBUpdate();
}
catch(Exception $e)
{
} catch (Exception $e) {
$this->m_aErrors[] = "The object changes could not be tracked - $sClass/$iExtKey - ".$e->getMessage();
}
}
}
}
return true;
}
}
?>