diff --git a/application/cmdbabstract.class.inc.php b/application/cmdbabstract.class.inc.php index eb52af98e..362b71d23 100644 --- a/application/cmdbabstract.class.inc.php +++ b/application/cmdbabstract.class.inc.php @@ -4544,7 +4544,7 @@ HTML; return $res; } - public function PostInsertActions(): void + protected function PostInsertActions(): void { parent::PostInsertActions(); @@ -4610,7 +4610,7 @@ HTML; return $res; } - public function PostUpdateActions(array $aChanges): void + protected function PostUpdateActions(array $aChanges): void { parent::PostUpdateActions($aChanges); @@ -5981,33 +5981,27 @@ JS /** @var AttributeExternalKey $oAttDef */ $oAttDef = MetaModel::GetAttributeDef($sClass, $sExternalKeyAttCode); - if (false === $this->DoesRemoteObjectHavePhpComputation($oAttDef)) { + if (false === $this->DoesTargetObjectHavePhpComputation($oAttDef)) { continue; } - $sRemoteObjectId = $this->Get($sExternalKeyAttCode); - if ($sRemoteObjectId > 0) { - self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sRemoteObjectId); + $sTargetObjectId = $this->Get($sExternalKeyAttCode); + if ($sTargetObjectId > 0) { + self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sTargetObjectId); } - $sPreviousRemoteObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0; - if ($sPreviousRemoteObjectId > 0) { - self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sPreviousRemoteObjectId); + $sPreviousTargetObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0; + if ($sPreviousTargetObjectId > 0) { + self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sPreviousTargetObjectId); } } } - private function DoesRemoteObjectHavePhpComputation(AttributeExternalKey $oAttDef): bool + private function DoesTargetObjectHavePhpComputation(AttributeExternalKey $oAttDef): bool { - $sRemoteObjectClass = $oAttDef->GetTargetClass(); - - if (utils::IsNullOrEmptyString($sRemoteObjectClass)) { - return false; - } - /** @var AttributeLinkedSet $oAttDefMirrorLink */ $oAttDefMirrorLink = $oAttDef->GetMirrorLinkAttribute(); - if (is_null($oAttDefMirrorLink) || false === $oAttDefMirrorLink->GetHasComputation()){ + if (is_null($oAttDefMirrorLink) || false === $oAttDefMirrorLink->HasPHPComputation()){ return false; } diff --git a/core/attributedef.class.inc.php b/core/attributedef.class.inc.php index ed3495d78..c30a8389b 100644 --- a/core/attributedef.class.inc.php +++ b/core/attributedef.class.inc.php @@ -1739,7 +1739,7 @@ class AttributeLinkedSet extends AttributeDefinition * @return bool true if Attribute has constraints * @since 3.1.0 N°6228 */ - public function GetHasConstraint() + public function HasPHPConstraint(): bool { return $this->GetOptional('with_php_constraint', false); } @@ -1748,7 +1748,7 @@ class AttributeLinkedSet extends AttributeDefinition * @return bool true if Attribute has computation (DB_LINKS_CHANGED event propagation, `with_php_computation` attribute xml property), false otherwise * @since 3.1.1 3.2.0 N°6228 */ - public function GetHasComputation() + public function HasPHPComputation(): bool { return $this->GetOptional('with_php_computation', false); } diff --git a/core/dbobject.class.php b/core/dbobject.class.php index 412d7b88d..8bb13c0fb 100644 --- a/core/dbobject.class.php +++ b/core/dbobject.class.php @@ -2463,11 +2463,11 @@ abstract class DBObject implements iDisplay } /** - * Trigger onObjectUpdate on the remote object when an object pointed by a LinkSet is modified, added or removed + * Trigger onObjectUpdate on the target object when an object pointed by a LinkSet is modified, added or removed * * @since 3.1.1 3.2.0 N°6531 method creation */ - final protected function TriggerOnRemoteObjectUpdate(): void + final protected function ActivateOnObjectUpdateTriggersForTargetObjects(): void { $aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes(); @@ -2476,72 +2476,42 @@ abstract class DBObject implements iDisplay /** @var AttributeExternalKey $oExtKeyWithMirrorLinkAttDef */ $oExtKeyWithMirrorLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyWithMirrorLinkAttCode); - $oRemoteObject = $this->GetRemoteObject($oExtKeyWithMirrorLinkAttDef, $this->Get($sExtKeyWithMirrorLinkAttCode)); - if (is_null($oRemoteObject)) { - continue; - } - /** @var AttributeLinkedSet $oAttDefMirrorLink */ $oAttDefMirrorLink = $oExtKeyWithMirrorLinkAttDef->GetMirrorLinkAttribute(); if (is_null($oAttDefMirrorLink)) { + // No LinkSet pointing to me continue; } $sAttCodeMirrorLink = $oAttDefMirrorLink->GetCode(); + $sTargetObjectClass = $oExtKeyWithMirrorLinkAttDef->GetTargetClass(); if (array_key_exists($sExtKeyWithMirrorLinkAttCode, $aPreviousValues)) { - // need to update remote old + new - $sPreviousRemoteObjectKey = $aPreviousValues[$sExtKeyWithMirrorLinkAttCode]; - $oPreviousRemoteObject = $this->GetRemoteObject($oExtKeyWithMirrorLinkAttDef, $sPreviousRemoteObjectKey); - if (false === is_null($oPreviousRemoteObject)) { - $this->TriggerRemoteObject($oPreviousRemoteObject, $sAttCodeMirrorLink); - } + // need to update old target also + $sPreviousTargetObjectKey = $aPreviousValues[$sExtKeyWithMirrorLinkAttCode]; + $oPreviousTargetObject = static::GetObjectIfNotInCRUDStack($sTargetObjectClass, $sPreviousTargetObjectKey); + $this->ActivateOnObjectUpdateTriggers($oPreviousTargetObject, [$sAttCodeMirrorLink]); } + // we need to update remote with current lnk instance - $this->TriggerRemoteObject($oRemoteObject, $sAttCodeMirrorLink); + $oTargetObject = static::GetObjectIfNotInCRUDStack($sTargetObjectClass, $this->Get($sExtKeyWithMirrorLinkAttCode)); + $this->ActivateOnObjectUpdateTriggers($oTargetObject, [$sAttCodeMirrorLink]); } } - private function TriggerRemoteObject(DBObject $oRemoteObject, string $sAttCodeMirrorLink): void + final static protected function GetObjectIfNotInCRUDStack($sClass, $sKey) { - // indicates that $sAttCodeMirrorLink has been modified for the trigger - $oRemoteObject->m_aPreviousValuesForUpdatedAttributes[$sAttCodeMirrorLink] = $oRemoteObject->Get($sAttCodeMirrorLink); - $this->TriggerOnObjectUpdate($oRemoteObject); - } - - private function GetRemoteObject(AttributeExternalKey $oAttDef, $sRemoteObjectKey, bool $bWithConstraintProperty = false) - { - $sRemoteObjectClass = $oAttDef->GetTargetClass(); - - /** @noinspection NotOptimalIfConditionsInspection */ - /** @noinspection TypeUnsafeComparisonInspection */ - if (utils::IsNullOrEmptyString($sRemoteObjectClass) - || utils::IsNullOrEmptyString($sRemoteObjectKey) - || ($sRemoteObjectKey == 0) // non-strict comparison as we might have bad surprises - ) { + if (DBObject::IsObjectCurrentlyInCrud($sClass, $sKey)) { return null; } - /** @var AttributeLinkedSet $oAttDefMirrorLink */ - $oAttDefMirrorLink = $oAttDef->GetMirrorLinkAttribute(); - if (is_null($oAttDefMirrorLink)) { - return null; - } - - if ($bWithConstraintProperty && (false === $oAttDefMirrorLink->GetHasConstraint())) { - return null; - } - - if (DBObject::IsObjectCurrentlyInCrud($sRemoteObjectClass, $sRemoteObjectKey)) { - return null; - } - - return MetaModel::GetObject($sRemoteObjectClass, $sRemoteObjectKey, false); + return MetaModel::GetObject($sClass, $sKey, false); } /** + * Cascade CheckToWrite to Target Objects With LinkSet Pointing To Me * @since 3.1.1 3.2.0 N°6228 method creation */ - final protected function CheckPhpConstraint(bool $bIsCheckToDelete = false): void + final protected function CheckToWriteForTargetObjects(bool $bIsCheckToDelete = false): void { $aChanges = $this->ListChanges(); @@ -2550,58 +2520,58 @@ abstract class DBObject implements iDisplay /** @var AttributeExternalKey $oExtKeyWithMirrorLinkAttDef */ $oExtKeyWithMirrorLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyWithMirrorLinkAttCode); - $oRemoteObject = $this->GetRemoteObject($oExtKeyWithMirrorLinkAttDef, $this->Get($sExtKeyWithMirrorLinkAttCode), true); - if (is_null($oRemoteObject)) { - continue; - } - /** @var AttributeLinkedSet $oAttDefMirrorLink */ $oAttDefMirrorLink = $oExtKeyWithMirrorLinkAttDef->GetMirrorLinkAttribute(); - if (is_null($oAttDefMirrorLink)) { + if (is_null($oAttDefMirrorLink) || (false === $oAttDefMirrorLink->HasPHPConstraint())) { continue; } $sAttCodeMirrorLink = $oAttDefMirrorLink->GetCode(); + $sTargetObjectClass = $oExtKeyWithMirrorLinkAttDef->GetTargetClass(); + + $oTargetObject = static::GetObjectIfNotInCRUDStack($sTargetObjectClass, $this->Get($sExtKeyWithMirrorLinkAttCode)); if ($this->IsNew()) { - $this->CheckRemotePhpConstraintOnObject('add', $oRemoteObject, $sAttCodeMirrorLink, false); + $this->CheckToWriteForSingleTargetObject_Internal('add', $oTargetObject, $sAttCodeMirrorLink, false); } else if ($bIsCheckToDelete) { - $this->CheckRemotePhpConstraintOnObject('remove', $oRemoteObject, $sAttCodeMirrorLink, true); + $this->CheckToWriteForSingleTargetObject_Internal('remove', $oTargetObject, $sAttCodeMirrorLink, true); } else { if (array_key_exists($sExtKeyWithMirrorLinkAttCode, $aChanges)) { // need to update remote old + new $aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes(); - $sPreviousRemoteObjectKey = $aPreviousValues[$sExtKeyWithMirrorLinkAttCode]; - $oPreviousRemoteObject = $this->GetRemoteObject($oExtKeyWithMirrorLinkAttDef, $sPreviousRemoteObjectKey, true); - if (false === is_null($oPreviousRemoteObject)) { - $this->CheckRemotePhpConstraintOnObject('remove', $oPreviousRemoteObject, $sAttCodeMirrorLink, false); - } - $this->CheckRemotePhpConstraintOnObject('add', $oRemoteObject, $sAttCodeMirrorLink, false); + $sPreviousTargetObjectKey = $aPreviousValues[$sExtKeyWithMirrorLinkAttCode]; + $oPreviousTargetObject = static::GetObjectIfNotInCRUDStack($sTargetObjectClass, $sPreviousTargetObjectKey); + $this->CheckToWriteForSingleTargetObject_Internal('remove', $oPreviousTargetObject, $sAttCodeMirrorLink, false); + $this->CheckToWriteForSingleTargetObject_Internal('add', $oTargetObject, $sAttCodeMirrorLink, false); } else { - $this->CheckRemotePhpConstraintOnObject('modify', $oRemoteObject, $sAttCodeMirrorLink, false); // we need to update remote with current lnk instance + $this->CheckToWriteForSingleTargetObject_Internal('modify', $oTargetObject, $sAttCodeMirrorLink, false); // we need to update remote with current lnk instance } } } } - private function CheckRemotePhpConstraintOnObject(string $sAction, DBObject $oRemoteObject, string $sAttCodeMirrorLink, bool $bIsCheckToDelete): void + private function CheckToWriteForSingleTargetObject_Internal(string $sAction, ?DBObject $oTargetObject, string $sAttCodeMirrorLink, bool $bIsCheckToDelete): void { - $this->LogCRUDDebug(__METHOD__, "action: $sAction ".get_class($oRemoteObject).'::'.$oRemoteObject->GetKey()." ($sAttCodeMirrorLink)"); + if (is_null($oTargetObject)) { + return; + } - /** @var \ormLinkSet $oRemoteValue */ - $oRemoteValue = $oRemoteObject->Get($sAttCodeMirrorLink); + $this->LogCRUDDebug(__METHOD__, "action: $sAction ".get_class($oTargetObject).'::'.$oTargetObject->GetKey()." ($sAttCodeMirrorLink)"); + + /** @var \ormLinkSet $oTargetValue */ + $oTargetValue = $oTargetObject->Get($sAttCodeMirrorLink); switch ($sAction) { case 'add': - $oRemoteValue->AddItem($this); + $oTargetValue->AddItem($this); break; case 'remove': - $oRemoteValue->RemoveItem($this->GetKey()); + $oTargetValue->RemoveItem($this->GetKey()); break; case 'modify': - $oRemoteValue->ModifyItem($this); + $oTargetValue->ModifyItem($this); break; } - $oRemoteObject->Set($sAttCodeMirrorLink, $oRemoteValue); - [$bCheckStatus, $aCheckIssues, $bSecurityIssue] = $oRemoteObject->CheckToWrite(); + $oTargetObject->Set($sAttCodeMirrorLink, $oTargetValue); + [$bCheckStatus, $aCheckIssues, $bSecurityIssue] = $oTargetObject->CheckToWrite(); if (false === $bCheckStatus) { if ($bIsCheckToDelete) { $this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues ?? [], $aCheckIssues); @@ -2610,9 +2580,9 @@ abstract class DBObject implements iDisplay } $this->m_bSecurityIssue = $this->m_bSecurityIssue || $bSecurityIssue; } - $aRemoteCheckWarnings = $oRemoteObject->GetCheckWarnings(); - if (is_array($aRemoteCheckWarnings)) { - $this->m_aCheckWarnings = array_merge($this->m_aCheckWarnings ?? [], $aRemoteCheckWarnings); + $aTargetCheckWarnings = $oTargetObject->GetCheckWarnings(); + if (is_array($aTargetCheckWarnings)) { + $this->m_aCheckWarnings = array_merge($this->m_aCheckWarnings ?? [], $aTargetCheckWarnings); } } @@ -2657,7 +2627,7 @@ abstract class DBObject implements iDisplay $this->DoCheckToWrite(); $oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite'); - $this->CheckPhpConstraint(); + $this->CheckToWriteForTargetObjects(); if (count($this->m_aCheckIssues) == 0) { @@ -2754,7 +2724,7 @@ abstract class DBObject implements iDisplay * * an array of displayable error is added in {@see DBObject::$m_aDeleteIssues} * - * @internal + * @internal * * @param \DeletionPlan $oDeletionPlan * @@ -2819,8 +2789,14 @@ abstract class DBObject implements iDisplay */ public function CheckToDelete(&$oDeletionPlan) { - $this->MakeDeletionPlan($oDeletionPlan); - $oDeletionPlan->ComputeResults(); + $this->AddCurrentObjectInCrudStack('DELETE'); + try { + $this->MakeDeletionPlan($oDeletionPlan); + $oDeletionPlan->ComputeResults(); + } + finally { + $this->RemoveCurrentObjectInCrudStack(); + } return (!$oDeletionPlan->FoundStopper()); } @@ -2872,7 +2848,7 @@ abstract class DBObject implements iDisplay { // The value is a scalar, the comparison must be 100% strict if($this->m_aOrigValues[$sAtt] !== $proposedValue) - { + { //echo "$sAtt:
\n";
//var_dump($this->m_aOrigValues[$sAtt]);
//var_dump($proposedValue);
@@ -2994,7 +2970,7 @@ abstract class DBObject implements iDisplay
/**
* Used only by insert, Meant to be overloaded
- *
+ *
* @overwritable-hook You can extend this method in order to provide your own logic.
*/
protected function OnObjectKeyReady()
@@ -3102,7 +3078,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
-
+
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -3111,7 +3087,7 @@ abstract class DBObject implements iDisplay
}
$aHierarchicalKeys = array();
-
+
foreach(MetaModel::ListAttributeDefs($sTableClass) as $sAttCode=>$oAttDef) {
// Skip this attribute if not defined in this table
if ((!MetaModel::IsAttributeOrigin($sTableClass, $sAttCode) && !$oAttDef->CopyOnAllTables())
@@ -3121,7 +3097,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($this->m_aCurrValues[$sAttCode]);
foreach($aAttColumns as $sColumn => $sValue)
{
- $aFieldsToWrite[] = "`$sColumn`";
+ $aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -3145,7 +3121,7 @@ abstract class DBObject implements iDisplay
self::$m_aBulkInsertCols[$sClass][$sTable] = implode(', ', $aFieldsToWrite);
}
self::$m_aBulkInsertItems[$sClass][$sTable][] = '('.implode (', ', $aValuesToWrite).')';
-
+
$iNewKey = 999999; // TODO - compute next id....
}
else
@@ -3230,7 +3206,7 @@ abstract class DBObject implements iDisplay
// fields in first array, values in the second
$aFieldsToWrite = array();
$aValuesToWrite = array();
-
+
if (!empty($this->m_iKey) && ($this->m_iKey >= 0))
{
// Add it to the list of fields to write
@@ -3265,7 +3241,7 @@ abstract class DBObject implements iDisplay
$aAttColumns = $oAttDef->GetSQLValues($value);
foreach($aAttColumns as $sColumn => $sValue)
{
- $aFieldsToWrite[] = "`$sColumn`";
+ $aFieldsToWrite[] = "`$sColumn`";
$aValuesToWrite[] = CMDBSource::Quote($sValue);
}
if ($oAttDef->IsHierarchicalKey())
@@ -3496,7 +3472,7 @@ abstract class DBObject implements iDisplay
* @throws \MySQLException
* @throws \OQLException
*/
- public function PostInsertActions(): void
+ protected function PostInsertActions(): void
{
$this->FireEventAfterWrite([], true);
$oKPI = new ExecutionKPI();
@@ -3522,7 +3498,7 @@ abstract class DBObject implements iDisplay
$this->ActivateOnMentionTriggers(true);
// - Trigger for object pointing to the current object
- $this->TriggerOnRemoteObjectUpdate();
+ $this->ActivateOnObjectUpdateTriggersForTargetObjects();
}
/**
@@ -3550,7 +3526,7 @@ abstract class DBObject implements iDisplay
$this->RecordObjCreation();
return $ret;
}
-
+
/**
* This function is automatically called after cloning an object with the "clone" PHP language construct
* The purpose of this method is to reset the appropriate attributes of the object in
@@ -3815,7 +3791,7 @@ abstract class DBObject implements iDisplay
* @throws \MySQLException
* @throws \OQLException
*/
- public function PostUpdateActions(array $aChanges): void
+ protected function PostUpdateActions(array $aChanges): void
{
$this->FireEventAfterWrite($aChanges, false);
$oKPI = new ExecutionKPI();
@@ -3823,10 +3799,10 @@ abstract class DBObject implements iDisplay
$oKPI->ComputeStatsForExtension($this, 'AfterUpdate');
// - TriggerOnObjectUpdate
- $this->TriggerOnObjectUpdate($this);
+ $this->ActivateOnObjectUpdateTriggers($this);
// - Trigger for object pointing to the current object
- $this->TriggerOnRemoteObjectUpdate();
+ $this->ActivateOnObjectUpdateTriggersForTargetObjects();
$sClass = get_class($this);
if (MetaModel::HasLifecycle($sClass))
@@ -3874,15 +3850,19 @@ abstract class DBObject implements iDisplay
/**
* @param \DBObject $oObject
+ * @param array|null $aAttributes
*
- * @return void
* @throws \CoreException
* @throws \CoreUnexpectedValue
* @throws \MySQLException
* @throws \OQLException
*/
- private function TriggerOnObjectUpdate(DBObject $oObject): void
+ private function ActivateOnObjectUpdateTriggers(?DBObject $oObject, array $aAttributes = null): void
{
+ if (is_null($oObject)) {
+ return;
+ }
+
// - TriggerOnObjectUpdate
$aClassList = MetaModel::EnumParentClasses(get_class($oObject), ENUM_PARENT_CLASSES_ALL);
$aParams = array('class_list' => $aClassList);
@@ -3891,7 +3871,7 @@ abstract class DBObject implements iDisplay
while ($oTrigger = $oSet->Fetch()) {
/** @var \TriggerOnObjectUpdate $oTrigger */
try {
- $oTrigger->DoActivate($oObject->ToArgs());
+ $oTrigger->DoActivateForSpecificAttributes($oObject->ToArgs(), $aAttributes);
}
catch (Exception $e) {
$oTrigger->LogException($e, $oObject);
@@ -4225,7 +4205,7 @@ abstract class DBObject implements iDisplay
$oKPI->ComputeStatsForExtension($this, 'AfterDelete');
// - Trigger for object pointing to the current object
- $this->TriggerOnRemoteObjectUpdate();
+ $this->ActivateOnObjectUpdateTriggersForTargetObjects();
$this->m_bIsInDB = false;
$this->LogCRUDExit(__METHOD__);
@@ -4240,7 +4220,7 @@ abstract class DBObject implements iDisplay
* First, checks if the object can be deleted regarding database integrity.
* If the answer is yes, it performs any required cleanup (delete other objects or reset external keys) in addition to the object
* deletion.
- *
+ *
* @api
*
* @param \DeletionPlan $oDeletionPlan Do not use: aims at dealing with recursion
@@ -4259,9 +4239,7 @@ abstract class DBObject implements iDisplay
public function DBDelete(&$oDeletionPlan = null)
{
$this->LogCRUDEnter(__METHOD__);
- $this->AddCurrentObjectInCrudStack('DELETE');
try {
-
static $iLoopTimeLimit = null;
if ($iLoopTimeLimit == null) {
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
@@ -4269,17 +4247,15 @@ abstract class DBObject implements iDisplay
if (is_null($oDeletionPlan)) {
$oDeletionPlan = new DeletionPlan();
}
- $this->MakeDeletionPlan($oDeletionPlan);
- $oDeletionPlan->ComputeResults();
- if ($oDeletionPlan->FoundStopper()) {
+ if (false === $this->CheckToDelete($oDeletionPlan)) {
$aIssues = $oDeletionPlan->GetIssues();
$this->LogCRUDError(__METHOD__, ' Errors: '.implode(', ', $aIssues));
throw new DeleteException('Found issue(s)', array('target_class' => get_class($this), 'target_id' => $this->GetKey(), 'issues' => implode(', ', $aIssues)));
}
- // Getting and setting time limit are not symetric:
+ // Getting and setting time limit are not symmetric:
// www.php.net/manual/fr/function.set-time-limit.php#72305
$iPreviousTimeLimit = ini_get('max_execution_time');
@@ -4320,7 +4296,6 @@ abstract class DBObject implements iDisplay
set_time_limit(intval($iPreviousTimeLimit));
} finally {
$this->LogCRUDExit(__METHOD__);
- $this->RemoveCurrentObjectInCrudStack();
}
return $oDeletionPlan;
@@ -4629,7 +4604,7 @@ abstract class DBObject implements iDisplay
*
* @api
*
- */
+ */
public function Reset($sAttCode)
{
$this->Set($sAttCode, $this->GetDefaultValue($sAttCode));
@@ -4641,7 +4616,7 @@ abstract class DBObject implements iDisplay
* Suitable for use as a lifecycle action
*
* @api
- */
+ */
public function Copy($sDestAttCode, $sSourceAttCode)
{
$oTypeValueToCopy = MetaModel::GetAttributeDef(get_class($this), $sSourceAttCode);
@@ -4971,7 +4946,7 @@ abstract class DBObject implements iDisplay
{
throw new CoreException("Unknown attribute '$sExtKeyAttCode' for the class ".get_class($this));
}
-
+
$oKeyAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyAttCode);
if (!$oKeyAttDef instanceof AttributeExternalKey)
{
@@ -4989,14 +4964,14 @@ abstract class DBObject implements iDisplay
$ret = $oRemoteObj->GetForTemplate($sRemoteAttCode);
}
}
- else
+ else
{
switch($sPlaceholderAttCode)
{
case 'id':
$ret = $this->GetKey();
break;
-
+
case 'name()':
$ret = $this->GetName();
break;
@@ -5183,7 +5158,7 @@ abstract class DBObject implements iDisplay
if ($oOwner)
{
$sLinkSetOwnerClass = get_class($oOwner);
-
+
$oMyChangeOp = MetaModel::NewObject($sChangeOpClass);
$oMyChangeOp->Set("objclass", $sLinkSetOwnerClass);
$oMyChangeOp->Set("objkey", $iLinkSetOwnerId);
@@ -5210,7 +5185,7 @@ abstract class DBObject implements iDisplay
{
/** @var \AttributeLinkedSet $oLinkSet */
if (($oLinkSet->GetTrackingLevel() & LINKSET_TRACKING_LIST) == 0) continue;
-
+
$iLinkSetOwnerId = $this->Get($sExtKeyAttCode);
$oMyChangeOp = $this->PrepareChangeOpLinkSet($iLinkSetOwnerId, $oLinkSet, 'CMDBChangeOpSetAttributeLinksAddRemove');
if ($oMyChangeOp)
@@ -5428,9 +5403,9 @@ abstract class DBObject implements iDisplay
$this->m_aDeleteIssues = array(); // Ok
$this->FireEventCheckToDelete($oDeletionPlan);
$this->DoCheckToDelete($oDeletionPlan);
- $this->CheckPhpConstraint(true);
+ $this->CheckToWriteForTargetObjects(true);
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
-
+
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
// Getting and setting time limit are not symmetric:
@@ -5612,7 +5587,7 @@ abstract class DBObject implements iDisplay
$aSynchroClasses[] = $sTarget;
}
}
-
+
foreach($aSynchroClasses as $sClass)
{
if ($this instanceof $sClass)
@@ -6678,14 +6653,10 @@ abstract class DBObject implements iDisplay
// during insert key is reset from -1 to null
// so we need to handle null values (will give empty string after conversion)
$sConvertedId = (string)$sId;
-
- if (((int)$sId) > 0) {
- // When having a class hierarchy, we are saving the leaf class in the stack
- $sClass = MetaModel::GetFinalClassName($sClass, $sId);
- }
+ $oRootClass = MetaModel::GetRootClass($sClass);
foreach (self::$m_aCrudStack as $aCrudStackEntry) {
- if (($sClass === $aCrudStackEntry['class'])
+ if (($oRootClass === $aCrudStackEntry['class'])
&& ($sConvertedId === $aCrudStackEntry['id'])) {
return true;
}
@@ -6700,12 +6671,14 @@ abstract class DBObject implements iDisplay
* @param string $sClass
*
* @return bool
+ * @throws \CoreException
* @since 3.1.0 N°5609
*/
final public static function IsClassCurrentlyInCrud(string $sClass): bool
{
+ $sRootClass = MetaModel::GetRootClass($sClass);
foreach (self::$m_aCrudStack as $aCrudStackEntry) {
- if ($sClass === $aCrudStackEntry['class']) {
+ if ($sRootClass === $aCrudStackEntry['class']) {
return true;
}
}
@@ -6724,9 +6697,10 @@ abstract class DBObject implements iDisplay
private function AddCurrentObjectInCrudStack(string $sCrudType): void
{
$this->LogCRUDDebug(__METHOD__);
+ $sRootClass = MetaModel::GetRootClass(get_class($this));
self::$m_aCrudStack[] = [
'type' => $sCrudType,
- 'class' => get_class($this),
+ 'class' => $sRootClass,
'id' => (string)$this->GetKey(), // GetKey() doesn't have type hinting, so forcing type to avoid getting an int
];
}
diff --git a/core/metamodel.class.php b/core/metamodel.class.php
index e725c31d5..82de32529 100644
--- a/core/metamodel.class.php
+++ b/core/metamodel.class.php
@@ -6928,8 +6928,7 @@ abstract class MetaModel
public static function GetFinalClassName(string $sClass, int $iKey): string
{
- $sFinalClassField = Metamodel::DBGetClassField($sClass);
- if (utils::IsNullOrEmptyString($sFinalClassField)) {
+ if (MetaModel::IsStandaloneClass($sClass)) {
return $sClass;
}
@@ -6937,6 +6936,7 @@ abstract class MetaModel
$sTable = MetaModel::DBGetTable($sRootClass);
$sKeyCol = MetaModel::DBGetKey($sRootClass);
$sEscapedKey = CMDBSource::Quote($iKey);
+ $sFinalClassField = Metamodel::DBGetClassField($sRootClass);
$sQuery = "SELECT `{$sFinalClassField}` FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
return CMDBSource::QueryToScalar($sQuery);
diff --git a/core/trigger.class.inc.php b/core/trigger.class.inc.php
index b0222184c..7cc3a1d80 100644
--- a/core/trigger.class.inc.php
+++ b/core/trigger.class.inc.php
@@ -546,6 +546,38 @@ class TriggerOnObjectUpdate extends TriggerOnObject
MetaModel::Init_SetZListItems('standard_search', array('description', 'target_class')); // Criteria of the std search form
}
+ /**
+ * Activate trigger based on attribute list given instead of changed attributes
+ *
+ * @param array $aContextArgs
+ * @param array|null $aAttributes if null default to changed attributes
+ *
+ * @throws \ArchivedObjectException
+ * @throws \CoreException
+ * @throws \MissingQueryArgument
+ * @throws \MySQLException
+ * @throws \MySQLHasGoneAwayException
+ * @throws \OQLException
+ * @since 3.1.1 3.2.0 N°6228
+ */
+ public function DoActivateForSpecificAttributes(array $aContextArgs, ?array $aAttributes)
+ {
+ if (isset($aContextArgs['this->object()']))
+ {
+ /** @var \DBObject $oObject */
+ $oObject = $aContextArgs['this->object()'];
+ if (is_null($aAttributes)) {
+ $aChanges = $oObject->ListPreviousValuesForUpdatedAttributes();
+ } else {
+ $aChanges = array_fill_keys($aAttributes, true);
+ }
+ if (false === $this->IsTargetObject($oObject->GetKey(), $aChanges)) {
+ return;
+ }
+ }
+ parent::DoActivate($aContextArgs);
+ }
+
public function IsTargetObject($iObjectId, $aChanges = array())
{
if (!parent::IsTargetObject($iObjectId, $aChanges))
diff --git a/core/userrights.class.inc.php b/core/userrights.class.inc.php
index a099e96fd..aea8658e4 100644
--- a/core/userrights.class.inc.php
+++ b/core/userrights.class.inc.php
@@ -1,8 +1,6 @@
ListChanges())) {
- return;
- }
-
- $oProfileLinkSet = $this->Get('profile_list');
- if ($oProfileLinkSet->Count() > 1) {
- return;
- }
- $oProfileLinkSet->Rewind();
- $iPowerPortalCount = 0;
- $iTotalCount = 0;
- while ($oUserProfile = $oProfileLinkSet->Fetch()) {
- $sProfile = $oUserProfile->Get('profile');
- if ($sProfile === 'Portal power user') {
- $iPowerPortalCount = 1;
- }
- $iTotalCount++;
- }
- if ($iTotalCount === $iPowerPortalCount) {
- $this->AddCheckIssue(Dict::S('Class:User/Error:PortalPowerUserHasInsufficientRights'));
- }
- }
-
/**
* @inheritDoc
* @since 3.0.0
diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
index c14340cbc..29429f0fe 100644
--- a/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
+++ b/tests/php-unit-tests/src/BaseTestCase/ItopDataTestCase.php
@@ -79,6 +79,22 @@ abstract class ItopDataTestCase extends ItopTestCase
const USE_TRANSACTION = true;
const CREATE_TEST_ORG = false;
+ protected static $aURP_Profiles = [
+ 'Administrator' => 1,
+ 'Portal user' => 2,
+ 'Configuration Manager' => 3,
+ 'Service Desk Agent' => 4,
+ 'Support Agent' => 5,
+ 'Problem Manager' => 6,
+ 'Change Implementor' => 7,
+ 'Change Supervisor' => 8,
+ 'Change Approver' => 9,
+ 'Service Manager' => 10,
+ 'Document author' => 11,
+ 'Portal power user' => 12,
+ 'REST Services User' => 1024,
+ ];
+
/**
* This method is called before the first test of this test class is run (in the current process).
*/
diff --git a/tests/php-unit-tests/unitary-tests/core/AttributeDefinitionTest.php b/tests/php-unit-tests/unitary-tests/core/AttributeDefinitionTest.php
index 418b5fef1..254c9789e 100644
--- a/tests/php-unit-tests/unitary-tests/core/AttributeDefinitionTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/AttributeDefinitionTest.php
@@ -223,9 +223,10 @@ PHP
public function testWithConstraintAndComputationParameters(string $sClass, string $sAttCode, bool $bConstraintExpected, bool $bComputationExpected)
{
$oAttDef = \MetaModel::GetAttributeDef($sClass, $sAttCode);
- $this->assertTrue(method_exists($oAttDef, 'GetHasConstraint'));
- $this->assertEquals($bConstraintExpected, $oAttDef->GetHasConstraint());
- $this->assertEquals($bComputationExpected, $oAttDef->GetHasComputation());
+ $sConstraintExpected = $bConstraintExpected ? 'true' : 'false';
+ $sComputationExpected = $bComputationExpected ? 'true' : 'false';
+ $this->assertEquals($bConstraintExpected, $oAttDef->HasPHPConstraint(), "Standard DataModel should be configured with property 'has_php_constraint'=$sConstraintExpected for $sClass:$sAttCode");
+ $this->assertEquals($bComputationExpected, $oAttDef->HasPHPComputation(), "Standard DataModel should be configured with property 'has_php_computation'=$sComputationExpected for $sClass:$sAttCode");
}
public function WithConstraintParameterProvider()
diff --git a/tests/php-unit-tests/unitary-tests/core/DBObject/CheckToWritePropagationTest.php b/tests/php-unit-tests/unitary-tests/core/DBObject/CheckToWritePropagationTest.php
index f4e025983..8ab191336 100644
--- a/tests/php-unit-tests/unitary-tests/core/DBObject/CheckToWritePropagationTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/DBObject/CheckToWritePropagationTest.php
@@ -4,356 +4,74 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
-/**
- * Created by PhpStorm.
- * Date: 25/01/2018
- * Time: 11:12
- */
-
namespace Combodo\iTop\Test\UnitTest\Core\DBObject;
-use Combodo\iTop\Application\UI\Base\Layout\NavigationMenu\NavigationMenuFactory;
+use Combodo\iTop\Service\Events\EventData;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
-use CoreCannotSaveObjectException;
-use DBObjectSet;
-use DBSearch;
-use DeleteException;
-use MetaModel;
use URP_UserProfile;
-use User;
-use UserExternal;
use UserLocal;
-use UserRights;
-/**
- * @group itopRequestMgmt
- * @group userRights
- * @group defaultProfiles
- *
- * @runTestsInSeparateProcesses
- * @preserveGlobalState disabled
- * @backupGlobals disabled
- */
class CheckToWritePropagationTest extends ItopDataTestCase
{
- public function PortaPowerUserProvider()
+ private static array $aEventCalls;
+
+ public function testCascadeCheckToWrite()
{
- return [
- 'No profile' => [
- 'aProfilesBeforeUserCreation' => [
- ],
- 'bWaitForException' => 'CoreCannotSaveObjectException',
- ],
- 'Portal power user' => [
- 'aProfilesBeforeUserCreation' => [
- 'Portal power user',
- ],
- 'bWaitForException' => 'CoreCannotSaveObjectException',
- ],
- 'Portal power user + Configuration Manager' => [
- 'aProfilesBeforeUserCreation' => [
- 'Portal power user',
- 'Configuration Manager',
- ],
- 'bWaitForException' => false,
- ],
- 'Portal power user + Configuration Manager + Admin' => [
- 'aProfilesBeforeUserCreation' => [
- 'Portal power user',
- 'Configuration Manager',
- 'Administrator',
- ],
- 'bWaitForException' => false,
- ],
- ];
+ $sLogin = 'testCascadeCheckToWrite-'.uniqid('', true);
+ $oUser1 = $this->CreateUser($sLogin, self::$aURP_Profiles['Administrator'], 'ABCD1234@gabuzomeu');
+ $sUserId1 = $oUser1->GetKey();
+ $sLogin = 'testCascadeCheckToWrite-'.uniqid('', true);
+ $oUser2 = $this->CreateUser($sLogin, self::$aURP_Profiles['Administrator'], 'ABCD1234@gabuzomeu');
+ $sUserId2 = $oUser2->GetKey();
+
+ $this->EventService_RegisterListener(EVENT_DB_CHECK_TO_WRITE, [$this, 'CheckToWriteEventListener'], 'User');
+ $sEventKeyUser1 = $this->GetEventKey(EVENT_DB_CHECK_TO_WRITE, UserLocal::class, $sUserId1);
+ $sEventKeyUser2 = $this->GetEventKey(EVENT_DB_CHECK_TO_WRITE, UserLocal::class, $sUserId2);
+
+ // Add URP_UserProfile
+ self::$aEventCalls = [];
+ $oURPUserProfile = new URP_UserProfile();
+ $oURPUserProfile->Set('profileid', self::$aURP_Profiles['Support Agent']);
+ $oURPUserProfile->Set('userid', $sUserId1);
+ $oURPUserProfile->Set('reason', 'UNIT Tests');
+ $oURPUserProfile->DBInsert();
+ $this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is created');
+
+ // Update URP_UserProfile (change profile)
+ self::$aEventCalls = [];
+ $oURPUserProfile->Set('profileid', self::$aURP_Profiles['Problem Manager']);
+ $oURPUserProfile->DBUpdate();
+ $this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is updated');
+
+ // Update URP_UserProfile (move from User1 to User2)
+ self::$aEventCalls = [];
+ $oURPUserProfile->Set('userid', $sUserId2);
+ $oURPUserProfile->DBUpdate();
+ $this->assertCount(2, self::$aEventCalls, 'Previous User and new User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
+ $this->assertArrayHasKey($sEventKeyUser1, self::$aEventCalls, 'Previous User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
+ $this->assertArrayHasKey($sEventKeyUser2, self::$aEventCalls, 'New User checkToWrite should be called when a URP_UserProfile is moved from a User to another');
+
+ // Delete URP_UserProfile from User2
+ self::$aEventCalls = [];
+ $oURPUserProfile->DBDelete();
+ $this->assertArrayHasKey($sEventKeyUser2, self::$aEventCalls, 'User checkToWrite should be called when a URP_UserProfile is deleted');
+
+ $oUser1->DBDelete();
+ $oUser2->DBDelete();
}
- /**
- * @dataProvider PortaPowerUserProvider
- * @covers User::CheckPortalProfiles
- */
- public function testUserLocalCreation($aProfilesBeforeUserCreation, $sWaitForException)
+ public function CheckToWriteEventListener(EventData $oEventData)
{
- $oUser = new UserLocal();
- $sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin);
- $oUser->Set('password', 'ABCD1234@gabuzomeu');
- $oUser->Set('language', 'EN US');
- if (false !== $sWaitForException) {
- $this->expectException($sWaitForException);
- }
- $this->commonUserCreationTest($oUser, $aProfilesBeforeUserCreation);
+ $oObject = $oEventData->GetEventData()['object'];
+ $sEvent = $oEventData->GetEvent();
+ $sClass = get_class($oObject);
+ $sId = $oObject->GetKey();
+ self::$aEventCalls[$this->GetEventKey($sEvent, $sClass, $sId)] = true;
}
- /**
- * @dataProvider PortaPowerUserProvider
- * @covers User::CheckPortalProfiles
- */
- public function testUserLocalDelete($aProfilesBeforeUserCreation, $sWaitForException)
+ private function GetEventKey($sEvent, $sClass, $sId)
{
- $oUser = new UserLocal();
- $sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin);
- $oUser->Set('password', 'ABCD1234@gabuzomeu');
- $oUser->Set('language', 'EN US');
- if (false !== $sWaitForException) {
- $this->expectException($sWaitForException);
- }
- $this->commonUserCreationTest($oUser, $aProfilesBeforeUserCreation, false);
-
- $oUser->DBDelete();
+ return "event: $sEvent, class: $sClass, id: $sId";
}
- /**
- * @dataProvider PortaPowerUserProvider
- * @covers User::CheckPortalProfiles
- */
- public function testUserLocalUpdate($aProfilesBeforeUserCreation, $sWaitForException)
- {
- $oUser = new UserLocal();
- $sLogin = 'testUserLocalUpdateWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin);
- $oUser->Set('password', 'ABCD1234@gabuzomeu');
- $oUser->Set('language', 'EN US');
- if (false !== $sWaitForException) {
- $this->expectException($sWaitForException);
- }
- $this->commonUserUpdateTest($oUser, $aProfilesBeforeUserCreation);
- }
-
- private function commonUserCreationTest($oUserToCreate, $aProfilesBeforeUserCreation, $bTestUserItopAccess = true)
- {
- $sUserClass = get_class($oUserToCreate);
- list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUserToCreate, $aProfilesBeforeUserCreation);
-
- $this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aProfilesBeforeUserCreation, $bTestUserItopAccess);
- }
-
- private function commonUserUpdateTest($oUserToCreate, $aProfilesBeforeUserCreation)
- {
- $sUserClass = get_class($oUserToCreate);
- list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUserToCreate, ['Administrator']);
-
- $oUserToUpdate = MetaModel::GetObject($sUserClass, $sId);
- $oProfileList = $oUserToUpdate->Get('profile_list');
- while ($oObj = $oProfileList->Fetch()) {
- $oProfileList->RemoveItem($oObj->GetKey());
- }
-
- foreach ($aProfilesBeforeUserCreation as $sProfileName) {
- $oAdminUrpProfile = new URP_UserProfile();
- $oProfile = $aProfiles[$sProfileName];
- $oAdminUrpProfile->Set('profileid', $oProfile->GetKey());
- $oAdminUrpProfile->Set('reason', 'UNIT Tests');
- $oProfileList->AddItem($oAdminUrpProfile);
- }
-
- $oUserToUpdate->Set('profile_list', $oProfileList);
- $oUserToUpdate->DBWrite();
-
- $this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aProfilesBeforeUserCreation);
- }
-
- private function CreateUserForProfileTesting(User $oUserToCreate, array $aProfilesBeforeUserCreation, $bDbInsert = true): array
- {
- $aProfiles = [];
- $oSearch = DBSearch::FromOQL('SELECT URP_Profiles');
- $oProfileSet = new DBObjectSet($oSearch);
- while (($oProfile = $oProfileSet->Fetch()) != null) {
- $aProfiles[$oProfile->Get('name')] = $oProfile;
- }
-
- $this->CreateTestOrganization();
- $oContact = $this->CreatePerson('1');
- $iContactId = $oContact->GetKey();
-
- $oUserToCreate->Set('contactid', $iContactId);
-
- $oUserProfileList = $oUserToCreate->Get('profile_list');
- foreach ($aProfilesBeforeUserCreation as $sProfileName) {
- $oUserProfile = new URP_UserProfile();
- $oProfile = $aProfiles[$sProfileName];
- $oUserProfile->Set('profileid', $oProfile->GetKey());
- $oUserProfile->Set('reason', 'UNIT Tests');
- $oUserProfileList->AddItem($oUserProfile);
- }
-
- $oUserToCreate->Set('profile_list', $oUserProfileList);
- if ($bDbInsert) {
- $sId = $oUserToCreate->DBInsert();
- } else {
- $sId = -1;
- }
-
- return [$sId, $aProfiles];
- }
-
- private function CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedAssociatedProfilesAfterUserCreation, $bTestItopConnection = true)
- {
- $oUser = MetaModel::GetObject($sUserClass, $sId);
- $oUserProfileList = $oUser->Get('profile_list');
- $aProfilesAfterCreation = [];
- while (($oProfile = $oUserProfileList->Fetch()) != null) {
- $aProfilesAfterCreation[] = $oProfile->Get('profile');
- }
-
- foreach ($aExpectedAssociatedProfilesAfterUserCreation as $sExpectedProfileName) {
- $this->assertTrue(in_array($sExpectedProfileName, $aProfilesAfterCreation),
- "profile \'$sExpectedProfileName\' should be asociated to user after creation. ".var_export($aProfilesAfterCreation, true));
- }
-
- if (!$bTestItopConnection) {
- return;
- }
-
- $_SESSION = [];
-
- UserRights::Login($oUser->Get('login'));
-
- if (!UserRights::IsPortalUser()) {
- //calling this API triggers Fatal Error on below OQL used by \User->GetContactObject() for a user with only 'portal power user' profile
- /**
- * Error: No result for the single row query: 'SELECT DISTINCT `Contact`.`id` AS `Contactid`, `Contact`.`name` AS `Contactname`, `Contact`.`status` AS `Contactstatus`, `Contact`.`org_id` AS `Contactorg_id`, `Organization_org_id`.`name` AS `Contactorg_name`, `Contact`.`email` AS `Contactemail`, `Contact`.`phone` AS `Contactphone`, `Contact`.`notify` AS `Contactnotify`, `Contact`.`function` AS `Contactfunction`, `Contact`.`finalclass` AS `Contactfinalclass`, IF((`Contact`.`finalclass` IN ('Team', 'Contact')), CAST(CONCAT(COALESCE(`Contact`.`name`, '')) AS CHAR), CAST(CONCAT(COALESCE(`Contact_poly_Person`.`first_name`, ''), COALESCE(' ', ''), COALESCE(`Contact`.`name`, '')) AS CHAR)) AS `Contactfriendlyname`, COALESCE((`Contact`.`status` = 'inactive'), 0) AS `Contactobsolescence_flag`, `Contact`.`obsolescence_date` AS `Contactobsolescence_date`, CAST(CONCAT(COALESCE(`Organization_org_id`.`name`, '')) AS CHAR) AS `Contactorg_id_friendlyname`, COALESCE((`Organization_org_id`.`status` = 'inactive'), 0) AS `Contactorg_id_obsolescence_flag` FROM `contact` AS `Contact` INNER JOIN `organization` AS `Organization_org_id` ON `Contact`.`org_id` = `Organization_org_id`.`id` LEFT JOIN `person` AS `Contact_poly_Person` ON `Contact`.`id` = `Contact_poly_Person`.`id` WHERE ((`Contact`.`id` = 40) AND 0) '.
- */
- NavigationMenuFactory::MakeStandard();
- }
-
- $this->assertTrue(true, 'after fix N°5324 no exception raised');
- // logout
- $_SESSION = [];
- }
-
- /**
- * @dataProvider ProfilesLinksProvider
- */
- public function testProfilesLinksDBDelete(string $sProfileNameToRemove, $bRaiseException = false)
- {
- $aInitialProfiles = [$sProfileNameToRemove, 'Portal power user'];
-
- $oUser = new UserExternal();
- $sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin);
-
- [$sId, $aProfiles] = $this->CreateUserForProfileTesting($oUser, $aInitialProfiles);
-
- if ($bRaiseException) {
- $this->expectException(DeleteException::class);
- }
-
- $aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
- if (array_key_exists($sProfileNameToRemove, $aURPUserProfileByUser)) {
- $oURPUserProfile = $aURPUserProfileByUser[$sProfileNameToRemove];
- $oURPUserProfile->DBDelete();
- }
- }
-
- /**
- * @dataProvider ProfilesLinksProvider
- */
- public function testProfilesLinksEdit_ChangeProfileId(string $sInitialProfile, $bRaiseException = false)
- {
- $oUser = new UserExternal();
- $sLogin = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin);
-
- $sUserClass = get_class($oUser);
- list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, [$sInitialProfile]);
-
- $oURP_Profile = MetaModel::GetObjectByColumn('URP_Profiles', 'name', 'Portal power user');
-
- $aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
-
- if ($bRaiseException) {
- $this->expectException(CoreCannotSaveObjectException::class);
- }
-
- if (array_key_exists($sInitialProfile, $aURPUserProfileByUser)) {
- $oURPUserProfile = $aURPUserProfileByUser[$sInitialProfile];
- $oURPUserProfile->Set('profileid', $oURP_Profile->GetKey());
- $oURPUserProfile->DBWrite();
- }
-
- if (!$bRaiseException) {
- $aExpectedProfilesAfterUpdate = ['Portal power user', 'Portal user'];
- $this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedProfilesAfterUpdate);
- }
- }
-
- public function ProfilesLinksProvider()
- {
- return [
- 'Administrator' => ['sProfileNameToMove' => 'Administrator', 'bRaiseException' => true],
- 'Portal user' => ['sProfileNameToMove' => 'Portal user', 'bRaiseException' => true],
- ];
- }
-
- /**
- * @dataProvider ProfilesLinksProvider
- */
- public function testProfilesLinksEdit_ChangeUserId($sProfileNameToMove, $bRaiseException = false)
- {
- $aInitialProfiles = [$sProfileNameToMove, 'Portal power user'];
-
- $oUser = new UserExternal();
- $sLogin1 = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin1);
-
- $sUserClass = get_class($oUser);
- list ($sId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, $aInitialProfiles);
-
- $oUser = new UserExternal();
- $sLogin2 = 'testUserLDAPUpdateWithPortalPowerUserProfile-'.uniqid('', true);
- $oUser->Set('login', $sLogin2);
- list ($sAnotherUserId, $aProfiles) = $this->CreateUserForProfileTesting($oUser, ['Configuration Manager']);
-
- if ($bRaiseException) {
- $this->expectException(CoreCannotSaveObjectException::class);
- }
-
- $aURPUserProfileByUser = $this->GetURPUserProfileByUser($sId);
- if (array_key_exists($sProfileNameToMove, $aURPUserProfileByUser)) {
- $oURPUserProfile = $aURPUserProfileByUser[$sProfileNameToMove];
- $oURPUserProfile->Set('userid', $sAnotherUserId);
- $oURPUserProfile->DBWrite();
- }
-
- if (!$bRaiseException) {
- $aExpectedProfilesAfterUpdate = [$sProfileNameToMove, 'Configuration Manager'];
- $this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sAnotherUserId, $aExpectedProfilesAfterUpdate);
-
- $aExpectedProfilesAfterUpdate = ['Portal power user', 'Portal user'];
- $this->CheckProfilesAreOkAndThenConnectToITop($sUserClass, $sId, $aExpectedProfilesAfterUpdate);
- }
- }
-
- private function GetURPUserProfileByUser($iUserId): array
- {
- $aRes = [];
- $oSearch = DBSearch::FromOQL("SELECT URP_UserProfile WHERE userid=$iUserId");
- $oSet = new DBObjectSet($oSearch);
- while (($oURPUserProfile = $oSet->Fetch()) != null) {
- $aRes[$oURPUserProfile->Get('profile')] = $oURPUserProfile;
- }
-
- return $aRes;
- }
-
- public function CustomizedPortalsProvider()
- {
- return [
- 'console + customized portal' => [
- 'aPortalDispatcherData' => [
- 'customer-portal',
- 'backoffice',
- ],
- ],
- 'console + itop portal + customized portal' => [
- 'aPortalDispatcherData' => [
- 'itop-portal',
- 'customer-portal',
- 'backoffice',
- ],
- ],
- ];
- }
}
diff --git a/tests/php-unit-tests/unitary-tests/core/DBObject/CustomCheckToWriteTest.php b/tests/php-unit-tests/unitary-tests/core/DBObject/CustomCheckToWriteTest.php
new file mode 100644
index 000000000..93cea9f58
--- /dev/null
+++ b/tests/php-unit-tests/unitary-tests/core/DBObject/CustomCheckToWriteTest.php
@@ -0,0 +1,64 @@
+ [
+ 'aProfiles' => [],
+ 'bExpectedCheckStatus' => false,
+ ],
+ 'Portal power user' => [
+ 'aProfiles' => ['Portal power user',],
+ 'bExpectedCheckStatus' => true,
+ ],
+ 'Portal power user + Configuration Manager' => [
+ 'aProfiles' => ['Portal power user', 'Configuration Manager',],
+ 'bExpectedCheckStatus' => true,
+ ],
+ 'Portal power user + Configuration Manager + Admin' => [
+ 'aProfiles' => ['Portal power user', 'Configuration Manager', 'Administrator',],
+ 'bExpectedCheckStatus' => true,
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider PortaPowerUserProvider
+ * @covers User::CheckPortalProfiles
+ */
+ public function testUserLocalCheckPortalProfiles($aProfiles, $bExpectedCheckStatus)
+ {
+ $oUser = new UserLocal();
+ $sLogin = 'testUserLocalCreationWithPortalPowerUserProfile-'.uniqid('', true);
+ $oUser->Set('login', $sLogin);
+ $oUser->Set('password', 'ABCD1234@gabuzomeu');
+ $oUser->Set('language', 'EN US');
+ $oProfileList = $oUser->Get('profile_list');
+
+ foreach ($aProfiles as $sProfileName) {
+ $oAdminUrpProfile = new URP_UserProfile();
+ $oAdminUrpProfile->Set('profileid', self::$aURP_Profiles[$sProfileName]);
+ $oAdminUrpProfile->Set('reason', 'UNIT Tests');
+ $oProfileList->AddItem($oAdminUrpProfile);
+ }
+
+ $oUser->Set('profile_list', $oProfileList);
+
+ [$bCheckStatus, $aCheckIssues, $bSecurityIssue] = $oUser->CheckToWrite();
+ $this->assertEquals($bExpectedCheckStatus, $bCheckStatus);
+ }
+
+}
diff --git a/tests/php-unit-tests/unitary-tests/core/MetaModelTest.php b/tests/php-unit-tests/unitary-tests/core/MetaModelTest.php
index 721d87d52..e60006e22 100644
--- a/tests/php-unit-tests/unitary-tests/core/MetaModelTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/MetaModelTest.php
@@ -33,6 +33,29 @@ class MetaModelTest extends ItopDataTestCase
parent::tearDown();
}
+ /**
+ * @covers MetaModel::GetObjectByName
+ * @return void
+ * @throws \CoreException
+ */
+ public function testGetFinalClassName()
+ {
+ // Standalone classes
+ $this->assertEquals('Organization', MetaModel::GetFinalClassName('Organization', 1), 'Should work with standalone classes');
+ $this->assertEquals('SynchroDataSource', MetaModel::GetFinalClassName('SynchroDataSource', 1), 'Should work with standalone classes');
+
+ // 2 levels hierarchy
+ $this->assertEquals('Person', MetaModel::GetFinalClassName('Contact', 1));
+ $this->assertEquals('Person', MetaModel::GetFinalClassName('Person', 1));
+
+ // multi-level hierarchy
+ $oServer1 = MetaModel::GetObjectByName('Server', 'Server1');
+ $sServer1Id = $oServer1->GetKey();
+ foreach (MetaModel::EnumParentClasses('Server',ENUM_PARENT_CLASSES_ALL) as $sClass) {
+ $this->assertEquals('Server', MetaModel::GetFinalClassName($sClass, $sServer1Id), 'Should return Server for all the classes in the hierarchy');
+ }
+ }
+
/**
* @group itopRequestMgmt
* @covers MetaModel::ApplyParams()