mirror of
https://github.com/Combodo/iTop.git
synced 2026-07-01 20:26:38 +02:00
Compare commits
18 Commits
issue/9746
...
feature/96
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
588d50c86b | ||
|
|
8c80d3a616 | ||
|
|
31581f8bd6 | ||
|
|
7ddc090514 | ||
|
|
3da34dabd8 | ||
|
|
fe32ebcbca | ||
|
|
a1dcd0300c | ||
|
|
b73960a571 | ||
|
|
357ae223fd | ||
|
|
e5e91e8135 | ||
|
|
87dcbf7b53 | ||
|
|
51d5692f81 | ||
|
|
dc3f6a9f72 | ||
|
|
4d2da15c96 | ||
|
|
63879a63cc | ||
|
|
bcb42fdc81 | ||
|
|
fc9df64eea | ||
|
|
41d4f04375 |
@@ -916,7 +916,9 @@ class CMDBSource
|
||||
$aColumn = [];
|
||||
$aData = self::QueryToArray($sSql);
|
||||
foreach ($aData as $aRow) {
|
||||
@$aColumn[] = $aRow[$col];
|
||||
if ($aRow[$col] !== null) {
|
||||
$aColumn[] = $aRow[$col];
|
||||
}
|
||||
}
|
||||
return $aColumn;
|
||||
}
|
||||
|
||||
@@ -18,6 +18,7 @@ use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||
use ContextTag;
|
||||
@@ -222,6 +223,7 @@ class DataFeatureRemovalController extends Controller
|
||||
$bIsDirEmpty = count(scandir($sBuildDir)) === 2;
|
||||
|
||||
if ($bIsDirEmpty || $bForceCompilation) {
|
||||
Session::Unset('bForceCompilation');
|
||||
DataFeatureRemovalLog::Debug(
|
||||
__METHOD__,
|
||||
null,
|
||||
@@ -272,7 +274,7 @@ class DataFeatureRemovalController extends Controller
|
||||
private function GetDeletionPlanSummaryTable(array $aRemovedClasses): array
|
||||
{
|
||||
$sName = 'DeletionPlanSummary';
|
||||
$oDataCleanupService = new DataCleanupService();
|
||||
$oDataCleanupService = new StaticDeletionPlan();
|
||||
$aDeletionPlanSummaryEntities = $oDataCleanupService->GetCleanupSummary($aRemovedClasses);
|
||||
$aColumns = ['Class', 'Delete Count' , 'Update Count', 'Issue Count'];
|
||||
$aRows = [];
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
||||
|
||||
use MetaModel;
|
||||
|
||||
class DeletionPlanEntity
|
||||
{
|
||||
public readonly DeletionPlanItem $oDelete;
|
||||
public readonly DeletionPlanItem $oUpdate;
|
||||
public readonly DeletionPlanItem $oIssue;
|
||||
|
||||
public array $aQueriesForTempTable;
|
||||
public array $aDependsOnTempTable;
|
||||
public string $sClass;
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oDelete
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oUpdate
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oIssue
|
||||
*/
|
||||
public function __construct(string $sClass)
|
||||
{
|
||||
$this->sClass = $sClass;
|
||||
$this->oDelete = $oDelete ?? new DeletionPlanItem();
|
||||
$this->oUpdate = $oUpdate ?? new DeletionPlanItem();
|
||||
$this->oIssue = $oIssue ?? new DeletionPlanItem();
|
||||
$this->aQueriesForTempTable = [];
|
||||
$this->aDependsOnTempTable = [];
|
||||
}
|
||||
|
||||
public function TotalCount(): int
|
||||
{
|
||||
return $this->oDelete->Count() + $this->oUpdate->Count() + $this->oIssue->Count();
|
||||
}
|
||||
|
||||
public function FilterUpdatesByDeletes()
|
||||
{
|
||||
$this->oUpdate->FilterBy($this->oDelete);
|
||||
}
|
||||
|
||||
public function AddQueryForTempTable(string $sQuery, ?string $sTempTableName = null): void
|
||||
{
|
||||
if (!in_array($sQuery, $this->aQueriesForTempTable)) {
|
||||
$this->aQueriesForTempTable[] = $sQuery;
|
||||
if (!is_null($sTempTableName) && !in_array($sTempTableName, $this->aDependsOnTempTable)) {
|
||||
$this->aDependsOnTempTable[] = $sTempTableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetTempTableName(string $sClass): string
|
||||
{
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
return 'temp_'.MetaModel::DBGetTable($sRootClass);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
||||
|
||||
class DeletionPlanItem
|
||||
{
|
||||
public array $aIds = [];
|
||||
|
||||
/**
|
||||
* @param array $aIds
|
||||
*/
|
||||
public function __construct(array $aIds = [])
|
||||
{
|
||||
$this->aIds = $aIds;
|
||||
}
|
||||
|
||||
public function Merge(DeletionPlanItem $oItem): void
|
||||
{
|
||||
$this->aIds = array_unique(array_merge($this->aIds, $oItem->aIds));
|
||||
}
|
||||
|
||||
public function Count(): int
|
||||
{
|
||||
return count($this->aIds);
|
||||
}
|
||||
|
||||
public function FilterBy(DeletionPlanItem $oItem): void
|
||||
{
|
||||
$this->aIds = array_diff($this->aIds, $oItem->aIds);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,319 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use AttributeExternalKey;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem;
|
||||
use MetaModel;
|
||||
|
||||
class StaticDeletionPlan
|
||||
{
|
||||
/** @var array<DeletionPlanEntity> */
|
||||
private array $aDeletionPlan = [];
|
||||
|
||||
/**
|
||||
* Get a summary of the deletion plan computed for the classes.
|
||||
* The result is used for display
|
||||
*
|
||||
* @param array|null $aClasses
|
||||
*
|
||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity>
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function GetCleanupSummary(?array $aClasses): array
|
||||
{
|
||||
$aSummary = [];
|
||||
$aDeletionPlan = $this->GetStaticDeletionPlan($aClasses ?? []);
|
||||
|
||||
foreach ($aDeletionPlan as $sClass => $oDeletionPlanEntity) {
|
||||
if ($oDeletionPlanEntity->TotalCount() === 0) {
|
||||
continue;
|
||||
}
|
||||
$oDeletionPlanEntity->FilterUpdatesByDeletes();
|
||||
$oDataCleanupSummary = new DataCleanupSummaryEntity($sClass);
|
||||
$oDataCleanupSummary->iUpdateCount = $oDeletionPlanEntity->oUpdate->Count();
|
||||
$oDataCleanupSummary->iDeleteCount = $oDeletionPlanEntity->oDelete->Count();
|
||||
$oDataCleanupSummary->iIssueCount = $oDeletionPlanEntity->oIssue->Count();
|
||||
|
||||
$aSummary[$sClass] = $oDataCleanupSummary;
|
||||
}
|
||||
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
public function GetTempTableDefinitions(array $aClasses): array
|
||||
{
|
||||
$aTempTables = [];
|
||||
$aDeletionPlan = $this->GetStaticDeletionPlan($aClasses ?? []);
|
||||
|
||||
foreach ($aDeletionPlan as $sClass => $oDeletionPlanEntity) {
|
||||
$sTempTableName = DeletionPlanEntity::GetTempTableName($sClass);
|
||||
$aTempTables[$sTempTableName]['name'] = $sTempTableName;
|
||||
$aTempTables[$sTempTableName]['queries'] = array_unique(array_merge($aTempTables[$sTempTableName]['queries'] ?? [], $oDeletionPlanEntity->aQueriesForTempTable));
|
||||
$aTempTables[$sTempTableName]['depends_on'] = array_unique(array_merge($aTempTables[$sTempTableName]['depends_on'] ?? [], $oDeletionPlanEntity->aDependsOnTempTable));
|
||||
}
|
||||
|
||||
usort($aTempTables, function ($a, $b) use ($aTempTables) {
|
||||
if (empty($a['depends_on']) && empty($b['depends_on'])) {
|
||||
// Both initial classes
|
||||
return 0;
|
||||
}
|
||||
return $this->CompareEntries($a, $b, $aTempTables);
|
||||
});
|
||||
|
||||
$aTableDefinitions = [];
|
||||
|
||||
foreach ($aTempTables as $aTempTable) {
|
||||
if (count($aTempTable['queries']) > 0) {
|
||||
$TempTableSelect = implode("\nUNION\n", $aTempTable['queries']);
|
||||
$sTempTableName = $aTempTable['name'];
|
||||
$aTableDefinitions[$sTempTableName] = [
|
||||
"DROP TEMPORARY TABLE IF EXISTS `$sTempTableName`",
|
||||
"CREATE TEMPORARY TABLE `$sTempTableName` ($TempTableSelect)",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aTableDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* tells if name1 depends on name2
|
||||
* @param string $sName1
|
||||
* @param string $sName2
|
||||
* @param array $aAllTables
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function DependsOn(string $sName1, string $sName2, array $aAllTables): bool
|
||||
{
|
||||
$aDependsOn = $aAllTables[$sName1]['depends_on'] ?? [];
|
||||
if (in_array($sName2, $aDependsOn)) {
|
||||
return true;
|
||||
}
|
||||
// Search on step further
|
||||
foreach ($aAllTables[$sName1]['depends_on'] as $sParent) {
|
||||
if ($this->DependsOn($sParent, $sName2, $aAllTables)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function CompareEntries(array $aTable1, array $aTable2, array $aAllTables): int
|
||||
{
|
||||
return $this->DependsOn($aTable1['name'], $aTable2['name'], $aAllTables) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aClasses Classes to clean entirely
|
||||
*
|
||||
* @return array ['class' => DeletionPlanEntity];
|
||||
*
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function GetStaticDeletionPlan(array $aClasses): array
|
||||
{
|
||||
foreach ($aClasses as $sClass) {
|
||||
$oDeletionPlanItem = $this->GetInitialClassDeletionPlan($sClass);
|
||||
$oDeletionPlanEntity = new DeletionPlanEntity($sClass);
|
||||
$oDeletionPlanEntity->oDelete->Merge($oDeletionPlanItem);
|
||||
$oDeletionPlanEntity->AddQueryForTempTable($this->GetQueryForInitialClass($sClass));
|
||||
$this->aDeletionPlan[$sClass] = $oDeletionPlanEntity;
|
||||
|
||||
$this->DeletionPlanForReferencingClasses($sClass);
|
||||
}
|
||||
|
||||
return $this->aDeletionPlan;
|
||||
}
|
||||
|
||||
private function DeletionPlanForReferencingClasses(string $sClass): void
|
||||
{
|
||||
$sIdsToRemove = implode(', ', $this->aDeletionPlan[$sClass]->oDelete->aIds);
|
||||
$aReferencingMe = MetaModel::EnumReferencingClasses($sClass);
|
||||
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
|
||||
if (!isset($this->aDeletionPlan[$sRemoteClass])) {
|
||||
$this->aDeletionPlan[$sRemoteClass] = new DeletionPlanEntity($sRemoteClass);
|
||||
}
|
||||
$oDeletionPlanEntity = $this->aDeletionPlan[$sRemoteClass];
|
||||
/** @var \AttributeExternalKey $oExtKeyAttDef */
|
||||
foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) {
|
||||
// skip if this external key is behind an external field
|
||||
if (!$oExtKeyAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($oExtKeyAttDef->IsNullAllowed()) {
|
||||
// update
|
||||
$oUpdateItem = $this->UpdateExtKeyNullable($sRemoteClass, $sExtKeyAttCode, $sIdsToRemove);
|
||||
$oDeletionPlanEntity->oUpdate->Merge($oUpdateItem);
|
||||
} else {
|
||||
// delete
|
||||
$aRemoteIdsToRemove = $this->GetRemoteIdsForExtKey($sRemoteClass, $sExtKeyAttCode, $sIdsToRemove);
|
||||
|
||||
$iDeletePropagationOption = $oExtKeyAttDef->GetDeletionPropagationOption();
|
||||
if ($iDeletePropagationOption == DEL_MANUAL) {
|
||||
// Issue, do not recurse
|
||||
$oDeletionPlanItem = new DeletionPlanItem($aRemoteIdsToRemove);
|
||||
$oDeletionPlanEntity->oIssue->Merge($oDeletionPlanItem);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (($iDeletePropagationOption == DEL_MOVEUP) && ($oExtKeyAttDef->IsHierarchicalKey())) {
|
||||
// update hierarchical keys due to row cleanup in the same table
|
||||
$sTargetIdsToRemove = implode(',', $this->aDeletionPlan[$sRemoteClass]->oDelete->aIds);
|
||||
$oUpdateItem = $this->UpdateHierarchicalExtKey($sRemoteClass, $sExtKeyAttCode, $sTargetIdsToRemove);
|
||||
$oDeletionPlanEntity->oUpdate->Merge($oUpdateItem);
|
||||
|
||||
// Delete current entry an recurse !
|
||||
}
|
||||
|
||||
// Delete entries in Remote Class
|
||||
[$sQueryForTempTable, $sDependsOnTempTable] = $this->GetQueryForExtKey($sRemoteClass, $oExtKeyAttDef);
|
||||
$oDeletionPlanEntity->AddQueryForTempTable($sQueryForTempTable, $sDependsOnTempTable);
|
||||
$oDeletionPlanEntity->oDelete->Merge(new DeletionPlanItem($aRemoteIdsToRemove));
|
||||
// Infinite loops do not occurs due to the datamodel structure
|
||||
$this->DeletionPlanForReferencingClasses($sRemoteClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sRemoteClass
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param string $sIdsToRemoveInTargetClass
|
||||
*
|
||||
* @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem
|
||||
* @throws \CoreException
|
||||
*/
|
||||
public function UpdateExtKeyNullable(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): DeletionPlanItem
|
||||
{
|
||||
$aIds = $this->GetRemoteIdsForExtKey($sRemoteClass, $sExtKeyAttCode, $sIdsToRemoveInTargetClass);
|
||||
|
||||
return new DeletionPlanItem($aIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sRemoteClass
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param string $sIdsToRemoveInTargetClass
|
||||
*
|
||||
* @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function UpdateHierarchicalExtKey(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): DeletionPlanItem
|
||||
{
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sRemoteClass, $sExtKeyAttCode);
|
||||
|
||||
$sSQL = <<<SQL
|
||||
SELECT `$sDBKey`
|
||||
FROM `$sDBTable` AS `updated`
|
||||
INNER JOIN `$sDBTable` AS `removed` ON `updated`.`$sDBField` = `removed`.`$sDBField`
|
||||
WHERE `removed`.`$sDBKey` IN ($sIdsToRemoveInTargetClass)
|
||||
SQL;
|
||||
$aIds = CMDBSource::QueryToCol($sSQL, $sDBKey);
|
||||
|
||||
return new DeletionPlanItem($aIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sRemoteClass
|
||||
* @param string $sExtKeyAttCode
|
||||
* @param string $sIdsToRemoveInTargetClass
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function GetRemoteIdsForExtKey(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): array
|
||||
{
|
||||
if (\utils::IsNullOrEmptyString($sIdsToRemoveInTargetClass)) {
|
||||
return [];
|
||||
}
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sRemoteClass, $sExtKeyAttCode);
|
||||
$sSQL = "SELECT `$sDBKey` AS id FROM `$sDBTable` WHERE `$sDBField` IN ($sIdsToRemoveInTargetClass)";
|
||||
|
||||
return CMDBSource::QueryToCol($sSQL, 'id');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
*
|
||||
* @return \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem
|
||||
* @throws \CoreException
|
||||
* @throws \MySQLException
|
||||
*/
|
||||
public function GetInitialClassDeletionPlan(string $sClass): DeletionPlanItem
|
||||
{
|
||||
[$sDBTable, $sDBKey] = $this->GetDBInfo($sClass);
|
||||
|
||||
$sSQL = "SELECT `$sDBKey` FROM `$sDBTable`";
|
||||
$aIds = CMDBSource::QueryToCol($sSQL, $sDBKey);
|
||||
|
||||
return new DeletionPlanItem($aIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get database table for an attcode
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sExtKeyAttCode
|
||||
*
|
||||
* @return array
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetDBInfo(string $sClass, ?string $sExtKeyAttCode = null): array
|
||||
{
|
||||
if (!is_null($sExtKeyAttCode)) {
|
||||
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode);
|
||||
|
||||
$sDBTable = MetaModel::DBGetTable($sOriginClass);
|
||||
$sDBKey = MetaModel::DBGetKey($sOriginClass);
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sExtKeyAttCode);
|
||||
// External key is on a single DB column
|
||||
$sDBField = array_keys($oAttDef->GetSQLColumns())[0];
|
||||
|
||||
return [$sDBTable, $sDBKey, $sDBField];
|
||||
}
|
||||
|
||||
$sDBTable = MetaModel::DBGetTable($sClass);
|
||||
$sDBKey = MetaModel::DBGetKey($sClass);
|
||||
|
||||
return [$sDBTable, $sDBKey];
|
||||
}
|
||||
|
||||
private function GetQueryForInitialClass(mixed $sClass): string
|
||||
{
|
||||
[$sDBTable, $sDBKey] = $this->GetDBInfo($sClass);
|
||||
|
||||
return "SELECT `$sDBKey` AS id FROM `$sDBTable`";
|
||||
}
|
||||
|
||||
private function GetQueryForExtKey(string $sClass, AttributeExternalKey $oExtKeyAttDef)
|
||||
{
|
||||
$sExtKeyAttCode = $oExtKeyAttDef->GetCode();
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sClass, $sExtKeyAttCode);
|
||||
|
||||
$sTargetClass = $oExtKeyAttDef->GetTargetClass();
|
||||
$sTempTable = DeletionPlanEntity::GetTempTableName($sTargetClass);
|
||||
|
||||
$sQuery = <<<SQL
|
||||
SELECT `$sDBTable`.`$sDBKey` AS id FROM `$sDBTable`
|
||||
INNER JOIN `$sTempTable` ON `$sTempTable`.`id` = `$sDBTable`.`$sDBField`
|
||||
SQL;
|
||||
|
||||
return [$sQuery, $sTempTable];
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,8 @@ $baseDir = dirname($vendorDir);
|
||||
return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => $baseDir . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => $baseDir . '/src/Entity/DeletionPlanEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => $baseDir . '/src/Entity/DeletionPlanItem.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => $baseDir . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
@@ -16,6 +18,7 @@ return array(
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => $baseDir . '/src/Service/ObjectService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectServiceSummary' => $baseDir . '/src/Service/ObjectServiceSummary.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\StaticDeletionPlan' => $baseDir . '/src/Service/StaticDeletionPlan.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\iObjectService' => $baseDir . '/src/Service/iObjectService.php',
|
||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
@@ -23,6 +23,8 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
public static $classMap = array (
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanEntity.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanItem' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanItem.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalConfig.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||
@@ -31,6 +33,7 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectService' => __DIR__ . '/../..' . '/src/Service/ObjectService.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\ObjectServiceSummary' => __DIR__ . '/../..' . '/src/Service/ObjectServiceSummary.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\StaticDeletionPlan' => __DIR__ . '/../..' . '/src/Service/StaticDeletionPlan.php',
|
||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\iObjectService' => __DIR__ . '/../..' . '/src/Service/iObjectService.php',
|
||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||
);
|
||||
|
||||
@@ -242,8 +242,7 @@ class AjaxController extends Controller
|
||||
throw new SecurityException('Access forbidden');
|
||||
}
|
||||
|
||||
$sConfigFile = APPCONF.'production/config-itop.php';
|
||||
@chmod($sConfigFile, 0770); // Allow overwriting the file
|
||||
SetupUtils::CreateSetupToken();
|
||||
|
||||
header('Location: ../setup/');
|
||||
}
|
||||
|
||||
@@ -166,8 +166,6 @@ class UpdateController extends Controller
|
||||
public function OperationRunSetup()
|
||||
{
|
||||
SetupUtils::CheckSetupToken(true);
|
||||
$sConfigFile = APPCONF.'production/'.ITOP_CONFIG_FILE;
|
||||
@chmod($sConfigFile, 0770);
|
||||
$sRedirectURL = utils::GetAbsoluteUrlAppRoot().'setup/index.php';
|
||||
header("Location: $sRedirectURL");
|
||||
}
|
||||
|
||||
@@ -1344,7 +1344,7 @@ class RunTimeEnvironment
|
||||
$iCount = $oSetupAudit->GetDataToCleanupCount();
|
||||
|
||||
if ($iCount > 0) {
|
||||
throw new Exception("$iCount elements require data adjustments or cleanup in the backoffice", DataAuditSequencer::DATA_AUDIT_FAILED);
|
||||
throw new Exception("Elements require data adjustments or cleanup in the backoffice", DataAuditSequencer::DATA_AUDIT_FAILED);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
</selected_modules>
|
||||
<selected_extensions type="array">
|
||||
</selected_extensions>
|
||||
<use_symbolic_links>off</use_symbolic_links>
|
||||
<use_symbolic_links>on</use_symbolic_links>
|
||||
</installation>
|
||||
|
||||
@@ -74,7 +74,6 @@ if (SetupUtils::IsSessionSetupTokenValid()) {
|
||||
// The configuration file already exists
|
||||
if (!is_writable($sConfigFile)) {
|
||||
SetupUtils::ExitReadOnlyMode(false); // Reset readonly mode in case of problem
|
||||
SetupUtils::EraseSetupToken();
|
||||
$sRelativePath = utils::GetConfigFilePathRelative(ITOP_DEFAULT_ENV);
|
||||
$oP = new SetupPage('Installation Cannot Continue');
|
||||
$oP->add("<h2>Fatal error</h2>\n");
|
||||
@@ -87,7 +86,6 @@ HTML;
|
||||
$oP->p($sButtonsHtml);
|
||||
|
||||
$oP->output();
|
||||
// Prevent token creation
|
||||
exit;
|
||||
} else {
|
||||
chmod($sConfigFile, 0440);
|
||||
|
||||
@@ -196,7 +196,6 @@ class WizardController
|
||||
SetupLog::Info("=== Setup screen: ".$oStep->GetTitle().' ('.get_class($oStep).')');
|
||||
$oPage = new SetupPage($oStep->GetTitle());
|
||||
$oPage->LinkScriptFromAppRoot('setup/setup.js');
|
||||
$oStep->PreFormDisplay($oPage);
|
||||
|
||||
$oPage->add('<form id="wiz_form" class="ibo-setup--wizard" method="post">');
|
||||
$oPage->add('<div class="ibo-setup--wizard--content">');
|
||||
|
||||
@@ -124,18 +124,18 @@ HTML
|
||||
if (file_exists($sBuildConfigFile)) {
|
||||
$oPage->add(
|
||||
<<<HTML
|
||||
<form method="post">
|
||||
<form id="fast_setup" method="post">
|
||||
<input type="hidden" name="_class" value="WizStepLandingBeforeAudit"/>
|
||||
<input type="hidden" name="operation" value="next"/>
|
||||
<input type="hidden" name="_params[skip_wizard]" value="1"/>
|
||||
<table style="width:100%;" class="ibo-setup--wizard--buttons-container">
|
||||
<tr>
|
||||
<td style="text-align: right"><button type="submit" class="ibo-button ibo-is-regular ibo-is-secondary">Keep current choices</button></td>
|
||||
</tr>
|
||||
</table>
|
||||
</form>
|
||||
HTML
|
||||
);
|
||||
$oPage->add_ready_script(
|
||||
<<<JS
|
||||
$('.ibo-setup--wizard--buttons-container tr td:nth-child(1)').before('<td style="text-align:center;"><button class="ibo-button ibo-is-alternative ibo-is-neutral" form="fast_setup"><span class="ibo-button--label">Keep current choices</span></button></td>');
|
||||
JS
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,10 +76,6 @@ abstract class WizardStep
|
||||
{
|
||||
}
|
||||
|
||||
public function PreFormDisplay(SetupPage $oPage)
|
||||
{
|
||||
}
|
||||
|
||||
protected function CheckDependencies()
|
||||
{
|
||||
if (is_null($this->bDependencyCheck)) {
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||
|
||||
class AbstractCleanup extends ItopCustomDatamodelTestCase
|
||||
{
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/data_cleanup_delta.xml';
|
||||
}
|
||||
|
||||
protected array $aIdByClass;
|
||||
protected array $aIdByObjectName = [];
|
||||
|
||||
protected function GivenDFRTreeInDB(string $sTree)
|
||||
{
|
||||
$this->aIdByClass = [];
|
||||
$aTree = explode("\n", $sTree);
|
||||
foreach ($aTree as $sLine) {
|
||||
if (trim($sLine) === '') {
|
||||
continue;
|
||||
}
|
||||
$this->GivenDFRTreeLineInDB($sLine);
|
||||
}
|
||||
}
|
||||
|
||||
protected function GivenDFRTreeLineInDB(string $sLine)
|
||||
{
|
||||
[$sLeft, $sRight] = explode('<-', $sLine);
|
||||
$sLeft = trim($sLeft);
|
||||
|
||||
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
|
||||
if ($iLeftId === 0) {
|
||||
[$sChildClass] = explode('_', $sLeft, 2);
|
||||
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
|
||||
$this->aIdByClass[$sChildClass][] = $iLeftId;
|
||||
$this->aIdByObjectName[$sLeft] = $iLeftId;
|
||||
}
|
||||
|
||||
$sRight = trim($sRight);
|
||||
if (preg_match("/(?<name>(?<class>[^_]+)_\d+)(\s+\((?<extkey>\w+)\))?/", $sRight, $aMatches) !== false) {
|
||||
$sName = $aMatches['name'];
|
||||
$sChildClass = $aMatches['class'];
|
||||
$sExtKey = $aMatches['extkey'] ?? 'extkey_id';
|
||||
|
||||
$iRightId = $this->aIdByObjectName[$sName] ?? 0;
|
||||
if ($iRightId === 0) {
|
||||
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sName, $sExtKey => $iLeftId]);
|
||||
$this->aIdByClass[$sChildClass][] = $iRightId;
|
||||
$this->aIdByObjectName[$sName] = $iRightId;
|
||||
} else {
|
||||
// Update object
|
||||
$oObj = MetaModel::GetObject($sChildClass, $iRightId);
|
||||
$oObj->Set($sExtKey, $iLeftId);
|
||||
$oObj->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* format of object:
|
||||
* [create|update] CLASS (name = NAME, ...)
|
||||
*
|
||||
* @param string $sObjects
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
protected function GivenDFRObjectsInDB(string $sObjects)
|
||||
{
|
||||
$this->aIdByClass = [];
|
||||
$sObjects = explode("\n", $sObjects);
|
||||
foreach ($sObjects as $sLine) {
|
||||
$sLine = trim($sLine);
|
||||
if ($sLine === '') {
|
||||
continue;
|
||||
}
|
||||
$this->GivenDFRObjectLineInDB($sLine);
|
||||
}
|
||||
}
|
||||
|
||||
protected function GivenDFRObjectLineInDB(string $sLine)
|
||||
{
|
||||
if (preg_match("/(?<verb>\w+)\s+(?<class>\w+)\s*\((?<att_list>[^)]+)\)/", $sLine, $aMatches) !== false) {
|
||||
$sVerb = $aMatches['verb'];
|
||||
$sClass = $aMatches['class'];
|
||||
$sAttList = $aMatches['att_list'];
|
||||
|
||||
$aAttSet = explode(',', $sAttList);
|
||||
$aAttributes = [];
|
||||
foreach ($aAttSet as $sAtt) {
|
||||
[$sAttName, $sAttValue] = explode('=', $sAtt, 2);
|
||||
$sAttName = trim($sAttName);
|
||||
$sAttValue = trim($sAttValue);
|
||||
$aAttributes[$sAttName] = $sAttValue;
|
||||
}
|
||||
|
||||
$sName = $aAttributes['name'] ?? '';
|
||||
// Transform names in external keys into ids
|
||||
foreach ($aAttributes as $sAttName => $sAttValue) {
|
||||
if ($sAttName !== 'name') {
|
||||
$aAttributes[$sAttName] = $this->aIdByObjectName[$sAttValue] ?? $sAttValue;
|
||||
}
|
||||
}
|
||||
|
||||
switch ($sVerb) {
|
||||
case 'create':
|
||||
$sId = $this->GivenObjectInDB($sClass, $aAttributes);
|
||||
$this->aIdByClass[$sClass][] = $sId;
|
||||
$this->aIdByObjectName[$sName] = $sId;
|
||||
break;
|
||||
case 'update':
|
||||
$sId = $this->aIdByObjectName[$sName];
|
||||
$oObj = MetaModel::GetObject($sClass, $sId);
|
||||
foreach ($aAttributes as $sAttName => $sAttValue) {
|
||||
$oObj->Set($sAttName, $sAttValue);
|
||||
}
|
||||
$oObj->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -34,15 +34,10 @@ use PHPUnit\Framework\MockObject\MockObject;
|
||||
* @see DataCleanupSummaryEntity
|
||||
* @see ItopDataTestCase
|
||||
*/
|
||||
class DataCleanupServiceTest extends ItopCustomDatamodelTestCase
|
||||
class DataCleanupServiceTest extends \AbstractCleanup
|
||||
{
|
||||
private ExecutionLimits&MockObject $oExecutionLimits;
|
||||
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/data_cleanup_delta.xml';
|
||||
}
|
||||
|
||||
//--- GetCleanupSummary tests ---
|
||||
|
||||
/**
|
||||
@@ -261,36 +256,6 @@ class DataCleanupServiceTest extends ItopCustomDatamodelTestCase
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
private function GivenDFRTreeInDB(string $sTree)
|
||||
{
|
||||
$aTree = explode("\n", $sTree);
|
||||
foreach ($aTree as $sLine) {
|
||||
if (trim($sLine) === "") {
|
||||
continue;
|
||||
}
|
||||
$this->GivenDFRTreeLineInDB($sLine);
|
||||
}
|
||||
}
|
||||
|
||||
private array $aIdByObjectName = [];
|
||||
private function GivenDFRTreeLineInDB(string $sLine)
|
||||
{
|
||||
[$sLeft, $sRight] = explode('<-', $sLine);
|
||||
$sLeft = trim($sLeft);
|
||||
|
||||
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
|
||||
if ($iLeftId === 0) {
|
||||
[$sChildClass, ] = explode('_', $sLeft, 2);
|
||||
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
|
||||
$this->aIdByObjectName[$sLeft] = $iLeftId;
|
||||
}
|
||||
|
||||
$sRight = trim($sRight);
|
||||
[$sChildClass, ] = explode('_', $sRight, 2);
|
||||
$iRightId = $this->GivenObjectInDB($sChildClass, ['name' => $sRight, 'extkey_id' => $iLeftId]);
|
||||
$this->aIdByObjectName[$sRight] = $iRightId;
|
||||
}
|
||||
|
||||
private function GivenExecutionLimits(int $iStopAfterCallNumberReached): void
|
||||
{
|
||||
$matcher = $this->any();
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval;
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
use MetaModel;
|
||||
|
||||
class StaticDeletionPlanTest extends \AbstractCleanup
|
||||
{
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/data_cleanup_delta.xml';
|
||||
}
|
||||
|
||||
public function testGetInitialClassDeletionPlan()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_2
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_3
|
||||
EOF);
|
||||
|
||||
$oService = new StaticDeletionPlan();
|
||||
$oDeletionPlanItem = $oService->GetInitialClassDeletionPlan('DFRToRemoveLeaf');
|
||||
self::assertEquals(2, $oDeletionPlanItem->Count(), 'All entries of root table should be removed');
|
||||
self::assertEquals($this->aIdByClass['DFRToRemoveLeaf'], $oDeletionPlanItem->aIds, 'All the Ids found in root table should correspond to the one created');
|
||||
}
|
||||
|
||||
public function testUpdateExtKeyNullable()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_2
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_3
|
||||
DFRLeafNotToRemove_1 <- DFRToUpdate_4
|
||||
EOF);
|
||||
|
||||
// WHEN
|
||||
$oService = new StaticDeletionPlan();
|
||||
$sRemoteClass = 'DFRToUpdate';
|
||||
$oDeletionPlanItem = $oService->UpdateExtKeyNullable(
|
||||
$sRemoteClass,
|
||||
'extkey_id',
|
||||
implode(',', $this->aIdByClass['DFRToRemoveLeaf'])
|
||||
);
|
||||
|
||||
// THEN
|
||||
self::assertEquals(3, $oDeletionPlanItem->Count(), 'All entries of root table should be removed');
|
||||
$sIdsToRemoveInTargetClass = implode(',', $this->aIdByClass['DFRToRemoveLeaf']);
|
||||
$aExpectedIds = $oService->GetRemoteIdsForExtKey($sRemoteClass, 'extkey_id', $sIdsToRemoveInTargetClass);
|
||||
|
||||
self::assertEquals($aExpectedIds, $oDeletionPlanItem->aIds, 'All entries pointing on root class should be removed');
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that GetCleanupSummary returns an empty array when passed null as input.
|
||||
*/
|
||||
public function testGetCleanupSummaryReturnsEmptyArrayWhenNull(): void
|
||||
{
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aResult = $oService->GetStaticDeletionPlan([]);
|
||||
|
||||
$this->assertIsArray($aResult, 'Expected result to be an array when input is null.');
|
||||
$this->assertEmpty($aResult, 'Expected result to be empty array when input is null.');
|
||||
}
|
||||
|
||||
public function testGetStaticDeletionPlan_DeleteObjRecursively()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRToUpdate (name = DFRToUpdate_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateral (name = DFRRemovedCollateral_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_1, extkey_id = DFRRemovedCollateral_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_2, extkey_id = DFRRemovedCollateral_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
|
||||
self::assertArrayHasKey('DFRRemovedCollateralCascade', $aRes, 'The cleanup should descend to the cascaded classes');
|
||||
|
||||
// echo json_encode($this->aIdByClass, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
public function testGetStaticDeletionPlan_IssuesArePresent()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_2
|
||||
DFRToRemoveLeaf_1 <- DFRManual_1
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
|
||||
self::assertEquals(1, $aRes['DFRManual']->oIssue->Count(), 'Issue should be found because of DEL_MANUAL deletion policy');
|
||||
self::assertEquals($this->aIdByClass['DFRManual'], $aRes['DFRManual']->oIssue->aIds, 'Issue should be correspond to the entries created');
|
||||
}
|
||||
|
||||
public function testGetStaticDeletionPlan_UpdateMultipleExtKeys()
|
||||
{
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1 (extkey_id)
|
||||
DFRToRemoveLeaf_2 <- DFRToUpdate_2 (extkey2_id)
|
||||
DFRLeafNotToRemove_1 <- DFRToUpdate_3 (extkey_id)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
|
||||
self::assertArrayHasKey('DFRToUpdate', $aRes, 'Class to update should be targeted');
|
||||
self::assertEquals(2, $aRes['DFRToUpdate']->oUpdate->Count(), 'Update should be counted only for removed pointed classes');
|
||||
}
|
||||
|
||||
public function testGetCleanupSummaryWithIssues()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRToUpdate (name = DFRToUpdate_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateral (name = DFRRemovedCollateral_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_1, extkey_id = DFRRemovedCollateral_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_2, extkey_id = DFRRemovedCollateral_1)
|
||||
create DFRManual (name = DFRManual_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
|
||||
'DFRRemovedCollateral' => ['iDeleteCount' => 1],
|
||||
'DFRRemovedCollateralCascade' => ['iDeleteCount' => 2],
|
||||
'DFRToUpdate' => ['iUpdateCount' => 1],
|
||||
'DFRManual' => ['iIssueCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testCircularRefsShouldNotRunInfinitely()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateral (name = DFRRemovedCollateral_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRCircularRefs (name = DFRCircularRefs_1, extkey_id = DFRRemovedCollateral_1)
|
||||
update DFRRemovedCollateral (name = DFRRemovedCollateral_1, circular_id = DFRCircularRefs_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRToRemoveLeaf' => ['iDeleteCount' => 1],
|
||||
'DFRRemovedCollateral' => ['iDeleteCount' => 1],
|
||||
'DFRCircularRefs' => ['iDeleteCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testMultipleExtKeys()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRC01' => ['iDeleteCount' => 5],
|
||||
'DFRC21' => ['iDeleteCount' => 1],
|
||||
'DFRC3' => ['iDeleteCount' => 3],
|
||||
'DFRC4' => ['iDeleteCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testMultipleInitialSubClassesFromSameRoot()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC02 (name = DFRC02_1)
|
||||
create DFRC02 (name = DFRC02_2)
|
||||
create DFRC02 (name = DFRC02_3)
|
||||
|
||||
create DFRC03 (name = DFRC03_1)
|
||||
create DFRC03 (name = DFRC03_2)
|
||||
create DFRC03 (name = DFRC03_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_4, extkey1_id = DFRC02_1)
|
||||
create DFRC3 (name = DFRC3_5, extkey1_id = DFRC02_2)
|
||||
create DFRC3 (name = DFRC3_6, extkey1_id = DFRC02_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_7, extkey1_id = DFRC03_1)
|
||||
create DFRC3 (name = DFRC3_8, extkey1_id = DFRC03_2)
|
||||
create DFRC3 (name = DFRC3_9, extkey1_id = DFRC03_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01', 'DFRC03' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aExpected = [
|
||||
'DFRC01' => ['iDeleteCount' => 5],
|
||||
'DFRC2' => ['iDeleteCount' => 1],
|
||||
'DFRC21' => ['iDeleteCount' => 1],
|
||||
'DFRC3' => ['iDeleteCount' => 6],
|
||||
'DFRC4' => ['iDeleteCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
private function AssertSummaryEquals(array $aExpected, array $aActual, string $sMessage = '')
|
||||
{
|
||||
foreach ($aExpected as $sClass => $aExpectedCounts) {
|
||||
$oExpectedCleanupSummaryEntity = new DataCleanupSummaryEntity($sClass);
|
||||
foreach ($aExpectedCounts as $sCount => $iExpectedValue) {
|
||||
$oExpectedCleanupSummaryEntity->$sCount = $iExpectedValue;
|
||||
}
|
||||
|
||||
$this->assertEquals($oExpectedCleanupSummaryEntity, $aActual[$sClass], $sMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,124 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval;
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
|
||||
class TempTablesTest extends \AbstractCleanup
|
||||
{
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/data_cleanup_delta.xml';
|
||||
}
|
||||
|
||||
public function testMultipleExtKeys()
|
||||
{
|
||||
echo "--------------------------------------------\n".__METHOD__."\n";
|
||||
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aTempTables = $oService->GetTempTableDefinitions($aClasses);
|
||||
echo json_encode($aTempTables, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function testMultipleInitialSubClassesFromSameRoot()
|
||||
{
|
||||
|
||||
echo "--------------------------------------------\n".__METHOD__."\n";
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC02 (name = DFRC02_1)
|
||||
create DFRC02 (name = DFRC02_2)
|
||||
create DFRC02 (name = DFRC02_3)
|
||||
|
||||
create DFRC03 (name = DFRC03_1)
|
||||
create DFRC03 (name = DFRC03_2)
|
||||
create DFRC03 (name = DFRC03_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_4, extkey1_id = DFRC02_1)
|
||||
create DFRC3 (name = DFRC3_5, extkey1_id = DFRC02_2)
|
||||
create DFRC3 (name = DFRC3_6, extkey1_id = DFRC02_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_7, extkey1_id = DFRC03_1)
|
||||
create DFRC3 (name = DFRC3_8, extkey1_id = DFRC03_2)
|
||||
create DFRC3 (name = DFRC3_9, extkey1_id = DFRC03_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC22 (name = DFRC22_1, extkey1_id = DFRC03_1)
|
||||
create DFRC22 (name = DFRC22_2, extkey1_id = DFRC03_1)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
create DFRC4 (name = DFRC4_2, extkey1_id = DFRC22_1)
|
||||
create DFRC4 (name = DFRC4_3, extkey1_id = DFRC22_2)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
update DFRC3 (name = DFRC3_5, extkey2_id = DFRC4_2)
|
||||
update DFRC3 (name = DFRC3_6, extkey2_id = DFRC4_3)
|
||||
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01', 'DFRC03' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aTempTables = $oService->GetTempTableDefinitions($aClasses);
|
||||
echo json_encode($aTempTables, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$this->GetIds($aTempTables);
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo "\nDeletionPlan: ".json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
private function GetIds(array $aTempTables): void
|
||||
{
|
||||
foreach ($aTempTables as $sDBName => $aQueries) {
|
||||
foreach ($aQueries as $sQuery) {
|
||||
CMDBSource::Query($sQuery);
|
||||
}
|
||||
|
||||
$aIds = CMDBSource::QueryToCol("SELECT id FROM `$sDBName`", 'id');
|
||||
echo "\n$sDBName: ".implode(', ', $aIds)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -71,6 +71,15 @@
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey2_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey2_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DFRToRemove</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
@@ -123,6 +132,15 @@
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="circular_id" xsi:type="AttributeExternalKey">
|
||||
<sql>circular_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DFRCircularRefs</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
@@ -145,11 +163,11 @@
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRToRemoveLeaf" _created_in="itop-structure" _delta="define">
|
||||
<class id="DFRCircularRefs" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrtoremoveleaf</db_table>
|
||||
<db_table>dfrcircularrefs</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
@@ -158,14 +176,23 @@
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>desc</sql>
|
||||
<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>false</is_null_allowed>
|
||||
<target_class>DFRRemovedCollateral</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
@@ -180,13 +207,13 @@
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="desc">
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRToRemove</parent>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRRemovedCollateralCascade" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
@@ -292,6 +319,539 @@
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRLeafNotToRemove" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrleafnottoremove</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="info" xsi:type="AttributeString">
|
||||
<sql>info</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<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="info">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRToRemove</parent>
|
||||
</class>
|
||||
<class id="DFRToRemoveLeaf" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrtoremoveleaf</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>desc</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<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="desc">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRToRemove</parent>
|
||||
</class>
|
||||
|
||||
<class id="DFRC0" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<db_table>dfrc0</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>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRC00" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<db_table>dfrc00</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC0</parent>
|
||||
</class>
|
||||
<class id="DFRC01" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc01</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC02" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc02</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC03" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc03</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC2" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<db_table>dfrc2</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="extkey1_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey1_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC0</target_class>
|
||||
<on_target_delete>DEL_AUTO</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="DFRC21" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc21</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="extkey2_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey2_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC0</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey3_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey3_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC3</target_class>
|
||||
<on_target_delete>DEL_AUTO</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>DFRC2</parent>
|
||||
</class>
|
||||
<class id="DFRC22" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc22</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
</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>DFRC2</parent>
|
||||
</class>
|
||||
<class id="DFRC3" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc3</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="extkey1_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey1_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC0</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
<field id="extkey2_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey2_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<target_class>DFRC4</target_class>
|
||||
<on_target_delete>DEL_AUTO</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="DFRC4" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc4</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="extkey1_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey1_id</sql>
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC21</target_class>
|
||||
<on_target_delete>DEL_AUTO</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>
|
||||
|
||||
</classes>
|
||||
<dictionaries>
|
||||
<dictionary id="EN US">
|
||||
|
||||
Reference in New Issue
Block a user