mirror of
https://github.com/Combodo/iTop.git
synced 2026-07-09 08:16:44 +02:00
Compare commits
8 Commits
feature/96
...
issue/9746
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
50c1807824 | ||
|
|
445b77f162 | ||
|
|
2ba13bb7f8 | ||
|
|
2604881303 | ||
|
|
3a7439e970 | ||
|
|
7fca6424ab | ||
|
|
f09a73c45a | ||
|
|
5a174dd8fa |
@@ -582,16 +582,26 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
*/
|
||||
public function ListProfiles($oUser)
|
||||
{
|
||||
$aRet = [];
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch()) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
if (count($oUser->ListChanges()) === 0) { // backward compatibility
|
||||
$aRet = [];
|
||||
$oSearch = new DBObjectSearch('URP_UserProfile');
|
||||
$oSearch->AllowAllData();
|
||||
$oSearch->NoContextParameters();
|
||||
$oSearch->Addcondition('userid', $oUser->GetKey(), '=');
|
||||
$oProfiles = new DBObjectSet($oSearch);
|
||||
while ($oUserProfile = $oProfiles->Fetch()) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
|
||||
return $aRet;
|
||||
} else {
|
||||
$aRet = [];
|
||||
$oProfilesSet = $oUser->Get('profile_list');
|
||||
foreach ($oProfilesSet as $oUserProfile) {
|
||||
$aRet[$oUserProfile->Get('profileid')] = $oUserProfile->Get('profileid_friendlyname');
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
return $aRet;
|
||||
}
|
||||
|
||||
public function GetSelectFilter($oUser, $sClass, $aSettings = [])
|
||||
@@ -705,26 +715,23 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
protected function GetUserActionGrant($oUser, $sClass, $iActionCode)
|
||||
{
|
||||
$this->LoadCache();
|
||||
|
||||
// load and cache permissions for the current user on the given class
|
||||
//
|
||||
$iUser = $oUser->GetKey();
|
||||
if (isset($this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode])) {
|
||||
$aTest = $this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) {
|
||||
return $aTest;
|
||||
if (count($oUser->ListChanges()) === 0) {
|
||||
// load and cache permissions for the current user on the given class
|
||||
if (isset($this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode])) {
|
||||
$aTest = $this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode];
|
||||
if (is_array($aTest)) {
|
||||
return $aTest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$sAction = self::$m_aActionCodes[$iActionCode];
|
||||
|
||||
$bStatus = null;
|
||||
// Cache user's profiles
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
|
||||
$aProfileList = $this->GetProfileList($oUser);
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) {
|
||||
foreach ($aProfileList as $iProfile => $oProfile) {
|
||||
$bGrant = $this->GetProfileActionGrant($iProfile, $sClass, $sAction);
|
||||
if (!is_null($bGrant)) {
|
||||
if ($bGrant) {
|
||||
@@ -742,7 +749,9 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
$aRes = [
|
||||
'permission' => $iPermission,
|
||||
];
|
||||
$this->m_aObjectActionGrants[$iUser][$sClass][$iActionCode] = $aRes;
|
||||
if (count($oUser->ListChanges()) === 0) {
|
||||
$this->m_aObjectActionGrants[$oUser->GetKey()][$sClass][$iActionCode] = $aRes;
|
||||
}
|
||||
return $aRes;
|
||||
}
|
||||
|
||||
@@ -824,18 +833,14 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
{
|
||||
$this->LoadCache();
|
||||
// Note: this code is VERY close to the code of IsActionAllowed()
|
||||
$iUser = $oUser->GetKey();
|
||||
|
||||
// Cache user's profiles
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$this->aUsersProfilesList[$iUser] = UserRights::ListProfiles($oUser);
|
||||
}
|
||||
$aProfileList = $this->GetProfileList($oUser);
|
||||
|
||||
// Note: The object set is ignored because it was interesting to optimize for huge data sets
|
||||
// and acceptable to consider only the root class of the object set
|
||||
$bStatus = null;
|
||||
// Call the API of UserRights because it caches the list for us
|
||||
foreach ($this->aUsersProfilesList[$iUser] as $iProfile => $oProfile) {
|
||||
foreach ($aProfileList as $iProfile => $oProfile) {
|
||||
$bGrant = $this->GetClassStimulusGrant($iProfile, $sClass, $sStimulusCode);
|
||||
if (!is_null($bGrant)) {
|
||||
if ($bGrant) {
|
||||
@@ -893,6 +898,25 @@ class UserRightsProfile extends UserRightsAddOnAPI
|
||||
}
|
||||
return $bHasSharing;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \User $oUser
|
||||
*
|
||||
* @return array
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetProfileList(User $oUser): array
|
||||
{
|
||||
if (count($oUser->ListChanges()) === 0) { // if user is already in db and not changed
|
||||
$iUser = $oUser->GetKey();
|
||||
if (false === array_key_exists($iUser, $this->aUsersProfilesList)) {
|
||||
$aProfiles = UserRights::ListProfiles($oUser);
|
||||
$this->aUsersProfilesList[$iUser] = $aProfiles;
|
||||
}
|
||||
return $this->aUsersProfilesList[$iUser];
|
||||
}
|
||||
return UserRights::ListProfiles($oUser);
|
||||
}
|
||||
}
|
||||
|
||||
UserRights::SelectModule('UserRightsProfile');
|
||||
|
||||
@@ -1530,6 +1530,37 @@ class UserRights
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param User $oUser
|
||||
* @param array $aExcludedProfilesId Administrator by default, but can also be other proofiles depending on needs (e.g. power portal user or REST profile)
|
||||
* @return bool
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws CoreUnexpectedValue
|
||||
* @throws MySQLException
|
||||
*/
|
||||
public static function IsUserReadOnly(User $oUser, array $aExcludedProfilesId = [1]): bool
|
||||
{
|
||||
$oUserProfiles = $oUser->Get('profile_list');
|
||||
$oUserRights = UserRights::GetModuleInstance();
|
||||
while ($oUserProfile = $oUserProfiles->Fetch()) {
|
||||
$iProfileId = $oUserProfile->Get('profileid');
|
||||
if (in_array($iProfileId, $aExcludedProfilesId)) {
|
||||
return false;
|
||||
}
|
||||
foreach (MetaModel::GetClasses('bizmodel,grant_by_profile') as $sClass) {
|
||||
foreach (['w', 'bw', 'd', 'bd'] as $sWriteActionCode) {
|
||||
$bIsGranted = $oUserRights->GetProfileActionGrant($iProfileId, $sClass, $sWriteActionCode);
|
||||
if ($bIsGranted === true) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sClass
|
||||
* @param int $iActionCode see UR_ACTION_* constants
|
||||
|
||||
@@ -7,31 +7,22 @@
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Entity;
|
||||
|
||||
use MetaModel;
|
||||
|
||||
class DeletionPlanEntity
|
||||
{
|
||||
public readonly DeletionPlanItem $oDelete;
|
||||
public readonly DeletionPlanItem $oUpdate;
|
||||
public readonly DeletionPlanItem $oIssue;
|
||||
|
||||
public array $aQueriesForTempTable;
|
||||
public array $aDependsOnTempTable;
|
||||
public string $sClass;
|
||||
|
||||
/**
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oDelete
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oUpdate
|
||||
* @param \Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanItem $oIssue
|
||||
*/
|
||||
public function __construct(string $sClass)
|
||||
public function __construct()
|
||||
{
|
||||
$this->sClass = $sClass;
|
||||
$this->oDelete = $oDelete ?? new DeletionPlanItem();
|
||||
$this->oUpdate = $oUpdate ?? new DeletionPlanItem();
|
||||
$this->oIssue = $oIssue ?? new DeletionPlanItem();
|
||||
$this->aQueriesForTempTable = [];
|
||||
$this->aDependsOnTempTable = [];
|
||||
}
|
||||
|
||||
public function TotalCount(): int
|
||||
@@ -43,20 +34,4 @@ class DeletionPlanEntity
|
||||
{
|
||||
$this->oUpdate->FilterBy($this->oDelete);
|
||||
}
|
||||
|
||||
public function AddQueryForTempTable(string $sQuery, ?string $sTempTableName = null): void
|
||||
{
|
||||
if (!in_array($sQuery, $this->aQueriesForTempTable)) {
|
||||
$this->aQueriesForTempTable[] = $sQuery;
|
||||
if (!is_null($sTempTableName) && !in_array($sTempTableName, $this->aDependsOnTempTable)) {
|
||||
$this->aDependsOnTempTable[] = $sTempTableName;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static function GetTempTableName(string $sClass): string
|
||||
{
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
return 'temp_'.MetaModel::DBGetTable($sRootClass);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
namespace Combodo\iTop\DataFeatureRemoval\Service;
|
||||
|
||||
use AttributeExternalKey;
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DeletionPlanEntity;
|
||||
@@ -49,70 +48,6 @@ class StaticDeletionPlan
|
||||
return $aSummary;
|
||||
}
|
||||
|
||||
public function GetTempTableDefinitions(array $aClasses): array
|
||||
{
|
||||
$aTempTables = [];
|
||||
$aDeletionPlan = $this->GetStaticDeletionPlan($aClasses ?? []);
|
||||
|
||||
foreach ($aDeletionPlan as $sClass => $oDeletionPlanEntity) {
|
||||
$sTempTableName = DeletionPlanEntity::GetTempTableName($sClass);
|
||||
$aTempTables[$sTempTableName]['name'] = $sTempTableName;
|
||||
$aTempTables[$sTempTableName]['queries'] = array_unique(array_merge($aTempTables[$sTempTableName]['queries'] ?? [], $oDeletionPlanEntity->aQueriesForTempTable));
|
||||
$aTempTables[$sTempTableName]['depends_on'] = array_unique(array_merge($aTempTables[$sTempTableName]['depends_on'] ?? [], $oDeletionPlanEntity->aDependsOnTempTable));
|
||||
}
|
||||
|
||||
usort($aTempTables, function ($a, $b) use ($aTempTables) {
|
||||
if (empty($a['depends_on']) && empty($b['depends_on'])) {
|
||||
// Both initial classes
|
||||
return 0;
|
||||
}
|
||||
return $this->CompareEntries($a, $b, $aTempTables);
|
||||
});
|
||||
|
||||
$aTableDefinitions = [];
|
||||
|
||||
foreach ($aTempTables as $aTempTable) {
|
||||
if (count($aTempTable['queries']) > 0) {
|
||||
$TempTableSelect = implode("\nUNION\n", $aTempTable['queries']);
|
||||
$sTempTableName = $aTempTable['name'];
|
||||
$aTableDefinitions[$sTempTableName] = [
|
||||
"DROP TEMPORARY TABLE IF EXISTS `$sTempTableName`",
|
||||
"CREATE TEMPORARY TABLE `$sTempTableName` ($TempTableSelect)",
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return $aTableDefinitions;
|
||||
}
|
||||
|
||||
/**
|
||||
* tells if name1 depends on name2
|
||||
* @param string $sName1
|
||||
* @param string $sName2
|
||||
* @param array $aAllTables
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private function DependsOn(string $sName1, string $sName2, array $aAllTables): bool
|
||||
{
|
||||
$aDependsOn = $aAllTables[$sName1]['depends_on'] ?? [];
|
||||
if (in_array($sName2, $aDependsOn)) {
|
||||
return true;
|
||||
}
|
||||
// Search on step further
|
||||
foreach ($aAllTables[$sName1]['depends_on'] as $sParent) {
|
||||
if ($this->DependsOn($sParent, $sName2, $aAllTables)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public function CompareEntries(array $aTable1, array $aTable2, array $aAllTables): int
|
||||
{
|
||||
return $this->DependsOn($aTable1['name'], $aTable2['name'], $aAllTables) ? 1 : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aClasses Classes to clean entirely
|
||||
*
|
||||
@@ -124,9 +59,8 @@ class StaticDeletionPlan
|
||||
{
|
||||
foreach ($aClasses as $sClass) {
|
||||
$oDeletionPlanItem = $this->GetInitialClassDeletionPlan($sClass);
|
||||
$oDeletionPlanEntity = new DeletionPlanEntity($sClass);
|
||||
$oDeletionPlanEntity = new DeletionPlanEntity();
|
||||
$oDeletionPlanEntity->oDelete->Merge($oDeletionPlanItem);
|
||||
$oDeletionPlanEntity->AddQueryForTempTable($this->GetQueryForInitialClass($sClass));
|
||||
$this->aDeletionPlan[$sClass] = $oDeletionPlanEntity;
|
||||
|
||||
$this->DeletionPlanForReferencingClasses($sClass);
|
||||
@@ -141,7 +75,7 @@ class StaticDeletionPlan
|
||||
$aReferencingMe = MetaModel::EnumReferencingClasses($sClass);
|
||||
foreach ($aReferencingMe as $sRemoteClass => $aExtKeys) {
|
||||
if (!isset($this->aDeletionPlan[$sRemoteClass])) {
|
||||
$this->aDeletionPlan[$sRemoteClass] = new DeletionPlanEntity($sRemoteClass);
|
||||
$this->aDeletionPlan[$sRemoteClass] = new DeletionPlanEntity();
|
||||
}
|
||||
$oDeletionPlanEntity = $this->aDeletionPlan[$sRemoteClass];
|
||||
/** @var \AttributeExternalKey $oExtKeyAttDef */
|
||||
@@ -162,7 +96,7 @@ class StaticDeletionPlan
|
||||
$iDeletePropagationOption = $oExtKeyAttDef->GetDeletionPropagationOption();
|
||||
if ($iDeletePropagationOption == DEL_MANUAL) {
|
||||
// Issue, do not recurse
|
||||
$oDeletionPlanItem = new DeletionPlanItem($aRemoteIdsToRemove);
|
||||
$oDeletionPlanItem = new DeletionPlanItem(aIds: $aRemoteIdsToRemove);
|
||||
$oDeletionPlanEntity->oIssue->Merge($oDeletionPlanItem);
|
||||
continue;
|
||||
}
|
||||
@@ -172,16 +106,14 @@ class StaticDeletionPlan
|
||||
$sTargetIdsToRemove = implode(',', $this->aDeletionPlan[$sRemoteClass]->oDelete->aIds);
|
||||
$oUpdateItem = $this->UpdateHierarchicalExtKey($sRemoteClass, $sExtKeyAttCode, $sTargetIdsToRemove);
|
||||
$oDeletionPlanEntity->oUpdate->Merge($oUpdateItem);
|
||||
|
||||
// Delete current entry an recurse !
|
||||
}
|
||||
|
||||
// Delete entries in Remote Class
|
||||
[$sQueryForTempTable, $sDependsOnTempTable] = $this->GetQueryForExtKey($sRemoteClass, $oExtKeyAttDef);
|
||||
$oDeletionPlanEntity->AddQueryForTempTable($sQueryForTempTable, $sDependsOnTempTable);
|
||||
$oDeletionPlanEntity->oDelete->Merge(new DeletionPlanItem($aRemoteIdsToRemove));
|
||||
// Infinite loops do not occurs due to the datamodel structure
|
||||
$this->DeletionPlanForReferencingClasses($sRemoteClass);
|
||||
if (count($aRemoteIdsToRemove) !== 0) {
|
||||
$oDeletionPlanEntity->oDelete->Merge(new DeletionPlanItem($aRemoteIdsToRemove));
|
||||
// Infinite loops do not occurs due to the datamodel structure
|
||||
$this->DeletionPlanForReferencingClasses($sRemoteClass);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -213,12 +145,12 @@ class StaticDeletionPlan
|
||||
*/
|
||||
public function UpdateHierarchicalExtKey(string $sRemoteClass, string $sExtKeyAttCode, string $sIdsToRemoveInTargetClass): DeletionPlanItem
|
||||
{
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sRemoteClass, $sExtKeyAttCode);
|
||||
[$sDBTable, $sDBField, $sDBKey] = $this->GetDBInfoForAttcode($sRemoteClass, $sExtKeyAttCode);
|
||||
|
||||
$sSQL = <<<SQL
|
||||
SELECT `$sDBKey`
|
||||
FROM `$sDBTable` AS `updated`
|
||||
INNER JOIN `$sDBTable` AS `removed` ON `updated`.`$sDBField` = `removed`.`$sDBField`
|
||||
INNER JOIN `$sDBTable` AS `removed` ON `updated`.`$sDBField` = `removed`.`$sDBKey`
|
||||
WHERE `removed`.`$sDBKey` IN ($sIdsToRemoveInTargetClass)
|
||||
SQL;
|
||||
$aIds = CMDBSource::QueryToCol($sSQL, $sDBKey);
|
||||
@@ -240,10 +172,10 @@ SQL;
|
||||
if (\utils::IsNullOrEmptyString($sIdsToRemoveInTargetClass)) {
|
||||
return [];
|
||||
}
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sRemoteClass, $sExtKeyAttCode);
|
||||
$sSQL = "SELECT `$sDBKey` AS id FROM `$sDBTable` WHERE `$sDBField` IN ($sIdsToRemoveInTargetClass)";
|
||||
[$sDBTable, $sDBField, $sDBKey] = $this->GetDBInfoForAttcode($sRemoteClass, $sExtKeyAttCode);
|
||||
$sSQL = "SELECT `$sDBKey` FROM `$sDBTable` WHERE `$sDBField` IN ($sIdsToRemoveInTargetClass)";
|
||||
|
||||
return CMDBSource::QueryToCol($sSQL, 'id');
|
||||
return CMDBSource::QueryToCol($sSQL, $sDBKey);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -255,9 +187,9 @@ SQL;
|
||||
*/
|
||||
public function GetInitialClassDeletionPlan(string $sClass): DeletionPlanItem
|
||||
{
|
||||
[$sDBTable, $sDBKey] = $this->GetDBInfo($sClass);
|
||||
|
||||
$sSQL = "SELECT `$sDBKey` FROM `$sDBTable`";
|
||||
$sTable = MetaModel::DBGetTable($sClass);
|
||||
$sDBKey = MetaModel::DBGetKey($sClass);
|
||||
$sSQL = "SELECT `$sDBKey` FROM `$sTable`";
|
||||
$aIds = CMDBSource::QueryToCol($sSQL, $sDBKey);
|
||||
|
||||
return new DeletionPlanItem($aIds);
|
||||
@@ -273,47 +205,15 @@ SQL;
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function GetDBInfo(string $sClass, ?string $sExtKeyAttCode = null): array
|
||||
public function GetDBInfoForAttcode(string $sClass, string $sExtKeyAttCode): array
|
||||
{
|
||||
if (!is_null($sExtKeyAttCode)) {
|
||||
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode);
|
||||
|
||||
$sDBTable = MetaModel::DBGetTable($sOriginClass);
|
||||
$sDBKey = MetaModel::DBGetKey($sOriginClass);
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sExtKeyAttCode);
|
||||
// External key is on a single DB column
|
||||
$sDBField = array_keys($oAttDef->GetSQLColumns())[0];
|
||||
|
||||
return [$sDBTable, $sDBKey, $sDBField];
|
||||
}
|
||||
|
||||
$sDBTable = MetaModel::DBGetTable($sClass);
|
||||
$sOriginClass = MetaModel::GetAttributeOrigin($sClass, $sExtKeyAttCode);
|
||||
$sDBTable = MetaModel::DBGetTable($sOriginClass);
|
||||
$oAttDef = MetaModel::GetAttributeDef($sOriginClass, $sExtKeyAttCode);
|
||||
// External key is on a single DB column
|
||||
$sDBField = array_keys($oAttDef->GetSQLColumns())[0];
|
||||
$sDBKey = MetaModel::DBGetKey($sClass);
|
||||
|
||||
return [$sDBTable, $sDBKey];
|
||||
return [$sDBTable, $sDBField, $sDBKey];
|
||||
}
|
||||
|
||||
private function GetQueryForInitialClass(mixed $sClass): string
|
||||
{
|
||||
[$sDBTable, $sDBKey] = $this->GetDBInfo($sClass);
|
||||
|
||||
return "SELECT `$sDBKey` AS id FROM `$sDBTable`";
|
||||
}
|
||||
|
||||
private function GetQueryForExtKey(string $sClass, AttributeExternalKey $oExtKeyAttDef)
|
||||
{
|
||||
$sExtKeyAttCode = $oExtKeyAttDef->GetCode();
|
||||
[$sDBTable, $sDBKey, $sDBField] = $this->GetDBInfo($sClass, $sExtKeyAttCode);
|
||||
|
||||
$sTargetClass = $oExtKeyAttDef->GetTargetClass();
|
||||
$sTempTable = DeletionPlanEntity::GetTempTableName($sTargetClass);
|
||||
|
||||
$sQuery = <<<SQL
|
||||
SELECT `$sDBTable`.`$sDBKey` AS id FROM `$sDBTable`
|
||||
INNER JOIN `$sTempTable` ON `$sTempTable`.`id` = `$sDBTable`.`$sDBField`
|
||||
SQL;
|
||||
|
||||
return [$sQuery, $sTempTable];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,6 +85,13 @@
|
||||
<class id="Attachment"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Ticket" _delta="define">
|
||||
<classes>
|
||||
<class id="Ticket"/>
|
||||
<class id="WorkOrder"/>
|
||||
<class id="Attachment"/>
|
||||
</classes>
|
||||
</group>
|
||||
<group id="Portal" _delta="define">
|
||||
<classes>
|
||||
<class id="lnkFunctionalCIToTicket"/>
|
||||
@@ -205,6 +212,60 @@
|
||||
</group>
|
||||
</groups>
|
||||
<profiles>
|
||||
<profile id="5500" _delta="define">
|
||||
<name>Configuration ReadOnly</name>
|
||||
<description>This read-only profile allows to see CIs objects.</description>
|
||||
<groups>
|
||||
<group id="Configuration">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="General">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="5501" _delta="define">
|
||||
<name>Ticket ReadOnly</name>
|
||||
<description>This read-only profile allows to see Ticket objects.</description>
|
||||
<groups>
|
||||
<group id="Ticket">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="General">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="5502" _delta="define">
|
||||
<name>Service Catalog ReadOnly</name>
|
||||
<description>This read-only profile allows to see Service Catalog objects.</description>
|
||||
<groups>
|
||||
<group id="Service">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
<group id="General">
|
||||
<actions>
|
||||
<action id="action:read">allow</action>
|
||||
<action id="action:bulk read">allow</action>
|
||||
</actions>
|
||||
</group>
|
||||
</groups>
|
||||
</profile>
|
||||
<profile id="117" _delta="define">
|
||||
<name>SuperUser</name>
|
||||
<description>This profile allows all actions which are not Administrator restricted.</description>
|
||||
|
||||
@@ -4,80 +4,64 @@
|
||||
<contactid>9</contactid>
|
||||
<email>jules.vernes@it.com</email>
|
||||
<org_id_friendlyname>IT Department</org_id_friendlyname>
|
||||
<login>Agent</login>
|
||||
<login>SupportAgent</login>
|
||||
<language>EN US</language>
|
||||
<status>disabled</status>
|
||||
<profile_list>
|
||||
<Set>
|
||||
<URP_UserProfile id="5">
|
||||
<profileid>5</profileid>
|
||||
<profile>Support Agent</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
</Set>
|
||||
</profile_list>
|
||||
<allowed_org_list>
|
||||
<Set>
|
||||
</Set>
|
||||
</allowed_org_list>
|
||||
<password><![CDATA[R6rQ;p]JT*FA$aaP^4]]></password>
|
||||
<password><![CDATA[SupportAgent]]></password>
|
||||
<expiration>force_expire</expiration>
|
||||
<password_renewed_date>2026-06-04</password_renewed_date>
|
||||
</UserLocal>
|
||||
<URP_UserProfile id="5">
|
||||
<userid>4</userid>
|
||||
<userlogin>SupportAgent</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='5'</profileid>
|
||||
<profile>Support Agent</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<UserLocal alias="UserLocal" id="5">
|
||||
<contactid>26</contactid>
|
||||
<contactid_friendlyname>Jean Ferrat</contactid_friendlyname>
|
||||
<org_id_friendlyname>IT Department</org_id_friendlyname>
|
||||
<login>config</login>
|
||||
<login>ConfigManager</login>
|
||||
<language>EN US</language>
|
||||
<status>disabled</status>
|
||||
<profile_list>
|
||||
<Set>
|
||||
<URP_UserProfile id="6">
|
||||
<profileid>3</profileid>
|
||||
<profile>Configuration Manager</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
</Set>
|
||||
</profile_list>
|
||||
<allowed_org_list>
|
||||
<Set>
|
||||
</Set>
|
||||
</allowed_org_list>
|
||||
<password><![CDATA[$W[:"7+Gf"Y\sd8#E~]]></password>
|
||||
<password><![CDATA[$W[ConfigManager]]></password>
|
||||
<expiration>force_expire</expiration>
|
||||
<password_renewed_date>2026-06-04</password_renewed_date>
|
||||
</UserLocal>
|
||||
<URP_UserProfile id="6">
|
||||
<userid>5</userid>
|
||||
<userlogin>ConfigManager</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='3'</profileid>
|
||||
<profile>Configuration Manager</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<UserLocal alias="UserLocal" id="2">
|
||||
<contactid>15</contactid>
|
||||
<email>agatha.christie@demo.com</email>
|
||||
<org_id_friendlyname>Sales Department</org_id_friendlyname>
|
||||
<login>Portal</login>
|
||||
<login>PortalUser</login>
|
||||
<language>EN US</language>
|
||||
<status>disabled</status>
|
||||
<profile_list>
|
||||
<Set>
|
||||
<URP_UserProfile id="2">
|
||||
<profileid>2</profileid>
|
||||
<profile>Portal user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
</Set>
|
||||
</profile_list>
|
||||
<allowed_org_list>
|
||||
<Set>
|
||||
<URP_UserOrg id="2">
|
||||
<allowed_org_id>6</allowed_org_id>
|
||||
<allowed_org_name>Sales Department</allowed_org_name>
|
||||
<reason></reason>
|
||||
<allowed_org_id_obsolescence_flag>no</allowed_org_id_obsolescence_flag>
|
||||
</URP_UserOrg>
|
||||
</Set>
|
||||
</allowed_org_list>
|
||||
<password><![CDATA[-Why]KDdMJvkuB8#e]]></password>
|
||||
<password><![CDATA[PortalUser]]></password>
|
||||
<expiration>force_expire</expiration>
|
||||
<password_renewed_date>2026-04-17</password_renewed_date>
|
||||
</UserLocal>
|
||||
<URP_UserProfile id="2">
|
||||
<userid>2</userid>
|
||||
<userlogin>PortalUser</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='2'</profileid>
|
||||
<profile>Portal user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<URP_UserOrg id="2">
|
||||
<userid>2</userid>
|
||||
<userlogin>PortalUser</userlogin>
|
||||
<allowed_org_id>6</allowed_org_id>
|
||||
<allowed_org_name>Sales Department</allowed_org_name>
|
||||
<reason></reason>
|
||||
<allowed_org_id_obsolescence_flag>no</allowed_org_id_obsolescence_flag>
|
||||
</URP_UserOrg>
|
||||
<UserLocal alias="UserLocal" id="3">
|
||||
<contactid>6</contactid>
|
||||
<email>claude.monet@demo.com</email>
|
||||
@@ -85,32 +69,48 @@
|
||||
<login>SalesManager</login>
|
||||
<language>EN US</language>
|
||||
<status>disabled</status>
|
||||
<profile_list>
|
||||
<Set>
|
||||
<URP_UserProfile id="4">
|
||||
<profileid>12</profileid>
|
||||
<profile>Portal power user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<URP_UserProfile id="3">
|
||||
<profileid>2</profileid>
|
||||
<profile>Portal user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
</Set>
|
||||
</profile_list>
|
||||
<allowed_org_list>
|
||||
<Set>
|
||||
<URP_UserOrg id="1">
|
||||
<allowed_org_id>6</allowed_org_id>
|
||||
<allowed_org_name>Sales Department</allowed_org_name>
|
||||
<reason></reason>
|
||||
<allowed_org_id_obsolescence_flag>no</allowed_org_id_obsolescence_flag>
|
||||
</URP_UserOrg>
|
||||
</Set>
|
||||
</allowed_org_list>
|
||||
<password><![CDATA[D%r7hoZ})5*hvq5`{Q]]></password>
|
||||
<password><![CDATA[SalesManager]]></password>
|
||||
<expiration>force_expire</expiration>
|
||||
<password_renewed_date>2026-06-04</password_renewed_date>
|
||||
</UserLocal>
|
||||
<URP_UserProfile id="4">
|
||||
<userid>3</userid>
|
||||
<userlogin>SalesManager</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='12'</profileid>
|
||||
<profile>Portal power user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<URP_UserProfile id="3">
|
||||
<userid>3</userid>
|
||||
<userlogin>SalesManager</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='2'</profileid>
|
||||
<profile>Portal user</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
<URP_UserOrg id="1">
|
||||
<userid>3</userid>
|
||||
<userlogin>SalesManager</userlogin>
|
||||
<allowed_org_id>6</allowed_org_id>
|
||||
<allowed_org_name>Sales Department</allowed_org_name>
|
||||
<reason></reason>
|
||||
<allowed_org_id_obsolescence_flag>no</allowed_org_id_obsolescence_flag>
|
||||
</URP_UserOrg>
|
||||
<UserLocal alias="UserLocal" id="6">
|
||||
<contactid>18</contactid>
|
||||
<email>rene.descartes@demo.com</email>
|
||||
<org_id_friendlyname>Demo</org_id_friendlyname>
|
||||
<login>SuperUser</login>
|
||||
<language>EN US</language>
|
||||
<status>disabled</status>
|
||||
<password><![CDATA[SuperUser]]></password>
|
||||
<expiration>force_expire</expiration>
|
||||
<password_renewed_date>2026-04-17</password_renewed_date>
|
||||
</UserLocal>
|
||||
<URP_UserProfile id="7">
|
||||
<userid>6</userid>
|
||||
<userlogin>SuperUser</userlogin>
|
||||
<profileid>SELECT URP_Profiles WHERE id='117'</profileid>
|
||||
<profile>Super User</profile>
|
||||
<reason></reason>
|
||||
</URP_UserProfile>
|
||||
</Set>
|
||||
|
||||
@@ -41,5 +41,5 @@
|
||||
</selected_modules>
|
||||
<selected_extensions type="array">
|
||||
</selected_extensions>
|
||||
<use_symbolic_links>on</use_symbolic_links>
|
||||
<use_symbolic_links>off</use_symbolic_links>
|
||||
</installation>
|
||||
|
||||
@@ -3002,8 +3002,6 @@ class SynchroExecution
|
||||
* </ul>
|
||||
*/
|
||||
protected $m_oLastFullLoadStartDate = null;
|
||||
/** @var bool true if the caller script gave the datetime before import phase was launched */
|
||||
protected $m_bIsImportPhaseDateKnown;
|
||||
|
||||
/** @var \CMDBChange */
|
||||
protected $m_oChange = null;
|
||||
@@ -3028,10 +3026,7 @@ class SynchroExecution
|
||||
public function __construct($oDataSource, $oImportPhaseStartDate = null)
|
||||
{
|
||||
$this->m_oDataSource = $oDataSource;
|
||||
|
||||
$this->m_bIsImportPhaseDateKnown = ($oImportPhaseStartDate != null);
|
||||
$this->m_oImportPhaseStartDate = $oImportPhaseStartDate;
|
||||
|
||||
$this->m_oCtx = new ContextTag(ContextTag::TAG_SYNCHRO);
|
||||
$this->m_oCtx1 = new ContextTag('Synchro:'.$oDataSource->GetRawName()); // More precise context information
|
||||
}
|
||||
@@ -3108,7 +3103,7 @@ class SynchroExecution
|
||||
$this->m_oStatLog->Set('stats_nb_replica_total', $this->m_iCountAllReplicas);
|
||||
|
||||
$this->m_oStatLog->DBInsert();
|
||||
$sLastFullLoad = ($this->m_bIsImportPhaseDateKnown) ? $this->m_oImportPhaseStartDate->format('Y-m-d H:i:s') : 'not specified';
|
||||
$sLastFullLoad = (is_null($this->m_oImportPhaseStartDate)) ? 'not specified' : $this->m_oImportPhaseStartDate->format('Y-m-d H:i:s');
|
||||
$this->m_oStatLog->AddTrace("###### STARTING SYNCHRONIZATION ##### Total: {$this->m_iCountAllReplicas} replica(s). Last full load: '$sLastFullLoad' ");
|
||||
$sSql = 'SELECT NOW();';
|
||||
$sDBNow = CMDBSource::QueryToScalar($sSql);
|
||||
@@ -3212,21 +3207,18 @@ class SynchroExecution
|
||||
// Compute and keep track of the limit date taken into account for obsoleting replicas
|
||||
//
|
||||
$iFullLoadInterval = $this->m_oDataSource->Get('full_load_periodicity'); // Duration in seconds
|
||||
if ($this->m_bIsImportPhaseDateKnown) {
|
||||
$oLimitDate = clone $this->m_oImportPhaseStartDate;
|
||||
$sInterval = "-$iFullLoadInterval seconds";
|
||||
$oLimitDate->Modify($sInterval);
|
||||
} else {
|
||||
if (is_null($this->m_oImportPhaseStartDate)) {
|
||||
if ($iFullLoadInterval <= 0) {
|
||||
// we are doing exec phase alone, and the full load interval is set to 0 => we should not update/delete replicas !!
|
||||
// This will prevent actions in DoJob1() method
|
||||
$oLimitDate = new DateTime('1970-01-01');
|
||||
} else {
|
||||
$oLimitDate = self::GetDataBaseCurrentDateTime();
|
||||
$sInterval = "-$iFullLoadInterval seconds";
|
||||
$oLimitDate->Modify($sInterval);
|
||||
}
|
||||
} else {
|
||||
$oLimitDate = clone $this->m_oImportPhaseStartDate;
|
||||
}
|
||||
$this->ExactlySubtractSeconds($oLimitDate, $iFullLoadInterval);
|
||||
$this->m_oLastFullLoadStartDate = $oLimitDate;
|
||||
if ($bFirstPass) {
|
||||
$this->m_oStatLog->AddTrace('Limit Date: '.$this->m_oLastFullLoadStartDate->Format('Y-m-d H:i:s'));
|
||||
@@ -3346,10 +3338,10 @@ class SynchroExecution
|
||||
$aArguments['log'] = $this->m_oStatLog->GetKey();
|
||||
$aArguments['change'] = $this->m_oChange->GetKey();
|
||||
$aArguments['chunk'] = $iMaxChunkSize;
|
||||
if ($this->m_bIsImportPhaseDateKnown) {
|
||||
$aArguments['last_full_load'] = $this->m_oImportPhaseStartDate->Format('Y-m-d H:i:s');
|
||||
} else {
|
||||
if (is_null($this->m_oImportPhaseStartDate)) {
|
||||
$aArguments['last_full_load'] = '';
|
||||
} else {
|
||||
$aArguments['last_full_load'] = $this->m_oImportPhaseStartDate->Format('Y-m-d H:i:s');
|
||||
}
|
||||
|
||||
$this->m_oStatLog->DBUpdate();
|
||||
@@ -3701,13 +3693,11 @@ class SynchroExecution
|
||||
|
||||
// Get all the replicas that are to be deleted
|
||||
//
|
||||
$oDeletionDate = $this->m_oLastFullLoadStartDate;
|
||||
$oDeletionDate = clone $this->m_oLastFullLoadStartDate;
|
||||
$iDeleteRetention = $this->m_oDataSource->Get('delete_policy_retention'); // Duration in seconds
|
||||
if ($iDeleteRetention > 0) {
|
||||
$sInterval = "-$iDeleteRetention seconds";
|
||||
$oDeletionDate->Modify($sInterval);
|
||||
}
|
||||
$this->ExactlySubtractSeconds($oDeletionDate, $iDeleteRetention);
|
||||
$sDeletionDate = $oDeletionDate->Format('Y-m-d H:i:s');
|
||||
|
||||
if ($bFirstPass) {
|
||||
$this->m_oStatLog->AddTrace("Deletion date: $sDeletionDate");
|
||||
}
|
||||
@@ -3757,4 +3747,21 @@ class SynchroExecution
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Take into account timechange to apply date difference operation
|
||||
* @param \DateTime $oDate
|
||||
* @param $iDurationInSeconds
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function ExactlySubtractSeconds(DateTime $oDate, $iDurationInSeconds): void
|
||||
{
|
||||
if ($iDurationInSeconds > 0) {
|
||||
$oDate->setTimezone(new DateTimeZone('UTC'));
|
||||
$sInterval = "-$iDurationInSeconds seconds";
|
||||
$oDate->Modify($sInterval);
|
||||
$sTimezone = MetaModel::GetConfig()->Get('timezone');
|
||||
$oDate->setTimezone(new DateTimeZone($sTimezone));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,8 +87,6 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
*/
|
||||
public const DEFAULT_TEST_ENVIRONMENT = 'production';
|
||||
public const USE_TRANSACTION = true;
|
||||
public const CREATE_TEST_ORG = false;
|
||||
|
||||
protected static $aURP_Profiles = [
|
||||
'Administrator' => 1,
|
||||
'Portal user' => 2,
|
||||
@@ -102,9 +100,15 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
'Service Manager' => 10,
|
||||
'Document author' => 11,
|
||||
'Portal power user' => 12,
|
||||
'Business partner user' => 40,
|
||||
'REST Services User' => 1024,
|
||||
'Configuration ReadOnly' => 5500,
|
||||
'Ticket ReadOnly' => 5501,
|
||||
'Service Catalog ReadOnly' => 5502,
|
||||
];
|
||||
|
||||
public const CREATE_TEST_ORG = false;
|
||||
|
||||
/**
|
||||
* This method is called before the first test of this test class is run (in the current process).
|
||||
*/
|
||||
@@ -1463,16 +1467,42 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @description To avoid adding finalclasses parameters to GivenUserInDB
|
||||
* @param string $sPassword
|
||||
* @param array $aProfiles Profile names Example: ['Administrator']
|
||||
* @param bool $bReturnLogin
|
||||
*
|
||||
* @return string|int The unique login
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GivenTokenUserInDB(array $aProfiles, bool $bReturnLogin = true): string|int
|
||||
{
|
||||
$sLogin = 'demo_test_'.uniqid(__CLASS__, true);
|
||||
|
||||
$aProfileList = array_map(function ($sProfileId) {
|
||||
return 'profileid:'.self::$aURP_Profiles[$sProfileId];
|
||||
}, $aProfiles);
|
||||
|
||||
$iUser = $this->GivenObjectInDB('UserToken', [
|
||||
'login' => $sLogin,
|
||||
'language' => 'EN US',
|
||||
'profile_list' => $aProfileList,
|
||||
]);
|
||||
return $bReturnLogin ? $sLogin : $iUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sPassword
|
||||
* @param array $aProfiles Profile names Example: ['Administrator']
|
||||
* @param string|null $sLogin
|
||||
* @param string|null $sUserId
|
||||
* @param bool $bReturnLogin
|
||||
*
|
||||
* @return string The unique login
|
||||
* @throws \Exception
|
||||
*/
|
||||
protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null): string
|
||||
protected function GivenUserInDB(string $sPassword, array $aProfiles, ?string $sLogin = null, ?string &$sUserId = null, bool $bReturnLogin = true): string
|
||||
{
|
||||
if (is_null($sLogin)) {
|
||||
$sLogin = 'demo_test_'.uniqid(__CLASS__, true);
|
||||
@@ -1489,7 +1519,7 @@ abstract class ItopDataTestCase extends ItopTestCase
|
||||
'profile_list' => $aProfileList,
|
||||
]);
|
||||
|
||||
return $sLogin;
|
||||
return $bReturnLogin ? $sLogin : $sUserId;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,11 +29,11 @@ namespace Combodo\iTop\Test\UnitTest\Core;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use CoreCannotSaveObjectException;
|
||||
use CoreException;
|
||||
use DBObject;
|
||||
use DBObjectSearch;
|
||||
use DBObjectSet;
|
||||
use DeleteException;
|
||||
use Dict;
|
||||
use MetaModel;
|
||||
use UserLocal;
|
||||
use UserRights;
|
||||
@@ -81,6 +81,65 @@ class UserRightsTest extends ItopDataTestCase
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
public function testIsActionAllowedWithNonInstantiatedUserObject()
|
||||
{
|
||||
$oUser = $this->GivenUserWithProfiles('test1', [self::$aURP_Profiles['Configuration Manager']]); // not a readonly profile
|
||||
$oAdminUser = $this->GivenUserWithProfiles('test2', [self::$aURP_Profiles['Administrator']]);
|
||||
$oAdminUser->DBInsert();
|
||||
$_SESSION = [];
|
||||
UserRights::Login($oAdminUser->Get('login'));
|
||||
|
||||
self::assertTrue(UserRights::IsActionAllowed('Server', UR_ACTION_MODIFY, null, $oUser) === UR_ALLOWED_YES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array $aProfileIds
|
||||
* @param array $aShouldBeAllowedToSeeClass
|
||||
* @param array $aShouldBeAllowedToEditClass
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \DictExceptionUnknownLanguage
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
* @dataProvider ReadOnlyProvider
|
||||
*/
|
||||
public function testReadOnlyUser(array $aProfileIds, array $aShouldBeAllowedToSeeClass, array $aShouldBeAllowedToEditClass): void
|
||||
{
|
||||
|
||||
$oUser = $this->GivenUserWithProfiles('test1', $aProfileIds);
|
||||
$oUser->DBInsert();
|
||||
$_SESSION = [];
|
||||
UserRights::Login($oUser->Get('login'));
|
||||
|
||||
$aClassesToTest = ['FunctionalCI', 'Ticket', 'ServiceFamily'];
|
||||
|
||||
foreach ($aClassesToTest as $sClass) {
|
||||
$bShouldBeAllowedToSee = in_array($sClass, $aShouldBeAllowedToSeeClass);
|
||||
$bIsAllowedReading = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_READ);
|
||||
|
||||
$this->assertSame(
|
||||
$bShouldBeAllowedToSee,
|
||||
$bIsAllowedReading,
|
||||
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToSee ? "" : "NOT ")."be allowed to see class $sClass"
|
||||
);
|
||||
|
||||
$bShouldBeAllowedToEdit = in_array($sClass, $aShouldBeAllowedToEditClass);
|
||||
|
||||
$bIsAllowedEditing = (bool)UserRights::IsActionAllowed($sClass, UR_ACTION_MODIFY);
|
||||
|
||||
$this->assertSame(
|
||||
$bIsAllowedEditing,
|
||||
$bShouldBeAllowedToEdit,
|
||||
"User with profiles ".implode(',', $aProfileIds)." should ".($bShouldBeAllowedToEdit ? "" : "NOT ")."be allowed to edit class $sClass"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
protected function GivenUserWithProfiles(string $sLogin, array $aProfileIds): DBObject
|
||||
{
|
||||
$oProfiles = new \ormLinkSet(\UserLocal::class, 'profile_list', \DBObjectSet::FromScratch(\URP_UserProfile::class));
|
||||
@@ -433,7 +492,7 @@ class UserRightsTest extends ItopDataTestCase
|
||||
$oUser = $this->GivenUserWithProfiles('test1', [$iProfileId, 2]);
|
||||
|
||||
$this->expectException(CoreCannotSaveObjectException::class);
|
||||
$this->expectExceptionMessage('Profile "Portal user" cannot be given to privileged Users (Administrators, SuperUsers and REST Services Users)');
|
||||
$this->expectExceptionMessage(Dict::Format('Class:User/Error:PrivilegedUserMustHaveAccessToBackOffice', PORTAL_PROFILE_NAME));
|
||||
$oUser->DBInsert();
|
||||
|
||||
}
|
||||
@@ -572,4 +631,82 @@ class UserRightsTest extends ItopDataTestCase
|
||||
$oUser = $this->InvokeNonPublicStaticMethod(UserRights::class, "FindUser", [$sLogin]);
|
||||
static::assertNull($oUser, 'FindUser should return null when the login is unknown');
|
||||
}
|
||||
|
||||
protected function ReadOnlyProvider(): array
|
||||
{
|
||||
return [
|
||||
'CI' => [
|
||||
'ProfilesId' => [
|
||||
5500,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'Tickets' => [
|
||||
'ProfilesId' => [
|
||||
5501,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'Ticket',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'CI and Tickets' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5501,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'CI and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'Tickets and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5501, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
'Tickets and Catalog + profile Ccnfiguration Manager' => [
|
||||
'ProfilesId' => [
|
||||
5501, 5502, 3,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => ['FunctionalCI'],
|
||||
],
|
||||
'CI, Tickets and Catalog' => [
|
||||
'ProfilesId' => [
|
||||
5500, 5501, 5502,
|
||||
],
|
||||
'ShouldBeAllowedToSeeClasses' => [
|
||||
'FunctionalCI', 'Ticket', 'ServiceFamily',
|
||||
],
|
||||
'ShouldBeAllowedToEditClasses' => [],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,13 +72,12 @@ class StaticDeletionPlanTest extends \AbstractCleanup
|
||||
|
||||
public function testGetStaticDeletionPlan_DeleteObjRecursively()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRToRemoveLeaf (name = DFRToRemoveLeaf_1)
|
||||
create DFRToUpdate (name = DFRToUpdate_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateral (name = DFRRemovedCollateral_1, extkey_id = DFRToRemoveLeaf_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_1, extkey_id = DFRRemovedCollateral_1)
|
||||
create DFRRemovedCollateralCascade (name = DFRRemovedCollateralCascade_2, extkey_id = DFRRemovedCollateral_1)
|
||||
EOF);
|
||||
$this->GivenDFRTreeInDB(<<<EOF
|
||||
DFRToRemoveLeaf_1 <- DFRToUpdate_1
|
||||
DFRToRemoveLeaf_1 <- DFRRemovedCollateral_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_1
|
||||
DFRRemovedCollateral_1 <- DFRRemovedCollateralCascade_2
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRToRemoveLeaf' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
@@ -86,6 +85,7 @@ EOF);
|
||||
|
||||
self::assertArrayHasKey('DFRRemovedCollateralCascade', $aRes, 'The cleanup should descend to the cascaded classes');
|
||||
|
||||
// echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
// echo json_encode($this->aIdByClass, JSON_PRETTY_PRINT);
|
||||
}
|
||||
|
||||
@@ -174,95 +174,37 @@ EOF);
|
||||
public function testMultipleExtKeys()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
create DFRC1 (name = DFRC1_1)
|
||||
create DFRC1 (name = DFRC1_2)
|
||||
create DFRC1 (name = DFRC1_3)
|
||||
create DFRC1 (name = DFRC1_4)
|
||||
create DFRC1 (name = DFRC1_5)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC1_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC1_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC1_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
create DFRC2 (name = DFRC2_1, extkey1_id = DFRC1_4, extkey2_id = DFRC1_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC2_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01' ];
|
||||
$aClasses = [ 'DFRC1' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
|
||||
$aExpected = [
|
||||
'DFRC01' => ['iDeleteCount' => 5],
|
||||
'DFRC21' => ['iDeleteCount' => 1],
|
||||
'DFRC1' => ['iDeleteCount' => 5],
|
||||
'DFRC2' => ['iDeleteCount' => 1],
|
||||
'DFRC3' => ['iDeleteCount' => 3],
|
||||
'DFRC4' => ['iDeleteCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
public function testMultipleInitialSubClassesFromSameRoot()
|
||||
{
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC02 (name = DFRC02_1)
|
||||
create DFRC02 (name = DFRC02_2)
|
||||
create DFRC02 (name = DFRC02_3)
|
||||
|
||||
create DFRC03 (name = DFRC03_1)
|
||||
create DFRC03 (name = DFRC03_2)
|
||||
create DFRC03 (name = DFRC03_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_4, extkey1_id = DFRC02_1)
|
||||
create DFRC3 (name = DFRC3_5, extkey1_id = DFRC02_2)
|
||||
create DFRC3 (name = DFRC3_6, extkey1_id = DFRC02_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_7, extkey1_id = DFRC03_1)
|
||||
create DFRC3 (name = DFRC3_8, extkey1_id = DFRC03_2)
|
||||
create DFRC3 (name = DFRC3_9, extkey1_id = DFRC03_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01', 'DFRC03' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aRes = $oService->GetCleanupSummary($aClasses);
|
||||
echo json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$aExpected = [
|
||||
'DFRC01' => ['iDeleteCount' => 5],
|
||||
'DFRC2' => ['iDeleteCount' => 1],
|
||||
'DFRC21' => ['iDeleteCount' => 1],
|
||||
'DFRC3' => ['iDeleteCount' => 6],
|
||||
'DFRC4' => ['iDeleteCount' => 1],
|
||||
];
|
||||
|
||||
$this->AssertSummaryEquals($aExpected, $aRes);
|
||||
}
|
||||
|
||||
private function AssertSummaryEquals(array $aExpected, array $aActual, string $sMessage = '')
|
||||
|
||||
@@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Module\DataFeatureRemoval;
|
||||
|
||||
/*
|
||||
* @copyright Copyright (C) 2010-2026 Combodo SAS
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
use CMDBSource;
|
||||
use Combodo\iTop\DataFeatureRemoval\Entity\DataCleanupSummaryEntity;
|
||||
use Combodo\iTop\DataFeatureRemoval\Service\StaticDeletionPlan;
|
||||
|
||||
class TempTablesTest extends \AbstractCleanup
|
||||
{
|
||||
public function GetDatamodelDeltaAbsPath(): string
|
||||
{
|
||||
return __DIR__.'/data_cleanup_delta.xml';
|
||||
}
|
||||
|
||||
public function testMultipleExtKeys()
|
||||
{
|
||||
echo "--------------------------------------------\n".__METHOD__."\n";
|
||||
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aTempTables = $oService->GetTempTableDefinitions($aClasses);
|
||||
echo json_encode($aTempTables, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
public function testMultipleInitialSubClassesFromSameRoot()
|
||||
{
|
||||
|
||||
echo "--------------------------------------------\n".__METHOD__."\n";
|
||||
$this->GivenDFRObjectsInDB(<<<EOF
|
||||
create DFRC01 (name = DFRC01_1)
|
||||
create DFRC01 (name = DFRC01_2)
|
||||
create DFRC01 (name = DFRC01_3)
|
||||
create DFRC01 (name = DFRC01_4)
|
||||
create DFRC01 (name = DFRC01_5)
|
||||
|
||||
create DFRC02 (name = DFRC02_1)
|
||||
create DFRC02 (name = DFRC02_2)
|
||||
create DFRC02 (name = DFRC02_3)
|
||||
|
||||
create DFRC03 (name = DFRC03_1)
|
||||
create DFRC03 (name = DFRC03_2)
|
||||
create DFRC03 (name = DFRC03_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_1, extkey1_id = DFRC01_1)
|
||||
create DFRC3 (name = DFRC3_2, extkey1_id = DFRC01_2)
|
||||
create DFRC3 (name = DFRC3_3, extkey1_id = DFRC01_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_4, extkey1_id = DFRC02_1)
|
||||
create DFRC3 (name = DFRC3_5, extkey1_id = DFRC02_2)
|
||||
create DFRC3 (name = DFRC3_6, extkey1_id = DFRC02_3)
|
||||
|
||||
create DFRC3 (name = DFRC3_7, extkey1_id = DFRC03_1)
|
||||
create DFRC3 (name = DFRC3_8, extkey1_id = DFRC03_2)
|
||||
create DFRC3 (name = DFRC3_9, extkey1_id = DFRC03_3)
|
||||
|
||||
create DFRC21 (name = DFRC21_1, extkey1_id = DFRC01_4, extkey2_id = DFRC01_5, extkey3_id = DFRC3_3)
|
||||
|
||||
create DFRC22 (name = DFRC22_1, extkey1_id = DFRC03_1)
|
||||
create DFRC22 (name = DFRC22_2, extkey1_id = DFRC03_1)
|
||||
|
||||
create DFRC4 (name = DFRC4_1, extkey1_id = DFRC21_1)
|
||||
create DFRC4 (name = DFRC4_2, extkey1_id = DFRC22_1)
|
||||
create DFRC4 (name = DFRC4_3, extkey1_id = DFRC22_2)
|
||||
|
||||
update DFRC3 (name = DFRC3_2, extkey2_id = DFRC4_1)
|
||||
update DFRC3 (name = DFRC3_5, extkey2_id = DFRC4_2)
|
||||
update DFRC3 (name = DFRC3_6, extkey2_id = DFRC4_3)
|
||||
|
||||
EOF);
|
||||
|
||||
$aClasses = [ 'DFRC01', 'DFRC03' ];
|
||||
$oService = new StaticDeletionPlan();
|
||||
|
||||
$aTempTables = $oService->GetTempTableDefinitions($aClasses);
|
||||
echo json_encode($aTempTables, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
$this->GetIds($aTempTables);
|
||||
|
||||
$aRes = $oService->GetStaticDeletionPlan($aClasses);
|
||||
echo "\nDeletionPlan: ".json_encode($aRes, JSON_PRETTY_PRINT)."\n";
|
||||
|
||||
self::assertTrue(true);
|
||||
}
|
||||
|
||||
private function GetIds(array $aTempTables): void
|
||||
{
|
||||
foreach ($aTempTables as $sDBName => $aQueries) {
|
||||
foreach ($aQueries as $sQuery) {
|
||||
CMDBSource::Query($sQuery);
|
||||
}
|
||||
|
||||
$aIds = CMDBSource::QueryToCol("SELECT id FROM `$sDBName`", 'id');
|
||||
echo "\n$sDBName: ".implode(', ', $aIds)."\n";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -446,11 +446,11 @@
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRC00" _created_in="itop-structure" _delta="define">
|
||||
<class id="DFRC1" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<db_table>dfrc00</db_table>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc1</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
@@ -459,6 +459,14 @@
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
@@ -478,130 +486,10 @@
|
||||
</presentation>
|
||||
<parent>DFRC0</parent>
|
||||
</class>
|
||||
<class id="DFRC01" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc01</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC02" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc02</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC03" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc03</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="desc" xsi:type="AttributeString">
|
||||
<sql>name</sql>
|
||||
<default_value/>
|
||||
<is_null_allowed>true</is_null_allowed>
|
||||
<validation_pattern/>
|
||||
<dependencies/>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC00</parent>
|
||||
</class>
|
||||
<class id="DFRC2" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>true</abstract>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc2</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
@@ -628,41 +516,6 @@
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRC21" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc21</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
<field id="extkey2_id" xsi:type="AttributeExternalKey">
|
||||
<sql>extkey2_id</sql>
|
||||
<filter/>
|
||||
@@ -701,42 +554,7 @@
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC2</parent>
|
||||
</class>
|
||||
<class id="DFRC22" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
<category>bizmodel,searchable</category>
|
||||
<abstract>false</abstract>
|
||||
<db_table>dfrc22</db_table>
|
||||
<naming>
|
||||
<attributes/>
|
||||
</naming>
|
||||
<reconciliation>
|
||||
<attributes/>
|
||||
</reconciliation>
|
||||
</properties>
|
||||
<fields>
|
||||
</fields>
|
||||
<methods/>
|
||||
<presentation>
|
||||
<list>
|
||||
<items/>
|
||||
</list>
|
||||
<search>
|
||||
<items/>
|
||||
</search>
|
||||
<details>
|
||||
<items>
|
||||
<item id="name">
|
||||
<rank>10</rank>
|
||||
</item>
|
||||
<item id="extkey_id">
|
||||
<rank>20</rank>
|
||||
</item>
|
||||
</items>
|
||||
</details>
|
||||
</presentation>
|
||||
<parent>DFRC2</parent>
|
||||
<parent>cmdbAbstractObject</parent>
|
||||
</class>
|
||||
<class id="DFRC3" _created_in="itop-structure" _delta="define">
|
||||
<properties>
|
||||
@@ -825,7 +643,7 @@
|
||||
<filter/>
|
||||
<dependencies/>
|
||||
<is_null_allowed>false</is_null_allowed>
|
||||
<target_class>DFRC21</target_class>
|
||||
<target_class>DFRC2</target_class>
|
||||
<on_target_delete>DEL_AUTO</on_target_delete>
|
||||
<tracking_level>all</tracking_level>
|
||||
</field>
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class SynchroExecutionTest extends ItopDataTestCase
|
||||
{
|
||||
private function InitAndGetTZ($sTZ = null): string
|
||||
{
|
||||
$sTZ = $sTZ ?? 'Europe/Paris';
|
||||
MetaModel::GetConfig()->Set('timezone', $sTZ);
|
||||
return $sTZ;
|
||||
}
|
||||
|
||||
public function testGetDateMinusRetentionWithTimeChange()
|
||||
{
|
||||
$sTZ = $this->InitAndGetTZ();
|
||||
$oObj = new SynchroExecution($this->createMock(SynchroDataSource::class));
|
||||
$oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-03-29 03:30:01", new DateTimeZone($sTZ));
|
||||
|
||||
$oObj->ExactlySubtractSeconds($oDate, 3600);
|
||||
//03h30 minus 1hours => 03h minus 30mn => 03h
|
||||
// => 03h with timechange => 2h
|
||||
// => 02 minus 30mn => 01h30
|
||||
$this->assertEquals("2026-03-29 01:30:01", $oDate->Format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testGetDateMinusRetentioLinuxTimeZero()
|
||||
{
|
||||
$oObj = new SynchroExecution($this->createMock(SynchroDataSource::class));
|
||||
$oDate = new DateTime('1970-01-01');
|
||||
|
||||
$oObj->ExactlySubtractSeconds($oDate, 3600);
|
||||
$this->assertEquals("1969-12-31 23:00:00", $oDate->Format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testGetDateMinusRetentionWithTimeChangeAndUTC()
|
||||
{
|
||||
$sTZ = $this->InitAndGetTZ('UTC');
|
||||
$oObj = new SynchroExecution($this->createMock(SynchroDataSource::class));
|
||||
$oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-03-29 03:30:01", new DateTimeZone($sTZ));
|
||||
|
||||
$oObj->ExactlySubtractSeconds($oDate, 3600);
|
||||
$this->assertEquals("2026-03-29 02:30:01", $oDate->Format('Y-m-d H:i:s'));
|
||||
}
|
||||
|
||||
public function testGetDateMinusRetention()
|
||||
{
|
||||
$sTZ = $this->InitAndGetTZ();
|
||||
$oObj = new SynchroExecution($this->createMock(SynchroDataSource::class));
|
||||
$oDate = DateTime::createFromFormat('Y-m-d H:i:s', "2026-04-01 03:30:01", new DateTimeZone($sTZ));
|
||||
|
||||
$oObj->ExactlySubtractSeconds($oDate, 3600);
|
||||
$this->assertEquals("2026-04-01 02:30:01", $oDate->Format('Y-m-d H:i:s'));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user