mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-22 00:32:16 +02:00
N°9165 - secure data cleanup
This commit is contained in:
@@ -3190,4 +3190,30 @@ TXT
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Read memory limit from the php.ini file
|
||||||
|
*
|
||||||
|
* @return int Memory limit in bytes
|
||||||
|
*/
|
||||||
|
public static function GetMemoryLimit(): int
|
||||||
|
{
|
||||||
|
$sLimit = ini_get('memory_limit');
|
||||||
|
if ($sLimit == '-1') {
|
||||||
|
return 128 * 1048576;
|
||||||
|
}
|
||||||
|
switch (substr($sLimit, -1)) {
|
||||||
|
case 'M':
|
||||||
|
case 'm':
|
||||||
|
return (int)$sLimit * 1048576;
|
||||||
|
case 'K':
|
||||||
|
case 'k':
|
||||||
|
return (int)$sLimit * 1024;
|
||||||
|
case 'G':
|
||||||
|
case 'g':
|
||||||
|
return (int)$sLimit * 1073741824;
|
||||||
|
default:
|
||||||
|
return (int)$sLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@
|
|||||||
},
|
},
|
||||||
"autoload": {
|
"autoload": {
|
||||||
"psr-4": {
|
"psr-4": {
|
||||||
"Combodo\\iTop\\DataFeatureRemoval\\": "src",
|
"Combodo\\iTop\\DataFeatureRemoval\\": "src"
|
||||||
"": "src/NoNamespace"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"name": "combodo/combodo-data-feature-removal",
|
"name": "combodo/combodo-data-feature-removal",
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Dict::Add('EN US', 'English', 'English', [
|
|||||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s rows to clean before continuing',
|
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s rows to clean before continuing',
|
||||||
'DataFeatureRemoval:DoDeletion:Title' => 'Do deletion',
|
'DataFeatureRemoval:DoDeletion:Title' => 'Do deletion',
|
||||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Remove all the entries from the database',
|
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Remove all the entries from the database',
|
||||||
'DataFeatureRemoval:DeletionPlan:ToManyOperations' => 'Too many entries to clean',
|
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Some objects must be deleted manually prior to cleanup',
|
||||||
|
|
||||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Element to remove',
|
||||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Feature name',
|
||||||
@@ -51,7 +51,7 @@ Dict::Add('EN US', 'English', 'English', [
|
|||||||
'DataFeatureRemoval:Column:Class' => 'Class',
|
'DataFeatureRemoval:Column:Class' => 'Class',
|
||||||
'DataFeatureRemoval:Column:DeleteCount' => 'Entries to delete',
|
'DataFeatureRemoval:Column:DeleteCount' => 'Entries to delete',
|
||||||
'DataFeatureRemoval:Column:UpdateCount' => 'Entries to update',
|
'DataFeatureRemoval:Column:UpdateCount' => 'Entries to update',
|
||||||
'DataFeatureRemoval:Column:Issue' => 'Issue',
|
'DataFeatureRemoval:Column:IssueCount' => 'Issues found preventing automatic cleanup',
|
||||||
|
|
||||||
'DataFeatureRemoval:Column:DeletedCount' => 'Deleted entries',
|
'DataFeatureRemoval:Column:DeletedCount' => 'Deleted entries',
|
||||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Updated entries',
|
'DataFeatureRemoval:Column:UpdatedCount' => 'Updated entries',
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
|||||||
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s ligne(s) à nettoyer avant de poursuivre',
|
'DataFeatureRemoval:DeletionPlan:SubTitle' => '%1$s ligne(s) à nettoyer avant de poursuivre',
|
||||||
'DataFeatureRemoval:DoDeletion:Title' => 'Exécuter la suppression',
|
'DataFeatureRemoval:DoDeletion:Title' => 'Exécuter la suppression',
|
||||||
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Supprime toutes les entrées de la base de données',
|
'DataFeatureRemoval:DoDeletion:SubTitle' => 'Supprime toutes les entrées de la base de données',
|
||||||
'DataFeatureRemoval:DeletionPlan:ToManyOperations' => 'Trop d’entrées à nettoyer',
|
'DataFeatureRemoval:DeletionPlan:Error:Issues' => 'Certains objets doivent être supprimés manuellement avant le nettoyage',
|
||||||
|
|
||||||
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Élément à supprimer',
|
'DataFeatureRemoval:Table:Analysis:ClassName' => 'Élément à supprimer',
|
||||||
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Fonctionnalité',
|
'DataFeatureRemoval:Table:Analysis:FeatureName' => 'Fonctionnalité',
|
||||||
@@ -51,7 +51,7 @@ Dict::Add('FR FR', 'French', 'Français', [
|
|||||||
'DataFeatureRemoval:Column:Class' => 'Classe',
|
'DataFeatureRemoval:Column:Class' => 'Classe',
|
||||||
'DataFeatureRemoval:Column:DeleteCount' => 'Entrées à supprimer',
|
'DataFeatureRemoval:Column:DeleteCount' => 'Entrées à supprimer',
|
||||||
'DataFeatureRemoval:Column:UpdateCount' => 'Entrées à mettre à jour',
|
'DataFeatureRemoval:Column:UpdateCount' => 'Entrées à mettre à jour',
|
||||||
'DataFeatureRemoval:Column:Issue' => 'Problème',
|
'DataFeatureRemoval:Column:IssueCount' => 'Problèmes empêchant le nettoyage automatique',
|
||||||
|
|
||||||
'DataFeatureRemoval:Column:DeletedCount' => 'Entrées supprimées',
|
'DataFeatureRemoval:Column:DeletedCount' => 'Entrées supprimées',
|
||||||
'DataFeatureRemoval:Column:UpdatedCount' => 'Entrées mises à jour',
|
'DataFeatureRemoval:Column:UpdatedCount' => 'Entrées mises à jour',
|
||||||
|
|||||||
@@ -14,8 +14,8 @@ use Combodo\iTop\Application\TwigBase\Controller\Controller;
|
|||||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalConfig;
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalConfig;
|
||||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalHelper;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
|
||||||
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
|
use Combodo\iTop\DataFeatureRemoval\Service\DataFeatureRemoverExtensionService;
|
||||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
|
||||||
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
use Combodo\iTop\Setup\FeatureRemoval\DryRemovalRuntimeEnvironment;
|
||||||
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
use Combodo\iTop\Setup\FeatureRemoval\SetupAudit;
|
||||||
use Dict;
|
use Dict;
|
||||||
@@ -104,17 +104,20 @@ class DataFeatureRemovalController extends Controller
|
|||||||
|
|
||||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||||
|
|
||||||
$aDeletionPlanSummaryEntities = DeletionPlanService::GetInstance()->GetDeletionPlanSummary($aClasses);
|
$oDataCleanupService = new DataCleanupService();
|
||||||
$aColumns = ['Class', 'DeleteCount' , 'UpdateCount', 'Issue'];
|
$aDeletionPlanSummaryEntities = $oDataCleanupService->GetCleanupSummary($aClasses);
|
||||||
|
$aColumns = ['Class', 'DeleteCount' , 'UpdateCount', 'IssueCount'];
|
||||||
$aRows = [];
|
$aRows = [];
|
||||||
$iQueryCount = 0;
|
$iQueryCount = 0;
|
||||||
|
$bHasIssues = false;
|
||||||
foreach ($aDeletionPlanSummaryEntities as $oDeletionPlanSummaryEntity) {
|
foreach ($aDeletionPlanSummaryEntities as $oDeletionPlanSummaryEntity) {
|
||||||
$aRows[] = [
|
$aRows[] = [
|
||||||
$oDeletionPlanSummaryEntity->sClass,
|
$oDeletionPlanSummaryEntity->sClass,
|
||||||
$oDeletionPlanSummaryEntity->iDeleteCount,
|
$oDeletionPlanSummaryEntity->iDeleteCount,
|
||||||
$oDeletionPlanSummaryEntity->iUpdateCount,
|
$oDeletionPlanSummaryEntity->iUpdateCount,
|
||||||
$oDeletionPlanSummaryEntity->sIssue ?? '',
|
$oDeletionPlanSummaryEntity->iIssueCount,
|
||||||
];
|
];
|
||||||
|
$bHasIssues |= ($oDeletionPlanSummaryEntity->iIssueCount !== 0);
|
||||||
$iQueryCount += $oDeletionPlanSummaryEntity->iDeleteCount;
|
$iQueryCount += $oDeletionPlanSummaryEntity->iDeleteCount;
|
||||||
$iQueryCount += $oDeletionPlanSummaryEntity->iUpdateCount;
|
$iQueryCount += $oDeletionPlanSummaryEntity->iUpdateCount;
|
||||||
}
|
}
|
||||||
@@ -123,7 +126,7 @@ class DataFeatureRemovalController extends Controller
|
|||||||
$aParams['aDeletionPlanSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
$aParams['aDeletionPlanSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
||||||
$aParams['aClasses'] = $aClasses;
|
$aParams['aClasses'] = $aClasses;
|
||||||
$aParams['iQueryCount'] = $iQueryCount;
|
$aParams['iQueryCount'] = $iQueryCount;
|
||||||
$aParams['bDeletionPossible'] = ($iQueryCount <= DataFeatureRemovalConfig::GetInstance()->Get('max_count_estimation_for_safe_cleanup', 100));
|
$aParams['bDeletionPossible'] = !$bHasIssues;
|
||||||
|
|
||||||
$this->DisplayPage($aParams);
|
$this->DisplayPage($aParams);
|
||||||
}
|
}
|
||||||
@@ -135,7 +138,8 @@ class DataFeatureRemovalController extends Controller
|
|||||||
|
|
||||||
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
$aClasses = utils::ReadPostedParam('classes', null, utils::ENUM_SANITIZATION_FILTER_CLASS);
|
||||||
|
|
||||||
$aDeletionExecutionSummary = DeletionPlanService::GetInstance()->ExecuteDeletionPlan($aClasses);
|
$oDataCleanupService = new DataCleanupService();
|
||||||
|
$aDeletionExecutionSummary = $oDataCleanupService->ExecuteCleanup($aClasses);
|
||||||
$aColumns = ['Class', 'DeletedCount' , 'UpdatedCount'];
|
$aColumns = ['Class', 'DeletedCount' , 'UpdatedCount'];
|
||||||
$aRows = [];
|
$aRows = [];
|
||||||
foreach ($aDeletionExecutionSummary as $oDeletionExecutionSummaryEntity) {
|
foreach ($aDeletionExecutionSummary as $oDeletionExecutionSummaryEntity) {
|
||||||
@@ -148,6 +152,7 @@ class DataFeatureRemovalController extends Controller
|
|||||||
|
|
||||||
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
$aParams['sTransactionId'] = utils::GetNewTransactionId();
|
||||||
$aParams['aDeletionExecutionSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
$aParams['aDeletionExecutionSummary'] = $this->GetTableData('Extensions', $aColumns, $aRows);
|
||||||
|
|
||||||
$this->DisplayPage($aParams);
|
$this->DisplayPage($aParams);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,16 +2,10 @@
|
|||||||
|
|
||||||
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
||||||
|
|
||||||
class DeletionPlanSummaryEntity
|
class DataCleanupSummaryEntity
|
||||||
{
|
{
|
||||||
public string $sClass;
|
public string $sClass;
|
||||||
|
public int $iIssueCount = 0;
|
||||||
/**
|
|
||||||
* @var int : DEL_MANUAL|DEL_AUTO|DEL_SILENT|DEL_MOVEUP|DEL_NONE
|
|
||||||
* @see \AttributeDefinition DEL_xxx
|
|
||||||
*/
|
|
||||||
public int $iMode = 0;
|
|
||||||
public ?string $sIssue = null;
|
|
||||||
public int $iUpdateCount = 0;
|
public int $iUpdateCount = 0;
|
||||||
public int $iDeleteCount = 0;
|
public int $iDeleteCount = 0;
|
||||||
|
|
||||||
@@ -20,11 +20,11 @@ class DataFeatureRemovalConfig
|
|||||||
|
|
||||||
final public static function GetInstance(): DataFeatureRemovalConfig
|
final public static function GetInstance(): DataFeatureRemovalConfig
|
||||||
{
|
{
|
||||||
if (!isset(static::$oInstance)) {
|
if (!isset(self::$oInstance)) {
|
||||||
static::$oInstance = new DataFeatureRemovalConfig();
|
self::$oInstance = new DataFeatureRemovalConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
return static::$oInstance;
|
return self::$oInstance;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function Get(string $sParamName, $default = null)
|
public function Get(string $sParamName, $default = null)
|
||||||
|
|||||||
@@ -0,0 +1,177 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||||
|
|
||||||
|
use CMDBObjectSet;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||||
|
use Combodo\iTop\Service\Limits\ExecutionLimits;
|
||||||
|
use DBObject;
|
||||||
|
use DBObjectSearch;
|
||||||
|
use MetaModel;
|
||||||
|
|
||||||
|
class DataCleanupService
|
||||||
|
{
|
||||||
|
private array $aVisited = [];
|
||||||
|
private iObjectService $oObjectService;
|
||||||
|
private ExecutionLimits $oExecutionLimits;
|
||||||
|
|
||||||
|
public function __construct(int $iMaxExecutionTime = 30, int $iMaxMemoryPercent = 80)
|
||||||
|
{
|
||||||
|
DataFeatureRemovalLog::Enable();
|
||||||
|
$this->oExecutionLimits = new ExecutionLimits($iMaxExecutionTime, $iMaxMemoryPercent);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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
|
||||||
|
* @throws \CoreUnexpectedValue
|
||||||
|
* @throws \MySQLException
|
||||||
|
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||||
|
*/
|
||||||
|
public function GetCleanupSummary(?array $aClasses): array
|
||||||
|
{
|
||||||
|
return $this->ExecuteCleanup($aClasses ?? [], oObjectService: new ObjectServiceSummary());
|
||||||
|
}
|
||||||
|
|
||||||
|
private function GetNextObjectToDelete(array $aClasses): ?DBObject
|
||||||
|
{
|
||||||
|
foreach ($aClasses as $sClass) {
|
||||||
|
$oFilter = new DBObjectSearch($sClass);
|
||||||
|
$oFilter->AllowAllData();
|
||||||
|
$oSet = new \DBObjectSet($oFilter);
|
||||||
|
while ($oObject = $oSet->Fetch()) {
|
||||||
|
if (!$this->IsVisited($oObject)) {
|
||||||
|
return $oObject;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param array $aClasses
|
||||||
|
* @param \Combodo\iTop\DataFeatureRemoval\Service\iObjectService|null $oObjectService
|
||||||
|
*
|
||||||
|
* @return array execution summary
|
||||||
|
* @throws \ArchivedObjectException
|
||||||
|
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||||
|
* @throws \CoreException
|
||||||
|
* @throws \CoreUnexpectedValue
|
||||||
|
* @throws \MySQLException
|
||||||
|
*/
|
||||||
|
public function ExecuteCleanup(array $aClasses, ?iObjectService $oObjectService = null): array
|
||||||
|
{
|
||||||
|
$this->oObjectService = $oObjectService ?? new ObjectService();
|
||||||
|
|
||||||
|
$this->aVisited = [];
|
||||||
|
|
||||||
|
while ($oObject = $this->GetNextObjectToDelete($aClasses)) {
|
||||||
|
if ($this->RecursiveDeletion($oObject) === false) {
|
||||||
|
// Timeout, stop here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $this->oObjectService->GetSummary();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function MarkObjectAsVisited(DBObject $oObject): void
|
||||||
|
{
|
||||||
|
$sClass = get_class($oObject);
|
||||||
|
$sId = $oObject->GetKey();
|
||||||
|
$sKey = "$sClass-$sId";
|
||||||
|
$this->aVisited[$sKey] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function IsVisited(DBObject $oObject): bool
|
||||||
|
{
|
||||||
|
$sClass = get_class($oObject);
|
||||||
|
$sId = $oObject->GetKey();
|
||||||
|
$sKey = "$sClass-$sId";
|
||||||
|
|
||||||
|
$bRes = $this->aVisited[$sKey] ?? false;
|
||||||
|
DataFeatureRemovalLog::Debug('Checking if object is visited', null, [$sKey, $bRes]);
|
||||||
|
return $bRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param \DBObject $oObjectToClean
|
||||||
|
*
|
||||||
|
* @return bool true if deletion is complete, false in case of timeout or memory limit reached
|
||||||
|
*
|
||||||
|
* @throws \ArchivedObjectException
|
||||||
|
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
||||||
|
* @throws \CoreException
|
||||||
|
* @throws \CoreUnexpectedValue
|
||||||
|
* @throws \MySQLException
|
||||||
|
*/
|
||||||
|
private function RecursiveDeletion(DBObject $oObjectToClean): bool
|
||||||
|
{
|
||||||
|
$this->MarkObjectAsVisited($oObjectToClean);
|
||||||
|
$sClass = get_class($oObjectToClean);
|
||||||
|
|
||||||
|
$aReferencingMe = MetaModel::EnumReferencingClasses($sClass);
|
||||||
|
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
|
||||||
|
/** @var \AttributeExternalKey $oExtKeyAttDef */
|
||||||
|
foreach ($aExtKeys as $sExtKeyAttCode => $oExtKeyAttDef) {
|
||||||
|
// skip if this external key is behind an external field
|
||||||
|
if (!$oExtKeyAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$oSearch = new DBObjectSearch($sRemoteClass);
|
||||||
|
$oSearch->AddCondition($sExtKeyAttCode, $oObjectToClean->GetKey(), '=');
|
||||||
|
$oSearch->AllowAllData();
|
||||||
|
$oSet = new CMDBObjectSet($oSearch);
|
||||||
|
$oSet->OptimizeColumnLoad([$sRemoteClass => [$oExtKeyAttDef->GetCode()]]);
|
||||||
|
/** @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
|
||||||
|
if (($iDeletePropagationOption == DEL_MOVEUP) && ($oExtKeyAttDef->IsHierarchicalKey())) {
|
||||||
|
// Move the child up one level i.e. set the same parent as the current object
|
||||||
|
$iParentId = $oObjectToClean->Get($oExtKeyAttDef->GetCode());
|
||||||
|
$this->oObjectService->Update($oDependentObj, $oExtKeyAttDef->GetCode(), $iParentId);
|
||||||
|
} else {
|
||||||
|
$this->oObjectService->Update($oDependentObj, $oExtKeyAttDef->GetCode(), 0);
|
||||||
|
}
|
||||||
|
if ($this->oExecutionLimits->ShouldStopExecution()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Propagate deletion only if not visited
|
||||||
|
if ($this->IsVisited($oDependentObj)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!$this->RecursiveDeletion($oDependentObj)) {
|
||||||
|
// Timeout
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->oObjectService->Delete($sClass, $oObjectToClean->GetKey());
|
||||||
|
|
||||||
|
if ($this->oExecutionLimits->ShouldStopExecution()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
|
||||||
|
|
||||||
use CMDBSource;
|
|
||||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
|
||||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
|
||||||
use DBObjectSearch;
|
|
||||||
use DeletionPlan;
|
|
||||||
use MetaModel;
|
|
||||||
|
|
||||||
class DeletionPlanService
|
|
||||||
{
|
|
||||||
private static DeletionPlanService $oInstance;
|
|
||||||
|
|
||||||
protected function __construct()
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
final public static function GetInstance(): DeletionPlanService
|
|
||||||
{
|
|
||||||
if (!isset(self::$oInstance)) {
|
|
||||||
self::$oInstance = new DeletionPlanService();
|
|
||||||
}
|
|
||||||
|
|
||||||
return self::$oInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
final public static function SetInstance(?DeletionPlanService $oInstance): void
|
|
||||||
{
|
|
||||||
self::$oInstance = $oInstance;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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\DeletionPlanSummaryEntity>
|
|
||||||
* @throws \CoreException
|
|
||||||
* @throws \CoreUnexpectedValue
|
|
||||||
* @throws \MySQLException
|
|
||||||
*/
|
|
||||||
public function GetDeletionPlanSummary(?array $aClasses): array
|
|
||||||
{
|
|
||||||
$aSummary = [];
|
|
||||||
if (is_null($aClasses)) {
|
|
||||||
return $aSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
|
|
||||||
|
|
||||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aUpdates) {
|
|
||||||
$oDeletionPlanSummaryEntity = new DeletionPlanSummaryEntity($sClass);
|
|
||||||
$oDeletionPlanSummaryEntity->iUpdateCount = count($aUpdates);
|
|
||||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aDeletes) {
|
|
||||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
|
||||||
$oDeletionPlanSummaryEntity->iDeleteCount = count($aDeletes);
|
|
||||||
|
|
||||||
$aDelete = array_shift($aDeletes);
|
|
||||||
$oDeletionPlanSummaryEntity->iMode = $aDelete['mode'];
|
|
||||||
$oDeletionPlanSummaryEntity->sIssue = $aDelete['issue'] ?? null;
|
|
||||||
|
|
||||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param string $sClass
|
|
||||||
*
|
|
||||||
* @return \DBObject[]
|
|
||||||
* @throws \CoreException
|
|
||||||
* @throws \CoreUnexpectedValue
|
|
||||||
* @throws \MySQLException
|
|
||||||
* @throws \Exception
|
|
||||||
*/
|
|
||||||
private function GetAllObjects(string $sClass): array
|
|
||||||
{
|
|
||||||
$oFilter = new DBObjectSearch($sClass);
|
|
||||||
$oFilter->AllowAllData();
|
|
||||||
$oSet = new \DBObjectSet($oFilter);
|
|
||||||
return $oSet->ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param array $aClasses
|
|
||||||
*
|
|
||||||
* @return array<\Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity>
|
|
||||||
* @throws \Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException
|
|
||||||
* @throws \CoreException
|
|
||||||
* @throws \CoreUnexpectedValue
|
|
||||||
* @throws \MySQLException
|
|
||||||
*/
|
|
||||||
public function ExecuteDeletionPlan(array $aClasses): array
|
|
||||||
{
|
|
||||||
$oDeletionPlan = $this->GetDeletionPlan($aClasses);
|
|
||||||
|
|
||||||
if (count($oDeletionPlan->GetIssues()) > 0) {
|
|
||||||
throw new DataFeatureRemovalException("Deletion Plan cannot be executed due to issues");
|
|
||||||
}
|
|
||||||
|
|
||||||
$aSummary = [];
|
|
||||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
|
|
||||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
|
||||||
|
|
||||||
foreach ($aToUpdate as $aData) {
|
|
||||||
$oToUpdate = $aData['to_reset'];
|
|
||||||
/** @var \DBObject $oToUpdate */
|
|
||||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
|
|
||||||
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
|
|
||||||
}
|
|
||||||
$oToUpdate->DBUpdate();
|
|
||||||
$oDeletionPlanSummaryEntity->iUpdateCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aDeletes) {
|
|
||||||
$oDeletionPlanSummaryEntity = $aSummary[$sClass] ?? new DeletionPlanSummaryEntity($sClass);
|
|
||||||
|
|
||||||
foreach ($aDeletes as $sId => $aDelete) {
|
|
||||||
try {
|
|
||||||
CMDBSource::Query('START TRANSACTION');
|
|
||||||
// Delete any existing change tracking about the current object
|
|
||||||
$oFilter = new DBObjectSearch('CMDBChangeOp');
|
|
||||||
$oFilter->AddCondition('objclass', $sClass, '=');
|
|
||||||
$oFilter->AddCondition('objkey', $sId, '=');
|
|
||||||
MetaModel::PurgeData($oFilter);
|
|
||||||
|
|
||||||
// Delete the entry
|
|
||||||
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
|
|
||||||
foreach ($aClassesToRemove as $sParentClass) {
|
|
||||||
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
|
|
||||||
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
|
||||||
CMDBSource::DeleteFrom($sQuery);
|
|
||||||
}
|
|
||||||
|
|
||||||
CMDBSource::Query('COMMIT');
|
|
||||||
} catch (\Exception $e) {
|
|
||||||
\IssueLog::Exception(__METHOD__.': Cleanup failed', $e);
|
|
||||||
CMDBSource::Query('ROLLBACK');
|
|
||||||
throw $e;
|
|
||||||
}
|
|
||||||
$oDeletionPlanSummaryEntity->iDeleteCount++;
|
|
||||||
}
|
|
||||||
|
|
||||||
$aSummary[$sClass] = $oDeletionPlanSummaryEntity;
|
|
||||||
}
|
|
||||||
|
|
||||||
return $aSummary;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get a deletion plan for all the objects of the classes
|
|
||||||
*
|
|
||||||
* @param array $aClasses array of class names to clean
|
|
||||||
*
|
|
||||||
* @return \DeletionPlan
|
|
||||||
* @throws \CoreException
|
|
||||||
* @throws \CoreUnexpectedValue
|
|
||||||
* @throws \MySQLException
|
|
||||||
*/
|
|
||||||
public function GetDeletionPlan(array $aClasses): DeletionPlan
|
|
||||||
{
|
|
||||||
$oDeletionPlan = new DeletionPlan();
|
|
||||||
foreach ($aClasses as $sClass) {
|
|
||||||
$aObjects = $this->GetAllObjects($sClass);
|
|
||||||
foreach ($aObjects as $oObject) {
|
|
||||||
$oObject->CheckToDelete($oDeletionPlan);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return $oDeletionPlan;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||||
|
|
||||||
|
use CMDBSource;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||||
|
use DBObject;
|
||||||
|
use DBObjectSearch;
|
||||||
|
use MetaModel;
|
||||||
|
|
||||||
|
class ObjectService extends ObjectServiceSummary
|
||||||
|
{
|
||||||
|
public function Update(DBObject $oToUpdate, string $sAttCode, $value): void
|
||||||
|
{
|
||||||
|
$oToUpdate->Set($sAttCode, $value);
|
||||||
|
$oToUpdate->DBUpdate();
|
||||||
|
parent::Update($oToUpdate, $sAttCode, $value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Delete(string $sClass, string $sId): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
CMDBSource::Query('START TRANSACTION');
|
||||||
|
// Delete any existing change tracking about the current object
|
||||||
|
$oFilter = new DBObjectSearch('CMDBChangeOp');
|
||||||
|
$oFilter->AddCondition('objclass', $sClass, '=');
|
||||||
|
$oFilter->AddCondition('objkey', $sId, '=');
|
||||||
|
MetaModel::PurgeData($oFilter);
|
||||||
|
|
||||||
|
// Delete the entry
|
||||||
|
$aClassesToRemove = array_merge(MetaModel::EnumChildClasses($sClass, ENUM_CHILD_CLASSES_ALL), MetaModel::EnumParentClasses($sClass, ENUM_PARENT_CLASSES_EXCLUDELEAF, false));
|
||||||
|
foreach ($aClassesToRemove as $sParentClass) {
|
||||||
|
/** @var DBObjectSearch $oFilter */
|
||||||
|
$oFilter = DBObjectSearch::FromOQL_AllData("SELECT $sParentClass WHERE id=:id");
|
||||||
|
$sQuery = $oFilter->MakeDeleteQuery(['id' => $sId]);
|
||||||
|
CMDBSource::DeleteFrom($sQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
CMDBSource::Query('COMMIT');
|
||||||
|
parent::Delete($sClass, $sId);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
DataFeatureRemovalLog::Exception(__METHOD__.': Cleanup failed', $e);
|
||||||
|
CMDBSource::Query('ROLLBACK');
|
||||||
|
throw $e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetIssue(string $sClass): void
|
||||||
|
{
|
||||||
|
throw new DataFeatureRemovalException('Deletion Plan cannot be executed due to issues');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||||
|
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalLog;
|
||||||
|
use DBObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manage operation summary instead of doing the actual update or delete
|
||||||
|
*
|
||||||
|
* The summary is an array [class => DeletionPlanSummaryEntity]
|
||||||
|
*/
|
||||||
|
class ObjectServiceSummary implements iObjectService
|
||||||
|
{
|
||||||
|
private array $aSummary = [];
|
||||||
|
|
||||||
|
public function Update(DBObject $oToUpdate, string $sAttCode, $value): void
|
||||||
|
{
|
||||||
|
$sClass = get_class($oToUpdate);
|
||||||
|
DataFeatureRemovalLog::Info('Update object', null, ['class' => $sClass, 'id' => $oToUpdate->GetKey(), 'code' => $sAttCode, 'value' => "$value"]);
|
||||||
|
if (! array_key_exists($sClass, $this->aSummary)) {
|
||||||
|
$this->aSummary[$sClass] = new DataCleanupSummaryEntity($sClass);
|
||||||
|
}
|
||||||
|
$oDeletionPlanSummaryEntity = $this->aSummary[$sClass];
|
||||||
|
$oDeletionPlanSummaryEntity->iUpdateCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function Delete(string $sClass, string $sId): void
|
||||||
|
{
|
||||||
|
DataFeatureRemovalLog::Info('Delete object', null, ['class' => $sClass, 'id' => $sId]);
|
||||||
|
if (!array_key_exists($sClass, $this->aSummary)) {
|
||||||
|
$this->aSummary[$sClass] = new DataCleanupSummaryEntity($sClass);
|
||||||
|
}
|
||||||
|
$oDeletionPlanSummaryEntity = $this->aSummary[$sClass];
|
||||||
|
$oDeletionPlanSummaryEntity->iDeleteCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function SetIssue(string $sClass): void
|
||||||
|
{
|
||||||
|
DataFeatureRemovalLog::Info('Issue on object', null, ['class' => $sClass]);
|
||||||
|
if (!array_key_exists($sClass, $this->aSummary)) {
|
||||||
|
$this->aSummary[$sClass] = new DataCleanupSummaryEntity($sClass);
|
||||||
|
}
|
||||||
|
$oDeletionPlanSummaryEntity = $this->aSummary[$sClass];
|
||||||
|
$oDeletionPlanSummaryEntity->iIssueCount++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetSummary(): array
|
||||||
|
{
|
||||||
|
return $this->aSummary;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||||
|
|
||||||
|
use DBObject;
|
||||||
|
|
||||||
|
interface iObjectService
|
||||||
|
{
|
||||||
|
public function Update(DBObject $oToUpdate, string $sAttCode, $value): void;
|
||||||
|
|
||||||
|
public function Delete(string $sClass, string $sId): void;
|
||||||
|
|
||||||
|
public function SetIssue(string $sClass): void;
|
||||||
|
|
||||||
|
public function GetSummary(): array;
|
||||||
|
}
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
{% EndUIToolbar %}
|
{% EndUIToolbar %}
|
||||||
{% EndUIForm %}
|
{% EndUIForm %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{{ 'DataFeatureRemoval:DeletionPlan:ToManyOperations'|dict_s }}
|
{{ 'DataFeatureRemoval:DeletionPlan:Error:Issues'|dict_s }}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
{% UIForm Standard {} %}
|
{% UIForm Standard {} %}
|
||||||
|
|||||||
396
datamodels/2.x/combodo-data-feature-removal/vendor/composer/InstalledVersions.php
vendored
Normal file
396
datamodels/2.x/combodo-data-feature-removal/vendor/composer/InstalledVersions.php
vendored
Normal file
@@ -0,0 +1,396 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* This file is part of Composer.
|
||||||
|
*
|
||||||
|
* (c) Nils Adermann <naderman@naderman.de>
|
||||||
|
* Jordi Boggiano <j.boggiano@seld.be>
|
||||||
|
*
|
||||||
|
* For the full copyright and license information, please view the LICENSE
|
||||||
|
* file that was distributed with this source code.
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Composer;
|
||||||
|
|
||||||
|
use Composer\Autoload\ClassLoader;
|
||||||
|
use Composer\Semver\VersionParser;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is copied in every Composer installed project and available to all
|
||||||
|
*
|
||||||
|
* See also https://getcomposer.org/doc/07-runtime.md#installed-versions
|
||||||
|
*
|
||||||
|
* To require its presence, you can require `composer-runtime-api ^2.0`
|
||||||
|
*
|
||||||
|
* @final
|
||||||
|
*/
|
||||||
|
class InstalledVersions
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var string|null if set (by reflection by Composer), this should be set to the path where this class is being copied to
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
private static $selfDir = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var mixed[]|null
|
||||||
|
* @psalm-var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}|array{}|null
|
||||||
|
*/
|
||||||
|
private static $installed;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool
|
||||||
|
*/
|
||||||
|
private static $installedIsLocalDir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var bool|null
|
||||||
|
*/
|
||||||
|
private static $canGetVendors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @var array[]
|
||||||
|
* @psalm-var array<string, array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
private static $installedByVendor = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all package names which are present, either by being installed, replaced or provided
|
||||||
|
*
|
||||||
|
* @return string[]
|
||||||
|
* @psalm-return list<string>
|
||||||
|
*/
|
||||||
|
public static function getInstalledPackages()
|
||||||
|
{
|
||||||
|
$packages = array();
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
$packages[] = array_keys($installed['versions']);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (1 === \count($packages)) {
|
||||||
|
return $packages[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of all package names with a specific type e.g. 'library'
|
||||||
|
*
|
||||||
|
* @param string $type
|
||||||
|
* @return string[]
|
||||||
|
* @psalm-return list<string>
|
||||||
|
*/
|
||||||
|
public static function getInstalledPackagesByType($type)
|
||||||
|
{
|
||||||
|
$packagesByType = array();
|
||||||
|
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
foreach ($installed['versions'] as $name => $package) {
|
||||||
|
if (isset($package['type']) && $package['type'] === $type) {
|
||||||
|
$packagesByType[] = $name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $packagesByType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given package is installed
|
||||||
|
*
|
||||||
|
* This also returns true if the package name is provided or replaced by another package
|
||||||
|
*
|
||||||
|
* @param string $packageName
|
||||||
|
* @param bool $includeDevRequirements
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function isInstalled($packageName, $includeDevRequirements = true)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (isset($installed['versions'][$packageName])) {
|
||||||
|
return $includeDevRequirements || !isset($installed['versions'][$packageName]['dev_requirement']) || $installed['versions'][$packageName]['dev_requirement'] === false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks whether the given package satisfies a version constraint
|
||||||
|
*
|
||||||
|
* e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
|
||||||
|
*
|
||||||
|
* Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
|
||||||
|
*
|
||||||
|
* @param VersionParser $parser Install composer/semver to have access to this class and functionality
|
||||||
|
* @param string $packageName
|
||||||
|
* @param string|null $constraint A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
|
||||||
|
* @return bool
|
||||||
|
*/
|
||||||
|
public static function satisfies(VersionParser $parser, $packageName, $constraint)
|
||||||
|
{
|
||||||
|
$constraint = $parser->parseConstraints((string) $constraint);
|
||||||
|
$provided = $parser->parseConstraints(self::getVersionRanges($packageName));
|
||||||
|
|
||||||
|
return $provided->matches($constraint);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a version constraint representing all the range(s) which are installed for a given package
|
||||||
|
*
|
||||||
|
* It is easier to use this via isInstalled() with the $constraint argument if you need to check
|
||||||
|
* whether a given version of a package is installed, and not just whether it exists
|
||||||
|
*
|
||||||
|
* @param string $packageName
|
||||||
|
* @return string Version constraint usable with composer/semver
|
||||||
|
*/
|
||||||
|
public static function getVersionRanges($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ranges = array();
|
||||||
|
if (isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
$ranges[] = $installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
if (array_key_exists('aliases', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('replaced', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
|
||||||
|
}
|
||||||
|
if (array_key_exists('provided', $installed['versions'][$packageName])) {
|
||||||
|
$ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
|
||||||
|
}
|
||||||
|
|
||||||
|
return implode(' || ', $ranges);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||||
|
*/
|
||||||
|
public static function getVersion($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
|
||||||
|
*/
|
||||||
|
public static function getPrettyVersion($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['pretty_version'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['pretty_version'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
|
||||||
|
*/
|
||||||
|
public static function getReference($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!isset($installed['versions'][$packageName]['reference'])) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed['versions'][$packageName]['reference'];
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param string $packageName
|
||||||
|
* @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
|
||||||
|
*/
|
||||||
|
public static function getInstallPath($packageName)
|
||||||
|
{
|
||||||
|
foreach (self::getInstalled() as $installed) {
|
||||||
|
if (!isset($installed['versions'][$packageName])) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array
|
||||||
|
* @psalm-return array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}
|
||||||
|
*/
|
||||||
|
public static function getRootPackage()
|
||||||
|
{
|
||||||
|
$installed = self::getInstalled();
|
||||||
|
|
||||||
|
return $installed[0]['root'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw installed.php data for custom implementations
|
||||||
|
*
|
||||||
|
* @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
|
||||||
|
* @return array[]
|
||||||
|
* @psalm-return array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}
|
||||||
|
*/
|
||||||
|
public static function getRawData()
|
||||||
|
{
|
||||||
|
@trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
|
||||||
|
|
||||||
|
if (null === self::$installed) {
|
||||||
|
// only require the installed.php file if this file is loaded from its dumped location,
|
||||||
|
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||||
|
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||||
|
self::$installed = include __DIR__ . '/installed.php';
|
||||||
|
} else {
|
||||||
|
self::$installed = array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the raw data of all installed.php which are currently loaded for custom implementations
|
||||||
|
*
|
||||||
|
* @return array[]
|
||||||
|
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
public static function getAllRawData()
|
||||||
|
{
|
||||||
|
return self::getInstalled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lets you reload the static array from another file
|
||||||
|
*
|
||||||
|
* This is only useful for complex integrations in which a project needs to use
|
||||||
|
* this class but then also needs to execute another project's autoloader in process,
|
||||||
|
* and wants to ensure both projects have access to their version of installed.php.
|
||||||
|
*
|
||||||
|
* A typical case would be PHPUnit, where it would need to make sure it reads all
|
||||||
|
* the data it needs from this class, then call reload() with
|
||||||
|
* `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
|
||||||
|
* the project in which it runs can then also use this class safely, without
|
||||||
|
* interference between PHPUnit's dependencies and the project's dependencies.
|
||||||
|
*
|
||||||
|
* @param array[] $data A vendor/composer/installed.php data set
|
||||||
|
* @return void
|
||||||
|
*
|
||||||
|
* @psalm-param array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $data
|
||||||
|
*/
|
||||||
|
public static function reload($data)
|
||||||
|
{
|
||||||
|
self::$installed = $data;
|
||||||
|
self::$installedByVendor = array();
|
||||||
|
|
||||||
|
// when using reload, we disable the duplicate protection to ensure that self::$installed data is
|
||||||
|
// always returned, but we cannot know whether it comes from the installed.php in __DIR__ or not,
|
||||||
|
// so we have to assume it does not, and that may result in duplicate data being returned when listing
|
||||||
|
// all installed packages for example
|
||||||
|
self::$installedIsLocalDir = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private static function getSelfDir()
|
||||||
|
{
|
||||||
|
if (self::$selfDir === null) {
|
||||||
|
self::$selfDir = strtr(__DIR__, '\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$selfDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array[]
|
||||||
|
* @psalm-return list<array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>}>
|
||||||
|
*/
|
||||||
|
private static function getInstalled()
|
||||||
|
{
|
||||||
|
if (null === self::$canGetVendors) {
|
||||||
|
self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
|
||||||
|
}
|
||||||
|
|
||||||
|
$installed = array();
|
||||||
|
$copiedLocalDir = false;
|
||||||
|
|
||||||
|
if (self::$canGetVendors) {
|
||||||
|
$selfDir = self::getSelfDir();
|
||||||
|
foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
|
||||||
|
$vendorDir = strtr($vendorDir, '\\', '/');
|
||||||
|
if (isset(self::$installedByVendor[$vendorDir])) {
|
||||||
|
$installed[] = self::$installedByVendor[$vendorDir];
|
||||||
|
} elseif (is_file($vendorDir.'/composer/installed.php')) {
|
||||||
|
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||||
|
$required = require $vendorDir.'/composer/installed.php';
|
||||||
|
self::$installedByVendor[$vendorDir] = $required;
|
||||||
|
$installed[] = $required;
|
||||||
|
if (self::$installed === null && $vendorDir.'/composer' === $selfDir) {
|
||||||
|
self::$installed = $required;
|
||||||
|
self::$installedIsLocalDir = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (self::$installedIsLocalDir && $vendorDir.'/composer' === $selfDir) {
|
||||||
|
$copiedLocalDir = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (null === self::$installed) {
|
||||||
|
// only require the installed.php file if this file is loaded from its dumped location,
|
||||||
|
// and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
|
||||||
|
if (substr(__DIR__, -8, 1) !== 'C') {
|
||||||
|
/** @var array{root: array{name: string, pretty_version: string, version: string, reference: string|null, type: string, install_path: string, aliases: string[], dev: bool}, versions: array<string, array{pretty_version?: string, version?: string, reference?: string|null, type?: string, install_path?: string, aliases?: string[], dev_requirement: bool, replaced?: string[], provided?: string[]}>} $required */
|
||||||
|
$required = require __DIR__ . '/installed.php';
|
||||||
|
self::$installed = $required;
|
||||||
|
} else {
|
||||||
|
self::$installed = array();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (self::$installed !== array() && !$copiedLocalDir) {
|
||||||
|
$installed[] = self::$installed;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $installed;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,12 +7,15 @@ $baseDir = dirname($vendorDir);
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => $baseDir . '/src/Controller/DataFeatureRemovalController.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => $baseDir . '/src/Entity/DeletionPlanSummaryEntity.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => $baseDir . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => $baseDir . '/src/Helper/DataFeatureRemovalConfig.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\\DataFeatureRemovalException' => $baseDir . '/src/Helper/DataFeatureRemovalException.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => $baseDir . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => $baseDir . '/src/Helper/DataFeatureRemovalLog.php',
|
||||||
|
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => $baseDir . '/src/Service/DataCleanupService.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => $baseDir . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => $baseDir . '/src/Service/DeletionPlanService.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\\iObjectService' => $baseDir . '/src/Service/iObjectService.php',
|
||||||
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -7,5 +7,4 @@ $baseDir = dirname($vendorDir);
|
|||||||
|
|
||||||
return array(
|
return array(
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\' => array($baseDir . '/src'),
|
'Combodo\\iTop\\DataFeatureRemoval\\' => array($baseDir . '/src'),
|
||||||
'' => array($baseDir . '/src/NoNamespace'),
|
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -20,19 +20,18 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
public static $fallbackDirsPsr4 = array (
|
|
||||||
0 => __DIR__ . '/../..' . '/src/NoNamespace',
|
|
||||||
);
|
|
||||||
|
|
||||||
public static $classMap = array (
|
public static $classMap = array (
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Controller\\DataFeatureRemovalController' => __DIR__ . '/../..' . '/src/Controller/DataFeatureRemovalController.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DeletionPlanSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DeletionPlanSummaryEntity.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Entity\\DataCleanupSummaryEntity' => __DIR__ . '/../..' . '/src/Entity/DataCleanupSummaryEntity.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalConfig' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalConfig.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\\DataFeatureRemovalException' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalException.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalHelper' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalHelper.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Helper\\DataFeatureRemovalLog' => __DIR__ . '/../..' . '/src/Helper/DataFeatureRemovalLog.php',
|
||||||
|
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataCleanupService' => __DIR__ . '/../..' . '/src/Service/DataCleanupService.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DataFeatureRemoverExtensionService' => __DIR__ . '/../..' . '/src/Service/DataFeatureRemoverExtensionService.php',
|
||||||
'Combodo\\iTop\\DataFeatureRemoval\\Service\\DeletionPlanService' => __DIR__ . '/../..' . '/src/Service/DeletionPlanService.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\\iObjectService' => __DIR__ . '/../..' . '/src/Service/iObjectService.php',
|
||||||
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -41,7 +40,6 @@ class ComposerStaticInit4f96a7199e2c0d90e547333758b26464
|
|||||||
return \Closure::bind(function () use ($loader) {
|
return \Closure::bind(function () use ($loader) {
|
||||||
$loader->prefixLengthsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixLengthsPsr4;
|
$loader->prefixLengthsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixLengthsPsr4;
|
||||||
$loader->prefixDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixDirsPsr4;
|
$loader->prefixDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$prefixDirsPsr4;
|
||||||
$loader->fallbackDirsPsr4 = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$fallbackDirsPsr4;
|
|
||||||
$loader->classMap = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$classMap;
|
$loader->classMap = ComposerStaticInit4f96a7199e2c0d90e547333758b26464::$classMap;
|
||||||
|
|
||||||
}, null, ClassLoader::class);
|
}, null, ClassLoader::class);
|
||||||
|
|||||||
5
datamodels/2.x/combodo-data-feature-removal/vendor/composer/installed.json
vendored
Normal file
5
datamodels/2.x/combodo-data-feature-removal/vendor/composer/installed.json
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"packages": [],
|
||||||
|
"dev": true,
|
||||||
|
"dev-package-names": []
|
||||||
|
}
|
||||||
23
datamodels/2.x/combodo-data-feature-removal/vendor/composer/installed.php
vendored
Normal file
23
datamodels/2.x/combodo-data-feature-removal/vendor/composer/installed.php
vendored
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<?php return array(
|
||||||
|
'root' => array(
|
||||||
|
'name' => 'combodo/combodo-data-feature-removal',
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'reference' => '19bbf6759bb4f6f5814d9ec1b0b5514208efc0b2',
|
||||||
|
'type' => 'itop-extension',
|
||||||
|
'install_path' => __DIR__ . '/../../',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev' => true,
|
||||||
|
),
|
||||||
|
'versions' => array(
|
||||||
|
'combodo/combodo-data-feature-removal' => array(
|
||||||
|
'pretty_version' => 'dev-develop',
|
||||||
|
'version' => 'dev-develop',
|
||||||
|
'reference' => '19bbf6759bb4f6f5814d9ec1b0b5514208efc0b2',
|
||||||
|
'type' => 'itop-extension',
|
||||||
|
'install_path' => __DIR__ . '/../../',
|
||||||
|
'aliases' => array(),
|
||||||
|
'dev_requirement' => false,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
@@ -14,10 +14,7 @@ if (PHP_VERSION_ID < 50600) {
|
|||||||
echo $err;
|
echo $err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trigger_error(
|
throw new RuntimeException($err);
|
||||||
$err,
|
|
||||||
E_USER_ERROR
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
require_once __DIR__ . '/composer/autoload_real.php';
|
require_once __DIR__ . '/composer/autoload_real.php';
|
||||||
|
|||||||
@@ -625,6 +625,7 @@ return array(
|
|||||||
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Service/Events/iEventServiceSetup.php',
|
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Service/Events/iEventServiceSetup.php',
|
||||||
'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => $baseDir . '/sources/Service/Import/CSVImportPageProcessor.php',
|
'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => $baseDir . '/sources/Service/Import/CSVImportPageProcessor.php',
|
||||||
'Combodo\\iTop\\Service\\InterfaceDiscovery\\InterfaceDiscovery' => $baseDir . '/sources/Service/InterfaceDiscovery/InterfaceDiscovery.php',
|
'Combodo\\iTop\\Service\\InterfaceDiscovery\\InterfaceDiscovery' => $baseDir . '/sources/Service/InterfaceDiscovery/InterfaceDiscovery.php',
|
||||||
|
'Combodo\\iTop\\Service\\Limits\\ExecutionLimits' => $baseDir . '/sources/Service/Limits/ExecutionLimits.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => $baseDir . '/sources/Service/Links/LinkSetDataTransformer.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => $baseDir . '/sources/Service/Links/LinkSetDataTransformer.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ return array(
|
|||||||
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
'Psr\\Cache\\' => array($vendorDir . '/psr/cache/src'),
|
||||||
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
'PhpParser\\' => array($vendorDir . '/nikic/php-parser/lib/PhpParser'),
|
||||||
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
'Pelago\\Emogrifier\\' => array($vendorDir . '/pelago/emogrifier/src'),
|
||||||
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-client/src', $vendorDir . '/league/oauth2-google/src'),
|
'League\\OAuth2\\Client\\' => array($vendorDir . '/league/oauth2-google/src', $vendorDir . '/league/oauth2-client/src'),
|
||||||
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
'GuzzleHttp\\Psr7\\' => array($vendorDir . '/guzzlehttp/psr7/src'),
|
||||||
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
'GuzzleHttp\\Promise\\' => array($vendorDir . '/guzzlehttp/promises/src'),
|
||||||
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
'GuzzleHttp\\' => array($vendorDir . '/guzzlehttp/guzzle/src'),
|
||||||
|
|||||||
@@ -360,8 +360,8 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
|
|||||||
),
|
),
|
||||||
'League\\OAuth2\\Client\\' =>
|
'League\\OAuth2\\Client\\' =>
|
||||||
array (
|
array (
|
||||||
0 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
0 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
||||||
1 => __DIR__ . '/..' . '/league/oauth2-google/src',
|
1 => __DIR__ . '/..' . '/league/oauth2-client/src',
|
||||||
),
|
),
|
||||||
'GuzzleHttp\\Psr7\\' =>
|
'GuzzleHttp\\Psr7\\' =>
|
||||||
array (
|
array (
|
||||||
@@ -1026,6 +1026,7 @@ class ComposerStaticInitfc0e9e9dea11dcbb6272414776c30685
|
|||||||
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Service/Events/iEventServiceSetup.php',
|
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Service/Events/iEventServiceSetup.php',
|
||||||
'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => __DIR__ . '/../..' . '/sources/Service/Import/CSVImportPageProcessor.php',
|
'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => __DIR__ . '/../..' . '/sources/Service/Import/CSVImportPageProcessor.php',
|
||||||
'Combodo\\iTop\\Service\\InterfaceDiscovery\\InterfaceDiscovery' => __DIR__ . '/../..' . '/sources/Service/InterfaceDiscovery/InterfaceDiscovery.php',
|
'Combodo\\iTop\\Service\\InterfaceDiscovery\\InterfaceDiscovery' => __DIR__ . '/../..' . '/sources/Service/InterfaceDiscovery/InterfaceDiscovery.php',
|
||||||
|
'Combodo\\iTop\\Service\\Limits\\ExecutionLimits' => __DIR__ . '/../..' . '/sources/Service/Limits/ExecutionLimits.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetDataTransformer.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetDataTransformer.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
|
||||||
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
|
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
|
||||||
|
|||||||
@@ -36,8 +36,7 @@ if ($issues) {
|
|||||||
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
trigger_error(
|
throw new \RuntimeException(
|
||||||
'Composer detected issues in your platform: ' . implode(' ', $issues),
|
'Composer detected issues in your platform: ' . implode(' ', $issues)
|
||||||
E_USER_ERROR
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -312,9 +312,6 @@ if ($bInstall) {
|
|||||||
} else {
|
} else {
|
||||||
echo "\nFailed to record designer updates(".$oMysqli->error.").\n";
|
echo "\nFailed to record designer updates(".$oMysqli->error.").\n";
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
echo "\nFailed to read the revision from $sDeltaFile file. No designer update information will be recorded.\n";
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -349,8 +346,7 @@ if (! $bFoundIssues) {
|
|||||||
$sLogMsg = "installed!";
|
$sLogMsg = "installed!";
|
||||||
|
|
||||||
if ($bUseItopConfig && is_file("$sConfigFile.backup")) {
|
if ($bUseItopConfig && is_file("$sConfigFile.backup")) {
|
||||||
echo "\nuse config file provided by backup in $sConfigFile.";
|
unlink("$sConfigFile.backup");
|
||||||
copy("$sConfigFile.backup", $sConfigFile);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SetupLog::Info($sLogMsg);
|
SetupLog::Info($sLogMsg);
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
use Combodo\iTop\Application\WebPage\WebPage;
|
|
||||||
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
use Combodo\iTop\PhpParser\Evaluation\PhpExpressionEvaluator;
|
||||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReader;
|
||||||
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
use Combodo\iTop\Setup\ModuleDiscovery\ModuleFileReaderException;
|
||||||
@@ -509,7 +508,7 @@ EOF
|
|||||||
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
|
$sDisplayChoices .= '<li>'.$aChoice['title'].'</li>';
|
||||||
if (isset($aChoice['modules'])) {
|
if (isset($aChoice['modules'])) {
|
||||||
if (count($aChoice['modules']) === 0) {
|
if (count($aChoice['modules']) === 0) {
|
||||||
throw new Exception('Extension '.$aChoice['extension_code'].' does not have any module associated');
|
//throw new Exception('Extension '.$aChoice['extension_code'].' does not have any module associated');
|
||||||
}
|
}
|
||||||
foreach ($aChoice['modules'] as $sModuleId) {
|
foreach ($aChoice['modules'] as $sModuleId) {
|
||||||
$bSelected = true;
|
$bSelected = true;
|
||||||
|
|||||||
49
sources/Service/Limits/ExecutionLimits.php
Normal file
49
sources/Service/Limits/ExecutionLimits.php
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Service\Limits;
|
||||||
|
|
||||||
|
use utils;
|
||||||
|
|
||||||
|
class ExecutionLimits
|
||||||
|
{
|
||||||
|
private int $iMaxTime;
|
||||||
|
private int $iMaxMemoryPercent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param int $iMaxDuration Max duration time in s
|
||||||
|
* @param int $iMaxMemoryPercent Max memory percent allowed (0-100)
|
||||||
|
*/
|
||||||
|
public function __construct(int $iMaxDuration = 0, int $iMaxMemoryPercent = 100)
|
||||||
|
{
|
||||||
|
public function __construct(int $iMaxDuration = 0, int $iMaxMemoryPercent = 100)
|
||||||
|
{
|
||||||
|
$this->iMaxTime = ($iMaxDuration > 0) ? ($iMaxDuration + time()) : 0;
|
||||||
|
$this->iMaxMemoryPercent = (int)min(max($iMaxMemoryPercent, 0), 100);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @return bool true when duration is
|
||||||
|
*/
|
||||||
|
public function ShouldStopExecution(): bool
|
||||||
|
{
|
||||||
|
if (($this->iMaxTime != 0) && (time() > $this->iMaxTime)) {
|
||||||
|
\IssueLog::Debug(__METHOD__.' timeout '.time()." (current) > $this->iMaxTime (max)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
$iMaxMemoryPercent = (int)min(max($this->iMaxMemoryPercent, 0), 100);
|
||||||
|
$iMemory = memory_get_usage(true);
|
||||||
|
$iMaxMemory = utils::GetMemoryLimit() * $iMaxMemoryPercent / 100;
|
||||||
|
if ($iMemory > $iMaxMemory) {
|
||||||
|
\IssueLog::Debug(__METHOD__." Memory limit $iMemory (current) > $iMaxMemory (max)");
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -36,6 +36,6 @@ parameters:
|
|||||||
- ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production`
|
- ../../../env-php-unit-tests (?) # Irrelevant as it will either already be in `env-production` or might be desynchronized from `env-production`
|
||||||
- ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI)
|
- ../../../env-toolkit (?) # Irrelevent as it will either already be in `env-production` or might be desynchronized from `env-production` (for local run only, not useful in the CI)
|
||||||
|
|
||||||
- ../../../tests (?) # Exclude tests for now
|
#- ../../../tests (?) # Exclude tests for now
|
||||||
- ../../../toolkit (?) # Exclude toolkit for now
|
- ../../../toolkit (?) # Exclude toolkit for now
|
||||||
- ../../../setup/compat # Exclude fake DOMDocument & DOMElement declarations
|
- ../../../setup/compat # Exclude fake DOMDocument & DOMElement declarations
|
||||||
|
|||||||
@@ -0,0 +1,301 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/*
|
||||||
|
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||||
|
* @license http://opensource.org/licenses/AGPL-3.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
|
||||||
|
|
||||||
|
use Cleanup;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
||||||
|
use Combodo\iTop\DataFeatureRemoval\Service\DataCleanupService;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopCustomDatamodelTestCase;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use PHPUnit\Framework\MockObject\MockObject;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Unit tests for the CleanupService cf the Combodo Data Feature Removal module.
|
||||||
|
*
|
||||||
|
* These tests cover:
|
||||||
|
* - GetCleanupSummary method: handling null and empty input, and verifying summary output for various delete/update scenarios.
|
||||||
|
* - ExecuteCleanup method: confirming that an exception is thrown when issues are detected in the deletion plan.
|
||||||
|
*
|
||||||
|
* Key aspects tested:
|
||||||
|
* - Consistent singleton instance management.
|
||||||
|
* - Accurate summary generation for deletion and update operations, including mode and issue reporting per class.
|
||||||
|
* - Edge cases such as null, empty, and multiple classes.
|
||||||
|
* - Proper exception handling when the deletion plan contains issues.
|
||||||
|
*
|
||||||
|
* The tests use PHPUnit, mocks for Cleanup and CleanupService, and data providers to cover multiple scenarios.
|
||||||
|
*
|
||||||
|
* @see DataCleanupService
|
||||||
|
* @see DataCleanupSummaryEntity
|
||||||
|
* @see ItopDataTestCase
|
||||||
|
*/
|
||||||
|
class DataCleanupServiceTest extends ItopCustomDatamodelTestCase
|
||||||
|
{
|
||||||
|
private ExecutionLimits&MockObject $oExecutionLimits;
|
||||||
|
|
||||||
|
public function GetDatamodelDeltaAbsPath(): string
|
||||||
|
{
|
||||||
|
return __DIR__.'/data_cleanup_delta.xml';
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- GetCleanupSummary tests ---
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that GetCleanupSummary returns an empty array when passed null as input.
|
||||||
|
*/
|
||||||
|
public function testGetCleanupSummaryReturnsEmptyArrayWhenNull(): void
|
||||||
|
{
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$aResult = $oService->GetCleanupSummary(null);
|
||||||
|
|
||||||
|
$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.');
|
||||||
|
}
|
||||||
|
|
||||||
|
//--- ExecuteCleanup tests ---
|
||||||
|
|
||||||
|
public function testExecuteCleanup_DeleteOneObjPerClassWithoutLimit()
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||||
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||||
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$aRes = $oService->ExecuteCleanup($aClasses);
|
||||||
|
$aExpected = [
|
||||||
|
['DFRToUpdate', 1, 0 ],
|
||||||
|
['DFRToRemoveLeaf', 0, 1 ],
|
||||||
|
['DFRRemovedCollateral', 0, 1 ],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 1 ],
|
||||||
|
];
|
||||||
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecuteCleanup_DeleteManyObjPerClassWithoutLimit()
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||||
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||||
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||||
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||||
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||||
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||||
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$aRes = $oService->ExecuteCleanup($aClasses);
|
||||||
|
$aExpected = [
|
||||||
|
['DFRToUpdate', 3, 0 ],
|
||||||
|
['DFRToRemoveLeaf', 0, 3 ],
|
||||||
|
['DFRRemovedCollateral', 0, 3 ],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 3 ],
|
||||||
|
];
|
||||||
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCleanupSummary_DeleteManyObjPerClassWithoutLimit()
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||||
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||||
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||||
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||||
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||||
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||||
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||||
|
$aExpected = [
|
||||||
|
['DFRToUpdate', 3, 0 ],
|
||||||
|
['DFRToRemoveLeaf', 0, 3 ],
|
||||||
|
['DFRRemovedCollateral', 0, 3 ],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 3 ],
|
||||||
|
];
|
||||||
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testExecuteCleanup_ManualDeleteShouldFail()
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRManual_1
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$this->expectException(DataFeatureRemovalException::class);
|
||||||
|
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$oService->ExecuteCleanup($aClasses);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testGetCleanupSummary_ManualDeleteShouldFail()
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRManual_1
|
||||||
|
DFRToRemoveLeaf_1 <- DFRManual_2
|
||||||
|
DFRToRemoveLeaf_2 <- DFRManual_3
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$oService = new DataCleanupService();
|
||||||
|
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||||
|
$aExpected = [
|
||||||
|
['DFRManual', 0, 0, 3 ],
|
||||||
|
['DFRToRemoveLeaf', 0, 2],
|
||||||
|
];
|
||||||
|
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function AssertSummaryEquals(array $expected, $actual, $sMessage = '')
|
||||||
|
{
|
||||||
|
$aExpected = [];
|
||||||
|
foreach ($expected as $line) {
|
||||||
|
$sClass = $line[0];
|
||||||
|
$iUpdate = $line[1];
|
||||||
|
$iDelete = $line[2];
|
||||||
|
$iIssue = $line[3] ?? 0;
|
||||||
|
|
||||||
|
$oCleanupSummaryEntity = new DataCleanupSummaryEntity($sClass);
|
||||||
|
$oCleanupSummaryEntity->iUpdateCount = $iUpdate;
|
||||||
|
$oCleanupSummaryEntity->iDeleteCount = $iDelete;
|
||||||
|
$oCleanupSummaryEntity->iIssueCount = $iIssue;
|
||||||
|
$aExpected[$sClass] = $oCleanupSummaryEntity;
|
||||||
|
}
|
||||||
|
$this->assertEquals($aExpected, $actual, $sMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function ExecuteCleanup_StopInProcessKeepDatabaseOk(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'Stop after 1' => [
|
||||||
|
1,
|
||||||
|
[
|
||||||
|
['DFRToUpdate', 1, 0],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'Stop after 2' => [
|
||||||
|
2,
|
||||||
|
[
|
||||||
|
['DFRToUpdate', 1, 0],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 1],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'Stop after 3' => [
|
||||||
|
3,
|
||||||
|
[
|
||||||
|
['DFRToUpdate', 1, 0],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 1],
|
||||||
|
['DFRRemovedCollateral', 0, 1],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'Stop after 4' => [
|
||||||
|
4,
|
||||||
|
[
|
||||||
|
['DFRToUpdate', 1, 0],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 1],
|
||||||
|
['DFRRemovedCollateral', 0, 1],
|
||||||
|
['DFRToRemoveLeaf', 0, 1],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
'Stop after 5' => [
|
||||||
|
5,
|
||||||
|
[
|
||||||
|
['DFRToUpdate', 2, 0],
|
||||||
|
['DFRRemovedCollateralCascade', 0, 1],
|
||||||
|
['DFRRemovedCollateral', 0, 1],
|
||||||
|
['DFRToRemoveLeaf', 0, 1],
|
||||||
|
],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ExecuteCleanup_StopInProcessKeepDatabaseOk
|
||||||
|
*/
|
||||||
|
public function testExecuteCleanup_StopInProcessKeepDatabaseOk(int $iExecutionCount, array $aExpected): void
|
||||||
|
{
|
||||||
|
$this->GivenDFRTreeInDB(<<<EOF
|
||||||
|
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||||
|
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||||
|
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_2 <- DFRToUpdate_2
|
||||||
|
DFRToRemoveLeaf_2 <- DFRRemovedCollateral_2
|
||||||
|
DFRRemovedCollateral_2 <- DFRRemovedCollateralCascade_2
|
||||||
|
|
||||||
|
DFRToRemoveLeaf_3 <- DFRToUpdate_3
|
||||||
|
DFRToRemoveLeaf_3 <- DFRRemovedCollateral_3
|
||||||
|
DFRRemovedCollateral_3 <- DFRRemovedCollateralCascade_3
|
||||||
|
EOF);
|
||||||
|
|
||||||
|
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||||
|
$oDeletionPlaService = new DataCleanupService();
|
||||||
|
$this->GivenExecutionLimits($iExecutionCount);
|
||||||
|
$this->SetNonPublicProperty($oDeletionPlaService, 'oExecutionLimits', $this->oExecutionLimits);
|
||||||
|
$aRes = $oDeletionPlaService->ExecuteCleanup($aClasses);
|
||||||
|
$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)
|
||||||
|
{
|
||||||
|
list($sLeft, $sRight) = explode('<-', $sLine);
|
||||||
|
$sLeft = trim($sLeft);
|
||||||
|
|
||||||
|
$iLeftId = $this->aIdByObjectName[$sLeft] ?? 0;
|
||||||
|
if ($iLeftId === 0) {
|
||||||
|
list($sChildClass, ) = explode('_', $sLeft, 2);
|
||||||
|
$iLeftId = $this->GivenObjectInDB($sChildClass, ['name' => $sLeft]);
|
||||||
|
$this->aIdByObjectName[$sLeft] = $iLeftId;
|
||||||
|
}
|
||||||
|
|
||||||
|
$sRight = trim($sRight);
|
||||||
|
list($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();
|
||||||
|
|
||||||
|
$this->oExecutionLimits = $this->createMock(ExecutionLimits::class);
|
||||||
|
$this->oExecutionLimits->expects($matcher)
|
||||||
|
->method('ShouldStopExecution')->willReturnCallback(function () use ($matcher, $iStopAfterCallNumberReached) {
|
||||||
|
$invocationCount = $matcher->getInvocationCount();
|
||||||
|
|
||||||
|
return ($invocationCount >= $iStopAfterCallNumberReached);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,277 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
/*
|
|
||||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
|
||||||
* @license http://opensource.org/licenses/AGPL-3.0
|
|
||||||
*/
|
|
||||||
|
|
||||||
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval\Service;
|
|
||||||
|
|
||||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanSummaryEntity;
|
|
||||||
use Combodo\iTop\DataFeatureRemoval\Helper\DataFeatureRemovalException;
|
|
||||||
use Combodo\iTop\DataFeatureRemoval\Service\DeletionPlanService;
|
|
||||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
|
||||||
use DeletionPlan;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Unit tests for the DeletionPlanService class in the Combodo Data Feature Removal module.
|
|
||||||
*
|
|
||||||
* These tests cover:
|
|
||||||
* - GetDeletionPlanSummary method: handling null and empty input, and verifying summary output for various delete/update scenarios.
|
|
||||||
* - ExecuteDeletionPlan method: confirming that an exception is thrown when issues are detected in the deletion plan.
|
|
||||||
*
|
|
||||||
* Key aspects tested:
|
|
||||||
* - Consistent singleton instance management.
|
|
||||||
* - Accurate summary generation for deletion and update operations, including mode and issue reporting per class.
|
|
||||||
* - Edge cases such as null, empty, and multiple classes.
|
|
||||||
* - Proper exception handling when the deletion plan contains issues.
|
|
||||||
*
|
|
||||||
* The tests use PHPUnit, mocks for DeletionPlan and DeletionPlanService, and data providers to cover multiple scenarios.
|
|
||||||
*
|
|
||||||
* @see DeletionPlanService
|
|
||||||
* @see DeletionPlanSummaryEntity
|
|
||||||
* @see ItopDataTestCase
|
|
||||||
*/
|
|
||||||
class DeletionPlanServiceTest extends ItopDataTestCase
|
|
||||||
{
|
|
||||||
protected function setUp(): void
|
|
||||||
{
|
|
||||||
parent::setUp();
|
|
||||||
$this->RequireOnceItopFile('env-production/combodo-data-feature-removal/vendor/autoload.php');
|
|
||||||
}
|
|
||||||
|
|
||||||
//--- GetDeletionPlanSummary tests ---
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests that GetDeletionPlanSummary returns an empty array when passed null as input.
|
|
||||||
*/
|
|
||||||
public function testGetDeletionPlanSummaryReturnsEmptyArrayWhenNull(): void
|
|
||||||
{
|
|
||||||
$oService = DeletionPlanService::GetInstance();
|
|
||||||
$aResult = $oService->GetDeletionPlanSummary(null);
|
|
||||||
|
|
||||||
$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.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests that GetDeletionPlanSummary returns an empty array when the input class list is empty.
|
|
||||||
*/
|
|
||||||
public function testGetDeletionPlanSummaryReturnsEmptyArrayWhenEmptyClasses(): void
|
|
||||||
{
|
|
||||||
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->onlyMethods(['GetDeletionPlan'])
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$oDeletionPlan = new DeletionPlan();
|
|
||||||
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
|
|
||||||
|
|
||||||
DeletionPlanService::SetInstance($oMockService);
|
|
||||||
|
|
||||||
$aResult = $oMockService->GetDeletionPlanSummary([]);
|
|
||||||
|
|
||||||
$this->assertIsArray($aResult, 'Expected result to be an array when input class list is empty.');
|
|
||||||
$this->assertEmpty($aResult, 'Expected result to be empty array when input class list is empty.');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests GetDeletionPlanSummary for various delete/update scenarios using a data provider.
|
|
||||||
* Verifies summary output for each class matches expected values.
|
|
||||||
*
|
|
||||||
* @dataProvider GetDeletionPlanSummaryWithDeletesProvider
|
|
||||||
*/
|
|
||||||
public function testGetDeletionPlanSummaryWithDeletes(array $aToDelete, array $aToUpdate, array $aExpected): void
|
|
||||||
{
|
|
||||||
$oDeletionPlan = $this->createMock(DeletionPlan::class);
|
|
||||||
$oDeletionPlan->method('ListDeletes')->willReturn($aToDelete);
|
|
||||||
$oDeletionPlan->method('ListUpdates')->willReturn($aToUpdate);
|
|
||||||
|
|
||||||
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->onlyMethods(['GetDeletionPlan'])
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
|
|
||||||
|
|
||||||
$aResult = $oMockService->GetDeletionPlanSummary(['SomeClass']);
|
|
||||||
|
|
||||||
foreach ($aExpected as $sClass => $aExpectedValues) {
|
|
||||||
$this->assertArrayHasKey(
|
|
||||||
$sClass,
|
|
||||||
$aResult,
|
|
||||||
"Expected key '$sClass' to exist in summary."
|
|
||||||
);
|
|
||||||
$this->assertInstanceOf(
|
|
||||||
DeletionPlanSummaryEntity::class,
|
|
||||||
$aResult[$sClass],
|
|
||||||
"Expected summary for '$sClass' to be instance of DeletionPlanSummaryEntity."
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$aExpectedValues['iDeleteCount'],
|
|
||||||
$aResult[$sClass]->iDeleteCount,
|
|
||||||
"Expected iDeleteCount for '$sClass' to be {$aExpectedValues['iDeleteCount']}, got {$aResult[$sClass]->iDeleteCount}."
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$aExpectedValues['iUpdateCount'],
|
|
||||||
$aResult[$sClass]->iUpdateCount,
|
|
||||||
"Expected iUpdateCount for '$sClass' to be {$aExpectedValues['iUpdateCount']}, got {$aResult[$sClass]->iUpdateCount}."
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$aExpectedValues['iMode'],
|
|
||||||
$aResult[$sClass]->iMode,
|
|
||||||
"Expected iMode for '$sClass' to be {$aExpectedValues['iMode']}, got {$aResult[$sClass]->iMode}."
|
|
||||||
);
|
|
||||||
$this->assertEquals(
|
|
||||||
$aExpectedValues['sIssue'],
|
|
||||||
$aResult[$sClass]->sIssue,
|
|
||||||
"Expected sIssue for '$sClass' to be '{$aExpectedValues['sIssue']}', got '{$aResult[$sClass]->sIssue}'."
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides multiple scenarios for testGetDeletionPlanSummaryWithDeletes, including deletes, updates, issues, and multiple classes.
|
|
||||||
*
|
|
||||||
* @return array
|
|
||||||
*/
|
|
||||||
public function GetDeletionPlanSummaryWithDeletesProvider(): array
|
|
||||||
{
|
|
||||||
return [
|
|
||||||
'single class with deletes only' => [
|
|
||||||
'aToDelete' => [
|
|
||||||
'Server' => [
|
|
||||||
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
|
|
||||||
2 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aToUpdate' => [],
|
|
||||||
'aExpected' => [
|
|
||||||
'Server' => [
|
|
||||||
'iDeleteCount' => 2,
|
|
||||||
'iUpdateCount' => 0,
|
|
||||||
'iMode' => DEL_AUTO,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'single class with deletes and issue' => [
|
|
||||||
'aToDelete' => [
|
|
||||||
'Server' => [
|
|
||||||
1 => ['to_delete' => null, 'mode' => DEL_MANUAL, 'requested_explicitely' => false, 'issue' => 'Cannot delete'],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aToUpdate' => [],
|
|
||||||
'aExpected' => [
|
|
||||||
'Server' => [
|
|
||||||
'iDeleteCount' => 1,
|
|
||||||
'iUpdateCount' => 0,
|
|
||||||
'iMode' => DEL_MANUAL,
|
|
||||||
'sIssue' => 'Cannot delete',
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'single class with updates only' => [
|
|
||||||
'aToDelete' => [],
|
|
||||||
'aToUpdate' => [
|
|
||||||
'Person' => [
|
|
||||||
10 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
|
|
||||||
11 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
|
|
||||||
12 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aExpected' => [
|
|
||||||
'Person' => [
|
|
||||||
'iDeleteCount' => 0,
|
|
||||||
'iUpdateCount' => 3,
|
|
||||||
'iMode' => 0,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'class with both deletes and updates' => [
|
|
||||||
'aToDelete' => [
|
|
||||||
'Server' => [
|
|
||||||
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aToUpdate' => [
|
|
||||||
'Server' => [
|
|
||||||
5 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aExpected' => [
|
|
||||||
'Server' => [
|
|
||||||
'iDeleteCount' => 1,
|
|
||||||
'iUpdateCount' => 1,
|
|
||||||
'iMode' => DEL_AUTO,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'multiple classes' => [
|
|
||||||
'aToDelete' => [
|
|
||||||
'Server' => [
|
|
||||||
1 => ['to_delete' => null, 'mode' => DEL_AUTO, 'requested_explicitely' => true],
|
|
||||||
],
|
|
||||||
'NetworkDevice' => [
|
|
||||||
3 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
|
|
||||||
4 => ['to_delete' => null, 'mode' => DEL_SILENT, 'requested_explicitely' => false],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aToUpdate' => [
|
|
||||||
'Person' => [
|
|
||||||
10 => ['to_reset' => null, 'attributes' => ['org_id' => null]],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
'aExpected' => [
|
|
||||||
'Server' => [
|
|
||||||
'iDeleteCount' => 1,
|
|
||||||
'iUpdateCount' => 0,
|
|
||||||
'iMode' => DEL_AUTO,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
'NetworkDevice' => [
|
|
||||||
'iDeleteCount' => 2,
|
|
||||||
'iUpdateCount' => 0,
|
|
||||||
'iMode' => DEL_SILENT,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
'Person' => [
|
|
||||||
'iDeleteCount' => 0,
|
|
||||||
'iUpdateCount' => 1,
|
|
||||||
'iMode' => 0,
|
|
||||||
'sIssue' => null,
|
|
||||||
],
|
|
||||||
],
|
|
||||||
],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
//--- ExecuteDeletionPlan tests ---
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests that ExecuteDeletionPlan throws a DataFeatureRemovalException when the deletion plan contains issues.
|
|
||||||
*/
|
|
||||||
public function testExecuteDeletionPlanThrowsExceptionWhenIssuesExist(): void
|
|
||||||
{
|
|
||||||
$this->RequireOnceItopFile('env-production/combodo-data-feature-removal/src/Helper/DataFeatureRemovalException.php');
|
|
||||||
|
|
||||||
$oDeletionPlan = $this->createMock(DeletionPlan::class);
|
|
||||||
$oDeletionPlan->method('GetIssues')->willReturn(['Some issue']);
|
|
||||||
$oDeletionPlan->method('ListDeletes')->willReturn([]);
|
|
||||||
$oDeletionPlan->method('ListUpdates')->willReturn([]);
|
|
||||||
|
|
||||||
$oMockService = $this->getMockBuilder(DeletionPlanService::class)
|
|
||||||
->disableOriginalConstructor()
|
|
||||||
->onlyMethods(['GetDeletionPlan'])
|
|
||||||
->getMock();
|
|
||||||
|
|
||||||
$oMockService->method('GetDeletionPlan')->willReturn($oDeletionPlan);
|
|
||||||
|
|
||||||
$this->expectException(DataFeatureRemovalException::class);
|
|
||||||
$this->expectExceptionMessage('Deletion Plan cannot be executed due to issues');
|
|
||||||
|
|
||||||
$oMockService->ExecuteDeletionPlan(['SomeClass']);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,346 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<itop_design xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="3.2">
|
||||||
|
<classes>
|
||||||
|
<class id="DFRToRemove" _created_in="itop-structure" _delta="define">
|
||||||
|
<properties>
|
||||||
|
<category>bizmodel,searchable</category>
|
||||||
|
<abstract>true</abstract>
|
||||||
|
<db_table>dfrtoremove</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="DFRToUpdate" _created_in="itop-structure" _delta="define">
|
||||||
|
<properties>
|
||||||
|
<category>bizmodel,searchable</category>
|
||||||
|
<abstract>false</abstract>
|
||||||
|
<db_table>dfrtoupdate</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_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="DFRRemovedCollateral" _created_in="itop-structure" _delta="define">
|
||||||
|
<properties>
|
||||||
|
<category>bizmodel,searchable</category>
|
||||||
|
<abstract>false</abstract>
|
||||||
|
<db_table>dfrremovedcollateral</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>false</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>
|
||||||
|
<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="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="DFRRemovedCollateralCascade" _created_in="itop-structure" _delta="define">
|
||||||
|
<properties>
|
||||||
|
<category>bizmodel,searchable</category>
|
||||||
|
<abstract>false</abstract>
|
||||||
|
<db_table>dfrremovedcollateralcascade</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>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>
|
||||||
|
<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="DFRManual" _created_in="itop-structure" _delta="define">
|
||||||
|
<properties>
|
||||||
|
<category>bizmodel,searchable</category>
|
||||||
|
<abstract>false</abstract>
|
||||||
|
<db_table>dfrmanual</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>false</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>
|
||||||
|
</classes>
|
||||||
|
<dictionaries>
|
||||||
|
<dictionary id="EN US">
|
||||||
|
<entries>
|
||||||
|
<entry id="Class:DFRToRemove/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemove/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemove" _delta="define"><![CDATA[DFRToRemove]]></entry>
|
||||||
|
<entry id="Class:DFRToRemove+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemove/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||||
|
<entry id="Class:DFRToRemove/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate" _delta="define"><![CDATA[DFRToUpdate]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||||
|
<entry id="Class:DFRToUpdate/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral" _delta="define"><![CDATA[DFRRemovedCollateral]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id" _delta="define"><![CDATA[Dfrtoremove id]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateral/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf" _delta="define"><![CDATA[DFRToRemoveLeaf]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf/Attribute:desc" _delta="define"><![CDATA[Desc]]></entry>
|
||||||
|
<entry id="Class:DFRToRemoveLeaf/Attribute:desc+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade" _delta="define"><![CDATA[DFRRemovedCollateralCascade]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/Attribute:name+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id" _delta="define"><![CDATA[Dfrremovedcollateral id]]></entry>
|
||||||
|
<entry id="Class:DFRRemovedCollateralCascade/Attribute:extkey_id+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRManual/Name" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRManual/ComplementaryName" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRManual" _delta="define"><![CDATA[DFRManual]]></entry>
|
||||||
|
<entry id="Class:DFRManual+" _delta="define"><![CDATA[]]></entry>
|
||||||
|
<entry id="Class:DFRManual/Attribute:name" _delta="define"><![CDATA[Name]]></entry>
|
||||||
|
<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>
|
||||||
|
</entries>
|
||||||
|
</dictionary>
|
||||||
|
</dictionaries>
|
||||||
|
</itop_design>
|
||||||
Reference in New Issue
Block a user