Compare commits

..

30 Commits

Author SHA1 Message Date
lenaick.moreira
c6e50a0ed7 N°9830 - Fix setup error when trying to remove class with a TagSet attribute 2026-07-23 11:39:42 +02:00
Molkobain
76a410e00d Revert "N°9541 itop-virtualization-mgmt cannot currently be uninstalled" now that N°9831 has been fixed
This reverts commit 52fca750b0.
2026-07-23 10:45:44 +02:00
Molkobain
c68887c2bc N°9831 - Fix ext. management deletion plan analysis by avoiding overwriting entity for a previously processed class 2026-07-23 10:21:31 +02:00
Molkobain
837b433622 N°9831 - Fix DataCleanupService so it handles external keys the same way as \Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan::DeletionPlanForReferencingClasses and \DBObject::MakeDeletionPlan
- Check if ext. key is nullable first
- Then check if its a DEL_MANUAL option
2026-07-23 10:21:31 +02:00
Timmy38
52fca750b0 N°9541 itop-virtualization-mgmt cannot currently be uninstalled 2026-07-22 17:37:19 +02:00
Timmy38
d1aedee245 N°9692 - Fix flag name 2026-07-21 17:04:37 +02:00
lenaick.moreira
85d50f31bd N°9692 - Fix null coalescing for extension installation status and clean up test cases 2026-07-17 17:05:06 +02:00
lenaick.moreira
3c301affb3 N°9679 - Add remote extensions as uninstallable extension in "Extension management" 2026-07-17 14:32:46 +02:00
Lenaick
147fae7f8c N°9679 - Forced uninstallation is not logged when done by extensions management (#956) 2026-07-17 11:25:36 +02:00
odain-cbd
a5aa8c279f fix broken cache warmup after setup (#974)
* fix broken cache warmup after setup

* clear all caches after setup
2026-07-17 10:40:55 +02:00
lenaick.moreira
04f91ab4a2 Fix error CI 2026-07-16 09:40:33 +02:00
lenaick.moreira
8e0246601a Fix unit tests failed 2026-07-15 14:33:55 +02:00
Eric Espie
e6cdcd0a5a N°9808 - Wrong dependency when module is in production-modules 2026-07-15 12:09:55 +02:00
Lenaick
2ab3081926 N°9692 - Uninstall / Disable install when extension has module with missing dependencies (#967)
* N°9692 - Uninstall / Disable install when extension has module with missing dependencies

* N°9692 - Uninstall / Disable install when extension has module with missing dependencies

* N°9692 - Uninstall / Disable install when extension has module with missing dependencies

* N°9692 - Refactor dependency handling for extensions to use HasDependencyIssue method

* Rename cannot-be-installed to dependency_issue

* Changes following the review
2026-07-15 09:20:25 +02:00
Molkobain
5cabac63e7 N°7984 - Complete previous revert 2026-07-14 23:32:52 +02:00
Molkobain
3918dcf830 N°7984 - Update setup compiled SCSS 2026-07-14 23:08:40 +02:00
Molkobain
489300552a Revert "Issue/7984 (#688)"
The ticket was not discussed.
2026-07-14 23:07:11 +02:00
Timmy38
8507c606ba N°9788 Adapt html for behat 2026-07-13 10:48:01 +02:00
Molkobain
128989450b 🌐 Translated missing french entries 2026-07-12 21:25:17 +02:00
Molkobain
1bd404af6f 💚 Add executable flag to scripts in .make/release folder 2026-07-12 21:10:10 +02:00
Molkobain
6c770d3e16 📝 Update third-party libraries licenses 2026-07-12 21:05:54 +02:00
Molkobain
a74dbb3a73 💚 Add executable flag to scripts in .make/license folder 2026-07-12 20:57:06 +02:00
Stephen Abello
b365aa0971 N°9628 - Fix issue following 5b9e0a1d being applied to non-jQuery elements (#961) 2026-07-10 15:57:04 +02:00
v-dumas
56aa50c9b1 N°9553 - Skip test due to unexplained CI failure 2026-07-10 15:53:37 +02:00
Vincent Dumas
eacb7f3401 N°9553 - Allow loading of localized sample and structural data based on iTop default language
* Add English, French and German structural and sample data in iTop Community

* Add helper methods to allow :
  * Checking of supported module version format
  * Finding a localized file name based on a provided en_us file name and a language
  * Loading a file if the module upgrade crossed a given version
2026-07-10 14:21:56 +02:00
Eric Espie
b14045af29 N°9648 - Better dependencies error message 2026-07-10 12:10:21 +02:00
Eric Espie
7321eeb23f N°9648 - Better dependencies error message 2026-07-10 12:02:12 +02:00
odain
05b8c529fe Fix cache-warmup after any succesfull setup 2026-07-10 11:14:59 +02:00
Timmy38
a68c017225 N°9681 Fixing edge case where an installed extension may have lost its dependencies 2026-07-09 16:40:43 +02:00
Timmy38
82b52b95a1 N°9681 Update badge tooltip 2026-07-09 14:17:42 +02:00
101 changed files with 2637 additions and 468 deletions

0
.make/license/gen-community-license.sh Normal file → Executable file
View File

0
.make/license/sortLicenceXml.php Normal file → Executable file
View File

0
.make/license/updateLicenses.php Normal file → Executable file
View File

0
.make/release/changelog.php Normal file → Executable file
View File

0
.make/release/update-versions.php Normal file → Executable file
View File

0
.make/release/update-xml.php Normal file → Executable file
View File

0
.make/release/update.classes.inc.php Normal file → Executable file
View File

View File

@@ -5368,7 +5368,6 @@ JS
/**
* @param array $aChanges
* @param bool $bIsNew
* @param string|null $sStimulusBeingApplied
*
* @return void
* @throws \ArchivedObjectException
@@ -5382,20 +5381,6 @@ JS
$this->FireEvent(EVENT_DB_AFTER_WRITE, ['is_new' => $bIsNew, 'changes' => $aChanges, 'stimulus_applied' => $sStimulusBeingApplied, 'cmdb_change' => self::GetCurrentChange()]);
}
//////////////
/// READ
///
/**
* @return void
* @throws \CoreException
* @since 3.3.0
*/
final public function FireEventReadDetails(string $sExportType): void
{
$this->FireEvent(EVENT_DATA_EXPORT, ['export_type' => $sExportType]);
}
//////////////
/// DELETE
///

View File

@@ -519,31 +519,6 @@ Call $this->AddInitialAttributeFlags($sAttCode, $iFlags) for all the initial att
</event_datum>
</event_data>
</event>
<event id="EVENT_DATA_EXPORT" _delta="define">
<name>Object details read from outside iTop</name>
<description><![CDATA[An object details has been read during an export]]></description>
<sources>
<source id="cmdbAbstractObject">cmdbAbstractObject</source>
</sources>
<event_data>
<event_datum id="object">
<description>The object unarchived</description>
<type>DBObject</type>
</event_datum>
<event_datum id="attributes">
<description>Attribute codes exposed (empty means potentially all attributes)</description>
<type>array</type>
</event_datum>
<event_datum id="export_type">
<description>Type of export</description>
<type>string</type>
</event_datum>
<event_datum id="debug_info">
<description>Debug string</description>
<type>string</type>
</event_datum>
</event_data>
</event>
<event id="EVENT_DOWNLOAD_DOCUMENT" _delta="define">
<name>Document downloaded</name>
<description><![CDATA[A document has been downloaded from the GUI]]></description>

View File

@@ -334,14 +334,12 @@ EOF
while ($aRow = $oSet->FetchAssoc()) {
set_time_limit(intval($iLoopTimeLimit));
$aData = [];
$aExportedObjects = [];
foreach ($this->aStatusInfo['fields'] as $aFieldSpec) {
foreach ($this->aStatusInfo['fields'] as $iCol => $aFieldSpec) {
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
$sField = '';
$oObj = $aRow[$sAlias];
$aExportedObjects[] = $oObj;
if ($oObj != null) {
switch ($sAttCode) {
case 'id':
@@ -368,13 +366,7 @@ EOF
}
$sData .= implode($this->aStatusInfo['separator'], $aData)."\n";
$iCount++;
$aExportedObjects = array_unique($aExportedObjects);
foreach ($aExportedObjects as $oExportedObject) {
$oExportedObject->FireEventReadDetails(get_class($this));
}
}
// Restore original date & time formats
AttributeDateTime::SetFormat($oPrevDateTimeFormat);
AttributeDate::SetFormat($oPrevDateFormat);

View File

@@ -6247,9 +6247,7 @@ abstract class DBObject implements iDisplay
}
/**
* @param array $aChanges
* @param bool $bIsNew
* @param string|null $sStimulusBeingApplied
*
* @return void
* @since 3.1.0
@@ -6258,18 +6256,6 @@ abstract class DBObject implements iDisplay
{
}
//////////////
/// READ
///
/**
* @return void
* @since 3.3.0
*/
public function FireEventReadDetails(string $sExportType): void
{
}
//////////////
/// DELETE
///

View File

@@ -288,13 +288,11 @@ EOF
while ($aRow = $oSet->FetchAssoc()) {
set_time_limit(intval($iLoopTimeLimit));
$aData = [];
$aExportedObjects = [];
foreach ($this->aStatusInfo['fields'] as $iCol => $aFieldSpec) {
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
$oObj = $aRow[$sAlias];
$aExportedObjects[] = $oObj;
$sField = '';
if ($oObj) {
$sField = $this->GetValue($oObj, $sAttCode);
@@ -303,11 +301,6 @@ EOF
}
fwrite($hFile, json_encode($aData)."\n");
$iCount++;
$aExportedObjects = array_unique($aExportedObjects);
foreach ($aExportedObjects as $oExportedObject) {
$oExportedObject->FireEventReadDetails(get_class($this));
}
}
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;

View File

@@ -139,7 +139,6 @@ class HTMLBulkExport extends TabularBulkExport
} else {
$sData .= "<tr>";
}
$aExportedObjects = [];
foreach ($this->aStatusInfo['fields'] as $iCol => $aFieldSpec) {
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
@@ -148,18 +147,12 @@ class HTMLBulkExport extends TabularBulkExport
$sField = '';
if ($oObj) {
$sField = $this->GetValue($oObj, $sAttCode);
$aExportedObjects[] = $oObj;
}
$sValue = ($sField === '') ? '&nbsp;' : $sField;
$sData .= "<td>$sValue</td>";
}
$sData .= "</tr>";
$iCount++;
$aExportedObjects = array_unique($aExportedObjects);
foreach ($aExportedObjects as $oExportedObject) {
$oExportedObject->FireEventReadDetails(get_class($this));
}
}
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;

View File

@@ -625,7 +625,6 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
}
while ($oObject = $oObjectSet->Fetch()) {
$oObject->FireEventReadDetails(get_class($this));
$oResult->AddObject(0, '', $oObject, $aShowFields, RestUtils::HasRequestedExtendedOutput($sShowFields));
}
$oResult->message = "Found: ".$oObjectSet->Count();
@@ -700,7 +699,6 @@ class CoreServices implements iRestServiceProvider, iRestInputSanitizer
if ($oElement instanceof RelationObjectNode) {
$oObject = $oElement->GetProperty('object');
if ($oObject) {
$oObject->FireEventReadDetails(get_class($this));
if ($bEnableRedundancy && $sDirection == 'down') {
// Add only the "reached" objects
if ($oElement->GetProperty('is_reached')) {

View File

@@ -233,6 +233,7 @@ EOF
public function GetNextChunk(&$aStatus)
{
$sRetCode = 'run';
$iPercentage = 0;
$oSet = new DBObjectSet($this->oSearch);
$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
@@ -253,8 +254,7 @@ EOF
set_time_limit(intval($iLoopTimeLimit));
$sData .= "<tr>";
$aExportedObjects = [];
foreach ($this->aStatusInfo['fields'] as $aFieldSpec) {
foreach ($this->aStatusInfo['fields'] as $iCol => $aFieldSpec) {
$sAlias = $aFieldSpec['sAlias'];
$sAttCode = $aFieldSpec['sAttCode'];
@@ -265,7 +265,7 @@ EOF
$sData .= "<td x:str></td>";
continue;
}
$aExportedObjects[] = $oObj;
switch ($sAttCode) {
case 'id':
$sField = $oObj->GetKey();
@@ -322,11 +322,6 @@ EOF
}
$sData .= "</tr>";
$iCount++;
$aExportedObjects = array_unique($aExportedObjects);
foreach ($aExportedObjects as $oExportedObject) {
$oExportedObject->FireEventReadDetails(get_class($this));
}
}
set_time_limit(intval($iPreviousTimeLimit));
$this->aStatusInfo['position'] += $this->iChunkSize;

View File

@@ -135,11 +135,6 @@ abstract class Trigger extends cmdbAbstractObject
if ($oAction->IsActive()) {
$oKPI = new ExecutionKPI();
$aContextArgs['action->object()'] = $oAction;
if (array_key_exists('this->object()', $aContextArgs)) {
/** @var \DBObject $oObject */
$oObject = $aContextArgs['this->object()'];
$oObject->FireEventReadDetails(get_class($oAction));
}
$oAction->DoExecute($this, $aContextArgs);
$oKPI->ComputeStatsForExtension($oAction, 'DoExecute');
}

View File

@@ -144,7 +144,6 @@ class XMLBulkExport extends BulkExport
if (count($aAuthorizedClasses) > 1) {
$sData .= "<Row>\n";
}
$aExportedObjects = [];
foreach ($aAuthorizedClasses as $sAlias => $sClassName) {
$oObj = $aObjects[$sAlias];
if (is_null($oObj)) {
@@ -152,7 +151,6 @@ class XMLBulkExport extends BulkExport
} else {
$sClassName = get_class($oObj);
$sData .= "<$sClassName alias=\"$sAlias\" id=\"".$oObj->GetKey()."\">\n";
$aExportedObjects[] = $oObj;
}
foreach ($aClass2Attributes[$sAlias] as $sAttCode => $oAttDef) {
if (is_null($oObj)) {
@@ -168,11 +166,6 @@ class XMLBulkExport extends BulkExport
$sData .= "</Row>\n";
}
$iCount++;
$aExportedObjects = array_unique($aExportedObjects);
foreach ($aExportedObjects as $oExportedObject) {
$oExportedObject->FireEventReadDetails(get_class($this));
}
}
set_time_limit(intval($iPreviousTimeLimit));

View File

@@ -4,7 +4,7 @@
*/
/* SCSS variables */
$common-has-description--content: "\1F6C8" !default;
$common-has-description--content: "?" !default;
$common-has-description--padding-left: $common-spacing-200 !default;
$common-has-description--color: $common-color-grey-600 !default;
$common-has-description--font-size: 0.7em !default; /* Font size is em on purpose as we want it to be proportional to its context */

View File

@@ -11,6 +11,9 @@
*
* Usage: @extend %common-font-size-XXX;
*/
%common-font-size-20 {
font-size: $common-font-size-20;
}
%common-font-size-50 {
font-size: $common-font-size-50;
}

File diff suppressed because one or more lines are too long

View File

@@ -127,6 +127,9 @@ class DataFeatureRemovalController extends Controller
$aRemovedExtensions = $this->aExtensionsToCheck['to_be_removed'];
$aHiddenInputs['removed_extensions'] = $this->ConvertIntoSetupFormat($aRemovedExtensions);
$aExtensionsNotUninstallable = $this->aExtensionsToCheck['extensions_not_uninstallable'];
$aHiddenInputs['extensions_not_uninstallable'] = $this->ConvertIntoSetupFormat($aExtensionsNotUninstallable);
}
$aParams['aAddedExtensions'] = $aAddedExtensions;
@@ -344,7 +347,7 @@ class DataFeatureRemovalController extends Controller
'uninstallable' => $oExtension->CanBeUninstalled(),
'remote' => $oExtension->IsRemote(),
'missing' => $oExtension->bRemovedFromDisk,
'cannot-be-installed' => (!$oExtension->bInstalled && count($oExtension->aMissingDependencies) > 0),
'dependency_issue' => $oExtension->HasDependencyIssue(),
],
];
@@ -425,6 +428,7 @@ class DataFeatureRemovalController extends Controller
$this->aExtensionsToCheck = [
'to_be_installed' => [],
'to_be_removed' => [],
'extensions_not_uninstallable' => [],
];
foreach ($aAvailableExtensions as $sCode => &$aExtensionData) {
if (!isset($aSelectedExtensionsFromUI[$sCode])) {
@@ -438,6 +442,9 @@ class DataFeatureRemovalController extends Controller
if (! $this->bForcedUninstallation && $aExtensionData['extra_flags']['uninstallable']) {
$this->bForcedUninstallation = true;
}
if (false === $aExtensionData['extra_flags']['uninstallable'] || true === $aExtensionData['extra_flags']['remote']) {
$this->aExtensionsToCheck['extensions_not_uninstallable'][] = $sCode;
}
} elseif (!$aExtensionData['installed'] && $aSelectedExtensionsFromUI[$sCode] === 'on') {
$aExtensionData['extra_flags']['selected'] = true;
$sLabel = $aAvailableExtensions[$sCode]['label'];

View File

@@ -134,10 +134,6 @@ class DataCleanupService
/** @var DBObject $oDependentObj */
while ($oDependentObj = $oSet->Fetch()) {
$iDeletePropagationOption = $oExtKeyAttDef->GetDeletionPropagationOption();
if ($iDeletePropagationOption == DEL_MANUAL) {
$this->oObjectService->SetIssue(get_class($oDependentObj));
continue;
}
if ($oExtKeyAttDef->IsNullAllowed()) {
// Optional external key, list to reset
@@ -152,6 +148,12 @@ class DataCleanupService
return false;
}
} else {
// Mandatory external key
if ($iDeletePropagationOption == DEL_MANUAL) {
// Cannot be deleted automatically, must be handled manually
$this->oObjectService->SetIssue(get_class($oDependentObj));
continue;
}
// Propagate deletion only if not visited
if ($this->IsVisited($oDependentObj)) {
continue;

View File

@@ -59,9 +59,11 @@ class StaticDeletionPlan
{
foreach ($aClasses as $sClass) {
$oDeletionPlanItem = $this->GetInitialClassDeletionPlan($sClass);
$oDeletionPlanEntity = new DeletionPlanEntity();
$oDeletionPlanEntity->oDelete->Merge($oDeletionPlanItem);
$this->aDeletionPlan[$sClass] = $oDeletionPlanEntity;
// N°9831 Do not overwrite existing entity as it may already exist for this class if a previously processed class references it (issues/updates already accumulated must be kept)
if (false === array_key_exists($sClass, $this->aDeletionPlan)) {
$this->aDeletionPlan[$sClass] = new DeletionPlanEntity();
}
$this->aDeletionPlan[$sClass]->oDelete->Merge($oDeletionPlanItem);
$this->DeletionPlanForReferencingClasses($sClass);
}

View File

@@ -90,6 +90,6 @@ Dict::Add('FR FR', 'French', 'Français', [
'Class:TriggerOnAttachmentDelete' => 'Déclencheur sur la suppression d\'une pièce jointe',
'Class:TriggerOnAttachmentDelete+' => 'Déclencheur sur la suppression d\'une pièce jointe d\'un objet',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email' => 'Ajoute le fichier supprimé dans l\'email',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'If checked, the deleted file will be automatically attached to the email when an email action is triggered~~',
'Class:TriggerOnAttachmentDelete/Attribute:file_in_email+' => 'Si coché, le fichier supprimé sera automatiquement joint à l\'email quand une action email sera lancée',
'Class:TriggerOnObject:TriggerClassAttachment/ReadOnlyMessage' => 'Les Triggers sur les objets ne sont pas autorisés sur la classe Attachement. Veuillez utiliser les triggers spécifiques pour cette classe',
]);

View File

@@ -1,9 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<NetworkDeviceType alias="NetworkDeviceType" id="10">
<name>Router</name>
</NetworkDeviceType>
<NetworkDeviceType alias="NetworkDeviceType" id="11">
<name>Switch</name>
</NetworkDeviceType>
</Set>

View File

@@ -32,7 +32,6 @@ SetupWebPage::AddModule(
],
'data.sample' => [
'data/data.sample.model.xml',
'data/data.sample.networkdevicetype.xml',
'data/data.sample.servers.xml',
'data/data.sample.nw-devices.xml',
'data/data.sample.software.xml',
@@ -106,6 +105,7 @@ if (!class_exists('ConfigMgmtInstaller')) {
*/
public static function AfterDatabaseCreation(Config $oConfiguration, $sPreviousVersion, $sCurrentVersion)
{
}
}
}

View File

@@ -159,7 +159,7 @@ function DoInstall(WebPage $oPage)
foreach ($oExtensionsMap->GetAllExtensions() as $oExtension) {
if ($oExtension->sSource == iTopExtension::SOURCE_REMOTE) {
if (count($oExtension->aMissingDependencies) > 0) {
if ($oExtension->HasDependencyIssue()) {
$oPage->add('<div class="choice">');
$oPage->add('<input type="checkbox" disabled>&nbsp;');
$sTitle = Dict::Format('iTopHub:InstallationEffect:MissingDependencies_Details', implode(', ', $oExtension->aMissingDependencies));

View File

@@ -50,13 +50,13 @@ const oOpenSignInWindow = function (url, name) {
then we load it in the already opened secondary window, and then
we bring such window back on top/in front of its parent window. */
oWindowObjectReference = window.open(url, name, sWindowFeatures);
oWindowObjectReference.trigger('focus');
oWindowObjectReference.focus();
} else {
/* Else the window reference must exist and the window
is not closed; therefore, we can bring it back on top of any other
window with the focus() method. There would be no need to re-create
the window or to reload the referenced resource. */
oWindowObjectReference.trigger('focus');
oWindowObjectReference.focus();
}
/* Let know every second our child window that we're waiting for it to complete,
once we reach our landing page, it'll send us a reply

File diff suppressed because one or more lines are too long

View File

@@ -36,7 +36,7 @@ $ipb-field--is-mandatory--font-size: 0.6rem !default;
.form_field_label > .control-label[data-tooltip-instantiated="true"]:after {
cursor: pointer;
@extend %common-font-size-50;
@extend %common-font-size-20;
}
.form_field .form_mandatory .control-label:after{

File diff suppressed because one or more lines are too long

View File

@@ -10,7 +10,7 @@ $ipb--fieldset--border: 1px solid $ipb--fieldset--border-color !default;
$ipb--fieldset--inside--fieldset--background-color: $ipb-color-grey-100 !default;
$ipb-field--has-tooltip--content: "\f05a" !default;
$ipb-field--has-tooltip--content: "\f128" !default;
$ipb-field--has-tooltip--color: $ipb-color-grey-700 !default;
$ipb-field--is-mandatory--content: "\f069" !default;

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ContractType alias="ContractType" id="78">
<name>Hosting</name>
</ContractType>
<ContractType alias="ContractType" id="79">
<name>IT-Outsourcing</name>
</ContractType>
<ContractType alias="ContractType" id="77">
<name>Wartung</name>
</ContractType>
<ContractType alias="ContractType" id="76">
<name>Support</name>
</ContractType>
</Set>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<CustomerContract alias="CustomerContract" id="3">
<name>Kundenvertrag</name>
<org_id>7</org_id>
<description>Kundenvertrag zur Bereitstellung von Services fuer einen externen Kunden.</description>
<start_date>2026-01-01</start_date>
<end_date>2030-12-31</end_date>
<cost>10000</cost>
<cost_currency>euros</cost_currency>
<contracttype_id>79</contracttype_id>
<billing_frequency>yearly</billing_frequency>
<provider_id>2</provider_id>
<status>production</status>
</CustomerContract>
<CustomerContract alias="CustomerContract" id="1">
<name>Kundenvertrag Vertriebsabteilung</name>
<org_id>6</org_id>
<description>Interner Vertrag zur Bereitstellung von Services fuer die Vertriebsabteilung</description>
<start_date>2019-01-01</start_date>
<end_date>2030-12-31</end_date>
<cost>0</cost>
<cost_currency>euros</cost_currency>
<contracttype_id>0</contracttype_id>
<billing_frequency>monthly</billing_frequency>
<provider_id>2</provider_id>
<status>production</status>
</CustomerContract>
</Set>

View File

@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ServiceSubcategory alias="ServiceSubcategory" id="28">
<name>Zugriffsprobleme</name>
<description>Hilfe bei einem Zugriffsproblem anfordern</description>
<service_id>7</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="17">
<name>API-Integration</name>
<description>Verbindung zwischen internen und externen Anwendungen.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="13">
<name>CRM (z. B. Salesforce, HubSpot)</name>
<description>Einrichtung, Anpassung und Support für das Customer-Relationship-Management.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="22">
<name>Sensibilisierung für Cybersicherheit</name>
<description>Schulung der Mitarbeitenden zu bewährten Praktiken (Phishing, Passwörter usw.).</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="3">
<name>Datensicherung</name>
<description>Automatische Sicherung kritischer Dateien (täglich/wöchentlich/monatlich).</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="21">
<name>Datenschutz (DSGVO)</name>
<description>Unterstützung bei der Einhaltung gesetzlicher Vorgaben (DSGVO, HIPAA usw.).</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="1">
<name>ERP (z. B. SAP, Odoo)</name>
<description>Zugriff, Support und Schulung für integrierte Managementsoftware.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="10">
<name>Dateispeicherung und -freigabe</name>
<description>Bereitstellung kollaborativer Speicherbereiche (z. B. SharePoint, Nextcloud).</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="12">
<name>Hardwarevorfall</name>
<description>Unterstützung bei einem Hardwareausfall anfordern.</description>
<service_id>1</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="19">
<name>Hardware-Upgrade</name>
<description>Austausch oder Erweiterung von Komponenten (RAM, Festplatte usw.).</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="7">
<name>Lokales Netzwerk (LAN/Wi-Fi)</name>
<description>Installation, Konfiguration und Wartung von Netzwerkgeräten.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="27">
<name>Multi-Faktor-Authentifizierung (MFA)</name>
<description>Konfiguration und Support für Multi-Faktor-Authentifizierung.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="4">
<name>Fehlerbehebung im Netzwerk</name>
<description>Hilfe bei der Behebung eines Netzwerkproblems anfordern.</description>
<service_id>3</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="5">
<name>Bestellung neuer Desktop-Computer</name>
<description>Einen neuen Desktop-Computer für neue Mitarbeitende oder als Ersatz für ein altes System bestellen.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="8">
<name>Bestellung neuer Laptops</name>
<description>Einen neuen Laptop für mobile Mitarbeitende oder als Ersatz für ein altes Gerät bestellen.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="11">
<name>Neues Peripheriegerät</name>
<description>Ein Peripheriegerät bestellen: Tastatur, Maus, persönlicher Drucker ...</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="20">
<name>Sicherheitsaudit</name>
<description>Schwachstellenanalysen, Penetrationstests und Empfehlungen.</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="23">
<name>Sicherheitsvorfall</name>
<description>Reaktion auf Cyberangriffe, Datenlecks oder Sicherheitsverletzungen.</description>
<service_id>6</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="6">
<name>Serververwaltung</name>
<description>Präventive Wartung, Software-Updates und 24/7-Überwachung.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="16">
<name>Softwarevorfall</name>
<description>Hilfe bei der Behebung eines Softwareproblems anfordern.</description>
<service_id>1</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="15">
<name>Softwarevorfall</name>
<description>Unterstützung bei einem softwarebezogenen Problem anfordern.</description>
<service_id>2</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="18">
<name>Softwareinstallation</name>
<description>Bereitstellung von Office-Software (z. B. Microsoft 365, LibreOffice) und Geschäftssoftware.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="14">
<name>Individuelle Entwicklung</name>
<description>Entwicklung maßgeschneiderter Tools (Dashboards, Automatisierungen, Skripte).</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="24">
<name>Benutzerkonto-Erstellung</name>
<description>Erstellung von Konten für neue Mitarbeitende oder Auftragnehmer.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="26">
<name>Benutzerkonto-Löschung</name>
<description>Deaktivierung von Konten für ausscheidende Mitarbeitende oder am Ende ihrer Verträge.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="25">
<name>Benutzerkonto-Aktualisierung</name>
<description>Hinzufügen oder Entfernen von Berechtigungen (z. B. Zugriff auf einen freigegebenen Ordner oder eine Anwendung).</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="2">
<name>VPN-Zugriff</name>
<description>Konfiguration und Support für sicheren Fernzugriff auf interne Ressourcen.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
</Set>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ServiceFamily alias="ServiceFamily" id="1">
<name>IT-Services</name>
<icon>
<mimetype>image/svg+xml</mimetype>
<filename>icons8-open-end-wrench.svg</filename>
<data>PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSIyNDBweCIgaGVpZ2h0PSIyNDBweCI+PHJhZGlhbEdyYWRpZW50IGlkPSIwQkRKczNXdHBwTWdvNFk5QTdHZ2phIiBjeD0iOS4xNjkiIGN5PSIzOC42NyIgcj0iMTIuMTM1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIuNzA2IiBzdG9wLWNvbG9yPSIjNzg3Zjg1Ii8+PHN0b3Agb2Zmc2V0PSIuNzEzIiBzdG9wLWNvbG9yPSIjN2E4MTg3Ii8+PHN0b3Agb2Zmc2V0PSIuNzkzIiBzdG9wLWNvbG9yPSIjOTA5ODlmIi8+PHN0b3Agb2Zmc2V0PSIuODY4IiBzdG9wLWNvbG9yPSIjOWRhNWFlIi8+PHN0b3Agb2Zmc2V0PSIuOTMiIHN0b3AtY29sb3I9IiNhMWFhYjMiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGZpbGw9InVybCgjMEJESnMzV3RwcE1nbzRZOUE3R2dqYSkiIGQ9Ik0xOS45MTUsMjEuOGMtNC42NzksNC42NzktOS4zNzUsOS4zNzUtOS41NTEsOS41NTFsNi4zNjQsNi4zNjQgYzAuMTc3LTAuMTc3LDQuODcyLTQuODcyLDkuNTUxLTkuNTUxTDE5LjkxNSwyMS44eiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iMEJESnMzV3RwcE1nbzRZOUE3R2dqYiIgY3g9IjEzLjQyNSIgY3k9IjM1LjQ5OSIgcj0iMTQuNjg4IiBncmFkaWVudFRyYW5zZm9ybT0icm90YXRlKDE4MCAyNC4zMjIgMjMuNzU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iLjcwNiIgc3RvcC1jb2xvcj0iIzc4N2Y4NSIvPjxzdG9wIG9mZnNldD0iLjcxMyIgc3RvcC1jb2xvcj0iIzdhODE4NyIvPjxzdG9wIG9mZnNldD0iLjc5MyIgc3RvcC1jb2xvcj0iIzkwOTg5ZiIvPjxzdG9wIG9mZnNldD0iLjg2OCIgc3RvcC1jb2xvcj0iIzlkYTVhZSIvPjxzdG9wIG9mZnNldD0iLjkzIiBzdG9wLWNvbG9yPSIjYTFhYWIzIi8+PC9yYWRpYWxHcmFkaWVudD48cGF0aCBmaWxsPSJ1cmwoIzBCREpzM1d0cHBNZ280WTlBN0dnamIpIiBkPSJNMjQuNzI4LDI5LjcxNWM0LjY3OS00LjY3OSw5LjM3NS05LjM3NSw5LjU1MS05LjU1MUwyNy45MTUsMTMuOCBjLTAuMTc3LDAuMTc3LTQuODcyLDQuODcyLTkuNTUxLDkuNTUxTDI0LjcyOCwyOS43MTV6Ii8+PGxpbmVhckdyYWRpZW50IGlkPSIwQkRKczNXdHBwTWdvNFk5QTdHZ2pjIiB4MT0iMjIuMTE5IiB4Mj0iMzMuOTI2IiB5MT0iLTI1NTIuODgzIiB5Mj0iLTI1NjQuNjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMzkuMDQ2IDI1OTguMDMzKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzhmOTc5ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ExYWFiMyIvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZmlsbD0idXJsKCMwQkRKczNXdHBwTWdvNFk5QTdHZ2pkKSIgZD0iTTQwLjY2MiwxMy4zMTJjLTEuNjk1LDEuNjk1LTQuNDQ1LDEuNjk1LTYuMTM5LDBzLTEuNjk1LTQuNDQ1LDAtNi4xMzkgYzAuNDE0LTAuNDE0LDIuMDgxLTIuMDgxLDMuNzgtMy43OGMwLjQwNy0wLjQwNywwLjIyNi0xLjExOS0wLjMzMi0xLjI1NmMtMS4zNzctMC4zMzgtMi44NTgtMC40MDktNC4zOTktMC4xMTkgYy00LjUxOSwwLjg1Mi04LjA0Myw0LjYzMy04LjUxMiw5LjIwN2MtMC42ODQsNi42Nyw0Ljg4MywxMi4yMzYsMTEuNTUzLDExLjU1MWM0LjU3NC0wLjQ3LDguMzU1LTMuOTk0LDkuMjA2LTguNTEzIGMwLjI5LTEuNTQxLDAuMjE5LTMuMDIxLTAuMTE5LTQuMzk4Yy0wLjEzOC0wLjU2LTAuODQ5LTAuNzQxLTEuMjU2LTAuMzMyQzQyLjc0MywxMS4yMzEsNDEuMDc2LDEyLjg5OSw0MC42NjIsMTMuMzEyeiIvPjwvc3ZnPgo=</data>
<downloads_count>0</downloads_count>
</icon>
</ServiceFamily>
</Set>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<SLA alias="SLA" id="4">
<name>SLA Bronze</name>
<description>Service-Level-Agreement Bronze fuer Services mit niedriger Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
<SLA alias="SLA" id="2">
<name>SLA Gold</name>
<description>Service-Level-Agreement Gold fuer Services mit hoher Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
<SLA alias="SLA" id="3">
<name>SLA Silver</name>
<description>Service-Level-Agreement Silver fuer Services mit mittlerer Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
</Set>

View File

@@ -0,0 +1,387 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<SLT alias="SLT" id="5">
<name>TTO Vorfall Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="3">
<name>TTO Vorfall Priorität kritisch 15 Minuten</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>15</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="4">
<name>TTO Vorfall Priorität kritisch 30 Minuten</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>30</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="7">
<name>TTO Vorfall Priorität hoch 1 Stunde</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="8">
<name>TTO Vorfall Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="6">
<name>TTO Vorfall Priorität hoch 30 Minuten</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>30</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="12">
<name>TTO Vorfall Priorität niedrig 2 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="13">
<name>TTO Vorfall Priorität niedrig 4 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="14">
<name>TTO Vorfall Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="9">
<name>TTO Vorfall Priorität mittel 1 Stunde</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="10">
<name>TTO Vorfall Priorität mittel 2 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="11">
<name>TTO Vorfall Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="27">
<name>TTO Serviceanfrage Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="28">
<name>TTO Serviceanfrage Priorität kritisch 2 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="29">
<name>TTO Serviceanfrage Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="30">
<name>TTO Serviceanfrage Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="31">
<name>TTO Serviceanfrage Priorität hoch 4 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="32">
<name>TTO Serviceanfrage Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="37">
<name>TTO Serviceanfrage Priorität niedrig 16 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="38">
<name>TTO Serviceanfrage Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="36">
<name>TTO Serviceanfrage Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="35">
<name>TTO Serviceanfrage Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="33">
<name>TTO Serviceanfrage Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="34">
<name>TTO Serviceanfrage Priorität mittel 8 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="15">
<name>TTR Vorfall Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="16">
<name>TTR Vorfall Priorität kritisch 2 Stunden</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="17">
<name>TTR Vorfall Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="18">
<name>TTR Vorfall Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="19">
<name>TTR Vorfall Priorität hoch 4 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="20">
<name>TTR Vorfall Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="25">
<name>TTR Vorfall Priorität niedrig 16 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="26">
<name>TTR Vorfall Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="24">
<name>TTR Vorfall Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="23">
<name>TTR Vorfall Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="21">
<name>TTR Vorfall Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="22">
<name>TTR Vorfall Priorität mittel 8 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="41">
<name>TTR Serviceanfrage Priorität kritisch 16 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="39">
<name>TTR Serviceanfrage Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="40">
<name>TTR Serviceanfrage Priorität kritisch 8 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="43">
<name>TTR Serviceanfrage Priorität hoch 16 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="44">
<name>TTR Serviceanfrage Priorität hoch 32 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="42">
<name>TTR Serviceanfrage Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="50">
<name>TTR Serviceanfrage Priorität niedrig 128 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>128</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="48">
<name>TTR Serviceanfrage Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="49">
<name>TTR Serviceanfrage Priorität niedrig 64 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>64</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="45">
<name>TTR Serviceanfrage Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="46">
<name>TTR Serviceanfrage Priorität mittel 32 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="47">
<name>TTR Serviceanfrage Priorität mittel 64 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>64</value>
<unit>hours</unit>
</SLT>
</Set>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ContractType alias="ContractType" id="78">
<name>Hosting</name>
</ContractType>
<ContractType alias="ContractType" id="79">
<name>IT-Outsourcing</name>
</ContractType>
<ContractType alias="ContractType" id="77">
<name>Wartung</name>
</ContractType>
<ContractType alias="ContractType" id="76">
<name>Support</name>
</ContractType>
</Set>

View File

@@ -0,0 +1,29 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<CustomerContract alias="CustomerContract" id="3">
<name>Kundenvertrag</name>
<org_id>7</org_id>
<description>Kundenvertrag zur Bereitstellung von Services fuer einen externen Kunden.</description>
<start_date>2026-01-01</start_date>
<end_date>2030-12-31</end_date>
<cost>10000</cost>
<cost_currency>euros</cost_currency>
<contracttype_id>79</contracttype_id>
<billing_frequency>yearly</billing_frequency>
<provider_id>2</provider_id>
<status>production</status>
</CustomerContract>
<CustomerContract alias="CustomerContract" id="1">
<name>Kundenvertrag Vertriebsabteilung</name>
<org_id>6</org_id>
<description>Interner Vertrag zur Bereitstellung von Services fuer die Vertriebsabteilung</description>
<start_date>2019-01-01</start_date>
<end_date>2030-12-31</end_date>
<cost>0</cost>
<cost_currency>euros</cost_currency>
<contracttype_id>0</contracttype_id>
<billing_frequency>monthly</billing_frequency>
<provider_id>2</provider_id>
<status>production</status>
</CustomerContract>
</Set>

View File

@@ -0,0 +1,219 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ServiceSubcategory alias="ServiceSubcategory" id="28">
<name>Zugriffsprobleme</name>
<description>Hilfe bei einem Zugriffsproblem anfordern</description>
<service_id>7</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="17">
<name>API-Integration</name>
<description>Verbindung zwischen internen und externen Anwendungen.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="13">
<name>CRM (z. B. Salesforce, HubSpot)</name>
<description>Einrichtung, Anpassung und Support für das Customer-Relationship-Management.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="22">
<name>Sensibilisierung für Cybersicherheit</name>
<description>Schulung der Mitarbeitenden zu bewährten Praktiken (Phishing, Passwörter usw.).</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="3">
<name>Datensicherung</name>
<description>Automatische Sicherung kritischer Dateien (täglich/wöchentlich/monatlich).</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="21">
<name>Datenschutz (DSGVO)</name>
<description>Unterstützung bei der Einhaltung gesetzlicher Vorgaben (DSGVO, HIPAA usw.).</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="1">
<name>ERP (z. B. SAP, Odoo)</name>
<description>Zugriff, Support und Schulung für integrierte Managementsoftware.</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="10">
<name>Dateispeicherung und -freigabe</name>
<description>Bereitstellung kollaborativer Speicherbereiche (z. B. SharePoint, Nextcloud).</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="12">
<name>Hardwarevorfall</name>
<description>Unterstützung bei einem Hardwareausfall anfordern.</description>
<service_id>1</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="19">
<name>Hardware-Upgrade</name>
<description>Austausch oder Erweiterung von Komponenten (RAM, Festplatte usw.).</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="7">
<name>Lokales Netzwerk (LAN/Wi-Fi)</name>
<description>Installation, Konfiguration und Wartung von Netzwerkgeräten.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="27">
<name>Multi-Faktor-Authentifizierung (MFA)</name>
<description>Konfiguration und Support für Multi-Faktor-Authentifizierung.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="4">
<name>Fehlerbehebung im Netzwerk</name>
<description>Hilfe bei der Behebung eines Netzwerkproblems anfordern.</description>
<service_id>3</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="5">
<name>Bestellung neuer Desktop-Computer</name>
<description>Einen neuen Desktop-Computer für neue Mitarbeitende oder als Ersatz für ein altes System bestellen.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="8">
<name>Bestellung neuer Laptops</name>
<description>Einen neuen Laptop für mobile Mitarbeitende oder als Ersatz für ein altes Gerät bestellen.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="11">
<name>Neues Peripheriegerät</name>
<description>Ein Peripheriegerät bestellen: Tastatur, Maus, persönlicher Drucker ...</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="20">
<name>Sicherheitsaudit</name>
<description>Schwachstellenanalysen, Penetrationstests und Empfehlungen.</description>
<service_id>6</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="23">
<name>Sicherheitsvorfall</name>
<description>Reaktion auf Cyberangriffe, Datenlecks oder Sicherheitsverletzungen.</description>
<service_id>6</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="6">
<name>Serververwaltung</name>
<description>Präventive Wartung, Software-Updates und 24/7-Überwachung.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="16">
<name>Softwarevorfall</name>
<description>Hilfe bei der Behebung eines Softwareproblems anfordern.</description>
<service_id>1</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="15">
<name>Softwarevorfall</name>
<description>Unterstützung bei einem softwarebezogenen Problem anfordern.</description>
<service_id>2</service_id>
<request_type>incident</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="18">
<name>Softwareinstallation</name>
<description>Bereitstellung von Office-Software (z. B. Microsoft 365, LibreOffice) und Geschäftssoftware.</description>
<service_id>1</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="14">
<name>Individuelle Entwicklung</name>
<description>Entwicklung maßgeschneiderter Tools (Dashboards, Automatisierungen, Skripte).</description>
<service_id>2</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="24">
<name>Benutzerkonto-Erstellung</name>
<description>Erstellung von Konten für neue Mitarbeitende oder Auftragnehmer.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="26">
<name>Benutzerkonto-Löschung</name>
<description>Deaktivierung von Konten für ausscheidende Mitarbeitende oder am Ende ihrer Verträge.</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="25">
<name>Benutzerkonto-Aktualisierung</name>
<description>Hinzufügen oder Entfernen von Berechtigungen (z. B. Zugriff auf einen freigegebenen Ordner oder eine Anwendung).</description>
<service_id>7</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
<ServiceSubcategory alias="ServiceSubcategory" id="2">
<name>VPN-Zugriff</name>
<description>Konfiguration und Support für sicheren Fernzugriff auf interne Ressourcen.</description>
<service_id>3</service_id>
<request_type>service_request</request_type>
<status>production</status>
<approvalrule_id>0</approvalrule_id>
</ServiceSubcategory>
</Set>

View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ServiceFamily alias="ServiceFamily" id="1">
<name>IT-Services</name>
<icon>
<mimetype>image/svg+xml</mimetype>
<filename>icons8-open-end-wrench.svg</filename>
<data>PD94bWwgdmVyc2lvbj0iMS4wIj8+CjxzdmcgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB2aWV3Qm94PSIwIDAgNDggNDgiIHdpZHRoPSIyNDBweCIgaGVpZ2h0PSIyNDBweCI+PHJhZGlhbEdyYWRpZW50IGlkPSIwQkRKczNXdHBwTWdvNFk5QTdHZ2phIiBjeD0iOS4xNjkiIGN5PSIzOC42NyIgcj0iMTIuMTM1IiBncmFkaWVudFVuaXRzPSJ1c2VyU3BhY2VPblVzZSI+PHN0b3Agb2Zmc2V0PSIuNzA2IiBzdG9wLWNvbG9yPSIjNzg3Zjg1Ii8+PHN0b3Agb2Zmc2V0PSIuNzEzIiBzdG9wLWNvbG9yPSIjN2E4MTg3Ii8+PHN0b3Agb2Zmc2V0PSIuNzkzIiBzdG9wLWNvbG9yPSIjOTA5ODlmIi8+PHN0b3Agb2Zmc2V0PSIuODY4IiBzdG9wLWNvbG9yPSIjOWRhNWFlIi8+PHN0b3Agb2Zmc2V0PSIuOTMiIHN0b3AtY29sb3I9IiNhMWFhYjMiLz48L3JhZGlhbEdyYWRpZW50PjxwYXRoIGZpbGw9InVybCgjMEJESnMzV3RwcE1nbzRZOUE3R2dqYSkiIGQ9Ik0xOS45MTUsMjEuOGMtNC42NzksNC42NzktOS4zNzUsOS4zNzUtOS41NTEsOS41NTFsNi4zNjQsNi4zNjQgYzAuMTc3LTAuMTc3LDQuODcyLTQuODcyLDkuNTUxLTkuNTUxTDE5LjkxNSwyMS44eiIvPjxyYWRpYWxHcmFkaWVudCBpZD0iMEJESnMzV3RwcE1nbzRZOUE3R2dqYiIgY3g9IjEzLjQyNSIgY3k9IjM1LjQ5OSIgcj0iMTQuNjg4IiBncmFkaWVudFRyYW5zZm9ybT0icm90YXRlKDE4MCAyNC4zMjIgMjMuNzU4KSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iLjcwNiIgc3RvcC1jb2xvcj0iIzc4N2Y4NSIvPjxzdG9wIG9mZnNldD0iLjcxMyIgc3RvcC1jb2xvcj0iIzdhODE4NyIvPjxzdG9wIG9mZnNldD0iLjc5MyIgc3RvcC1jb2xvcj0iIzkwOTg5ZiIvPjxzdG9wIG9mZnNldD0iLjg2OCIgc3RvcC1jb2xvcj0iIzlkYTVhZSIvPjxzdG9wIG9mZnNldD0iLjkzIiBzdG9wLWNvbG9yPSIjYTFhYWIzIi8+PC9yYWRpYWxHcmFkaWVudD48cGF0aCBmaWxsPSJ1cmwoIzBCREpzM1d0cHBNZ280WTlBN0dnamIpIiBkPSJNMjQuNzI4LDI5LjcxNWM0LjY3OS00LjY3OSw5LjM3NS05LjM3NSw5LjU1MS05LjU1MUwyNy45MTUsMTMuOCBjLTAuMTc3LDAuMTc3LTQuODcyLDQuODcyLTkuNTUxLDkuNTUxTDI0LjcyOCwyOS43MTV6Ii8+PGxpbmVhckdyYWRpZW50IGlkPSIwQkRKczNXdHBwTWdvNFk5QTdHZ2pjIiB4MT0iMjIuMTE5IiB4Mj0iMzMuOTI2IiB5MT0iLTI1NTIuODgzIiB5Mj0iLTI1NjQuNjkiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoLTEgMCAwIDEgMzkuMDQ2IDI1OTguMDMzKSIgZ3JhZGllbnRVbml0cz0idXNlclNwYWNlT25Vc2UiPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iIzhmOTc5ZSIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI2ExYWFiMyIvPjwvbGluZWFyR3JhZGllbnQ+PHBhdGggZmlsbD0idXJsKCMwQkRKczNXdHBwTWdvNFk5QTdHZ2pkKSIgZD0iTTQwLjY2MiwxMy4zMTJjLTEuNjk1LDEuNjk1LTQuNDQ1LDEuNjk1LTYuMTM5LDBzLTEuNjk1LTQuNDQ1LDAtNi4xMzkgYzAuNDE0LTAuNDE0LDIuMDgxLTIuMDgxLDMuNzgtMy43OGMwLjQwNy0wLjQwNywwLjIyNi0xLjExOS0wLjMzMi0xLjI1NmMtMS4zNzctMC4zMzgtMi44NTgtMC40MDktNC4zOTktMC4xMTkgYy00LjUxOSwwLjg1Mi04LjA0Myw0LjYzMy04LjUxMiw5LjIwN2MtMC42ODQsNi42Nyw0Ljg4MywxMi4yMzYsMTEuNTUzLDExLjU1MWM0LjU3NC0wLjQ3LDguMzU1LTMuOTk0LDkuMjA2LTguNTEzIGMwLjI5LTEuNTQxLDAuMjE5LTMuMDIxLTAuMTE5LTQuMzk4Yy0wLjEzOC0wLjU2LTAuODQ5LTAuNzQxLTEuMjU2LTAuMzMyQzQyLjc0MywxMS4yMzEsNDEuMDc2LDEyLjg5OSw0MC42NjIsMTMuMzEyeiIvPjwvc3ZnPgo=</data>
<downloads_count>0</downloads_count>
</icon>
</ServiceFamily>
</Set>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<SLA alias="SLA" id="4">
<name>SLA Bronze</name>
<description>Service-Level-Agreement Bronze fuer Services mit niedriger Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
<SLA alias="SLA" id="2">
<name>SLA Gold</name>
<description>Service-Level-Agreement Gold fuer Services mit hoher Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
<SLA alias="SLA" id="3">
<name>SLA Silver</name>
<description>Service-Level-Agreement Silver fuer Services mit mittlerer Kritikalitaet</description>
<org_id>2</org_id>
</SLA>
</Set>

View File

@@ -0,0 +1,387 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<SLT alias="SLT" id="5">
<name>TTO Vorfall Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="3">
<name>TTO Vorfall Priorität kritisch 15 Minuten</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>15</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="4">
<name>TTO Vorfall Priorität kritisch 30 Minuten</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>30</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="7">
<name>TTO Vorfall Priorität hoch 1 Stunde</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="8">
<name>TTO Vorfall Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="6">
<name>TTO Vorfall Priorität hoch 30 Minuten</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>30</value>
<unit>minutes</unit>
</SLT>
<SLT alias="SLT" id="12">
<name>TTO Vorfall Priorität niedrig 2 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="13">
<name>TTO Vorfall Priorität niedrig 4 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="14">
<name>TTO Vorfall Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="9">
<name>TTO Vorfall Priorität mittel 1 Stunde</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="10">
<name>TTO Vorfall Priorität mittel 2 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="11">
<name>TTO Vorfall Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="27">
<name>TTO Serviceanfrage Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="28">
<name>TTO Serviceanfrage Priorität kritisch 2 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="29">
<name>TTO Serviceanfrage Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="30">
<name>TTO Serviceanfrage Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="31">
<name>TTO Serviceanfrage Priorität hoch 4 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="32">
<name>TTO Serviceanfrage Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="37">
<name>TTO Serviceanfrage Priorität niedrig 16 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="38">
<name>TTO Serviceanfrage Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="36">
<name>TTO Serviceanfrage Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="35">
<name>TTO Serviceanfrage Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="33">
<name>TTO Serviceanfrage Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="34">
<name>TTO Serviceanfrage Priorität mittel 8 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>tto</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="15">
<name>TTR Vorfall Priorität kritisch 1 Stunde</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>1</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="16">
<name>TTR Vorfall Priorität kritisch 2 Stunden</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="17">
<name>TTR Vorfall Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="18">
<name>TTR Vorfall Priorität hoch 2 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>2</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="19">
<name>TTR Vorfall Priorität hoch 4 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="20">
<name>TTR Vorfall Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="25">
<name>TTR Vorfall Priorität niedrig 16 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="26">
<name>TTR Vorfall Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="24">
<name>TTR Vorfall Priorität niedrig 8 Stunden</name>
<priority>4</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="23">
<name>TTR Vorfall Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="21">
<name>TTR Vorfall Priorität mittel 4 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="22">
<name>TTR Vorfall Priorität mittel 8 Stunden</name>
<priority>3</priority>
<request_type>incident</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="41">
<name>TTR Serviceanfrage Priorität kritisch 16 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="39">
<name>TTR Serviceanfrage Priorität kritisch 4 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>4</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="40">
<name>TTR Serviceanfrage Priorität kritisch 8 Stunden</name>
<priority>1</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="43">
<name>TTR Serviceanfrage Priorität hoch 16 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="44">
<name>TTR Serviceanfrage Priorität hoch 32 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="42">
<name>TTR Serviceanfrage Priorität hoch 8 Stunden</name>
<priority>2</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>8</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="50">
<name>TTR Serviceanfrage Priorität niedrig 128 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>128</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="48">
<name>TTR Serviceanfrage Priorität niedrig 32 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="49">
<name>TTR Serviceanfrage Priorität niedrig 64 Stunden</name>
<priority>4</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>64</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="45">
<name>TTR Serviceanfrage Priorität mittel 16 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>16</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="46">
<name>TTR Serviceanfrage Priorität mittel 32 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>32</value>
<unit>hours</unit>
</SLT>
<SLT alias="SLT" id="47">
<name>TTR Serviceanfrage Priorität mittel 64 Stunden</name>
<priority>3</priority>
<request_type>service_request</request_type>
<metric>ttr</metric>
<value>64</value>
<unit>hours</unit>
</SLT>
</Set>

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ContactType alias="ContactType" id="75">
<name>Administrator</name>
</ContactType>
<ContactType alias="ContactType" id="73">
<name>Einkaeufer</name>
</ContactType>
<ContactType alias="ContactType" id="1">
<name>Kundenbetreuer</name>
</ContactType>
<ContactType alias="ContactType" id="2">
<name>Helpdesk</name>
</ContactType>
<ContactType alias="ContactType" id="3">
<name>Manager</name>
</ContactType>
<ContactType alias="ContactType" id="74">
<name>Vertrieb</name>
</ContactType>
<ContactType alias="ContactType" id="4">
<name>Supportmitarbeiter</name>
</ContactType>
<ContactType alias="ContactType" id="5">
<name>Support Stufe 1</name>
</ContactType>
<ContactType alias="ContactType" id="6">
<name>Teamleiter</name>
</ContactType>
</Set>

View File

@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<DocumentType alias="DocumentType" id="72">
<name>Architektur</name>
</DocumentType>
<DocumentType alias="DocumentType" id="69">
<name>Vertrag</name>
</DocumentType>
<DocumentType alias="DocumentType" id="71">
<name>Verfahren</name>
</DocumentType>
<DocumentType alias="DocumentType" id="70">
<name>Prozess</name>
</DocumentType>
</Set>

View File

@@ -1,21 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<ContactType alias="ContactType" id="17">
<name>Customer manager</name>
</ContactType>
<ContactType alias="ContactType" id="12">
<name>Helpdesk</name>
</ContactType>
<ContactType alias="ContactType" id="15">
<name>Manager</name>
</ContactType>
<ContactType alias="ContactType" id="14">
<name>Support Agent</name>
</ContactType>
<ContactType alias="ContactType" id="13">
<name>Support level1</name>
</ContactType>
<ContactType alias="ContactType" id="16">
<name>Team leader</name>
</ContactType>
</Set>

View File

@@ -2174,22 +2174,5 @@
</profiles>
</user_rights>
<constants/>
<snippets>
<snippet id="GestionCssInfo" _delta="define">
<placement>module</placement>
<module>itop-structure</module>
<rank>-1</rank>
<content>
<![CDATA[
class GestionCssInfo implements iBackofficeStyleExtension
{
public function GetStyle(): string
{
return '.ibo-field--label>.ibo-has-description::after, .ibo-input-with-label--label.ibo-has-description::after{content:"\1F6C8"; font-weight: 200;} ';
}
}
]]>
</content>
</snippet>
</snippets>
<snippets/>
</itop_design>

View File

@@ -32,7 +32,6 @@ SetupWebPage::AddModule(
'data/data.sample.persons.xml',
'data/data.sample.teams.xml',
'data/data.sample.contactteam.xml',
'data/data.sample.contacttype.xml',
'data/data.sample.auditdomain.xml',
'data/data.sample.auditcategory.xml',
'data/data.sample.auditcategory-auditdomain.xml',

View File

@@ -61,6 +61,6 @@ class TicketsInstaller extends ModuleInstallerAPI
}
}
// Load localized structural data: predefined query phrases for notifications
static::LoadLocalizedData($oConfiguration, $sPreviousVersion, $sCurrentVersion, '3.0.0', __DIR__."/data/{{language_code}}.data.itop-tickets.xml");
static::LoadLocalizedDataOnCrossingVersion($oConfiguration, $sPreviousVersion, $sCurrentVersion, '3.0.0', utils::GetAbsoluteModulePath('itop-tickets')."data/data.itop-tickets.en_us.xml");
}
}

View File

@@ -16,7 +16,7 @@ Dict::Add('CS CZ', 'Czech', 'Čeština', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('DA DA', 'Danish', 'Dansk', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('DE DE', 'German', 'Deutsch', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -14,7 +14,7 @@ Dict::Add('EN US', 'English', 'English', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.',
'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',

View File

@@ -16,7 +16,7 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -11,23 +11,23 @@
*
*/
Dict::Add('FR FR', 'French', 'Français', [
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installé',
'UI:Layout:ExtensionsDetails:BadgeInstalled' => 'installée',
'UI:Layout:ExtensionsDetails:BadgeInstalled+' => 'Cette extension fait partie de l\'installation actuelle.',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'va être installé',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'va être installée',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'Cette extension sera installée lors de l\'installation.',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'ne peut pas être installée',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'Cette extension ne peut pas être installée car une ou plusieurs dépendances ne sont pas satisfaites.',
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'pas installé',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'Cette extension ne peut pas être installée car une ou plusieurs dépendances ne peuvent pas être satisfaites.',
'UI:Layout:ExtensionsDetails:BadgeNotInstalled' => 'pas installée',
'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' => 'va être désinstallée',
'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' => 'supprimée 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',
'UI:Layout:ExtensionsDetails:MoreActions' => 'Plus d\'actions',
'UI:Layout:ExtensionsDetails:TogglerTooltip' => 'Toggle %1$s~~',
'UI:Layout:ExtensionsDetails:TogglerTooltip' => 'Basculer %1$s',
]);

View File

@@ -16,7 +16,7 @@ Dict::Add('HU HU', 'Hungarian', 'Magyar', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('IT IT', 'Italian', 'Italiano', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('JA JP', 'Japanese', '日本語', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('PL PL', 'Polish', 'Polski', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('RU RU', 'Russian', 'Русский', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('SK SK', 'Slovak', 'Slovenčina', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('TR TR', 'Turkish', 'Türkçe', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -16,7 +16,7 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', [
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled' => 'to be installed~~',
'UI:Layout:ExtensionsDetails:BadgeToBeInstalled+' => 'This extension will be installed during the setup.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled' => 'cannot be installed~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies are not satisfied.~~',
'UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+' => 'This extension cannot be installed because one or more dependencies cannot be fulfilled.~~',
'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~~',

View File

@@ -58,11 +58,11 @@ const CombodoCKEditorHandler = {
// Adjust size if passed in configuration
// - Width
if (aConfiguration.width !== undefined) {
editor.editing.view.on('change', writer => { writer.setStyle( 'width', aConfiguration.width, editor.editing.view.document.getRoot() ); } );
editor.editing.view.change( writer => { writer.setStyle( 'width', aConfiguration.width, editor.editing.view.document.getRoot() ); } );
}
// - Height
if (aConfiguration.height !== undefined) {
editor.editing.view.on('change', writer => { writer.setStyle( 'height', aConfiguration.height, editor.editing.view.document.getRoot() ); } );
editor.editing.view.change( writer => { writer.setStyle( 'height', aConfiguration.height, editor.editing.view.document.getRoot() ); } );
}
this.instances[sElem] = editor;

View File

@@ -51,7 +51,7 @@ $sOperation = utils::ReadParam('operation', '');
$oP->SetBreadCrumbEntry('ui-tool-universalsearch', Dict::S('Menu:UniversalSearchMenu'), Dict::S('Menu:UniversalSearchMenu+'), '', 'fas fa-search', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
//$sSearchHeaderForceDropdown
$sSearchHeaderForceDropdown = '<select id="select_class" name="baseClass" onChange="this.form.trigger(\'submit\');">';
$sSearchHeaderForceDropdown = '<select id="select_class" name="baseClass" onChange="this.form.submit();">';
$aClassLabels = [];
foreach (MetaModel::GetClasses('bizmodel, grant_by_profile') as $sCurrentClass) {
if ((MetaModel::HasCategory($sCurrentClass, 'grant_by_profile') && UserRights::IsActionAllowed($sCurrentClass, UR_ACTION_BULK_MODIFY))

View File

@@ -54,7 +54,7 @@ try {
$oP->SetBreadCrumbEntry('ui-tool-tag-admin', Dict::S('Menu:TagAdminMenu'), Dict::S('Menu:TagAdminMenu+'), '', 'fas fa-tags', iTopWebPage::ENUM_BREADCRUMB_ENTRY_ICON_TYPE_CSS_CLASSES);
$sSearchHeaderForceDropdown = '<select id="select_class" name="class" onChange="this.form.trigger(\'submit\');">';
$sSearchHeaderForceDropdown = '<select id="select_class" name="class" onChange="this.form.submit();">';
$aClassLabels = [];
foreach (MetaModel::EnumChildClasses($sBaseClass, ENUM_CHILD_CLASSES_EXCLUDETOP) as $sCurrentClass) {
$aClassLabels[$sCurrentClass] = MetaModel::GetName($sCurrentClass);

View File

@@ -545,7 +545,7 @@ class iTopExtensionsMap
continue;
}
if (! $bKeepExtensionsHavingMissingDependencies && count($oExtension->aMissingDependencies) > 0) {
if (! $bKeepExtensionsHavingMissingDependencies && $oExtension->HasDependencyIssue()) {
//skip extensions with dependency issues
continue;
}
@@ -562,19 +562,20 @@ class iTopExtensionsMap
public function GetAllExtensionsOptionInfo(bool $bRemoteExtensionsShouldBeMandatory = true): array
{
$aRes = [];
foreach ($this->GetAllExtensionsToDisplayInSetup(false, $bRemoteExtensionsShouldBeMandatory) as $sCode => $oExtension) {
foreach ($this->GetAllExtensionsToDisplayInSetup(true, $bRemoteExtensionsShouldBeMandatory) as $sCode => $oExtension) {
$aRes[] = [
'extension_code' => $oExtension->sCode,
'title' => $oExtension->sLabel,
'description' => $oExtension->sDescription,
'more_info' => $oExtension->sMoreInfoUrl,
'default' => true, // by default offer to install all modules
'modules' => $oExtension->aModules,
'mandatory' => $oExtension->bMandatory,
'source_label' => $oExtension->GetExtensionSourceLabel(),
'uninstallable' => $oExtension->CanBeUninstalled(),
'missing' => $oExtension->bRemovedFromDisk,
'version' => $oExtension->sVersion,
'extension_code' => $oExtension->sCode,
'title' => $oExtension->sLabel,
'description' => $oExtension->sDescription,
'more_info' => $oExtension->sMoreInfoUrl,
'default' => true, // by default offer to install all modules
'modules' => $oExtension->aModules,
'mandatory' => $oExtension->bMandatory,
'dependency_issue' => $oExtension->HasDependencyIssue(),
'source_label' => $oExtension->GetExtensionSourceLabel(),
'uninstallable' => $oExtension->CanBeUninstalled(),
'missing' => $oExtension->bRemovedFromDisk,
'version' => $oExtension->sVersion,
];
}

View File

@@ -8,6 +8,7 @@ use DBObjectSet;
use IssueLog;
use MetaModel;
use SetupLog;
use TagSetFieldData;
require_once APPROOT.'setup/feature_removal/ModelReflectionSerializer.php';
@@ -68,6 +69,10 @@ abstract class AbstractSetupAudit
continue;
}
if (MetaModel::IsSameFamily($sClass, TagSetFieldData::class)) {
continue;
}
if (!MetaModel::IsStandaloneClass($sClass)) {
$iCount = $this->Count($sClass);
$this->aFinalClassesToCleanup[$sClass] = $iCount;

View File

@@ -176,7 +176,7 @@ class iTopExtension
};
}
public function IsRemote(): string
public function IsRemote(): bool
{
return $this->sSource === self::SOURCE_REMOTE;
}
@@ -200,4 +200,9 @@ class iTopExtension
{
return $this->sLabel !== '';
}
public function HasDependencyIssue(): bool
{
return count($this->aMissingDependencies) > 0;
}
}

View File

@@ -3688,6 +3688,32 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/form</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -3818,6 +3844,58 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/options-resolver</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/password-hasher</product>
<author>Robin Chalas - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -3870,6 +3948,32 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/polyfill-intl-icu</product>
<author>Nicolas Grekas - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -3974,6 +4078,58 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/property-access</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/property-info</product>
<author>K&#xE9;vin Dunglas - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2015-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -4026,6 +4182,58 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/security-core</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/security-csrf</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -4182,6 +4390,32 @@ to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]></text>
</license>
<license>
<product scope="lib">symfony/validator</product>
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Copyright (c) 2004-present Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is furnished
to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -4248,32 +4482,12 @@ THE SOFTWARE.
<author>Fabien Potencier - Symfony Community</author>
<license_type>MIT</license_type>
<text><![CDATA[
Icons are from "Tabler Icons" (https://github.com/tabler/tabler-icons), a set of
free MIT-licensed high-quality SVG icons.
JetBrains Mono typeface (https://www.jetbrains.com/lp/mono/) is available
under the SIL Open Font License 1.1 and can be used free of charge, for both
commercial and non-commercial purposes. You do not need to give credit to
JetBrains, although we will appreciate it very much if you do.
-----
MIT License
Copyright (c) 2020-2022 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Licence: https://github.com/JetBrains/JetBrainsMono/blob/master/OFL.txt
]]></text>
</license>
<license>
@@ -4316,7 +4530,7 @@ THE SOFTWARE.
published by the Free Software Foundation, either version 3 of the
License, or (at your option) any later version.
2002-2025 Nicola Asuni - Tecnick.com LTD
2002-2026 Nicola Asuni - Tecnick.com LTD
**********************************************************************
**********************************************************************
@@ -4324,7 +4538,7 @@ THE SOFTWARE.
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://www.fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -4493,7 +4707,7 @@ Library.
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Copyright (C) 2007 Free Software Foundation, Inc. <https://www.fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
@@ -5137,7 +5351,7 @@ the "copyright" line and a pointer to where the full notice is found.
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
along with this program. If not, see <https://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
@@ -5156,14 +5370,14 @@ might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
<https://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
<https://www.gnu.org/philosophy/why-not-lgpl.html>.
**********************************************************************
**********************************************************************

View File

@@ -310,85 +310,47 @@ abstract class ModuleInstallerAPI
}
/**
* Helper to load localized data in iTop not only at the module installation but also if during an upgrade you pass over the version which brought those data
*
* @param \Config $oConfiguration
* @param string $sPreviousVersion The previous version of the module (empty string will force the loading)
* @param string $sCurrentVersion The current version of the module
* @param string $sFirstLoadingVersion The first module version for which the data loading should be performed (e.g. '3.0.0')
* @param string $sFilePattern The pattern of the file to load, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
* Expected values for the three version parameters: Valid examples: '3.2', '3.2.0', '3.2-beta1'. Invalid examples: '3', '3.2.', '3.2-beta-1', '3.2 beta1'.
* @param string $sPreviousVersion The previous version of the module (empty string in case of first install)
* @param string $sCurrentVersion The current version of the module. Cannot be empty.
* @param string $sFirstLoadingVersion If your upgrade passes over the FirstLoadingVersion or in case of fresh install, the localized data will be loaded. This is useful when you want to load (structural) data on a module upgrade. Cannot be empty.
* @param string $sDefaultFileName The file to load (If localization is expected then provided file name must end by '.en_us.xml')
* If the default iTop language is french and a localized variant of the $sDefaultFileName ending by '.fr_fr.xml' exists, then that file with french data will be loaded.
*
* @return void
* @throws \ConfigException
* @throws \CoreException
* @throws \CoreUnexpectedValue
*/
public static function LoadLocalizedData(Config $oConfiguration, string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sFilePattern): void
public static function LoadLocalizedDataOnCrossingVersion(Config $oConfiguration, string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sDefaultFileName): void
{
self::AssertLoadLocalizedDataParametersAreValid($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion, $sFilePattern);
// It's not very clear if it makes sense to test a particular version,
// as the loading mechanism checks object existence using reconc_keys
// and do not recreate them, nor update existing.
// Without test, new entries added to the data files, would be automatically loaded
if (($sPreviousVersion === '') ||
(version_compare($sPreviousVersion, $sCurrentVersion, '<')
&& version_compare($sPreviousVersion, $sFirstLoadingVersion, '<'))) {
// Note: There is an issue when upgrading, default language cannot be retrieved from the passed configuration, we have to read it from the disk
self::AssertLoadLocalizedDataOnCrossingVersionParametersAreValid($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion);
if (self::IsVersionCrossed($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion)) {
// Note: on upgrade, the default language cannot be retrieved from the passed configuration, it must be read from disk (see StructureInstaller::AfterDatabaseCreation)
if (utils::IsNullOrEmptyString($sPreviousVersion)) {
// Fresh install
$sDefaultLanguage = $oConfiguration->GetDefaultLanguage();
$sWishedLanguage = $oConfiguration->GetDefaultLanguage();
} else {
// Upgrade
$sDefaultLanguage = utils::GetConfig(true)->GetDefaultLanguage();
}
$sFileName = self::GetLocalizedFileName($sDefaultLanguage, $sFilePattern);
if ($sFileName !== '') {
self::XMLFileLoad($sFileName);
$sWishedLanguage = utils::GetConfig(true)->GetDefaultLanguage();
}
self::LoadLocalizedData($sWishedLanguage, $sDefaultFileName);
}
}
/**
* @throws \CoreUnexpectedValue
*/
private static function AssertLoadLocalizedDataParametersAreValid(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sFilePattern): void
{
if (($sPreviousVersion !== '') && !self::IsValidLocalizedDataVersion($sPreviousVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedData expects sPreviousVersion to be empty or match x.y[.z][-name], got '{$sPreviousVersion}'");
}
if (!self::IsValidLocalizedDataVersion($sCurrentVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedData expects sCurrentVersion to match x.y[.z][-name], got '{$sCurrentVersion}'");
}
if (!self::IsValidLocalizedDataVersion($sFirstLoadingVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedData expects sFirstLoadingVersion to match x.y[.z][-name], got '{$sFirstLoadingVersion}'");
}
if (utils::IsNullOrEmptyString($sFilePattern)) {
throw new CoreUnexpectedValue('LoadLocalizedData expects sFilePattern to be a non-empty string');
}
if (substr_count($sFilePattern, '{{language_code}}') !== 1) {
throw new CoreUnexpectedValue("LoadLocalizedData expects sFilePattern to contain the exact placeholder '{{language_code}}' exactly once");
}
}
private static function IsValidLocalizedDataVersion(string $sVersion): bool
{
return (preg_match('/^\d+\.\d+(?:\.\d+)?(?:-[A-Za-z0-9]+)?$/', $sVersion) === 1);
}
/**
* @param array|string $sFileName
* @param \XMLDataLoader $oDataLoader
* Helper to load a localized data file based on the default language of the application.
* @param string $sDefaultLanguage The default language of the application, used to build the localized file name
* @param string $sDefaultFile The default file to load, must be ending with .en_us.xml to be localized
*
* @return void
* @throws \Exception
*/
public static function XMLFileLoad(string $sFileName): void
public static function LoadLocalizedData(string $sDefaultLanguage, string $sDefaultFile): void
{
$sFileName = self::GetLocalizedFileName($sDefaultLanguage, $sDefaultFile);
if (!file_exists($sFileName)) {
throw new Exception("File $sFileName not found");
}
@@ -402,26 +364,77 @@ abstract class ModuleInstallerAPI
}
/**
* @param string $sLanguage The language code to use for localization (e.g. 'EN US')
* @param string $sFilePattern The full path+name of the file to localize, with {{language_code}} as placeholder for the language code (e.g. 'data.sample.{{language_code}}.xml')
* Helper to get the localized file name for a given language code, based on the original file name which must end by .en_us.xml
* @param string $sLanguage The language code to use for localization (e.g. 'FR FR' or 'fr_fr')
* @param string $sOriginalFileName The full path+name of the file to localize. If ending with .en_us.xml, it searches for a localized file.
*
* @return string The localized file name if found, or an empty string if not found
* @throws \ConfigException
* @throws \CoreException
* @return string The localized file name if found otherwise the original file name
* @throws \Exception If the original file name ends with .en_us.xml and does not exist
*/
public static function GetLocalizedFileName($sLanguage, string $sFilePattern): string
public static function GetLocalizedFileName(string $sLanguage, string $sOriginalFileName): string
{
$sLang = str_replace(' ', '_', strtolower($sLanguage));
$sFileName = str_replace('{{language_code}}', $sLang, $sFilePattern);
if (!file_exists($sFileName)) {
$sLang = 'en_us';
$sFileName = str_replace('{{language_code}}', $sLang, $sFilePattern);
}
if (file_exists($sFileName)) {
return $sFileName;
if (str_ends_with($sOriginalFileName, '.en_us.xml')) {
// If the original file which can be localized, does not exist, then even if the localized file itself exists, we want to raise this design issue.
if (!file_exists($sOriginalFileName)) {
throw new Exception("File $sOriginalFileName not found, it must exist when required by the module or as iTop fallback language when no localized file exists for the iTop default language");
}
// Search for a file for the requested language,
$sLang = '.'.str_replace(' ', '_', strtolower($sLanguage)).'.xml';
$sFileName = str_replace('.en_us.xml', $sLang, $sOriginalFileName);
// localized file not found, fall back to the original file (en_us)
if (!file_exists($sFileName)) {
SetupLog::Debug("File for iTop default language $sFileName not found, fall back to file $sOriginalFileName");
$sFileName = $sOriginalFileName;
}
} else {
SetupLog::Warning("No data file matching the pattern $sFilePattern and language_code $sLang was found.");
return '';
// If the original file is not localizable, then we just return it as is
$sFileName = $sOriginalFileName;
}
return $sFileName;
}
/**
* @throws \CoreUnexpectedValue
*/
private static function AssertLoadLocalizedDataOnCrossingVersionParametersAreValid(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion): void
{
if (($sPreviousVersion !== '') && !self::IsValidVersionFormat($sPreviousVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedDataOnCrossingVersion expects sPreviousVersion to be empty or match x.y[.z][-name], got '{$sPreviousVersion}'");
}
if (!self::IsValidVersionFormat($sCurrentVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedDataOnCrossingVersion expects sCurrentVersion to match x.y[.z][-name], got '{$sCurrentVersion}'");
}
if (utils::IsNullOrEmptyString($sFirstLoadingVersion) || !self::IsValidVersionFormat($sFirstLoadingVersion)) {
throw new CoreUnexpectedValue("LoadLocalizedDataOnCrossingVersion expects sFirstLoadingVersion to match x.y[.z][-name], got '{$sFirstLoadingVersion}'");
}
}
/**
* Checks if a module version matches x.y[.z][-name].
* Valid examples: '3.2', '3.2.1', '3.2.3-2', '3.3.0-dev', '10.14-beta2'.
* Invalid examples: '3', 'v3.2', '3.2.1.4', '3.2-rc.1', '3.2 beta1'.
*
* @param string $sVersion
*
* @return bool
*/
public static function IsValidVersionFormat(string $sVersion): bool
{
return (preg_match('/^\d+\.\d+(?:\.\d+)?(?:-[A-Za-z0-9]+)?$/', $sVersion) === 1);
}
/**
* @param string $sPreviousVersion
* @param string $sCurrentVersion
* @param string $sFirstLoadingVersion
*
* @return bool
*/
public static function IsVersionCrossed(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion): bool
{
return ($sPreviousVersion === '') ||
(version_compare($sPreviousVersion, $sCurrentVersion, '<')
&& version_compare($sPreviousVersion, $sFirstLoadingVersion, '<')
&& version_compare($sFirstLoadingVersion, $sCurrentVersion, '<='));
}
}

View File

@@ -933,6 +933,8 @@ class RunTimeEnvironment
@chmod($sFinalConfig, 0440); // Read-only for owner and group, nothing for others
SetupUtils::rrmdir(dirname($sBuildConfig)); // Cleanup the temporary build dir if empty
MetaModel::ResetAllCaches($this->sBuildEnv);
MetaModel::ResetAllCaches($this->sFinalEnv);
if (! isset($_SESSION)) {
//used in all UI setups (not unattended)
@@ -1101,7 +1103,7 @@ class RunTimeEnvironment
{
$oConfig = MetaModel::GetConfig();
$oConfig->Set('access_mode', ACCESS_FULL);
$sDefaultLanguage = $oConfig->GetDefaultLanguage();
$oDataLoader = new XMLDataLoader();
CMDBObject::SetCurrentChangeFromParams("Initialization from XML files for the selected modules ");
@@ -1147,7 +1149,7 @@ class RunTimeEnvironment
// in the current database
foreach ($aPreviouslyLoadedFiles as $sFileRelativePath) {
$sFileName = APPROOT.$sFileRelativePath;
SetupLog::Info("Loading file: $sFileName (just to get the keys mapping)");
SetupLog::Info("Loading file: $sFileName (just to build & cache the keys mapping)");
if (!file_exists($sFileName)) {
throw(new Exception("File $sFileName does not exist"));
}
@@ -1163,7 +1165,7 @@ class RunTimeEnvironment
if (!file_exists($sFileName)) {
throw(new Exception("File $sFileName does not exist"));
}
$sFileName = ModuleInstallerAPI::GetLocalizedFileName($sDefaultLanguage, $sFileName);
$oDataLoader->LoadFile($sFileName);
$sResult = sprintf("loading of %s done.", basename($sFileName));
SetupLog::Info($sResult);

View File

@@ -553,15 +553,14 @@ class SetupUtils
*
* @since 3.0.0 N°2214 replace SetupLog::Log calls by CheckResult::TRACE
*/
public static function CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules)
public static function CheckSelectedModules(array $aRelDirsToScan, $aSelectedModules)
{
$aResult = [];
$aDirsToScan = [APPROOT.$sSourceDir];
$sExtensionsPath = APPROOT.$sExtensionDir;
if (is_dir($sExtensionsPath)) {
// if the extensions dir exists, scan it for additional modules as well
$aDirsToScan[] = $sExtensionsPath;
$aDirsToScan = [];
foreach ($aRelDirsToScan as $sRelDir) {
if (is_dir(APPROOT.$sRelDir)) {
$aDirsToScan[] = APPROOT.$sRelDir;
}
}
require_once(APPROOT.'setup/modulediscovery.class.inc.php');
try {
@@ -1662,11 +1661,14 @@ JS
$aAvailableModules = $oProductionEnv->AnalyzeInstallation($oConfig, $aDirsToScan, $bAbortOnMissingDependency, $aModulesToLoad);
foreach ($aAvailableModules as $key => $aModule) {
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'], $sExtraDir) !== false)); // Some modules (root, datamodel) have no 'root_dir'
if ($bIsExtra) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$aAvailableModules[$key]['visible'] = false;
$bRemoteExtensionsShouldBeMandatory = !$oWizard->GetParameter('force-uninstall', false);
if ($bRemoteExtensionsShouldBeMandatory) {
foreach ($aAvailableModules as $key => $aModule) {
$bIsExtra = (array_key_exists('root_dir', $aModule) && (strpos($aModule['root_dir'], $sExtraDir) !== false)); // Some modules (root, datamodel) have no 'root_dir'
if ($bIsExtra) {
// Modules in data/production-modules/ are considered as mandatory and always installed
$aAvailableModules[$key]['visible'] = false;
}
}
}

View File

@@ -338,7 +338,7 @@ class InstallationFileService
continue;
}
if ($this->CanChooseUnpackageExtension($oExtension)) {
if (($oExtension->bVisible) && (count($oExtension->aMissingDependencies) === 0)) {
if ($oExtension->bVisible && !$oExtension->HasDependencyIssue()) {
$aCurrentModules = [];
$aUnselectableModules = [];
$bIsExtensionSelectable = true;

View File

@@ -244,7 +244,8 @@ $aChecks = SetupUtils::CheckBackupPrerequisites(APPROOT.'data'); // mmm should b
$aSelectedModules = $oParams->Get('selected_modules');
$sSourceDir = $oParams->Get('source_dir', 'datamodels/latest');
$sExtensionDir = $oParams->Get('extensions_dir', 'extensions');
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($sSourceDir, $sExtensionDir, $aSelectedModules));
$aRelDirsToScan = [$sSourceDir, $sExtensionDir, "data/$sTargetEnvironment-modules"];
$aChecks = array_merge($aChecks, SetupUtils::CheckSelectedModules($aRelDirsToScan, $aSelectedModules));
foreach ($aChecks as $oCheckResult) {
switch ($oCheckResult->iSeverity) {

View File

@@ -46,7 +46,7 @@ abstract class AbstractWizStepMiscParams extends WizardStep
$oPage->add('<fieldset>');
$oPage->add('<div id="prefix_option" class="collapsable-options">');
$oPage->add('<span data-role="setup-collapsable-options--toggler"><label style="font-weight: normal;">Advanced parameters</label></span>');
$oPage->add('<span data-role="setup-collapsable-options--toggler"><a href="#" style="font-weight: normal;color:black;">Advanced parameters</a></span>');
$oPage->add('<div class="" style="'.(mb_strlen($sChecked) === 0 ? 'display:none' : '').'">');
$oPage->add('<input id="force-uninstall" type="checkbox"'.$sChecked.' name="force-uninstall"><label for="force-uninstall">&nbsp;Unlock any extension uninstallation</label>');
$oPage->add('<div class="message message-warning">This could result in data corruption and application crashes.</div>');

View File

@@ -112,6 +112,22 @@ JS);
return;
}
// When the setup reach this step, it already checked whether extensions were uninstallable (during WizStepModulesChoice or combodo-data-feature-removal). We only need to log what has been done.
if ($this->oWizard->GetParameter('force-uninstall', false)) {
SetupLog::Warning("User disabled uninstallation checks");
}
$aExtensionsRemoved = json_decode($this->oWizard->GetParameter('removed_extensions'), true) ?? [];
$aExtensionsNotUninstallable = json_decode($this->oWizard->GetParameter('extensions_not_uninstallable'), true) ?? [];
$aExtensionsForceUninstalled = [];
foreach ($aExtensionsRemoved as $sExtensionCode => $sLabel) {
if (in_array($sExtensionCode, $aExtensionsNotUninstallable)) {
$aExtensionsForceUninstalled[] = $sExtensionCode;
}
}
if (count($aExtensionsForceUninstalled)) {
SetupLog::Warning("Extensions uninstalled forcefully : ".implode(',', $aExtensionsForceUninstalled));
}
$oPage->add_ready_script(<<<JS
$("#wiz_form").data("installation_status", "not started");
ExecuteStep("");

View File

@@ -31,8 +31,8 @@ class WizStepLandingBeforeAudit extends WizStepModulesChoice
$oWizard->SetParameter('db_tls_enabled', $this->oConfig->Get('db_tls.enabled'));
$oWizard->SetParameter('db_tls_ca', $this->oConfig->Get('db_tls.ca') ?? '');
$oWizard->SetParameter('display_choices', '');
$oWizard->SetParameter('extensions_not_uninstallable', '[]');
$oWizard->SaveParameter('extensions_not_uninstallable', '[]');
$oWizard->SaveParameter('use_symbolic_links', MFCompiler::UseSymbolicLinks());
$oWizard->SaveParameter('force-uninstall', false);
$oWizard->SaveParameter('skip_wizard', false);

View File

@@ -587,7 +587,7 @@ EOF
break;
}
}
if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) ||
if ((isset($aChoice['mandatory']) && $aChoice['mandatory'] && (!isset($aChoice['dependency_issue']) || !$aChoice['dependency_issue'])) ||
(isset($aSelectedChoices[$sChoiceId]) && ($aSelectedChoices[$sChoiceId] == $sChoiceId))) {
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
if (isset($aChoice['modules'])) {
@@ -637,7 +637,7 @@ EOF
if ($sChoiceName == null) {
$sChoiceName = $sChoiceId;
}
if ((isset($aChoice['mandatory']) && $aChoice['mandatory']) ||
if ((isset($aChoice['mandatory']) && $aChoice['mandatory'] && (!isset($aChoice['dependency_issue']) || !$aChoice['dependency_issue'])) ||
(isset($aSelectedChoices[$sChoiceName]) && ($aSelectedChoices[$sChoiceName] == $sChoiceId))) {
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
if ($aSelectedExtensions !== null) {
@@ -750,13 +750,17 @@ EOF
$bSelected = isset($aSelectedComponents[$sChoiceId]) && ($aSelectedComponents[$sChoiceId] === $sChoiceId);
$bMissingFromDisk = isset($aChoice['missing']) && $aChoice['missing'] === true;
$bMandatory = (isset($aChoice['mandatory']) && $aChoice['mandatory']);
$bInstalled = $bMissingFromDisk || $oITopExtension->bInstalled;
$bInstalled = $bMissingFromDisk || $oITopExtension?->bInstalled ?? false;
$bDependencyIssue = $oITopExtension?->HasDependencyIssue() ?? false;
$bChecked = $bSelected;
$bDisabled = false;
if ($bMissingFromDisk) {
$bDisabled = true;
$bChecked = false;
} elseif ($bDependencyIssue) {
$bDisabled = true;
$bChecked = false;
} elseif ($bMandatory) {
$bDisabled = true;
$bChecked = true;
@@ -787,6 +791,8 @@ EOF
return [
'uninstallable' => $bCanBeUninstalled,
'dependency_issue' => $bDependencyIssue,
'mandatory' => $bMandatory,
'missing' => $bMissingFromDisk,
'installed' => $bInstalled,
'disabled' => $bDisabled,
@@ -805,7 +811,7 @@ EOF
$sChoiceId = $sParentId.self::$SEP.$index;
$aFlags = $this->ComputeChoiceFlags($aChoice, $sChoiceId, $aSelectedComponents, $bAllDisabled, $bDisableUninstallCheck, $this->bUpgrade);
if ($aFlags['disabled'] && !$aFlags['checked'] && !$aFlags['uninstallable'] && !$bDisableUninstallCheck) {
if ($aFlags['disabled'] && !$aFlags['checked'] && !$bDisableUninstallCheck && (!$aFlags['uninstallable'] || $aFlags['mandatory'])) {
$this->bCanMoveForward = false;//Disable "Next"
}
@@ -882,6 +888,9 @@ EOF
if (!$aFlags['uninstallable']) {
$sTooltip .= '<div id="badge--'.$sId.'--not-uninstallable" class="ibo-badge ibo-block ibo-is-yellow" title="Once this extension has been installed, it should not be uninstalled." >cannot be uninstalled</div>';
}
if ($aFlags['dependency_issue']) {
$sTooltip .= '<div id="badge--'.$sId.'--cannot-be-installed" class="ibo-badge ibo-block ibo-is-orange" title="This extension cannot be installed because one or more dependencies are not satisfied." >cannot be installed</div>';
}
$sMetadata = '';
if (isset($aChoice['version']) && isset($aChoice['source_label'])) {
@@ -939,10 +948,6 @@ EOF
public function GetNextButtonLabel()
{
if (!$this->bCanMoveForward) {
return 'Non-uninstallable extension missing';
}
if ($this->GetStepInfo(1 + $this->GetStepIndex()) === null) {
return 'Check compatibility';
}

View File

@@ -116,7 +116,7 @@ class WizStepSummary extends AbstractWizStepInstall
$oPage->add('<div class="closed"><a class="title ibo-setup-summary-title" href="#" aria-label="Extensions to be uninstalled">Extensions to be uninstalled</a>');
$aExtensionsRemoved = json_decode($this->oWizard->GetParameter('removed_extensions'), true) ?? [];
$aExtensionsNotUninstallable = json_decode($this->oWizard->GetParameter('extensions_not_uninstallable')) ?? [];
$aExtensionsNotUninstallable = json_decode($this->oWizard->GetParameter('extensions_not_uninstallable'), true) ?? [];
if (count($aExtensionsRemoved) > 0) {
$sExtensionsRemoved = '<ul>';
foreach ($aExtensionsRemoved as $sExtensionCode => $sLabel) {

View File

@@ -26,10 +26,11 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
$aBadges = [];
$bUninstallable = $aExtraFlags['uninstallable'] ?? true;
$bMissingFromDisk = $aExtraFlags['missing'] ?? false;
$bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false;
$bSelected = $aExtraFlags['selected'] ?? true;
$bDisabled = $aExtraFlags['disabled'] ?? false;
$bRemote = $aExtraFlags['remote'] ?? false;
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $sCode);
self::AddExtraBadges($aBadges, $bUninstallable, $bMissingFromDisk, $bDependencyIssue, $sCode);
$oBadgeInstalled = BadgeUIBlockFactory::MakeGreen(
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeInstalled+'),
@@ -47,7 +48,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
$oExtensionDetails = new ExtensionDetails($sCode, $sLabel, $sDescription, $aMetaData, $aBadges, $sAbout);
$oExtensionDetails->GetToggler()->SetIsToggled(true);
if ($bMissingFromDisk) {
if ($bMissingFromDisk || $bDependencyIssue) {
$oExtensionDetails->GetToggler()->SetIsToggled(false);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
} elseif ((!$bUninstallable || $bRemote) && !$bDisabled) {
@@ -70,10 +71,10 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
{
$aBadges = [];
$bUninstallable = $aExtraFlags['uninstallable'] ?? true;
$bCannotBeInstalled = $aExtraFlags['cannot-be-installed'] ?? false;
$bDependencyIssue = $aExtraFlags['dependency_issue'] ?? false;
$bSelected = $aExtraFlags['selected'] ?? false;
$bDisabled = $aExtraFlags['disabled'] ?? false;
self::AddExtraBadges($aBadges, $bUninstallable, false, $sCode);
self::AddExtraBadges($aBadges, $bUninstallable, false, $bDependencyIssue, $sCode);
$oBadgeInstalled = BadgeUIBlockFactory::MakeGrey(
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeNotInstalled+'),
@@ -88,19 +89,13 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
);
$oBadgeToBeUninstalled->AddCSSClass('checked');
$aBadges[] = $oBadgeToBeUninstalled;
if ($bCannotBeInstalled) {
$bSelected = false;
$aBadges[] = BadgeUIBlockFactory::MakeOrange(
Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+'),
self::GetBadgeId($sCode, self::BADGE_ID_CANNOT_BE_INSTALLED)
);
}
$oExtensionDetails = new ExtensionDetails($sCode, $sLabel, $sDescription, $aMetaData, $aBadges, $sAbout);
if ($bCannotBeInstalled) {
if ($bDependencyIssue) {
$oExtensionDetails->GetToggler()->SetIsToggled(false);
$oExtensionDetails->GetToggler()->SetIsDisabled(true);
}
if ($bSelected) {
$oExtensionDetails->GetToggler()->SetIsToggled(true);
}
@@ -112,7 +107,7 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
return $oExtensionDetails;
}
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, string $sCode)
private static function AddExtraBadges(array &$aBadges, bool $bUninstallable, bool $bMissingFromDisk, bool $bDependencyIssue, string $sCode)
{
if (!$bUninstallable) {
$aBadges[] = BadgeUIBlockFactory::MakeYellow(
@@ -128,6 +123,13 @@ class ExtensionDetailsUIBlockFactory extends AbstractUIBlockFactory
self::GetBadgeId($sCode, self::BADGE_ID_MISSING_FROM_DISK)
);
}
if ($bDependencyIssue) {
$aBadges[] = BadgeUIBlockFactory::MakeOrange(
Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled'),
Dict::S('UI:Layout:ExtensionsDetails:BadgeCannotBeInstalled+'),
self::GetBadgeId($sCode, self::BADGE_ID_CANNOT_BE_INSTALLED)
);
}
}
/**

View File

@@ -5,7 +5,7 @@
{% include "base/components/input/inputlabel.html.twig" %}
{% endif %}
<select id="{{ oUIBlock.GetId() }}" name="{{ oUIBlock.GetName() }}"
{% if oUIBlock.GetSubmitOnChange() %} onChange="$(this).parents('form:first').trigger('submit');" {% endif %}
{% if oUIBlock.GetSubmitOnChange() %} onChange="$(this).parents('form').first().trigger('submit');" {% endif %}
{% if oUIBlock.IsMultiple() %} multiple {% endif %}
class="{% if oUIBlock.IsHidden() %}ibo-is-hidden{% endif %}{% if oUIBlock.GetAdditionalCSSClassesAsString() %} {{ oUIBlock.GetAdditionalCSSClassesAsString() }}{% endif %}"
{% if oUIBlock.GetDataAttributes() %}

View File

@@ -163,6 +163,58 @@ class DataCleanupServiceTest extends \AbstractCleanup
$this->AssertSummaryEquals($aExpected, $aRes);
}
/**
* N°9831
*
* A nullable external key configured as DEL_MANUAL must NOT be treated as an issue: it is simply
* reset (set to null), exactly like core DBObject::MakeDeletionPlan does. DEL_MANUAL only blocks
* for mandatory keys. This mirrors the real "Hypervisor.farm_id -> Farm" case (nullable + DEL_MANUAL).
*
* Regression guard for the ordering of the IsNullAllowed()/DEL_MANUAL checks in RecursiveDeletion:
* reverting that change would make ExecuteCleanup throw here instead of resetting the object.
*/
public function testExecuteCleanup_NullableManualShouldResetAndNotThrow()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRManualNullable_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
// Must NOT throw: the nullable external key is reset, not reported as an issue
$aRes = $oService->ExecuteCleanup($aClasses);
$aExpected = [
['DFRManualNullable', 1, 0 ],
['DFRToRemoveLeaf', 0, 1 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
/**
* N°9831
*
* Summary counterpart of the test above: a nullable DEL_MANUAL external key is reported as an
* update (reset), with an issue count of 0. Reverting the RecursiveDeletion change would report
* it as an issue instead.
*/
public function testGetCleanupSummary_NullableManualShouldBeUpdateNotIssue()
{
$this->GivenDFRTreeInDB(<<<EOF
DFRToRemoveLeaf_1 <- DFRManualNullable_1
EOF);
$aClasses = [ 'DFRToRemoveLeaf' ];
$oService = new DataCleanupService();
$aRes = $oService->GetCleanupSummary($aClasses);
$aExpected = [
['DFRManualNullable', 1, 0, 0 ],
['DFRToRemoveLeaf', 0, 1, 0 ],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
{
$aExpected = [];

View File

@@ -9,7 +9,6 @@ namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval;
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
use MetaModel;
require_once __DIR__."/AbstractCleanup.php";
class StaticDeletionPlanTest extends \AbstractCleanup
@@ -150,6 +149,66 @@ EOF);
$this->AssertSummaryEquals($aExpected, $aRes);
}
/**
* N°9831
*
* A DEL_MANUAL issue must still be reported when the referencing class (DFRManual) is itself part
* of the classes to remove AND is processed AFTER the class it points to (DFRToRemoveLeaf).
*
* This mirrors the real "VirtualMachine -> VirtualHost" case: the setup audit sorts the removed
* classes alphabetically and drops abstract classes, so a referencing class such as VirtualMachine
* ends up processed after its concrete targets (Farm/Hypervisor/Cloud). Here DFRToRemove is abstract
* and DFRToRemoveLeaf is its concrete child, while DFRManual points to DFRToRemove with a mandatory
* DEL_MANUAL external key.
*
* Regression: the plan entity accumulating the issue used to be overwritten when the referencing
* class was processed as a top-level class, silently dropping the issue (no blocking message on the
* summary page, then a crash at execution time).
*/
public function testGetCleanupSummary_IssueKeptWhenReferencingClassListedAfterTarget(): void
{
$this->GivenDFRObjectsInDB(<<<EOF
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
create DFRManual (name = DFRManual_1, extkey_id = DFRToRemoveLeaf_1)
EOF);
// Target listed BEFORE the referencing class, as produced by the alphabetically sorted audit
$aClasses = ['DFRToRemoveLeaf', 'DFRManual'];
$oService = new StaticDeletionPlan();
$aRes = $oService->GetCleanupSummary($aClasses);
$aExpected = [
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
'DFRManual' => ['iDeleteCount' => 1, 'iIssueCount' => 1],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
/**
* N°9831
*
* Control case: the same issue must also be reported when the referencing class is listed BEFORE
* its target (this order already worked, e.g. "Enclosure -> Rack"). Together with the test above,
* this locks in that issue detection is independent of the processing order.
*/
public function testGetCleanupSummary_IssueKeptWhenReferencingClassListedBeforeTarget(): void
{
$this->GivenDFRObjectsInDB(<<<EOF
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
create DFRManual (name = DFRManual_1, extkey_id = DFRToRemoveLeaf_1)
EOF);
$aClasses = ['DFRManual', 'DFRToRemoveLeaf'];
$oService = new StaticDeletionPlan();
$aRes = $oService->GetCleanupSummary($aClasses);
$aExpected = [
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
'DFRManual' => ['iDeleteCount' => 1, 'iIssueCount' => 1],
];
$this->AssertSummaryEquals($aExpected, $aRes);
}
public function testCircularRefsShouldNotRunInfinitely()
{
$this->GivenDFRObjectsInDB(<<<EOF

View File

@@ -319,6 +319,58 @@
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRManualNullable" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
<abstract>false</abstract>
<db_table>dfrmanualnullable</db_table>
<naming>
<attributes/>
</naming>
<reconciliation>
<attributes/>
</reconciliation>
</properties>
<fields>
<field id="name" xsi:type="AttributeString">
<sql>name</sql>
<default_value/>
<is_null_allowed>true</is_null_allowed>
<validation_pattern/>
<dependencies/>
<tracking_level>all</tracking_level>
</field>
<field id="extkey_id" xsi:type="AttributeExternalKey">
<sql>extkey_id</sql>
<filter/>
<dependencies/>
<is_null_allowed>true</is_null_allowed>
<target_class>DFRToRemove</target_class>
<on_target_delete>DEL_MANUAL</on_target_delete>
<tracking_level>all</tracking_level>
</field>
</fields>
<methods/>
<presentation>
<list>
<items/>
</list>
<search>
<items/>
</search>
<details>
<items>
<item id="name">
<rank>10</rank>
</item>
<item id="extkey_id">
<rank>20</rank>
</item>
</items>
</details>
</presentation>
<parent>cmdbAbstractObject</parent>
</class>
<class id="DFRLeafNotToRemove" _created_in="itop-structure" _delta="define">
<properties>
<category>bizmodel,searchable</category>
@@ -718,6 +770,14 @@
<entry id="Class:DFRManual/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRManual/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManualNullable/Name" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManualNullable/ComplementaryName" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManualNullable" _delta="define"><![CDATA[DFRManualNullable]]></entry>
<entry id="Class:DFRManualNullable+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManualNullable/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
<entry id="Class:DFRManualNullable/Attribute:name+" _delta="define"><![CDATA[]]></entry>
<entry id="Class:DFRManualNullable/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
<entry id="Class:DFRManualNullable/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
</entries>
</dictionary>
</dictionaries>

View File

@@ -285,75 +285,137 @@ SQL
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedData
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
* @dataProvider LoadLocalizedData_RequiredLanguageProvider
*/
public function testLoadLocalizedData_LoadsOnFirstInstall(): void
public function testLoadLocalizedData_LoadRequiredLanguageOnFirstInstall(string $sRequiredLanguage, array $aExpectedOrganizationNames): void
{
// Given
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_FirstInstall_', 'fr_fr');
$this->GivenLocalizedDataFile($sTmpDir, "en_us", $sOrgName);
$this->GivenLocalizedDataFile($sTmpDir, "fr_fr", $sOrgName);
[$oConfig, $sPattern] = $this->GivenLocalizedDataTestContext($sRequiredLanguage);
// When no previous version, and current version higher than the first loading version
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', '3.3.0', '3.0.0', $sPattern);
// Then data loaded
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 0);
$this->AssertOrganizationCountByName($sOrgName, 'fr_fr', 1);
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.3.0', '3.0.0', $sPattern);
// Then the expected localized organizations are loaded
$this->AssertOrganizationNamesExist($aExpectedOrganizationNames);
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedData
*/
public function testLoadLocalizedData_DoesNotLoadWhenVersionConditionIsNotMet(): void
{
// Given
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_NoLoad_', 'en_us');
$this->GivenLocalizedDataFile($sTmpDir, "en_us", $sOrgName);
// When a previous version that is lower than the first loading version, but higher or equal to the current version
ModuleInstallerAPI::LoadLocalizedData($oConfig, '3.0.0', '3.1.0', '3.0.0', $sPattern);
// Then no data loaded
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 0);
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedData
*/
public function testLoadLocalizedData_FallbacksToEnUsWhenLanguageFileIsMissing(): void
{
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_Fallback_', 'fr_fr');
// Intentionally create ONLY en_us file
$this->GivenLocalizedDataFile($sTmpDir, 'en_us', $sOrgName);
// When loading localized data in fr_fr, but only en_us file exists
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', '3.3.0', '3.0.0', $sPattern);
$this->AssertOrganizationCountByName($sOrgName, 'fr_fr', 0);
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 1);
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedData
* @dataProvider LoadLocalizedData_ValidVersionFormatsProvider
*/
public function testLoadLocalizedData_AcceptsSupportedVersionFormats(string $sCurrentVersion, string $sFirstLoadingVersion): void
{
[$oConfig, $sOrgName, $sTmpDir, $sPattern] = $this->GivenLocalizedDataTestContext('XML_Load_ValidVersion_', 'en_us');
$this->GivenLocalizedDataFile($sTmpDir, 'en_us', $sOrgName);
ModuleInstallerAPI::LoadLocalizedData($oConfig, '', $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
$this->AssertOrganizationCountByName($sOrgName, 'en_us', 1);
}
public function LoadLocalizedData_ValidVersionFormatsProvider(): array
public function LoadLocalizedData_RequiredLanguageProvider(): array
{
return [
'Current version with suffix' => ['3.2-dev', '3.0.0'],
'Current version x.y.z' => ['1.2.4', '1.0'],
'Current version x.y.z-suffix' => ['2.3.3-beta', '2.0.0'],
'Current version x.y.z-1' => ['1.2.4-1', '1.0.3-2'],
'Required fr_fr and file exists' => [
'required language' => 'fr_fr',
'expected organization names' => ['Client (Test)', 'Département informatique (Test)'],
],
'Required en_us and file exists' => [
'required language' => 'en_us',
'expected organization names' => ['Customer (Test)', 'IT Department (Test)'],
],
'Required de_de but fallback to en_us' => [
'required language' => 'de_de',
'expected organization names' => ['Customer (Test)', 'IT Department (Test)'],
],
];
}
/**
* @covers \ModuleInstallerAPI::IsVersionCrossed
* @dataProvider IsVersionCrossedProvider
*/
public function testIsVersionCrossed_ReturnsExpectedValue(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, bool $bExpected): void
{
$bIsVersionCrossed = ModuleInstallerAPI::IsVersionCrossed($sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion);
$this->assertSame($bExpected, $bIsVersionCrossed);
}
public function IsVersionCrossedProvider(): array
{
return [
'First install always crosses' => ['', '3.2.0', '3.0.0', true],
'Upgrade crosses first loading version' => ['3.0.0', '3.2.0', '3.1.0', true],
'Upgrade but first loading version already passed' => ['3.1.0', '3.2.0', '3.0.0', false],
'Reinstall same version does not cross' => ['3.1.0', '3.1.0', '3.0.0', false],
'Downgrade does not cross' => ['3.2.0', '3.1.0', '3.0.0', false],
'First install with suffixed current version' => ['', '3.2-dev', '3.0.0', true],
'Upgrade with suffixed current version crosses' => ['1.0.3-2', '1.2.4-1', '1.1.0', true],
'Upgrade with suffixed current version below threshold does not cross' => ['1.0.0', '1.2.0-beta', '1.2.0', false],
];
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
*/
public function testLoadLocalizedData_LoadsWhenVersionCrossingIsTrue(): void
{
$this->markTestSkipped('Test skipped because failing in CI. Investigation required, cf Bug N°9787');
[$oConfig, $sPattern] = $this->GivenLocalizedDataTestContext('FR FR', 'data.sample.organizations.en_us.xml');
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '3.0.0', '3.2.0', '3.1.0', $sPattern);
$this->AssertOrganizationNamesExist(['Client (Test)', 'Département informatique (Test)']);
}
/**
* Test that loading a file twice (because of the version conditions) does not create duplicates (idempotent loading).
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
*/
public function testLoadLocalizedData_IdempotentLoading(): void
{
// Given
[$oConfig, $sPattern] = $this->GivenLocalizedDataTestContext('en_us', 'data.sample.organizations.en_us.xml');
// When LoadLocalizedData is called twice with conditions that would load the file both times
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.0.1', '3.0.0', $sPattern);
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '3.0.1', '3.1.0', '3.0.2', $sPattern);
// Then no duplicated data load side effect and values remain stable
foreach (['Customer (Test)', 'IT Department (Test)'] as $sExpectedName) {
$oSet = new \DBObjectSet(\DBSearch::FromOQL("SELECT Organization WHERE name = :org_name"), [], ['org_name' => $sExpectedName]);
$this->assertSame(1, $oSet->Count(), "Expected exactly one Organization named '{$sExpectedName}' after loading twice (idempotency)");
}
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
*/
public function testLoadLocalizedData_LoadsRequestedNonEnUsFileWhenItExists(): void
{
[$oConfig, $sPattern] = $this->GivenLocalizedDataTestContext('en_us', 'data.sample.organizations.fr_fr.xml');
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.3.0', '3.0.0', $sPattern);
$this->AssertOrganizationNamesExist(['Client (Test)', 'Département informatique (Test)']);
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
*/
public function testLoadLocalizedData_ThrowsWhenRequestedEnUsFileDoesNotExist(): void
{
[$oConfig, $sPattern] = $this->GivenLocalizedDataTestContext('en_us', 'data.sample.missing-file.en_us.xml', false);
$this->expectException(\Exception::class);
$this->expectExceptionMessage("File $sPattern not found");
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, '', '3.3.0', '3.0.0', $sPattern);
}
/**
* @covers \ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion
* @dataProvider LoadLocalizedData_InvalidParametersProvider
*/
public function testLoadLocalizedData_ThrowsOnInvalidParameters(string $sPreviousVersion, string $sCurrentVersion, string $sFirstLoadingVersion, string $sPattern, string $sExpectedMessage): void
{
$oConfig = MetaModel::GetConfig();
$this->assertNotNull($oConfig);
$this->expectException(\CoreUnexpectedValue::class);
$this->expectExceptionMessage($sExpectedMessage);
ModuleInstallerAPI::LoadLocalizedDataOnCrossingVersion($oConfig, $sPreviousVersion, $sCurrentVersion, $sFirstLoadingVersion, $sPattern);
}
public function LoadLocalizedData_InvalidParametersProvider(): array
{
$sTmpDir = static::CreateTmpdir();
@@ -364,93 +426,59 @@ SQL
'previous' => 'v3.2',
'current' => '3.2.0',
'first' => '3.0.0',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.en_us.xml',
'message' => 'sPreviousVersion',
],
'Invalid current version format' => [
'previous' => '',
'current' => '3',
'first' => '3.0.0',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.en_us.xml',
'message' => 'sCurrentVersion',
],
'Invalid first loading version format' => [
'previous' => '',
'current' => '3.2.0',
'first' => '3.0.0-beta.1',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.en_us.xml',
'message' => 'sFirstLoadingVersion',
],
'Missing strict placeholder' => [
'previous' => '',
'current' => '3.2.0',
'first' => '3.0.0',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'data.{{LANGUAGE_CODE}}.xml',
'message' => "{{language_code}}",
],
'Parent directory does not exist' => [
'previous' => '',
'current' => '3.2.0',
'first' => '3.0.0',
'pattern' => $sTmpDir.DIRECTORY_SEPARATOR.'missing'.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml',
'message' => 'parent directory',
],
];
}
/**
* Prepare common context for LoadLocalizedData tests.
*
* @return array{0: Config, 1: string, 2: string, 3: string, 4: string}
* @return array{0: Config, 1: string}
*/
private function GivenLocalizedDataTestContext(string $sOrgNamePrefix, string $sLanguage): array
private function GivenLocalizedDataTestContext(string $sLanguage, string $sDataFileName = 'data.sample.organizations.en_us.xml', bool $bAssertFileExists = true): array
{
$oConfig = MetaModel::GetConfig();
$oConfig->SetDefaultLanguage($sLanguage);
$this->assertNotNull($oConfig);
$sOrgName = $sOrgNamePrefix.uniqid();
$sPattern = __DIR__.DIRECTORY_SEPARATOR.'resources2'.DIRECTORY_SEPARATOR.'data'.DIRECTORY_SEPARATOR.'load-localized-data'.DIRECTORY_SEPARATOR.$sDataFileName;
if ($bAssertFileExists) {
$this->assertFileExists($sPattern);
}
$sTmpDir = static::CreateTmpdir();
$this->aFileToClean[] = $sTmpDir;
$sPattern = $sTmpDir.DIRECTORY_SEPARATOR.'data.{{language_code}}.xml';
return [$oConfig, $sOrgName, $sTmpDir, $sPattern];
return [$oConfig, $sPattern];
}
private function GivenLocalizedDataFile(string $sDir, string $sLang, string $sOrgName): string
private function AssertOrganizationNamesExist(array $aExpectedOrganizationNames): void
{
$sFilePath = $sDir.DIRECTORY_SEPARATOR.'data.'.$sLang.'.xml';
file_put_contents($sFilePath, $this->BuildOrganizationXml($sOrgName, $sLang));
return $sFilePath;
foreach ($aExpectedOrganizationNames as $sExpectedName) {
$this->AssertOrganizationNameExists($sExpectedName);
}
}
private function BuildOrganizationXml(string $sOrgName, string $sLang): string
{
$iId = random_int(100000, 999999);
$sOrgNameXml = htmlspecialchars($sOrgName, ENT_XML1);
return <<<XML
<?xml version="1.0" encoding="UTF-8"?>
<Set>
<Organization alias="Organization" id="{$iId}">
<name>{$sOrgNameXml}</name>
<code>{$sLang}</code>
<status>active</status>
</Organization>
</Set>
XML;
}
private function AssertOrganizationCountByName(string $sOrgName, string $sLanguage, int $iExpectedCount): void
private function AssertOrganizationNameExists(string $sExpectedName): void
{
$oSet = new \DBObjectSet(
\DBSearch::FromOQL("SELECT Organization WHERE name = :org_name AND code = :language"),
\DBSearch::FromOQL("SELECT Organization WHERE name = :org_name"),
[],
['org_name' => $sOrgName, 'language' => $sLanguage]
['org_name' => $sExpectedName]
);
$iCount = $oSet->Count();
$this->assertEquals($iExpectedCount, $iCount, "Found $iCount changes for objects with name '{$sOrgName}' and language '{$sLanguage}', expected {$iExpectedCount}");
$this->assertEquals(1, $oSet->Count(), "Expected exactly one Organization named '{$sExpectedName}'");
}
}

View File

@@ -49,6 +49,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => false,
'checked' => false,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A selected but not installed extension should be checked and enabled' => [
@@ -70,6 +72,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => false,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A missing extension should be disabled and unchecked' => [
@@ -89,6 +93,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => true,
'checked' => false,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A missing extension should always be disabled and unchecked, even when mandatory' => [
@@ -108,6 +114,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => true,
'checked' => false,
'dependency_issue' => false,
'mandatory' => true,
],
],
'A missing extension should always be disabled and unchecked, even when non-uninstallable' => [
@@ -127,6 +135,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => true,
'checked' => false,
'dependency_issue' => false,
'mandatory' => true,
],
],
'An installed but not selected extension should not be checked and be enabled' => [
@@ -148,6 +158,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => false,
'checked' => false,
'dependency_issue' => false,
'mandatory' => false,
],
],
'An installed non uninstallable extension should be checked and disabled' => [
@@ -169,6 +181,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
],
],
'An installed non uninstallable extension should be enabled if the "disable uninstallation check" flag is set' => [
@@ -190,6 +204,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => false,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A mandatory extension should be checked and disabled' => [
@@ -211,6 +227,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => true,
],
],
'A mandatory extension should be checked and disabled even if the "disable uninstallation check" flag is set' => [
@@ -232,6 +250,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => true,
],
],
'An optional sub extension should not force its parent flags' => [
@@ -265,6 +285,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => false,
'checked' => false,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A mandatory sub extension should force its parent to be checked and disabled' => [
@@ -298,6 +320,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => false,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
],
],
'An installed non uninstallable sub extension should force its parent to be checked and disabled' => [
@@ -331,6 +355,8 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => true,
'checked' => true,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A non installed non uninstallable sub extension should not force its parent flags' => [
@@ -364,6 +390,62 @@ class WizStepModulesChoiceTest extends ItopTestCase
'installed' => true,
'disabled' => false,
'checked' => false,
'dependency_issue' => false,
'mandatory' => false,
],
],
'A non installed extension with missing dependencies should be not checked and disabled' => [
'aExtensionsOnDiskOrDb' => [
'itop-ext1' => [
'installed' => false,
'missing_dependencies' => [
'itop-ext1-1',
],
],
],
'aWizardStepDefinition' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
'missing_dependencies' => true,
],
'bCurrentSelected' => false,
'bDisableUninstallChecks' => false,
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => false,
'disabled' => true,
'checked' => false,
'dependency_issue' => true,
'mandatory' => false,
],
],
'An installed extension with missing dependencies should be not checked and disabled' => [
'aExtensionsOnDiskOrDb' => [
'itop-ext1' => [
'installed' => true,
'missing_dependencies' => [
'itop-ext1-1',
],
],
],
'aWizardStepDefinition' => [
'extension_code' => 'itop-ext1',
'mandatory' => false,
'uninstallable' => true,
'missing_dependencies' => true,
],
'bCurrentSelected' => false,
'bDisableUninstallChecks' => false,
'aExpectedFlags' => [
'uninstallable' => true,
'missing' => false,
'installed' => true,
'disabled' => true,
'checked' => false,
'dependency_issue' => true,
'mandatory' => false,
],
],
];

View File

@@ -79,6 +79,7 @@ class SetupAuditTest extends ItopCustomDatamodelTestCase
"Feature1Module1MyClass",
"FinalClassFeature2Module1MyClass",
"FinalClassFeature2Module1MyFinalClassFromLocation",
"TagSetFieldDataFor_FinalClassFeature2Module1MyFinalClassFromLocation__domains",
];
$this->assertEqualsCanonicalizing($expected, $oSetupAudit->GetRemovedClasses());

View File

@@ -64,6 +64,12 @@
<is_null_allowed>false</is_null_allowed>
<validation_pattern/>
</field>
<field id="domains" xsi:type="AttributeTagSet">
<sql>domains</sql>
<is_null_allowed>true</is_null_allowed>
<tracking_level>all</tracking_level>
<max_items>12</max_items>
</field>
</fields>
<methods/>
<presentation/>

View File

@@ -31,6 +31,12 @@
<default_value/>
<is_null_allowed>false</is_null_allowed>
</field>
<field id="domains" xsi:type="AttributeTagSet">
<sql>domains</sql>
<is_null_allowed>true</is_null_allowed>
<tracking_level>all</tracking_level>
<max_items>12</max_items>
</field>
</fields>
<methods/>
<presentation/>

View File

@@ -23,6 +23,7 @@ class iTopExtensionsMapFake extends iTopExtensionsMap
$oExtension->sVersion = $aExtension['version'] ?? '1.0.0';
$oExtension->sSource = $aExtension['source'] ?? iTopExtension::SOURCE_MANUAL;
$oExtension->aModuleInfo = $aExtension['module_info'] ?? [];
$oExtension->aMissingDependencies = $aExtension['missing_dependencies'] ?? [];
$oMap->AddExtension($oExtension);
}
return $oMap;

Some files were not shown because too many files have changed in this diff Show More