mirror of
https://github.com/Combodo/iTop.git
synced 2026-06-20 06:46:36 +02:00
Compare commits
15 Commits
feature/96
...
feature/lo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3244658686 | ||
|
|
67792155dd | ||
|
|
a23b9aa596 | ||
|
|
017481af21 | ||
|
|
4f70441618 | ||
|
|
fb6a332bd0 | ||
|
|
a9af4fc060 | ||
|
|
6a8dc159c1 | ||
|
|
2930a122c6 | ||
|
|
75433d3390 | ||
|
|
d4d6fa149d | ||
|
|
207ac373d0 | ||
|
|
806a5b92df | ||
|
|
7d0005acf3 | ||
|
|
9c782c41df |
@@ -5,7 +5,9 @@
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
class CoreException extends Exception
|
||||
use Combodo\iTop\Exception\ItopException;
|
||||
|
||||
class CoreException extends ItopException
|
||||
{
|
||||
protected $m_sIssue;
|
||||
protected $m_sImpact;
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with iTop. If not, see <http://www.gnu.org/licenses/>
|
||||
use Combodo\iTop\Application\Helper\ExceptionHandlerHelper;
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Service\Startup\StartupService;
|
||||
|
||||
@@ -64,6 +65,9 @@ register_shutdown_function(function () {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
set_exception_handler([ExceptionHandlerHelper::class, 'HandleException']);
|
||||
|
||||
$oKPI = new ExecutionKPI();
|
||||
Session::Start();
|
||||
$oKPI->ComputeAndReport("Session Start");
|
||||
|
||||
@@ -614,6 +614,12 @@ class LogChannels
|
||||
* @since 3.2.0
|
||||
*/
|
||||
public const SECURITY = 'Security';
|
||||
|
||||
/**
|
||||
* @var string
|
||||
* @since 3.3.0
|
||||
*/
|
||||
public const EXCEPTION = 'Exception';
|
||||
}
|
||||
|
||||
abstract class LogAPI
|
||||
@@ -708,8 +714,14 @@ abstract class LogAPI
|
||||
|
||||
private static function PrepareErrorLog(string $sMessage, throwable $oException, array $aContext, bool $isPrevious = false): array
|
||||
{
|
||||
$aContext['Error Message'] = $oException->getMessage();
|
||||
$aContext['Stack Trace'] = $oException->getTraceAsString();
|
||||
$aContext['exception'] = get_class($oException);
|
||||
$aContext['message'] = $oException->getMessage();
|
||||
$aContext['stack'] = $oException->getTraceAsString();
|
||||
|
||||
if(method_exists($oException, 'GetContext')) {
|
||||
$aContext = array_merge($aContext, $oException->GetContext());
|
||||
}
|
||||
|
||||
return ['message' => ($isPrevious ? "Previous " : '')."Exception: $sMessage", 'context' => $aContext];
|
||||
}
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@ class ormPassword
|
||||
public function SetPassword($sClearTextPassword)
|
||||
{
|
||||
$iHashAlgo = MetaModel::GetConfig()->GetPasswordHashAlgo();
|
||||
$this->m_sHashed = password_hash($sClearTextPassword, $iHashAlgo);
|
||||
$this->m_sHashed = password_hash($sClearTextPassword ?? '', $iHashAlgo);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -99,10 +99,10 @@ class ormPassword
|
||||
$aInfo = password_get_info($this->m_sHashed);
|
||||
if (is_null($aInfo["algo"]) || $aInfo["algo"] === 0) {
|
||||
// - Unknown algorithm, assume it's a legacy password
|
||||
$sHashedPwd = $this->ComputeHash($sClearTextPassword);
|
||||
$sHashedPwd = $this->ComputeHash($sClearTextPassword ?? '');
|
||||
$bResult = hash_equals($this->m_sHashed, $sHashedPwd);
|
||||
} else {
|
||||
$bResult = password_verify($sClearTextPassword, $this->m_sHashed);
|
||||
$bResult = password_verify($sClearTextPassword ?? '', $this->m_sHashed);
|
||||
}
|
||||
return $bResult;
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -145,7 +145,7 @@ class DataFeatureRemovalController extends Controller
|
||||
|
||||
$bForceCompilation = Session::Get('bForceCompilation', false);
|
||||
try {
|
||||
$this->Compile($aRemoveExtensionCodes, $bForceCompilation);
|
||||
$this->Compile($aAddedExtensions, $aRemoveExtensionCodes, $bForceCompilation);
|
||||
} catch (CoreException $e) {
|
||||
$aParams['DataFeatureRemovalErrorMessage'] = $e->getHtmlDesc();
|
||||
$this->DisplayPage($aParams, 'AnalysisResult');
|
||||
@@ -162,7 +162,7 @@ class DataFeatureRemovalController extends Controller
|
||||
$aSelectedExtensions = DataFeatureRemoverExtensionService::GetInstance()->GetExtensionMap()->GetSelectedExtensions($oConfig, array_keys($aAddedExtensions), array_keys($aRemovedExtensions));
|
||||
$aHiddenInputs['selected_extensions'] = $this->ConvertIntoSetupFormat($aSelectedExtensions);
|
||||
|
||||
$oRunTimeEnvironment = $this->GetRuntimeEnvironment($aRemovedExtensions);
|
||||
$oRunTimeEnvironment = $this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions);
|
||||
$aSearchDirs = [$oRunTimeEnvironment->GetBuildDir()];
|
||||
$aSelectedModules = $oRunTimeEnvironment->GetModulesToLoadFromChoices($oConfig, $aSelectedExtensions, $aSearchDirs);
|
||||
$aHiddenInputs['selected_modules'] = $this->ConvertIntoSetupFormat($aSelectedModules);
|
||||
@@ -213,13 +213,14 @@ class DataFeatureRemovalController extends Controller
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aAddedExtensions
|
||||
* @param array $aRemovedExtensions
|
||||
* @param bool $bForceCompilation
|
||||
* @return void
|
||||
* @throws \ConfigException
|
||||
* @throws \CoreException
|
||||
*/
|
||||
private function Compile(array $aRemovedExtensions, bool $bForceCompilation = true): void
|
||||
private function Compile(array $aAddedExtensions, array $aRemovedExtensions, bool $bForceCompilation = true): void
|
||||
{
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$sBuildDir = APPROOT."/env-$sSourceEnv-build";
|
||||
@@ -234,15 +235,15 @@ class DataFeatureRemovalController extends Controller
|
||||
null,
|
||||
['sSourceEnv' => $sSourceEnv, 'sBuildDir' => $sBuildDir, 'bIsDirEmpty' => $bIsDirEmpty, glob("$sBuildDir/*")]
|
||||
);
|
||||
$this->GetRuntimeEnvironment($aRemovedExtensions)->CompileFrom($sSourceEnv);
|
||||
$this->GetRuntimeEnvironment($aAddedExtensions, $aRemovedExtensions)->CompileFrom($sSourceEnv);
|
||||
}
|
||||
}
|
||||
|
||||
private function GetRuntimeEnvironment(array $aRemovedExtensions): RunTimeEnvironment
|
||||
private function GetRuntimeEnvironment(array $aAddedExtensions, array $aRemovedExtensions): RunTimeEnvironment
|
||||
{
|
||||
if (is_null($this->oRuntimeEnvironment)) {
|
||||
$sSourceEnv = MetaModel::GetEnvironment();
|
||||
$this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aRemovedExtensions);
|
||||
$this->oRuntimeEnvironment = new DryRemovalRuntimeEnvironment($sSourceEnv, $aAddedExtensions, $aRemovedExtensions);
|
||||
}
|
||||
|
||||
return $this->oRuntimeEnvironment;
|
||||
|
||||
@@ -66,7 +66,7 @@ class DataFeatureRemoverExtensionService
|
||||
public function GetExtensionMap(): iTopExtensionsMap
|
||||
{
|
||||
if (is_null($this->oMap)) {
|
||||
$this->oMap = new iTopExtensionsMap();
|
||||
$this->oMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$this->oMap->LoadInstalledExtensionsFromDatabase(MetaModel::GetConfig());
|
||||
}
|
||||
return $this->oMap;
|
||||
|
||||
@@ -14,13 +14,11 @@ use Combodo\iTop\CoreUpdate\Service\CoreUpdater;
|
||||
use Combodo\iTop\DBTools\Service\DBToolsUtils;
|
||||
use Combodo\iTop\FilesInformation\Service\FileNotExistException;
|
||||
use Combodo\iTop\FilesInformation\Service\FilesInformation;
|
||||
use Config;
|
||||
use ContextTag;
|
||||
use Dict;
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use MetaModel;
|
||||
use RunTimeEnvironment;
|
||||
use SecurityException;
|
||||
use SetupUtils;
|
||||
use utils;
|
||||
@@ -232,29 +230,6 @@ class AjaxController extends Controller
|
||||
$this->DisplayJSONPage($aParams, $iResponseCode);
|
||||
}
|
||||
|
||||
public function OperationRebuildToolkitEnvironment()
|
||||
{
|
||||
$sTransactionId = utils::GetNewTransactionId();
|
||||
$aParams = [];
|
||||
$aParams['sTransactionId'] = $sTransactionId;
|
||||
$aParams['bStatus'] = true;
|
||||
|
||||
$iResponseCode = 200;
|
||||
try {
|
||||
$aParams['sAjaxURL'] = utils::GetAbsoluteUrlAppRoot().'/pages/UI.php';
|
||||
$oConfig = new Config(APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE);
|
||||
$oEnvironment = new RunTimeEnvironment(ITOP_DEFAULT_ENV);
|
||||
$oEnvironment->WriteConfigFileSafe($oConfig);
|
||||
$oEnvironment->CompileFrom(ITOP_DEFAULT_ENV);
|
||||
} catch (Exception $e) {
|
||||
IssueLog::Error('RebuildToolkitEnvironment: '.$e->getMessage());
|
||||
$aParams['sError'] = $e->getMessage();
|
||||
$iResponseCode = 500;
|
||||
$aParams['bStatus'] = false;
|
||||
}
|
||||
$this->DisplayJSONPage($aParams, $iResponseCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws \SecurityException if CSRF token invalid
|
||||
*
|
||||
|
||||
@@ -163,7 +163,7 @@ final class CoreUpdater
|
||||
$oRuntimeEnv->LoadData($aAvailableModules, false /* no sample data*/);
|
||||
$oRuntimeEnv->CallInstallerHandlers($aAvailableModules, 'AfterDataLoad');
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
|
||||
@@ -90,13 +90,6 @@
|
||||
{% UIForm Standard {'sId':'launch-setup-form', Action:sLaunchSetupUrl} %}
|
||||
{% UIButton ForDestructiveAction {'sLabel':'iTopUpdate:UI:SetupLaunch'|dict_s, 'sName':'launch-setup', 'sValue':'launch-setup', 'bIsSubmit':true, 'sId':'launch-setup'} %}
|
||||
{% EndUIForm %}
|
||||
{% UIAlert ForInformation {sId:'fast-setup-alert', AddCSSClass:'ibo-is-hidden'} %}
|
||||
{% UIContentBlock Standard {sId:'fast-setup-content', aContainerClasses:['ibo-fast-setup-content']} %}
|
||||
{{ 'iTopUpdate:UI:SetupMessage:Compile'|dict_s }}
|
||||
{% EndUIContentBlock %}
|
||||
{% EndUIAlert %}
|
||||
{% UIButton ForDestructiveAction {sLabel:'iTopUpdate:UI:FastSetupLaunch'|dict_s, sName:'launch-fast-setup', sValue:'launch-fast-setup', sId:'launch-fast-setup'} %}
|
||||
{% UISpinner Standard {sId:'fast-setup-wait', IsHidden:true} %}
|
||||
{% EndUIFieldSet %}
|
||||
{% endif %}
|
||||
|
||||
|
||||
@@ -116,51 +116,4 @@ $("#launch-setup-form").on("submit", function () {
|
||||
return window.confirm("{{ 'iTopUpdate:UI:SetupLaunchConfirm'|dict_s }}");
|
||||
});
|
||||
|
||||
$("#launch-fast-setup").on("click", function(e) {
|
||||
var oMessage = $("#fast-setup-alert");
|
||||
var oContent = $("#fast-setup-content");
|
||||
oMessage.removeClass("ibo-is-hidden");
|
||||
oMessage.removeClass("ibo-is-failure");
|
||||
oMessage.removeClass("ibo-is-success");
|
||||
oMessage.addClass("ibo-is-information");
|
||||
oContent.html("{{ 'iTopUpdate:UI:SetupMessage:Compile'|dict_s }}");
|
||||
|
||||
let fast_setup_wait = $("#fast-setup-wait");
|
||||
fast_setup_wait.removeClass("ibo-is-hidden");
|
||||
$(this).prop("disabled", true);
|
||||
|
||||
$.ajax({
|
||||
method: "POST",
|
||||
url: "{{ sAjaxURL|raw }}",
|
||||
data: {
|
||||
route: "core_update_ajax.rebuild_toolkit_environment"
|
||||
},
|
||||
dataType: "json",
|
||||
complete: function(jqXHR, textStatus) {
|
||||
$("#fast-setup-wait").addClass("ibo-is-hidden");
|
||||
$("#launch-fast-setup").prop("disabled", false);
|
||||
fast_setup_wait.addClass("ibo-is-hidden");
|
||||
},
|
||||
success: function (data) {
|
||||
oMessage.removeClass("ibo-is-information");
|
||||
|
||||
if (data.bStatus) {
|
||||
oMessage.removeClass("ibo-is-failure");
|
||||
oMessage.addClass("ibo-is-success");
|
||||
oContent.html("{{ 'iTopUpdate:UI:SetupMessage:UpdateDone'|dict_s }}");
|
||||
} else {
|
||||
oMessage.removeClass("ibo-is-success");
|
||||
oMessage.addClass("ibo-is-failure");
|
||||
oContent.html(data.sError);
|
||||
}
|
||||
},
|
||||
error: function(jqXHR, textStatus, errorThrown) {
|
||||
oMessage.removeClass("ibo-is-information");
|
||||
oMessage.removeClass("ibo-is-success");
|
||||
oMessage.addClass("ibo-is-failure");
|
||||
oContent.html(textStatus + ' ' + errorThrown);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ function DisplayStatus(WebPage $oPage)
|
||||
if (is_dir($sPath)) {
|
||||
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
|
||||
}
|
||||
$oExtensionsMap = new iTopExtensionsMap(ITOP_DEFAULT_ENV, $aExtraDirs);
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap(ITOP_DEFAULT_ENV);
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
@@ -154,7 +154,7 @@ function DoInstall(WebPage $oPage)
|
||||
if (is_dir($sPath)) {
|
||||
$aExtraDirs[] = $sPath; // Also read the extra downloaded-modules directory
|
||||
}
|
||||
$oExtensionsMap = new iTopExtensionsMap(ITOP_DEFAULT_ENV, $aExtraDirs);
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap(ITOP_DEFAULT_ENV);
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
|
||||
@@ -193,7 +193,7 @@ function collect_configuration()
|
||||
}
|
||||
|
||||
// iTop Installation Options, i.e. "Extensions"
|
||||
$oExtensionMap = new iTopExtensionsMap();
|
||||
$oExtensionMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$oExtensionMap->LoadChoicesFromDatabase($oConfig);
|
||||
$aConfiguration['itop_extensions'] = [];
|
||||
foreach ($oExtensionMap->GetChoices() as $oExtension) {
|
||||
|
||||
@@ -41,7 +41,7 @@ function GetExtensionInfoComponent(iTopExtension $oExtension): UIBlock
|
||||
}
|
||||
|
||||
try {
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
|
||||
$oPage->AddUiBlock(TitleUIBlockFactory::MakeForPage(Dict::S('iTopHub:InstalledExtensions')));
|
||||
|
||||
@@ -208,7 +208,7 @@ class HubController
|
||||
// Record the installation so that the "about box" knows about the installed modules
|
||||
$sDataModelVersion = $oRuntimeEnv->GetCurrentDataModelVersion();
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
|
||||
// Default choices = as before
|
||||
$oExtensionsMap->LoadChoicesFromDatabase($oConfig);
|
||||
|
||||
@@ -300,7 +300,7 @@
|
||||
*
|
||||
* Add the current contact associated Person to the contacts_list of this Ticket
|
||||
* No error if there is no associated Person or if the Person is already in the list
|
||||
* @return bool Return true if link was created, false otherwise
|
||||
* @return bool Return true on success
|
||||
*/
|
||||
</comment>
|
||||
<static>false</static>
|
||||
@@ -314,11 +314,12 @@
|
||||
$oLnk = MetaModel::NewObject('lnkContactToTicket');
|
||||
$oLnk->Set('contact_id', $iPersonId);
|
||||
$oLnk->Set('ticket_id', $this->GetKey());
|
||||
$oLnk->DBInsert();
|
||||
return true;
|
||||
$oContactsSet->AddItem($oLnk);
|
||||
$this->Set('contacts_list', $oContactsSet);
|
||||
$this->DBUpdate();
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}]]></code>
|
||||
<arguments/>
|
||||
</method>
|
||||
@@ -327,7 +328,7 @@
|
||||
*
|
||||
* Remove the current user associated Person from the contacts_list of this Ticket
|
||||
* No error if there is no associated Person or if the Person is not in the list
|
||||
* @return bool Return true if link was deleted, false otherwise
|
||||
* @return bool Return true
|
||||
*/
|
||||
</comment>
|
||||
<static>false</static>
|
||||
@@ -339,12 +340,14 @@
|
||||
$oContactsSet = $this->Get('contacts_list');
|
||||
foreach ($oContactsSet as $oLnk) {
|
||||
if ($oLnk->Get('contact_id') == $iPersonId) {
|
||||
$oLnk->DBDelete();
|
||||
$oContactsSet->RemoveItem($oLnk->GetKey());
|
||||
$this->Set('contacts_list', $oContactsSet);
|
||||
$this->DBUpdate();
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
return true;
|
||||
}]]></code>
|
||||
<arguments/>
|
||||
</method>
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('CS CZ', 'Czech', 'Čeština', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('DA DA', 'Danish', 'Dansk', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('DE DE', 'German', 'Deutsch', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -10,11 +10,17 @@
|
||||
|
||||
Dict::Add('EN US', 'English', 'English', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('FR FR', 'French', 'Français', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'Cette extension fait partie de l\'installation actuelle.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'va être installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'Cette extension sera installée lors de l\'installation.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'pas installé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'Cette extension ne fait pas partie de l\'installation actuelle.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'va être désinstallé',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'Cette extension sera désinstallée lors de l\'installation.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'non désinstallable',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Une fois cette extension installée, elle ne devrait pas être désinstallée.',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'supprimé du disque',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'Le dossier local de l\'extension a été supprimé du disque. Cela forcera la désinstallation de cette extension.',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'À propos de %1$s',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'Plus d\'informations',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Forcer la désinstallation',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('HU HU', 'Hungarian', 'Magyar', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('IT IT', 'Italian', 'Italiano', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('JA JP', 'Japanese', '日本語', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('NL NL', 'Dutch', 'Nederlands', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('PL PL', 'Polish', 'Polski', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('RU RU', 'Russian', 'Русский', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('TR TR', 'Turkish', 'Türkçe', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -12,11 +12,17 @@
|
||||
*/
|
||||
Dict::Add('ZH CN', 'Chinese', '简体中文', [
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'This extension is part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'not installed~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotInstalled+' => 'This extension is not part of the current installation.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled' => 'to be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+' => 'This extension will be uninstalled during the setup.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable' => 'cannot be uninstalled~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeNotUninstallable+' => 'Once this extension has been installed, it should not be uninstalled.~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk' => 'missing from disk~~',
|
||||
'UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+' => 'The local extension folder has been removed from the disk. This will force the uninstallation of this extension.~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAboutTitle' => 'About %1$s~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuAbout' => 'More informations~~',
|
||||
'UI:Layout:ExtensionsDetails:MenuForce' => 'Force uninstall~~',
|
||||
|
||||
@@ -133,6 +133,7 @@ return array(
|
||||
'Combodo\\iTop\\Application\\EventRegister\\ApplicationEvents' => $baseDir . '/sources/Application/EventRegister/ApplicationEvents.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\BulkHelper' => $baseDir . '/sources/Application/Helper/BulkHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\CKEditorHelper' => $baseDir . '/sources/Application/Helper/CKEditorHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ExceptionHandlerHelper' => $baseDir . '/sources/Application/Helper/ExceptionHandlerHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ExportHelper' => $baseDir . '/sources/Application/Helper/ExportHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\FormHelper' => $baseDir . '/sources/Application/Helper/FormHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ImportHelper' => $baseDir . '/sources/Application/Helper/ImportHelper.php',
|
||||
@@ -446,6 +447,7 @@ return array(
|
||||
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => $baseDir . '/sources/Dependencies/NPM/iTopNPM.php',
|
||||
'Combodo\\iTop\\DesignDocument' => $baseDir . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\DesignElement' => $baseDir . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\Exception\\ItopException' => $baseDir . '/sources/Exception/ItopException.php',
|
||||
'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => $baseDir . '/sources/Form/Field/AbstractSimpleField.php',
|
||||
'Combodo\\iTop\\Form\\Field\\BlobField' => $baseDir . '/sources/Form/Field/BlobField.php',
|
||||
'Combodo\\iTop\\Form\\Field\\CaseLogField' => $baseDir . '/sources/Form/Field/CaseLogField.php',
|
||||
|
||||
@@ -534,6 +534,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
|
||||
'Combodo\\iTop\\Application\\EventRegister\\ApplicationEvents' => __DIR__ . '/../..' . '/sources/Application/EventRegister/ApplicationEvents.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\BulkHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/BulkHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\CKEditorHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/CKEditorHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ExceptionHandlerHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/ExceptionHandlerHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ExportHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/ExportHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\FormHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/FormHelper.php',
|
||||
'Combodo\\iTop\\Application\\Helper\\ImportHelper' => __DIR__ . '/../..' . '/sources/Application/Helper/ImportHelper.php',
|
||||
@@ -847,6 +848,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
|
||||
'Combodo\\iTop\\Dependencies\\NPM\\iTopNPM' => __DIR__ . '/../..' . '/sources/Dependencies/NPM/iTopNPM.php',
|
||||
'Combodo\\iTop\\DesignDocument' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\DesignElement' => __DIR__ . '/../..' . '/core/designdocument.class.inc.php',
|
||||
'Combodo\\iTop\\Exception\\ItopException' => __DIR__ . '/../..' . '/sources/Exception/ItopException.php',
|
||||
'Combodo\\iTop\\Form\\Field\\AbstractSimpleField' => __DIR__ . '/../..' . '/sources/Form/Field/AbstractSimpleField.php',
|
||||
'Combodo\\iTop\\Form\\Field\\BlobField' => __DIR__ . '/../..' . '/sources/Form/Field/BlobField.php',
|
||||
'Combodo\\iTop\\Form\\Field\\CaseLogField' => __DIR__ . '/../..' . '/sources/Form/Field/CaseLogField.php',
|
||||
|
||||
38
pages/UI.php
38
pages/UI.php
@@ -20,12 +20,12 @@ use Combodo\iTop\Application\UI\Base\Component\Toolbar\ToolbarUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\PageContent\PageContentFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlock;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
|
||||
use Combodo\iTop\Application\WebPage\ErrorPage;
|
||||
use Combodo\iTop\Application\WebPage\iTopWebPage;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Application\WelcomePopup\WelcomePopupService;
|
||||
use Combodo\iTop\Controller\Base\Layout\ObjectController;
|
||||
use Combodo\iTop\Controller\WelcomePopupController;
|
||||
use Combodo\iTop\Exception\ItopException;
|
||||
use Combodo\iTop\Service\Router\Router;
|
||||
|
||||
/**
|
||||
@@ -1321,37 +1321,7 @@ try {
|
||||
$oKPI->ComputeAndReport('Compute page');
|
||||
$oP->output();
|
||||
} catch (Exception $e) {
|
||||
$oErrorPage = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
if ($e instanceof SecurityException) {
|
||||
$oErrorPage->add("<h1>".Dict::S('UI:SystemIntrusion')."</h1>\n");
|
||||
} else {
|
||||
$oErrorPage->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
}
|
||||
$sErrorDetails = ($e instanceof CoreException) ? $e->getHtmlDesc() : $e->getMessage();
|
||||
$oErrorPage->error(Dict::Format('UI:Error_Details', utils::EscapeHtml($sErrorDetails)), $e);
|
||||
$oErrorPage->output();
|
||||
|
||||
$sErrorStackTrace = ($e instanceof CoreException) ? $e->getFullStackTraceAsString() : $e->getTraceAsString();
|
||||
if (MetaModel::IsLogEnabledIssue()) {
|
||||
if (MetaModel::IsValidClass('EventIssue')) {
|
||||
try {
|
||||
$oLog = new EventIssue();
|
||||
|
||||
$oLog->Set('message', $e->getMessage());
|
||||
$oLog->Set('userinfo', '');
|
||||
$sIssue = ($e instanceof CoreException) ? $e->GetIssue() : 'PHP Exception';
|
||||
$oLog->Set('issue', $sIssue);
|
||||
$oLog->Set('impact', 'Page could not be displayed');
|
||||
$oLog->Set('callstack', $sErrorStackTrace);
|
||||
$aData = ($e instanceof CoreException) ? $e->getContextData() : [];
|
||||
$oLog->Set('data', $aData);
|
||||
$oLog->DBInsertNoReload();
|
||||
} catch (Exception $e) {
|
||||
IssueLog::Exception("Failed to log issue into the DB", $e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sOperationToLog = $operation ?? 'N/A';
|
||||
IssueLog::Debug('UI.php operation='.$sOperationToLog.', error='.$e->getMessage()."\n".$sErrorStackTrace, LogChannels::CONSOLE);
|
||||
throw new ItopException("Unable to handle UI operation", previous: $e, aContext: [
|
||||
'operation' => ($operation ?? 'N/A'),
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -124,6 +124,7 @@ 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
|
||||
$oCtx = new ContextTag(ContextTag::TAG_SETUP);
|
||||
|
||||
/**
|
||||
* Main program
|
||||
|
||||
@@ -14,6 +14,8 @@ require_once(APPROOT.'/setup/moduleinstaller.class.inc.php');
|
||||
*/
|
||||
class iTopExtensionsMap
|
||||
{
|
||||
private static array $aInstancesByEnvironment = [];
|
||||
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
* @var array $aExtensions
|
||||
@@ -30,25 +32,36 @@ class iTopExtensionsMap
|
||||
* The list of directories browsed using the ReadDir method when building the map
|
||||
* @var string[]
|
||||
*/
|
||||
protected $aScannedDirs;
|
||||
protected array $aScannedDirs;
|
||||
|
||||
/** @var bool $bHasXmlInstallationFile : false when legacy 1.x package with no installation.xml */
|
||||
protected $bHasXmlInstallationFile = true;
|
||||
|
||||
//extension dirs apart from package
|
||||
protected array $aExtraDirs = [];
|
||||
/**
|
||||
* @throws \Exception
|
||||
*/
|
||||
public static function GetExtensionsMap(string $sFromEnvironment = ITOP_DEFAULT_ENV, ?string $sAppRootForTests = null): iTopExtensionsMap
|
||||
{
|
||||
if (!is_null($sAppRootForTests)) {
|
||||
return new iTopExtensionsMap($sFromEnvironment, $sAppRootForTests);
|
||||
}
|
||||
|
||||
if (!isset(self::$aInstancesByEnvironment[$sFromEnvironment])) {
|
||||
self::$aInstancesByEnvironment[$sFromEnvironment] = new iTopExtensionsMap($sFromEnvironment);
|
||||
}
|
||||
|
||||
return self::$aInstancesByEnvironment[$sFromEnvironment];
|
||||
}
|
||||
|
||||
/**
|
||||
* The list of all discovered extensions
|
||||
*
|
||||
* @param string $sFromEnvironment The environment to scan
|
||||
* @param array $aExtraDirs extensions dir to scan
|
||||
* @param array $aExtraDirs extensions dir to scan
|
||||
* @param string|null $sAppRootForTests
|
||||
* @param string|null $sAppRootForTests
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, array $aExtraDirs = [], ?string $sAppRootForTests = null)
|
||||
private function __construct(string $sFromEnvironment = ITOP_DEFAULT_ENV, ?string $sAppRootForTests = null)
|
||||
{
|
||||
$this->aExtensions = [];
|
||||
$this->aExtensionsByCode = [];
|
||||
@@ -56,18 +69,6 @@ class iTopExtensionsMap
|
||||
|
||||
$sAppRoot = $sAppRootForTests ?? APPROOT;
|
||||
$this->ScanDisk($sFromEnvironment, $sAppRoot);
|
||||
|
||||
$this->aExtraDirs = $aExtraDirs;
|
||||
if (is_dir($sAppRoot.'extensions')) {
|
||||
$this->aExtraDirs [] = $sAppRoot.'extensions';
|
||||
}
|
||||
if (is_dir($sAppRoot.'data/'.$sFromEnvironment.'-modules')) {
|
||||
$this->aExtraDirs [] = $sAppRoot.'data/'.$sFromEnvironment.'-modules';
|
||||
}
|
||||
|
||||
foreach ($aExtraDirs as $sDir) {
|
||||
$this->ReadDir($sDir, iTopExtension::SOURCE_REMOTE);
|
||||
}
|
||||
$this->CheckDependencies($sAppRoot);
|
||||
}
|
||||
|
||||
@@ -83,13 +84,15 @@ class iTopExtensionsMap
|
||||
if (!$this->ReadInstallationWizard($sAppRoot.'/datamodels/2.x')) {
|
||||
$this->bHasXmlInstallationFile = false;
|
||||
//no installation xml found in 2.x: let's read all extensions in 2.x first
|
||||
if (!$this->ReadDir($sAppRoot.'datamodels/2.x', iTopExtension::SOURCE_WIZARD)) {
|
||||
if (!$this->ReadDir($sAppRoot.'datamodels/2.x', iTopExtension::SOURCE_WIZARD, bIsRootDir: true)) {
|
||||
//nothing found in 2.x : fallback read in 1.x (flat structure)
|
||||
$this->ReadDir($sAppRoot.'datamodels/1.x', iTopExtension::SOURCE_WIZARD);
|
||||
$this->ReadDir($sAppRoot.'datamodels/1.x', iTopExtension::SOURCE_WIZARD, bIsRootDir: true);
|
||||
}
|
||||
} else {
|
||||
$this->aScannedDirs[] = $sAppRoot.'datamodels/2.x';
|
||||
}
|
||||
$this->ReadDir($sAppRoot.'extensions', iTopExtension::SOURCE_MANUAL);
|
||||
$this->ReadDir($sAppRoot.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE);
|
||||
$this->ReadDir($sAppRoot.'extensions', iTopExtension::SOURCE_MANUAL, bIsRootDir: true);
|
||||
$this->ReadDir($sAppRoot.'data/'.$sEnvironment.'-modules', iTopExtension::SOURCE_REMOTE, bIsRootDir: true);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -274,14 +277,14 @@ class iTopExtensionsMap
|
||||
*
|
||||
* @return boolean false if we cannot open dir
|
||||
*/
|
||||
protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null)
|
||||
protected function ReadDir($sSearchDir, $sSource, $sParentExtensionId = null, bool $bIsRootDir = false)
|
||||
{
|
||||
if (!is_readable($sSearchDir)) {
|
||||
return false;
|
||||
}
|
||||
$hDir = opendir($sSearchDir);
|
||||
if ($hDir !== false) {
|
||||
if ($sParentExtensionId == null) {
|
||||
if ($bIsRootDir) {
|
||||
// We're not recursing, let's add the directory to the list of scanned dirs
|
||||
$this->aScannedDirs[] = $sSearchDir;
|
||||
}
|
||||
@@ -478,16 +481,8 @@ class iTopExtensionsMap
|
||||
*/
|
||||
protected function CheckDependencies(string $sAppRoot)
|
||||
{
|
||||
$aSearchDirs = [];
|
||||
|
||||
if (is_dir($sAppRoot.'/datamodels/2.x')) {
|
||||
$aSearchDirs[] = $sAppRoot.'/datamodels/2.x';
|
||||
} elseif (is_dir($sAppRoot.'/datamodels/1.x')) {
|
||||
$aSearchDirs[] = $sAppRoot.'/datamodels/1.x';
|
||||
}
|
||||
$aSearchDirs = array_merge($aSearchDirs, $this->aScannedDirs);
|
||||
try {
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($aSearchDirs, true);
|
||||
ModuleDiscovery::GetModulesOrderedByDependencies($this->aScannedDirs, true);
|
||||
} catch (MissingDependencyException $e) {
|
||||
// Some modules have missing dependencies
|
||||
// Let's check what is the impact at the "extensions" level
|
||||
@@ -536,7 +531,7 @@ class iTopExtensionsMap
|
||||
public function GetAllExtensionsToDisplayInSetup(bool $bKeepExtensionsHavingMissingDependencies = false, bool $bRemoteExtensionsShouldBeMandatory = true): array
|
||||
{
|
||||
// all extensions are loaded at first screen when no installation xml: flat display
|
||||
//otherwhile wizard screen displays choice screens along extension tree (cf installation.xml)
|
||||
// otherwhile wizard screen displays choice screens along extension tree (cf installation.xml)
|
||||
$aRes = [];
|
||||
foreach ($this->GetAllExtensionsWithPreviouslyInstalled() as $oExtension) {
|
||||
/** @var \iTopExtension $oExtension */
|
||||
@@ -596,7 +591,7 @@ class iTopExtensionsMap
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
$oExtension->bMarkedAsChosen = $bMark;
|
||||
$oExtension->MarkAsChosen($bMark);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -609,7 +604,7 @@ class iTopExtensionsMap
|
||||
{
|
||||
$oExtension = $this->GetFromExtensionCode($sExtensionCode);
|
||||
if (!is_null($oExtension)) {
|
||||
return $oExtension->bMarkedAsChosen;
|
||||
return $oExtension->IsMarkedAsChosen();
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -636,8 +631,9 @@ class iTopExtensionsMap
|
||||
public function GetChoices()
|
||||
{
|
||||
$aResult = [];
|
||||
/** @var \iTopExtension $oExtension */
|
||||
foreach ($this->aExtensions as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
if ($oExtension->IsMarkedAsChosen()) {
|
||||
$aResult[] = $oExtension;
|
||||
}
|
||||
}
|
||||
@@ -745,11 +741,6 @@ class iTopExtensionsMap
|
||||
return array_merge($aDbChoices, $aAddedExtensions);
|
||||
}
|
||||
|
||||
public function GetExtraDirs(): array
|
||||
{
|
||||
return $this->aExtraDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tells if the given module name is "chosen" since it is part of a "chosen" extension (in the specified source dir)
|
||||
* @param string $sModuleNameToFind
|
||||
@@ -759,13 +750,17 @@ class iTopExtensionsMap
|
||||
public function ModuleIsChosenAsPartOfAnExtension($sModuleNameToFind, $sInSourceOnly = iTopExtension::SOURCE_REMOTE)
|
||||
{
|
||||
foreach ($this->GetAllExtensions() as $oExtension) {
|
||||
if (($oExtension->sSource == $sInSourceOnly) &&
|
||||
($oExtension->bMarkedAsChosen == true) &&
|
||||
if ($oExtension->IsMarkedAsChosen() &&
|
||||
(array_key_exists($sModuleNameToFind, $oExtension->aModuleVersion))) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function GetScannedModulesRootDirs(): array
|
||||
{
|
||||
return $this->aScannedDirs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,22 +2,24 @@
|
||||
|
||||
namespace Combodo\iTop\Setup\FeatureRemoval;
|
||||
|
||||
use iTopExtensionsMap;
|
||||
use Config;
|
||||
use RunTimeEnvironment;
|
||||
use SetupUtils;
|
||||
|
||||
class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
{
|
||||
protected array $aExtensionsToRemoveByCode;
|
||||
protected array $aExtensionCodesToAddByCode;
|
||||
|
||||
/**
|
||||
* Toolset for building a run-time environment
|
||||
*
|
||||
* @param string $sSourceEnv: environment from which setup is inspired to simulate extension removal and usee CompileFrom...
|
||||
*/
|
||||
public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToRemove = [])
|
||||
public function __construct($sSourceEnv = ITOP_DEFAULT_ENV, array $aExtensionCodesToAdd = [], array $aExtensionCodesToRemove = [])
|
||||
{
|
||||
parent::__construct($sSourceEnv, false);
|
||||
$this->aExtensionCodesToAddByCode = $aExtensionCodesToAdd;
|
||||
$this->aExtensionsToRemoveByCode = $aExtensionCodesToRemove;
|
||||
$this->Prepare($sSourceEnv, $this->sBuildEnv);
|
||||
}
|
||||
@@ -34,13 +36,18 @@ class DryRemovalRuntimeEnvironment extends RunTimeEnvironment
|
||||
SetupUtils::copydir(APPROOT."/data/$sSourceEnv-modules", APPROOT."/data/$sBuildEnv-modules");
|
||||
SetupUtils::copydir(APPROOT."/conf/$sSourceEnv", APPROOT."/conf/$sBuildEnv");
|
||||
|
||||
$this->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode);
|
||||
}
|
||||
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
|
||||
$sSourceDir = $oSourceConfig->Get('source_dir');
|
||||
list($aExtraDirs, ) = $this->GetDirsToCompile($sSourceDir, $sSourceEnv);
|
||||
|
||||
private function DeclareExtensionAsRemoved(array $aExtensionCodes): void
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv);
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aExtensionCodes);
|
||||
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
|
||||
$this->GetExtensionMap()->DeclareExtensionAsRemoved($this->aExtensionsToRemoveByCode);
|
||||
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if (array_key_exists($oExtension->sCode, $this->aExtensionCodesToAddByCode)) {
|
||||
$oExtension->MarkAsChosen();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function Cleanup(): void
|
||||
|
||||
@@ -17,6 +17,11 @@ class SetupAudit extends AbstractSetupAudit
|
||||
$this->sEnvAfter = $sEnvAfter ?? "$sEnvBefore-build";
|
||||
}
|
||||
|
||||
public function GetEnvAfter(): string
|
||||
{
|
||||
return $this->sEnvAfter;
|
||||
}
|
||||
|
||||
public function ComputeClasses(): void
|
||||
{
|
||||
if ($this->bClassesInitialized) {
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||
|
||||
require_once(APPROOT.'/setup/parameters.class.inc.php');
|
||||
require_once(APPROOT.'/core/cmdbsource.class.inc.php');
|
||||
require_once(APPROOT.'/setup/modulediscovery.class.inc.php');
|
||||
@@ -60,7 +57,7 @@ class iTopExtension
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $bMarkedAsChosen;
|
||||
private $bMarkedAsChosen;
|
||||
/**
|
||||
* If null, check if at least one module cannot be uninstalled
|
||||
* @var bool|null
|
||||
@@ -183,4 +180,14 @@ class iTopExtension
|
||||
{
|
||||
return $this->sSource === self::SOURCE_REMOTE;
|
||||
}
|
||||
|
||||
public function IsMarkedAsChosen(): bool
|
||||
{
|
||||
return $this->bMarkedAsChosen;
|
||||
}
|
||||
|
||||
public function MarkAsChosen(bool $bMarkedAsChosen = true): void
|
||||
{
|
||||
$this->bMarkedAsChosen = $bMarkedAsChosen;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -68,7 +68,12 @@ WHERE
|
||||
parent_id='$iRootId'
|
||||
OR id='$iRootId'
|
||||
SQL;
|
||||
return CMDBSource::QueryToArray($sSQL);
|
||||
try {
|
||||
return CMDBSource::QueryToArray($sSQL);
|
||||
} catch (MySQLException $e) {
|
||||
SetupLog::Exception(__METHOD__, $e);
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
private function GetTableWithPrefix(Config $oConfig)
|
||||
|
||||
@@ -71,15 +71,37 @@ class RunTimeEnvironment
|
||||
return $this->oExtensionsMap;
|
||||
}
|
||||
|
||||
public function InitExtensionMap($aExtraDirs, $oSourceConfig)
|
||||
protected function GetDirsToCompile(string $sSourceDir, string $sSourceEnv): array
|
||||
{
|
||||
// Actually read the modules available for the build environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen all the "remote" modules present in the
|
||||
// build environment (data/<build-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv, $aExtraDirs);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/';
|
||||
if (is_dir($sExtraDir)) {
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
return [$aExtraDirs, $aDirsToCompile];
|
||||
}
|
||||
|
||||
public function InitExtensionMap(array $aExtraDirs, Config $oSourceConfig)
|
||||
{
|
||||
if (is_null($this->oExtensionsMap)) {
|
||||
// Actually read the modules available for the build environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen all the "remote" modules present in the
|
||||
// build environment (data/<build-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sBuildEnv);
|
||||
$this->oExtensionsMap->LoadChoicesFromDatabase($oSourceConfig);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -168,7 +190,7 @@ class RunTimeEnvironment
|
||||
MetaModel::Startup($oConfig, $bModelOnly, $bUseCache, false, $this->sBuildEnv);
|
||||
|
||||
if ($this->oExtensionsMap === null) {
|
||||
$this->oExtensionsMap = new iTopExtensionsMap($this->sBuildEnv);
|
||||
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sBuildEnv);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -444,32 +466,12 @@ class RunTimeEnvironment
|
||||
|
||||
/**
|
||||
* Get the installed modules (only the installed ones)
|
||||
* @return \MFModule[]
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array
|
||||
{
|
||||
$sSourceDirFull = APPROOT.$sSourceDir;
|
||||
if (!is_dir($sSourceDirFull)) {
|
||||
throw new Exception("The source directory '$sSourceDirFull' does not exist (or could not be read)");
|
||||
}
|
||||
$aDirsToCompile = [$sSourceDirFull];
|
||||
if (is_dir(APPROOT.'extensions')) {
|
||||
$aDirsToCompile[] = APPROOT.'extensions';
|
||||
}
|
||||
$sExtraDir = utils::GetDataPath().$this->sBuildEnv.'-modules/';
|
||||
if (is_dir($sExtraDir)) {
|
||||
$aDirsToCompile[] = $sExtraDir;
|
||||
}
|
||||
|
||||
$aExtraDirs = $this->GetExtraDirsToScan($aDirsToCompile);
|
||||
$aDirsToCompile = array_merge($aDirsToCompile, $aExtraDirs);
|
||||
|
||||
list($aExtraDirs, $aDirsToCompile) = $this->GetDirsToCompile($sSourceDir, $sSourceEnv);
|
||||
$oSourceConfig = new Config(APPCONF.$sSourceEnv.'/'.ITOP_CONFIG_FILE);
|
||||
|
||||
// Actually read the modules available for the build environment,
|
||||
// but get the selection from the source environment and finally
|
||||
// mark as (automatically) chosen all the "remote" modules present in the
|
||||
// build environment (data/<build-env>-modules)
|
||||
// The actual choices will be recorded by RecordInstallation below
|
||||
$this->InitExtensionMap($aExtraDirs, $oSourceConfig);
|
||||
$this->GetExtensionMap()->LoadChoicesFromDatabase($oSourceConfig);
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
@@ -477,12 +479,10 @@ class RunTimeEnvironment
|
||||
$this->GetExtensionMap()->MarkAsChosen($oExtension->sCode);
|
||||
}
|
||||
}
|
||||
|
||||
$aModulesToLoad = $this->GetModulesToLoad($this->sFinalEnv, $aDirsToCompile);
|
||||
$aAvailableModules = $this->AnalyzeInstallation($oSourceConfig, $aDirsToCompile, true, $aModulesToLoad);
|
||||
|
||||
// Do load the required modules
|
||||
//
|
||||
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
|
||||
|
||||
$aRet = [];
|
||||
@@ -1032,7 +1032,8 @@ class RunTimeEnvironment
|
||||
@chmod($sFinalConfig, 0770); // In case it exists: RWX for owner and group, nothing for others
|
||||
$this->CommitFile($sBuildConfig, $sFinalConfig);
|
||||
@chmod($sFinalConfig, 0440); // Read-only for owner and group, nothing for others
|
||||
@rmdir(dirname($sBuildConfig)); // Cleanup the temporary build dir if empty
|
||||
|
||||
SetupUtils::rrmdir(dirname($sBuildConfig)); // Cleanup the temporary build dir if empty
|
||||
|
||||
if (! isset($_SESSION)) {
|
||||
//used in all UI setups (not unattended)
|
||||
@@ -1419,8 +1420,6 @@ class RunTimeEnvironment
|
||||
* @param array $aSelectedExtensionCodes
|
||||
* @param array $aRemovedExtensionCodes
|
||||
* @param array $aSelectedModules
|
||||
* @param string $sSourceDir
|
||||
* @param string $sExtensionDir
|
||||
* @param boolean $bUseSymbolicLinks
|
||||
*
|
||||
* @return void
|
||||
@@ -1428,33 +1427,13 @@ class RunTimeEnvironment
|
||||
* @throws \CoreException
|
||||
*
|
||||
*/
|
||||
public function DoCompile(array $aSelectedExtensionCodes, array $aRemovedExtensionCodes, array $aSelectedModules, string $sSourceDir, string $sExtensionDir, bool $bUseSymbolicLinks = false): void
|
||||
public function DoCompile(array $aSelectedExtensionCodes, array $aRemovedExtensionCodes, array $aSelectedModules, bool $bUseSymbolicLinks = false): void
|
||||
{
|
||||
SetupLog::Info('Compiling data model.');
|
||||
|
||||
$sEnvironment = $this->sBuildEnv;
|
||||
$sBuildPath = $this->GetBuildDir();
|
||||
|
||||
$sSourcePath = APPROOT.$sSourceDir;
|
||||
$aDirsToScan = [$sSourcePath];
|
||||
$sExtensionsPath = APPROOT.$sExtensionDir;
|
||||
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 the extra dir exists, scan it for additional modules as well
|
||||
$aDirsToScan[] = $sExtraPath;
|
||||
}
|
||||
|
||||
if (!is_dir($sSourcePath)) {
|
||||
$sErrorMessage = "Failed to find the source directory '$sSourcePath', please check the rights of the web server";
|
||||
$e = new CoreException($sErrorMessage);
|
||||
IssueLog::Exception($sErrorMessage, $e);
|
||||
throw $e;
|
||||
}
|
||||
|
||||
if (!is_dir($sBuildPath)) {
|
||||
if (!mkdir($sBuildPath)) {
|
||||
$sErrorMessage = "Failed to create directory '$sBuildPath', please check the rights of the web server";
|
||||
@@ -1471,7 +1450,7 @@ class RunTimeEnvironment
|
||||
SetupUtils::tidydir($sBuildPath);
|
||||
}
|
||||
|
||||
$oExtensionsMap = new iTopExtensionsMap($this->GetFinalEnv(), $aDirsToScan);
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->GetFinalEnv());
|
||||
// Removed modules are stored as static for FindModules()
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
|
||||
|
||||
@@ -1481,7 +1460,7 @@ class RunTimeEnvironment
|
||||
$aNoCodeExtensionLabelsThatBreakSetup = [];
|
||||
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
|
||||
if (in_array($oExtension->sCode, $aSelectedExtensionCodes)) {
|
||||
$oExtension->bMarkedAsChosen = true;
|
||||
$oExtension->MarkAsChosen();
|
||||
}
|
||||
|
||||
if (empty($oExtension->sCode)) {
|
||||
@@ -1493,7 +1472,7 @@ class RunTimeEnvironment
|
||||
$aNoCodeExtensionSourceDirs [$sExtensionLabel] = $oExtension->sSourceDir;
|
||||
}
|
||||
|
||||
if ($oExtension->bMarkedAsChosen) {
|
||||
if ($oExtension->IsMarkedAsChosen()) {
|
||||
$aNoCodeExtensionLabelsThatBreakSetup[] = $sExtensionLabel;
|
||||
$bSetupFailure = true;
|
||||
}
|
||||
@@ -1511,7 +1490,7 @@ class RunTimeEnvironment
|
||||
}
|
||||
}
|
||||
|
||||
$oFactory = new ModelFactory($aDirsToScan);
|
||||
$oFactory = new ModelFactory($oExtensionsMap->GetScannedModulesRootDirs());
|
||||
|
||||
$oDictModule = new MFDictModule('dictionaries', 'iTop Dictionaries', APPROOT.'dictionaries');
|
||||
$oFactory->LoadModule($oDictModule);
|
||||
@@ -1700,21 +1679,29 @@ class RunTimeEnvironment
|
||||
}
|
||||
|
||||
$aExtensionDirs = [];
|
||||
$aFromSelectedExtensionModules = [];
|
||||
foreach ($this->GetExtensionMap()->GetAllExtensions() as $oExtension) {
|
||||
if ($oExtension->bMarkedAsChosen && is_dir($oExtension->sSourceDir)) {
|
||||
if ($oExtension->IsMarkedAsChosen() && is_dir($oExtension->sSourceDir)) {
|
||||
$aExtensionDirs [] = $oExtension->sSourceDir;
|
||||
$aFromSelectedExtensionModules = array_merge($aFromSelectedExtensionModules, $oExtension->aModules);
|
||||
}
|
||||
}
|
||||
|
||||
SetupLog::Info(__METHOD__, null, ['ext_dirs' => $aExtensionDirs]);
|
||||
$aModuleIdsToLoad = InstallationChoicesToModuleConverter::GetInstance()->GetModules($aChoices, $aSearchDirs, $sInstallFilePath, $aExtensionDirs);
|
||||
$aModulesToLoad = [];
|
||||
|
||||
foreach ($aModuleIdsToLoad as $sModuleId) {
|
||||
$oModule = new Module($sModuleId);
|
||||
$sModuleName = $oModule->GetModuleName();
|
||||
$aModulesToLoad[] = $sModuleName;
|
||||
}
|
||||
|
||||
foreach ($aFromSelectedExtensionModules as $sModuleName) {
|
||||
if (! in_array($sModuleName, $aModulesToLoad)) {
|
||||
$aModulesToLoad[] = $sModuleName;
|
||||
}
|
||||
}
|
||||
return $aModulesToLoad;
|
||||
}
|
||||
|
||||
|
||||
@@ -74,8 +74,6 @@ class DataAuditSequencer extends StepSequencer
|
||||
$aSelectedExtensionCodes,
|
||||
$aRemovedExtensionCodes,
|
||||
$aSelectedModules,
|
||||
$sSourceDir,
|
||||
$sExtensionDir,
|
||||
$bUseSymbolicLinks
|
||||
);
|
||||
return $this->ComputeNextStep($sStep);
|
||||
|
||||
@@ -1602,7 +1602,7 @@ JS
|
||||
}
|
||||
$oProductionEnv = new RunTimeEnvironment($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
|
||||
$aRemovedExtensionCodes = json_decode($oWizard->GetParameter('removed_extensions'), true) ?? [];
|
||||
$oExtensionsMap = new iTopExtensionsMap($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV), $aDirsToScan);
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap($oWizard->GetParameter('target_env', ITOP_DEFAULT_ENV));
|
||||
$oExtensionsMap->DeclareExtensionAsRemoved($aRemovedExtensionCodes);
|
||||
|
||||
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);
|
||||
|
||||
@@ -79,7 +79,7 @@ class InstallationFileService
|
||||
public function GetItopExtensionsMap(): iTopExtensionsMap
|
||||
{
|
||||
if (is_null($this->oItopExtensionsMap)) {
|
||||
$this->oItopExtensionsMap = new iTopExtensionsMap($this->sTargetEnvironment);
|
||||
$this->oItopExtensionsMap = iTopExtensionsMap::GetExtensionsMap($this->sTargetEnvironment);
|
||||
}
|
||||
return $this->oItopExtensionsMap;
|
||||
}
|
||||
|
||||
@@ -13,10 +13,10 @@
|
||||
<target_env>production</target_env>
|
||||
<workspace_dir></workspace_dir>
|
||||
<database>
|
||||
<server>localhost</server>
|
||||
<user>iTop</user>
|
||||
<pwd>blob99</pwd>
|
||||
<name>configcomments</name>
|
||||
<server></server>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<name></name>
|
||||
<db_tls_enabled></db_tls_enabled>
|
||||
<db_tls_ca></db_tls_ca>
|
||||
<prefix></prefix>
|
||||
@@ -24,9 +24,9 @@
|
||||
<url></url>
|
||||
<graphviz_path>/usr/bin/dot</graphviz_path>
|
||||
<admin_account>
|
||||
<user>admin</user>
|
||||
<pwd>admin</pwd>
|
||||
<language>FR FR</language>
|
||||
<user></user>
|
||||
<pwd></pwd>
|
||||
<language></language>
|
||||
</admin_account>
|
||||
<language></language>
|
||||
<selected_modules type="array">
|
||||
|
||||
@@ -61,6 +61,7 @@ if (!function_exists('json_decode')) {
|
||||
//N°3671 setup context: force $bForceTrustProxy to be persisted in next calls
|
||||
utils::GetAbsoluteUrlAppRoot(true);
|
||||
$oWizard = new WizardController('WizStepWelcome');
|
||||
$oCtx = new ContextTag(ContextTag::TAG_SETUP);
|
||||
//N°3952
|
||||
if (SetupUtils::IsSessionSetupTokenValid()) {
|
||||
// Normal operation
|
||||
|
||||
@@ -196,6 +196,8 @@ class WizardController
|
||||
SetupLog::Info("=== Setup screen: ".$oStep->GetTitle().' ('.get_class($oStep).')');
|
||||
$oPage = new SetupPage($oStep->GetTitle());
|
||||
$oPage->LinkScriptFromAppRoot('setup/setup.js');
|
||||
$oStep->PreFormDisplay($oPage);
|
||||
|
||||
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
|
||||
$oPage->add('<div class="ibo-setup--wizard--content">');
|
||||
$oStep->Display($oPage);
|
||||
@@ -263,8 +265,8 @@ EOF
|
||||
$oPage->output();
|
||||
}
|
||||
/**
|
||||
* Make the wizard run: Start, Next or Back depending WizardUpdateButtons();
|
||||
on the page's parameters
|
||||
* Make the wizard run: 'Start', 'Next' or 'Back' depending WizardUpdateButtons();
|
||||
* on the page's parameters
|
||||
*/
|
||||
public function Run()
|
||||
{
|
||||
|
||||
@@ -52,6 +52,17 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
|
||||
*/
|
||||
public function UpdateWizardStateAndGetNextStep($bMoveForward = true): WizardState
|
||||
{
|
||||
if ($this->oWizard->GetParameter('skip_wizard', false)) {
|
||||
$oRuntimeEnv = new RunTimeEnvironment();
|
||||
$sBuildConfigFile = APPCONF.$oRuntimeEnv->GetBuildEnv().'/'.ITOP_CONFIG_FILE;
|
||||
$oConfig = new Config($sBuildConfigFile);
|
||||
$oExtensionMap = iTopExtensionsMap::GetExtensionsMap($oRuntimeEnv->GetBuildEnv());
|
||||
$aExtensionsFromDatabase = $oExtensionMap->GetChoicesFromDatabase($oConfig);
|
||||
$this->oWizard->SetParameter('selected_extensions', json_encode($aExtensionsFromDatabase));
|
||||
$adModulesFromDatabase = ModuleInstallationRepository::GetInstance()->ReadComputeInstalledModules($oConfig);
|
||||
$this->oWizard->SetParameter('selected_modules', json_encode(array_keys($adModulesFromDatabase)));
|
||||
}
|
||||
|
||||
$aWizardSteps = $this->GetWizardSteps();
|
||||
$this->oWizard->SetWizardSteps($aWizardSteps);
|
||||
$this->sCurrentState = count($aWizardSteps) - 1;
|
||||
|
||||
@@ -55,7 +55,7 @@ class WizStepModulesChoice extends AbstractWizStepInstall
|
||||
{
|
||||
parent::__construct($oWizard, $sCurrentState);
|
||||
$this->bChoicesFromDatabase = false;
|
||||
$this->oExtensionsMap = new iTopExtensionsMap();
|
||||
$this->oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$sPreviousSourceDir = $this->oWizard->GetParameter('previous_version_dir', '');
|
||||
$sConfigPath = null;
|
||||
if (($sPreviousSourceDir !== '') && is_readable($sPreviousSourceDir.'/conf/'.ITOP_DEFAULT_ENV.'/config-itop.php')) {
|
||||
@@ -81,7 +81,6 @@ class WizStepModulesChoice extends AbstractWizStepInstall
|
||||
// Sanity check (not stopper, to let developers go further...)
|
||||
try {
|
||||
$aModulesToLoad = json_decode($oWizard->GetParameter('selected_modules'), true) ?? null;
|
||||
SetupLog::Error(__METHOD__, null, [$aModulesToLoad]);
|
||||
$this->aAnalyzeInstallationModules = SetupUtils::AnalyzeInstallation($this->oWizard, true, $aModulesToLoad, $this->oConfig);
|
||||
} catch (MissingDependencyException $e) {
|
||||
$this->oMissingDependencyException = $e;
|
||||
@@ -870,18 +869,18 @@ EOF
|
||||
|
||||
$sTooltip = '';
|
||||
if ($aFlags['missing']) {
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block ibo-is-red" title="The local extension folder has been removed from the disk. This will force the uninstallation of this extension." >source removed</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--missing-from-disk" class="ibo-badge ibo-block ibo-is-red" title="The local extension folder has been removed from the disk. This will force the uninstallation of this extension." >source removed</div>';
|
||||
}
|
||||
if ($aFlags['installed']) {
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--installed" class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div>';
|
||||
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--to-be-uninstalled" class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>';
|
||||
} else {
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div>';
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>';
|
||||
}
|
||||
if (!$aFlags['uninstallable']) {
|
||||
$sTooltip .= '<div class="ibo-badge ibo-block ibo-is-orange" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</div>';
|
||||
$sTooltip .= '<div id="badge--'.$sId.'--not-uninstallable" class="ibo-badge ibo-block ibo-is-orange" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</div>';
|
||||
}
|
||||
|
||||
$sMetadata = '';
|
||||
|
||||
@@ -24,6 +24,15 @@
|
||||
class WizStepWelcome extends WizardStep
|
||||
{
|
||||
protected $bCanMoveForward;
|
||||
private array $aInfo;
|
||||
private array $aWarnings;
|
||||
private array $aErrors;
|
||||
|
||||
public function __construct(WizardController $oWizard, $sCurrentState)
|
||||
{
|
||||
parent::__construct($oWizard, $sCurrentState);
|
||||
$this->CheckInstallation();
|
||||
}
|
||||
|
||||
public function GetTitle()
|
||||
{
|
||||
@@ -66,39 +75,14 @@ class WizStepWelcome extends WizardStep
|
||||
EOF
|
||||
);
|
||||
$oPage->add('<h1>'.ITOP_APPLICATION.' Installation Wizard</h1>');
|
||||
$aResults = SetupUtils::CheckPhpAndExtensions();
|
||||
$this->bCanMoveForward = true;
|
||||
$aInfo = [];
|
||||
$aWarnings = [];
|
||||
$aErrors = [];
|
||||
foreach ($aResults as $oCheckResult) {
|
||||
switch ($oCheckResult->iSeverity) {
|
||||
case CheckResult::ERROR:
|
||||
$aErrors[] = $oCheckResult->sLabel;
|
||||
$this->bCanMoveForward = false;
|
||||
break;
|
||||
|
||||
case CheckResult::WARNING:
|
||||
$aWarnings[] = $oCheckResult->sLabel;
|
||||
break;
|
||||
|
||||
case CheckResult::INFO:
|
||||
$aInfo[] = $oCheckResult->sLabel;
|
||||
break;
|
||||
|
||||
case CheckResult::TRACE:
|
||||
SetupLog::Ok($oCheckResult->sLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$sStyle = 'style="display:none;overflow:auto;"';
|
||||
$sToggleButtons = '<button type="button" id="show_details" class="ibo-button ibo-is-alternative ibo-is-neutral" onclick="$(\'#details\').toggle(); $(this).toggle(); $(\'#hide_details\').toggle();"><span class="ibo-button--icon fa fa-caret-down"></span><span class="ibo-button--label">Show details</span></button><button type="button" id="hide_details" class="ibo-button ibo-is-alternative ibo-is-neutral" style="display:none;" onclick="$(\'#details\').toggle(); $(this).toggle(); $(\'#show_details\').toggle();"><span class="ibo-button--icon fa fa-caret-up"></span><span class="ibo-button--label">Hide details</span></button>';
|
||||
if (count($aErrors) > 0) {
|
||||
if (count($this->aErrors) > 0) {
|
||||
$sStyle = 'style="overflow:auto;"';
|
||||
$sTitle = count($aErrors).' Error(s), '.count($aWarnings).' Warning(s).';
|
||||
$sTitle = count($this->aErrors).' Error(s), '.count($this->aWarnings).' Warning(s).';
|
||||
$sH2Class = 'text-error';
|
||||
} elseif (count($aWarnings) > 0) {
|
||||
$sTitle = count($aWarnings).' Warning(s) '.$sToggleButtons;
|
||||
} elseif (count($this->aWarnings) > 0) {
|
||||
$sTitle = count($this->aWarnings).' Warning(s) '.$sToggleButtons;
|
||||
$sH2Class = 'text-warning';
|
||||
} else {
|
||||
$sTitle = 'Ok. '.$sToggleButtons;
|
||||
@@ -110,13 +94,13 @@ EOF
|
||||
<div id="details" $sStyle>
|
||||
HTML
|
||||
);
|
||||
foreach ($aErrors as $sText) {
|
||||
foreach ($this->aErrors as $sText) {
|
||||
$oPage->error($sText);
|
||||
}
|
||||
foreach ($aWarnings as $sText) {
|
||||
foreach ($this->aWarnings as $sText) {
|
||||
$oPage->warning($sText);
|
||||
}
|
||||
foreach ($aInfo as $sText) {
|
||||
foreach ($this->aInfo as $sText) {
|
||||
$oPage->ok($sText);
|
||||
}
|
||||
$oPage->add('</div>');
|
||||
@@ -127,8 +111,68 @@ HTML
|
||||
$oPage->add_ready_script('CheckDirectoryConfFilesPermissions("'.utils::GetItopVersionWikiSyntax().'")');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add post display stuff to the setup screen
|
||||
* @param \SetupPage $oPage
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public function PostFormDisplay(SetupPage $oPage)
|
||||
{
|
||||
if ($this->bCanMoveForward) {
|
||||
$sBuildConfigFile = APPCONF.ITOP_DEFAULT_ENV.'/'.ITOP_CONFIG_FILE;
|
||||
if (file_exists($sBuildConfigFile)) {
|
||||
$oPage->add(
|
||||
<<<HTML
|
||||
<form method="post">
|
||||
<input type="hidden" name="_class" value="WizStepLandingBeforeAudit"/>
|
||||
<input type="hidden" name="operation" value="next"/>
|
||||
<input type="hidden" name="_params[skip_wizard]" value="1"/>
|
||||
<table style="width:100%;" class="ibo-setup--wizard--buttons-container">
|
||||
<tr>
|
||||
<td style="text-align: right"><button type="submit" class="ibo-button ibo-is-regular ibo-is-secondary">Keep current choices</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function CanMoveForward()
|
||||
{
|
||||
return $this->bCanMoveForward;
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
public function CheckInstallation(): void
|
||||
{
|
||||
$aResults = SetupUtils::CheckPhpAndExtensions();
|
||||
$this->bCanMoveForward = true;
|
||||
$this->aInfo = [];
|
||||
$this->aWarnings = [];
|
||||
$this->aErrors = [];
|
||||
foreach ($aResults as $oCheckResult) {
|
||||
switch ($oCheckResult->iSeverity) {
|
||||
case CheckResult::ERROR:
|
||||
$this->aErrors[] = $oCheckResult->sLabel;
|
||||
$this->bCanMoveForward = false;
|
||||
break;
|
||||
|
||||
case CheckResult::WARNING:
|
||||
$this->aWarnings[] = $oCheckResult->sLabel;
|
||||
break;
|
||||
|
||||
case CheckResult::INFO:
|
||||
$this->aInfo[] = $oCheckResult->sLabel;
|
||||
break;
|
||||
|
||||
case CheckResult::TRACE:
|
||||
SetupLog::Ok($oCheckResult->sLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,6 +76,10 @@ abstract class WizardStep
|
||||
{
|
||||
}
|
||||
|
||||
public function PreFormDisplay(SetupPage $oPage)
|
||||
{
|
||||
}
|
||||
|
||||
protected function CheckDependencies()
|
||||
{
|
||||
if (is_null($this->bDependencyCheck)) {
|
||||
|
||||
119
sources/Application/Helper/ExceptionHandlerHelper.php
Normal file
119
sources/Application/Helper/ExceptionHandlerHelper.php
Normal file
@@ -0,0 +1,119 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Application\Helper;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\ErrorPage;
|
||||
use IssueLog;
|
||||
use SimpleXMLElement;
|
||||
use Throwable;
|
||||
|
||||
class ExceptionHandlerHelper
|
||||
{
|
||||
public static array $aSupportedMimeTypes = [
|
||||
'application/json' => 'json',
|
||||
'application/xml' => 'xml',
|
||||
'text/html' => 'html',
|
||||
'text/plain' => 'text',
|
||||
];
|
||||
|
||||
public static function HandleException(Throwable $oException)
|
||||
{
|
||||
$aStatus = ob_get_status();
|
||||
if (count($aStatus) !== 0) {
|
||||
ob_end_clean();
|
||||
}
|
||||
|
||||
// Log the exception
|
||||
IssueLog::Exception('Fatal error', $oException);
|
||||
|
||||
$mime = self::NegotiateMimeType();
|
||||
|
||||
if ($mime === null) {
|
||||
http_response_code(406);
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
header('Vary: Accept');
|
||||
echo json_encode(['error' => 'Not Acceptable'], JSON_UNESCAPED_UNICODE);
|
||||
return;
|
||||
}
|
||||
|
||||
http_response_code(500);
|
||||
header("Content-Type: {$mime}; charset=utf-8");
|
||||
header('Vary: Accept');
|
||||
|
||||
$aData = [
|
||||
'error' => 'Fatal error',
|
||||
'message' => 'We are sorry, an unexpected error has occurred. Please try again later.',
|
||||
|
||||
];
|
||||
|
||||
switch (self::$aSupportedMimeTypes[$mime]) {
|
||||
case 'json':
|
||||
echo json_encode($aData, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
|
||||
break;
|
||||
|
||||
case 'xml':
|
||||
$oXml = new SimpleXMLElement('<response/>');
|
||||
array_walk_recursive($aData, function ($sValue, $sKey) use ($oXml) {
|
||||
$oXml->addChild((string)$sKey, htmlspecialchars((string)$sValue, ENT_QUOTES | ENT_XML1, 'UTF-8'));
|
||||
});
|
||||
echo $oXml->asXML();
|
||||
break;
|
||||
|
||||
case 'html':
|
||||
// Create error page
|
||||
$oErrorPage = new ErrorPage('Fatal error');
|
||||
$oErrorPage->error('We are sorry, an unexpected error has occurred. Please try again later.<br><br>', $oException);
|
||||
$oErrorPage->output();
|
||||
break;
|
||||
|
||||
case 'text':
|
||||
echo "Fatal error\n";
|
||||
echo "We are sorry, an unexpected error has occurred. Please try again later.\n";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public static function NegotiateMimeType(): ?string
|
||||
{
|
||||
$supportedMimes = array_keys(self::$aSupportedMimeTypes);
|
||||
$acceptHeader = $_SERVER['HTTP_ACCEPT'] ?? '*/*';
|
||||
|
||||
if (trim($acceptHeader) === '' || $acceptHeader === '*/*') {
|
||||
return in_array('application/json', $supportedMimes, true) ? 'application/json' : $supportedMimes[0];
|
||||
}
|
||||
|
||||
$accepted = [];
|
||||
foreach (explode(',', $acceptHeader) as $part) {
|
||||
$part = trim($part);
|
||||
$q = 1.0;
|
||||
if (str_contains($part, ';')) {
|
||||
[$type, $params] = array_map('trim', explode(';', $part, 2));
|
||||
if (preg_match('/q=([0-9.]+)/', $params, $m)) {
|
||||
$q = (float)$m[1];
|
||||
}
|
||||
} else {
|
||||
$type = $part;
|
||||
}
|
||||
$accepted[] = ['type' => $type, 'q' => $q];
|
||||
}
|
||||
|
||||
usort($accepted, fn ($a, $b) => $b['q'] <=> $a['q']);
|
||||
|
||||
foreach ($accepted as $a) {
|
||||
foreach ($supportedMimes as $mime) {
|
||||
if ($a['type'] === $mime || $a['type'] === '*/*') {
|
||||
return $mime;
|
||||
}
|
||||
// Ex: application/* match application/json
|
||||
if (str_ends_with($a['type'], '/*')) {
|
||||
$prefix = explode('/', $a['type'])[0].'/';
|
||||
if (str_starts_with($mime, $prefix)) {
|
||||
return $mime;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,11 @@
|
||||
|
||||
namespace Combodo\iTop\Application\TwigBase\Controller;
|
||||
|
||||
require_once APPROOT.'setup/setuppage.class.inc.php';
|
||||
|
||||
use ApplicationMenu;
|
||||
use Combodo\iTop\Application\TwigBase\Twig\TwigHelper;
|
||||
use Combodo\iTop\Application\WebPage\AjaxPage;
|
||||
use Combodo\iTop\Application\WebPage\ErrorPage;
|
||||
use Combodo\iTop\Application\WebPage\iTopWebPage;
|
||||
use Combodo\iTop\Application\WebPage\WebPage;
|
||||
use Combodo\iTop\Controller\AbstractController;
|
||||
@@ -261,29 +262,29 @@ abstract class Controller extends AbstractController
|
||||
*/
|
||||
public function HandleOperation(): void
|
||||
{
|
||||
try {
|
||||
$this->CheckAccess();
|
||||
$this->m_sOperation = utils::ReadParam('operation', $this->sDefaultOperation);
|
||||
// try {
|
||||
$this->CheckAccess();
|
||||
$this->m_sOperation = utils::ReadParam('operation', $this->sDefaultOperation);
|
||||
|
||||
if ($this->CallOperation(utils::ToCamelCase($this->m_sOperation))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to unchanged names for compatibility
|
||||
if ($this->CallOperation($this->m_sOperation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->DisplayBadRequest();
|
||||
} catch (Exception $e) {
|
||||
http_response_code(500);
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->add(get_class($e).' : '.utils::EscapeHtml($e->GetMessage()));
|
||||
$oP->output();
|
||||
|
||||
IssueLog::Exception('HandleOperation failed for '.json_encode($this->m_sOperation), $e);
|
||||
if ($this->CallOperation(utils::ToCamelCase($this->m_sOperation))) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback to unchanged names for compatibility
|
||||
if ($this->CallOperation($this->m_sOperation)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->DisplayBadRequest();
|
||||
// } catch (Exception $e) {
|
||||
// http_response_code(500);
|
||||
// $oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
// $oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
// $oP->add(get_class($e).' : '.utils::EscapeHtml($e->GetMessage()));
|
||||
// $oP->output();
|
||||
//
|
||||
// IssueLog::Exception('HandleOperation failed for '.json_encode($this->m_sOperation), $e);
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -13,6 +13,13 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
|
||||
/** @inheritDoc */
|
||||
public const UI_BLOCK_CLASS_NAME = ExtensionDetails::class;
|
||||
|
||||
private const BADGE_ID_INSTALLED = 'installed';
|
||||
private const BADGE_ID_NOT_INSTALLED = 'not-installed';
|
||||
private const BADGE_ID_TO_BE_INSTALLED = 'to-be-installed';
|
||||
private const BADGE_ID_TO_BE_UNINSTALLED = 'to-be-uninstalled';
|
||||
private const BADGE_ID_NOT_UNINSTALLABLE = 'not-uninstallable';
|
||||
private const BADGE_ID_MISSING_FROM_DISK = 'missing-from-disk';
|
||||
|
||||
public static function MakeInstalled(string $sCode, string $sLabel, string $sDescription = '', array $aMetaData = [], array $aExtraFlags = [], string $sAbout = '')
|
||||
{
|
||||
$aBadges = [];
|
||||
@@ -21,11 +28,19 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
|
||||
$bSelected = $aExtraFlags['selected'] ?? true;
|
||||
$bDisabled = $aExtraFlags['disabled'] ?? false;
|
||||
$bRemote = $aExtraFlags['remote'] ?? false;
|
||||
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk);
|
||||
$oBadgeInstalled = BadgeUIBlockFactory::MakeGreen(Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled'));
|
||||
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $sCode);
|
||||
$oBadgeInstalled = BadgeUIBlockFactory::MakeGreen(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_INSTALLED)
|
||||
);
|
||||
$oBadgeInstalled->AddCSSClass('checked');
|
||||
$aBadges[] = $oBadgeInstalled;
|
||||
$oBadgeToBeUninstalled = BadgeUIBlockFactory::MakeRed(Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeUninstalled'));
|
||||
$oBadgeToBeUninstalled = BadgeUIBlockFactory::MakeRed(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeUninstalled'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeUninstalled+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_TO_BE_UNINSTALLED)
|
||||
);
|
||||
$oBadgeToBeUninstalled->AddCSSClass('unchecked');
|
||||
$aBadges[] = $oBadgeToBeUninstalled;
|
||||
|
||||
@@ -56,11 +71,19 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
|
||||
$bUninstallable = $aExtraFlags['uninstallable'] ?? true;
|
||||
$bSelected = $aExtraFlags['selected'] ?? false;
|
||||
$bDisabled = $aExtraFlags['disabled'] ?? false;
|
||||
self::AddExtraBadges($aBadges, $bUninstallable, false);
|
||||
$oBadgeInstalled = BadgeUIBlockFactory::MakeGrey(Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled'));
|
||||
self::AddExtraBadges($aBadges, $bUninstallable, false, $sCode);
|
||||
$oBadgeInstalled = BadgeUIBlockFactory::MakeGrey(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_NOT_INSTALLED)
|
||||
);
|
||||
$oBadgeInstalled->AddCSSClass('unchecked');
|
||||
$aBadges[] = $oBadgeInstalled;
|
||||
$oBadgeToBeUninstalled = BadgeUIBlockFactory::MakeCyan(Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeInstalled'));
|
||||
$oBadgeToBeUninstalled = BadgeUIBlockFactory::MakeCyan(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeInstalled'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeToBeInstalled+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_TO_BE_INSTALLED)
|
||||
);
|
||||
$oBadgeToBeUninstalled->AddCSSClass('checked');
|
||||
$aBadges[] = $oBadgeToBeUninstalled;
|
||||
$oExtensionDetails = new ExtensionDetails($sCode, $sLabel, $sDescription, $aMetaData, $aBadges, $sAbout);
|
||||
@@ -76,13 +99,35 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
|
||||
return $oExtensionDetails;
|
||||
}
|
||||
|
||||
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk)
|
||||
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, string $sCode)
|
||||
{
|
||||
if (!$bUninstallable) {
|
||||
$aBadges[] = BadgeUIBlockFactory::MakeOrange(Dict::S('UI:Layout:ExtensionsDetails:BadgeNotUninstallable'));
|
||||
$aBadges[] = BadgeUIBlockFactory::MakeOrange(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotUninstallable'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotUninstallable+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_NOT_UNINSTALLABLE)
|
||||
);
|
||||
}
|
||||
if ($bMissingFromDisk) {
|
||||
$aBadges[] = BadgeUIBlockFactory::MakeRed(Dict::S('UI:Layout:ExtensionsDetails:BadgeMissingFromDisk'));
|
||||
$aBadges[] = BadgeUIBlockFactory::MakeRed(
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeMissingFromDisk'),
|
||||
Dict::S('UI:Layout:ExtensionsDetails:BadgeMissingFromDisk+'),
|
||||
self::GetBadgeId($sCode, self::BADGE_ID_MISSING_FROM_DISK)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a badge ID for an extension
|
||||
*
|
||||
* @param string $sExtensionCode The extension code
|
||||
* @param string $sBadgeType The badge type (one of the BADGE_ID_* constants)
|
||||
*
|
||||
* @return string The badge ID in the format: badge--{extensionCode}--{badgeType}
|
||||
*/
|
||||
public static function GetBadgeId(string $sExtensionCode, string $sBadgeType): string
|
||||
{
|
||||
return 'badge--'.$sExtensionCode.'--'.$sBadgeType;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
namespace Combodo\iTop\Controller;
|
||||
|
||||
use Combodo\iTop\Application\WebPage\AjaxPage;
|
||||
use ApplicationContext;
|
||||
use ApplicationMenu;
|
||||
use AttributeLinkedSet;
|
||||
@@ -21,7 +20,8 @@ use CMDBObjectSet;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableSettings;
|
||||
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\Object\ObjectSummary;
|
||||
use Combodo\iTop\Application\WebPage\AjaxPage;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DBSearch;
|
||||
@@ -34,7 +34,6 @@ use FunctionExpression;
|
||||
use IssueLog;
|
||||
use iTopExtension;
|
||||
use iTopExtensionsMap;
|
||||
use Combodo\iTop\Application\WebPage\JsonPage;
|
||||
use LogChannels;
|
||||
use MetaModel;
|
||||
use ormSet;
|
||||
@@ -963,7 +962,7 @@ EOF
|
||||
$oPage->add('<ul style="margin: 0;">');
|
||||
|
||||
require_once(APPROOT.'setup/extensionsmap.class.inc.php');
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$oExtensionsMap->LoadChoicesFromDatabase(MetaModel::GetConfig());
|
||||
$aChoices = $oExtensionsMap->GetChoices();
|
||||
foreach ($aChoices as $oExtension) {
|
||||
|
||||
@@ -81,7 +81,7 @@ class AttributeOneWayPassword extends AttributeDefinition implements iAttributeN
|
||||
if (is_object($oPassword)) {
|
||||
$oPassword = clone $proposedValue;
|
||||
} else {
|
||||
$oPassword = new ormPassword('', '');
|
||||
$oPassword = new ormPassword();
|
||||
$oPassword->SetPassword($proposedValue);
|
||||
}
|
||||
|
||||
|
||||
28
sources/Exception/ItopException.php
Normal file
28
sources/Exception/ItopException.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Exception;
|
||||
|
||||
use Exception;
|
||||
use IssueLog;
|
||||
use LogChannels;
|
||||
use Throwable;
|
||||
|
||||
class ItopException extends Exception
|
||||
{
|
||||
private array $aContext;
|
||||
|
||||
public function __construct(string $message = '', int $code = 0, ?Throwable $previous = null, array $aContext = [])
|
||||
{
|
||||
$aContext['code'] = $code;
|
||||
IssueLog::Debug($message, LogChannels::EXCEPTION, $aContext);
|
||||
parent::__construct($message, $code, $previous);
|
||||
$this->aContext = $aContext;
|
||||
}
|
||||
|
||||
public function getContext(): array
|
||||
{
|
||||
return $this->aContext;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -108,7 +108,7 @@ class UnitTestRunTimeEnvironment extends RunTimeEnvironment
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir)
|
||||
protected function GetMFModulesToCompile($sSourceEnv, $sSourceDir): array
|
||||
{
|
||||
\SetupLog::Info(__METHOD__);
|
||||
$aRet = parent::GetMFModulesToCompile($sSourceEnv, $sSourceDir);
|
||||
|
||||
@@ -52,4 +52,11 @@ class ormPasswordTest extends ItopDataTestCase
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
public function testSetPasswordNullShouldNotCrash()
|
||||
{
|
||||
$oPassword = new ormPassword();
|
||||
$oPassword->SetPassword(null);
|
||||
static::assertTrue($oPassword->CheckPassword(null));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ $aAddedExtensions = [];
|
||||
if (mb_strlen($sAddedExtensions) > 0) {
|
||||
$aAddedExtensions = explode(',', $sAddedExtensions);
|
||||
}
|
||||
$oExtensionMap = new iTopExtensionsMap();
|
||||
$oExtensionMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
foreach ($aAddedExtensions as $iIndex => $sExtensionCode) {
|
||||
if (mb_strlen($sExtensionCode) <= 0) {
|
||||
unset($aAddedExtensions[$iIndex]);
|
||||
|
||||
@@ -19,7 +19,7 @@ class ExtensionsMapTest extends ItopTestCase
|
||||
|
||||
public function testGetAllExtensionsWithPreviouslyInstalledDoesNotCrash()
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$aExtensions = $oExtensionsMap->GetAllExtensionsWithPreviouslyInstalled();
|
||||
$this->assertGreaterThan(0, count($aExtensions));
|
||||
}
|
||||
@@ -84,7 +84,7 @@ class ExtensionsMapTest extends ItopTestCase
|
||||
|
||||
private function GiveExtensionMapWithAllTypeOfExtensions(): iTopExtensionsMap
|
||||
{
|
||||
$oExtensionsMap = new iTopExtensionsMap();
|
||||
$oExtensionsMap = iTopExtensionsMap::GetExtensionsMap();
|
||||
$this->SetNonPublicProperty($oExtensionsMap, 'aInstalledExtensions', []);
|
||||
$this->SetNonPublicProperty($oExtensionsMap, 'aExtensions', []);
|
||||
|
||||
@@ -153,9 +153,7 @@ class ExtensionsMapTest extends ItopTestCase
|
||||
|
||||
public function testiTopExtensionsMapInit()
|
||||
{
|
||||
$oiTopExtensionsMap = new iTopExtensionsMap(sAppRootForTests:__DIR__."/ressources");
|
||||
|
||||
//file_put_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json', json_encode($this->SerializeExtensionMap($oiTopExtensionsMap), JSON_PRETTY_PRINT));
|
||||
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(sAppRootForTests: __DIR__."/ressources/");
|
||||
|
||||
$sExpected = file_get_contents(__DIR__.'/ressources/all_extensions_from_datamodels.json');
|
||||
$sExpected = str_replace('"sVersion": "ITOP_VERSION"', '"sVersion": "'.ITOP_VERSION.'"', $sExpected);
|
||||
@@ -166,6 +164,19 @@ class ExtensionsMapTest extends ItopTestCase
|
||||
$this->assertEquals($sExpected, $actual);
|
||||
}
|
||||
|
||||
public function testGetScannedModulesRootDirs()
|
||||
{
|
||||
$sAppRootForTest = __DIR__."/resources2/";
|
||||
$oiTopExtensionsMap = iTopExtensionsMap::GetExtensionsMap(sAppRootForTests: $sAppRootForTest);
|
||||
|
||||
$aExpectedDirs = [
|
||||
"{$sAppRootForTest}datamodels/2.x",
|
||||
"{$sAppRootForTest}extensions",
|
||||
"{$sAppRootForTest}data/production-modules",
|
||||
];
|
||||
self::assertEquals($aExpectedDirs, $oiTopExtensionsMap->GetScannedModulesRootDirs());
|
||||
}
|
||||
|
||||
public function SerializeExtensionMap(iTopExtensionsMap $oiTopExtensionsMap): array
|
||||
{
|
||||
$aRes = [];
|
||||
|
||||
@@ -9,30 +9,15 @@ use RunTimeEnvironment;
|
||||
|
||||
class RunTimeEnvironmentTest extends ItopTestCase
|
||||
{
|
||||
private ?string $sFixtureRootDir = null;
|
||||
private ?string $sBuildDirToCleanup = null;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
parent::setUp();
|
||||
$this->RequireOnceItopFile('/setup/runtimeenv.class.inc.php');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
if (($this->sFixtureRootDir !== null) && is_dir($this->sFixtureRootDir)) {
|
||||
self::RecurseRmdir($this->sFixtureRootDir);
|
||||
}
|
||||
if (($this->sBuildDirToCleanup !== null) && is_dir($this->sBuildDirToCleanup)) {
|
||||
self::RecurseRmdir($this->sBuildDirToCleanup);
|
||||
}
|
||||
|
||||
parent::tearDown();
|
||||
}
|
||||
|
||||
public function testDoCompileDoesNotThrowWhenUnselectedExtensionCodeIsMissing(): void
|
||||
{
|
||||
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-code-');
|
||||
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-code-');
|
||||
|
||||
$sInvalidExtensionXml = <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -41,20 +26,20 @@ class RunTimeEnvironmentTest extends ItopTestCase
|
||||
<description>Test extension without code</description>
|
||||
<version>1.0.0</version>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
<more_info_url/>
|
||||
</extension>
|
||||
XML;
|
||||
file_put_contents(APPROOT.$sExtensionsDirRelative.'/extension.xml', $sInvalidExtensionXml);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
|
||||
|
||||
//early DOMFormatException to avoid any real compilation
|
||||
$this->expectException(DOMFormatException::class);
|
||||
$oRunTimeEnvironment->DoCompile([], [], [], $sSourceDirRelative, $sExtensionsDirRelative);
|
||||
$oRunTimeEnvironment->DoCompile([], [], []);
|
||||
}
|
||||
|
||||
public function testDoCompileThrowsWhenSelectedExtensionCodeIsMissing(): void
|
||||
{
|
||||
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-code-');
|
||||
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-code-');
|
||||
|
||||
$sInvalidExtensionXml = <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -63,21 +48,21 @@ XML;
|
||||
<description>Test extension without code</description>
|
||||
<version>1.0.0</version>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
<more_info_url/>
|
||||
</extension>
|
||||
XML;
|
||||
file_put_contents(APPROOT.$sExtensionsDirRelative.'/extension.xml', $sInvalidExtensionXml);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Selected extension(s) cannot be installed: Missing extension code (Broken extension)");
|
||||
|
||||
$oRunTimeEnvironment->DoCompile([""], [], [], $sSourceDirRelative, $sExtensionsDirRelative);
|
||||
$oRunTimeEnvironment->DoCompile([""], [], []);
|
||||
}
|
||||
|
||||
public function testDoCompileThrowsWhenSelectedExtensionCodeAndLabelAreMissing(): void
|
||||
{
|
||||
[$sToken, $sSourceDirRelative, $sExtensionsDirRelative] = $this->CreateFixtureContext('runtimeenv-missing-label-');
|
||||
[$sEnvironment, $sExtensionsDirRelative] = $this->CreateFixtureContext('env-missing-label-');
|
||||
|
||||
$sInvalidExtensionXml = <<<XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
@@ -85,36 +70,34 @@ XML;
|
||||
<description>Test extension without code and label</description>
|
||||
<version>1.0.0</version>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
<more_info_url/>
|
||||
</extension>
|
||||
XML;
|
||||
$sExtensionsDirAbsolute = APPROOT.$sExtensionsDirRelative;
|
||||
file_put_contents($sExtensionsDirAbsolute.'/extension.xml', $sInvalidExtensionXml);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sToken);
|
||||
$oRunTimeEnvironment = $this->CreateRunTimeEnvironment($sEnvironment);
|
||||
|
||||
$this->expectException(Exception::class);
|
||||
$this->expectExceptionMessage("Selected extension(s) cannot be installed: Missing extension code ($sExtensionsDirAbsolute)");
|
||||
|
||||
$oRunTimeEnvironment->DoCompile([""], [], [], $sSourceDirRelative, $sExtensionsDirRelative, false);
|
||||
$oRunTimeEnvironment->DoCompile([""], [], [], false);
|
||||
}
|
||||
|
||||
private function CreateFixtureContext(string $sTokenPrefix): array
|
||||
private function CreateFixtureContext(string $sEnvPrefix): array
|
||||
{
|
||||
$sToken = str_replace('.', '-', uniqid($sTokenPrefix, true));
|
||||
$this->sFixtureRootDir = APPROOT.'data/'.$sToken;
|
||||
$sSourceDirRelative = 'data/'.$sToken.'/source';
|
||||
$sExtensionsDirRelative = 'data/'.$sToken.'/extensions';
|
||||
$sEnvironment = str_replace('.', '-', uniqid($sEnvPrefix, true));
|
||||
$sExtensionsDirRelative = 'data/'.$sEnvironment.'-modules';
|
||||
|
||||
mkdir(APPROOT.$sSourceDirRelative, 0777, true);
|
||||
mkdir(APPROOT.$sExtensionsDirRelative, 0777, true);
|
||||
$this->aFileToClean[] = APPROOT.$sExtensionsDirRelative;
|
||||
|
||||
return [$sToken, $sSourceDirRelative, $sExtensionsDirRelative];
|
||||
return [$sEnvironment, $sExtensionsDirRelative];
|
||||
}
|
||||
|
||||
private function CreateRunTimeEnvironment(string $sToken): RunTimeEnvironment
|
||||
private function CreateRunTimeEnvironment(string $sEnvironment): RunTimeEnvironment
|
||||
{
|
||||
$oRunTimeEnvironment = new RunTimeEnvironment('test-'.$sToken, false);
|
||||
$this->sBuildDirToCleanup = $oRunTimeEnvironment->GetBuildDir();
|
||||
$oRunTimeEnvironment = new RunTimeEnvironment($sEnvironment, false);
|
||||
$this->aFileToClean[] = $oRunTimeEnvironment->GetBuildDir();
|
||||
|
||||
return $oRunTimeEnvironment;
|
||||
}
|
||||
|
||||
@@ -1004,7 +1004,7 @@ class WizStepModulesChoiceTest extends ItopTestCase
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-ext-not-installed"><b>My extension</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-ext-not-installed--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-ext-not-installed--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
<span>v1.2.3</span><span>Local extensions folder</span>
|
||||
@@ -1048,7 +1048,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-ext-installed"><b>My extension</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
<div id="badge--itop-ext-installed--installed" class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div id="badge--itop-ext-installed--to-be-uninstalled" class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
<span>v1.2.3</span><span>Local extensions folder</span>
|
||||
@@ -1093,7 +1093,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-ext-installed"><b>My extension</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div><div class="ibo-badge ibo-block ibo-is-orange" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</div>
|
||||
<div id="badge--itop-ext-installed--installed" class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div id="badge--itop-ext-installed--to-be-uninstalled" class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div><div id="badge--itop-ext-installed--not-uninstallable" class="ibo-badge ibo-block ibo-is-orange" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
<span>v1.2.3</span><span>Local extensions folder</span>
|
||||
@@ -1137,7 +1137,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-ext-not-installed"><b>My extension</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-ext-not-installed--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-ext-not-installed--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
<span>v1.2.3</span><span>Local extensions folder</span>
|
||||
@@ -1175,7 +1175,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-alt-nothing"><b>No Change</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-alt-nothing--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-alt-nothing--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
@@ -1221,7 +1221,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-alt-something"><b>Change</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
<div id="badge--itop-alt-something--installed" class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div id="badge--itop-alt-something--to-be-uninstalled" class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
@@ -1242,7 +1242,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-alt-nothing"><b>No Change</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-alt-nothing--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-alt-nothing--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
@@ -1302,7 +1302,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-alt-something"><b>Change</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
<div id="badge--itop-alt-something--installed" class="ibo-badge ibo-block checked ibo-is-green" title="This extension is part of the current installation." >installed</div><div id="badge--itop-alt-something--to-be-uninstalled" class="ibo-badge ibo-block unchecked ibo-is-red" title="This extension will be uninstalled during the setup." >to be uninstalled</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
@@ -1319,7 +1319,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-ext-not-installed"><b>My extension</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-ext-not-installed--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-ext-not-installed--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
@@ -1344,7 +1344,7 @@ HTML,
|
||||
<div class="ibo-extension-details--information--label">
|
||||
<label for="itop-alt-nothing"><b>No Change</b></label>
|
||||
|
||||
<div class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
<div id="badge--itop-alt-nothing--to-be-installed" class="ibo-badge ibo-block checked ibo-is-cyan" title="This extension will be installed during the setup." >to be installed</div><div id="badge--itop-alt-nothing--not-installed" class="ibo-badge ibo-block unchecked ibo-is-blue-grey" title="This extension is not part of the current installation." >not installed</div>
|
||||
</div>
|
||||
<div class="ibo-extension-details--information--metadata">
|
||||
|
||||
|
||||
@@ -9,11 +9,14 @@ use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
use Combodo\iTop\Test\UnitTest\Service\UnitTestRunTimeEnvironment;
|
||||
use MetaModel;
|
||||
use SetupUtils;
|
||||
|
||||
class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
public const ENVT = 'php-unit-extensionremoval-tests';
|
||||
|
||||
private ?string $sDirPathToRemoveFromExtensionFolder = null;
|
||||
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
//no delta: empty path provided
|
||||
@@ -44,6 +47,14 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
$this->RequireOnceItopFile('/setup/feature_removal/DryRemovalRuntimeEnvironment.php');
|
||||
}
|
||||
|
||||
protected function tearDown(): void
|
||||
{
|
||||
parent::tearDown();
|
||||
if (!is_null($this->sDirPathToRemoveFromExtensionFolder) && is_dir($this->sDirPathToRemoveFromExtensionFolder)) {
|
||||
SetupUtils::rrmdir($this->sDirPathToRemoveFromExtensionFolder);
|
||||
}
|
||||
}
|
||||
|
||||
public function GetTestEnvironment(): string
|
||||
{
|
||||
return self::ENVT;
|
||||
@@ -51,7 +62,11 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
|
||||
public function testComputeDryRemoval()
|
||||
{
|
||||
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['nominal_ext1', 'finalclass_ext2']);
|
||||
$this->sDirPathToRemoveFromExtensionFolder = APPROOT.'/extensions/finalclass_ext3';
|
||||
SetupUtils::copydir(__DIR__.'/other_features/finalclass_ext3', $this->sDirPathToRemoveFromExtensionFolder);
|
||||
clearstatcache();
|
||||
|
||||
$oDryRemovalRuntimeEnvt = new DryRemovalRuntimeEnvironment($this->GetTestEnvironment(), ['finalclass_ext3' => 'Ext For Test3'], ['nominal_ext1', 'finalclass_ext2']);
|
||||
$oDryRemovalRuntimeEnvt->CompileFrom($this->GetTestEnvironment());
|
||||
|
||||
$oSetupAudit = new SetupAudit($this->GetTestEnvironment());
|
||||
@@ -67,6 +82,19 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
|
||||
"FinalClassFeature2Module1MyFinalClassFromLocation" => 0,
|
||||
];
|
||||
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->RunDataAudit());
|
||||
|
||||
$aClassesAfter = ModelReflectionSerializer::GetInstance()->GetModelFromEnvironment($oSetupAudit->GetEnvAfter());
|
||||
$expected = [
|
||||
"FinalClassFeature3Module1MyClass",
|
||||
"FinalClassFeature3Module1MyFinalClassFromLocation",
|
||||
];
|
||||
foreach ($expected as $sAddedClass) {
|
||||
$this->assertContains(
|
||||
$sAddedClass,
|
||||
$aClassesAfter,
|
||||
"After DryRemoval compilation DM should contain classes coming from finalclass_ext3 extension"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public function testGetRemovedClassesFromSetupWizard()
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<extension format="1.0">
|
||||
<extension_code>finalclass_ext3</extension_code>
|
||||
<company>Combodo SARL</company>
|
||||
<author><![CDATA[Odain]]></author>
|
||||
<label><![CDATA[Ext For Test3]]></label>
|
||||
<description><![CDATA[Ext For Test]]></description>
|
||||
<version>6.6.6</version>
|
||||
<modules type="array">
|
||||
<module>
|
||||
<id>finalclass_ext3_module1</id>
|
||||
<version>tags/6.6.6</version>
|
||||
</module>
|
||||
</modules>
|
||||
<release_date>2023-07-19</release_date>
|
||||
<version_description><![CDATA[
|
||||
]]></version_description>
|
||||
<itop_version_min>3.2.0</itop_version_min>
|
||||
<status></status>
|
||||
<mandatory>false</mandatory>
|
||||
<more_info_url></more_info_url>
|
||||
</extension>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
|
||||
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.0">
|
||||
<classes>
|
||||
<class id="FinalClassFeature3Module1MyFinalClassFromLocation" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature3Module1MyFinalClassFromLocation</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name2"/>
|
||||
<attribute id="finalclass"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name2" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name2" xsi:type="AttributeString">
|
||||
<sql>name2</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>Location</parent>
|
||||
</class>
|
||||
<class id="FinalClassFeature3Module1MyClass" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>FinalClassFeature3Module1MyClass</db_table>
|
||||
<naming>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes>
|
||||
<attribute id="name"/>
|
||||
</attributes>
|
||||
</reconciliation>
|
||||
<order>
|
||||
<columns>
|
||||
<column id="name" ascending="false"/>
|
||||
</columns>
|
||||
</order>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="name" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation/>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
</classes>
|
||||
<menus/>
|
||||
<user_rights/>
|
||||
<module_parameters/>
|
||||
</itop_design>
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
|
||||
//// PHP Data Model definition file
|
||||
//
|
||||
//// WARNING - WARNING - WARNING
|
||||
//// DO NOT EDIT THIS FILE (unless you know what you are doing)
|
||||
////
|
||||
//// If you use supply a datamodel.xxxx.xml file with your module
|
||||
//// the this file WILL BE overwritten by the compilation of the
|
||||
//// module (during the setup) if the datamodel.xxxx.xml file
|
||||
//// contains the definition of new classes or menus.
|
||||
////
|
||||
//// The recommended way to define new classes (for iTop 2.0) is via the XML definition.
|
||||
//// This file remains in the module's template only for the cases where there is:
|
||||
//// - either no new class or menu defined in the XML file
|
||||
//// - or no XML file at all supplied by the module
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2021 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
//
|
||||
// iTop module definition file
|
||||
//
|
||||
|
||||
SetupWebPage::AddModule(
|
||||
__FILE__, // Path to the current file, all other file names are relative to the directory containing this file
|
||||
'finalclass_ext3_module1/6.6.6',
|
||||
[
|
||||
// Identification
|
||||
//
|
||||
'label' => 'Ext For Test',
|
||||
'category' => 'business',
|
||||
|
||||
// Setup
|
||||
//
|
||||
'dependencies' => [
|
||||
'itop-structure/3.2.0',
|
||||
],
|
||||
'mandatory' => false,
|
||||
'visible' => true,
|
||||
'installer' => '',
|
||||
|
||||
// Components
|
||||
//
|
||||
'datamodel' => [
|
||||
'model.finalclass_ext3_module1.php',
|
||||
],
|
||||
'webservice' => [],
|
||||
'data.struct' => [// add your 'structure' definition XML files here,
|
||||
],
|
||||
'data.sample' => [// add your sample data XML files here,
|
||||
],
|
||||
|
||||
// Documentation
|
||||
//
|
||||
'doc.manual_setup' => '', // hyperlink to manual setup documentation, if any
|
||||
'doc.more_information' => '', // hyperlink to more information, if any
|
||||
|
||||
// Default settings
|
||||
//
|
||||
'settings' => [// Module specific settings go here, if any
|
||||
],
|
||||
]
|
||||
);
|
||||
@@ -0,0 +1,243 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installation>
|
||||
<steps type="array">
|
||||
<step>
|
||||
<title>Configuration Management options</title>
|
||||
<description><![CDATA[<h2>The options below allow you to configure the type of elements that are to be managed inside iTop.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-apps-tab.svg</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-core</extension_code>
|
||||
<title>Configuration Management Core</title>
|
||||
<description>All the base objects that are mandatory in the iTop CMDB: Organizations, Locations, Teams, Persons, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-structure</module>
|
||||
<module>itop-config-mgmt</module>
|
||||
<module>itop-attachments</module>
|
||||
<module>itop-profiles-itil</module>
|
||||
<module>itop-welcome-itil</module>
|
||||
<module>itop-tickets</module>
|
||||
<module>itop-files-information</module>
|
||||
<module>combodo-db-tools</module>
|
||||
<module>itop-core-update</module>
|
||||
<module>itop-hub-connector</module>
|
||||
<module>itop-oauth-client</module>
|
||||
<module>combodo-backoffice-darkmoon-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-high-contrast-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-protanopia-deuteranopia-theme</module>
|
||||
<module>combodo-backoffice-fullmoon-tritanopia-theme</module>
|
||||
<module>itop-themes-compat</module>
|
||||
<module>combodo-my-account</module>
|
||||
<module>combodo-my-account-user-info</module>
|
||||
<module>combodo-oauth2-client</module>
|
||||
<module>itop-attribute-class-set</module>
|
||||
<module>itop-attribute-encrypted-password</module>
|
||||
<module>itop-ui-copypaste</module>
|
||||
</modules>
|
||||
<mandatory>true</mandatory>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-datacenter</extension_code>
|
||||
<title>Data Center Devices</title>
|
||||
<description>Manage Data Center devices such as Racks, Enclosures, PDUs, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-datacenter-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-end-user</extension_code>
|
||||
<title>End-User Devices</title>
|
||||
<description>Manage devices related to end-users: PCs, Phones, Tablets, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-endusers-devices</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-storage</extension_code>
|
||||
<title>Storage Devices</title>
|
||||
<description>Manage storage devices such as NAS, SAN Switches, Tape Libraries and Tapes, etc.</description>
|
||||
<modules type="array">
|
||||
<module>itop-storage-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-config-mgmt-virtualization</extension_code>
|
||||
<title>Virtualization</title>
|
||||
<description>Manage Hypervisors, Virtual Machines and Farms.</description>
|
||||
<modules type="array">
|
||||
<module>itop-virtualization-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-container-mgmt</extension_code>
|
||||
<title>Containerization</title>
|
||||
<description><![CDATA[Manage Container Images, Applications and Hosts]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-container-mgmt</module>
|
||||
</modules>
|
||||
<default>false</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
<step>
|
||||
<title>Service Management options</title>
|
||||
<description><![CDATA[<h2>Select the choice that best describes the relationships between the services and the IT infrastructure in your IT environment.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-services.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-enterprise</extension_code>
|
||||
<title>Service Management for Enterprises</title>
|
||||
<description>Select this option if the IT delivers services based on a shared infrastructure. For example if different organizations within your company subscribe to services (like Mail and Print services) delivered by a single shared backend.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-service-mgmt-service-provider</extension_code>
|
||||
<title>Service Management for Service Providers</title>
|
||||
<description>Select this option if the IT manages the infrastructure of independent customers. This is the most flexible model, since the services can be delivered with a mix of shared and customer specific infrastructure devices.</description>
|
||||
<modules type="array">
|
||||
<module>itop-service-mgmt-provider</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Tickets Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to respond to user requests and incidents.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-discussion-forum.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket</extension_code>
|
||||
<title>Simple Ticket Management</title>
|
||||
<description>Select this option to use one single type of tickets for all kind of requests.</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-simple-ticket-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil</extension_code>
|
||||
<title>ITIL Compliant Tickets Management</title>
|
||||
<description>Select this option to have different types of ticket for managing user requests and incidents. Each type of ticket has a specific life cycle and specific fields</description>
|
||||
<sub_options>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-user-request</extension_code>
|
||||
<title>User Request Management</title>
|
||||
<description>Manage User Request tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-request-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-incident</extension_code>
|
||||
<title>Incident Management</title>
|
||||
<description>Manage Incidents tickets in iTop</description>
|
||||
<modules type="array">
|
||||
<module>itop-incident-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-itil-enhanced-portal</extension_code>
|
||||
<title>Customer Portal</title>
|
||||
<description><![CDATA[Modern & responsive portal for the end-users]]></description>
|
||||
<modules type="array">
|
||||
<module>itop-portal</module>
|
||||
<module>itop-portal-base</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
</options>
|
||||
</sub_options>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-ticket-mgmt-none</extension_code>
|
||||
<title>No Tickets Management</title>
|
||||
<description>Don't manage incidents or user requests in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Change Management options</title>
|
||||
<description><![CDATA[<h2>Select the type of tickets you want to use in order to manage changes to the IT infrastructure.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-change.svg</banner>
|
||||
<alternatives type="array">
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-simple</extension_code>
|
||||
<title>Simple Change Management</title>
|
||||
<description>Select this option to use one type of ticket for all kind of changes.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt</module>
|
||||
</modules>
|
||||
<default>true</default>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-itil</extension_code>
|
||||
<title>ITIL Change Management</title>
|
||||
<description>Select this option to use Normal/Routine/Emergency change tickets.</description>
|
||||
<modules type="array">
|
||||
<module>itop-change-mgmt-itil</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-change-mgmt-none</extension_code>
|
||||
<title>No Change Management</title>
|
||||
<description>Don't manage changes in iTop</description>
|
||||
<modules type="array">
|
||||
</modules>
|
||||
</choice>
|
||||
</alternatives>
|
||||
</step>
|
||||
<step>
|
||||
<title>Additional ITIL tickets</title>
|
||||
<description><![CDATA[<h2>Pick from the list below the additional ITIL processes that are to be implemented in iTop.</h2>]]></description>
|
||||
<banner>/images/icons/icons8-important-book.svg</banner>
|
||||
<options type="array">
|
||||
<choice>
|
||||
<!-- Extension code has a typo but fixing it would remove that setup option for all existing iTop -->
|
||||
<extension_code>itop-kown-error-mgmt</extension_code>
|
||||
<title>Known Errors Management and FAQ</title>
|
||||
<description>Select this option to track "Known Errors" and FAQs in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-faq-light</module>
|
||||
<module>itop-knownerror-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
<choice>
|
||||
<extension_code>itop-problem-mgmt</extension_code>
|
||||
<title>Problem Management</title>
|
||||
<description>Select this option to track "Problems" in iTop.</description>
|
||||
<modules type="array">
|
||||
<module>itop-problem-mgmt</module>
|
||||
</modules>
|
||||
</choice>
|
||||
</options>
|
||||
</step>
|
||||
</steps>
|
||||
</installation>
|
||||
Reference in New Issue
Block a user