Merge branch 'support/3.2' into develop

This commit is contained in:
odain
2025-11-07 20:33:14 +01:00
1837 changed files with 33034 additions and 34549 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

@@ -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,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');
}
}
}

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();

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,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,21 +225,18 @@ 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;
}
@@ -284,10 +253,10 @@ abstract class ModuleInstallerAPI
}
// 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
@@ -317,25 +286,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");
}
}

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;