mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-13 07:24:13 +01:00
N°6228 - Refactor after review
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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:<pre>\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
|
||||
];
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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))
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Application\Helper\Session;
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
|
||||
define('UR_ALLOWED_NO', 0);
|
||||
define('UR_ALLOWED_YES', 1);
|
||||
@@ -262,15 +260,6 @@ abstract class User extends cmdbAbstractObject
|
||||
MetaModel::Init_SetZListItems('default_search', array('login', 'contactid', 'status', 'org_id')); // Default criteria of the search banner
|
||||
}
|
||||
|
||||
/**
|
||||
* @see RegisterCRUDListener
|
||||
* @see EventService::RegisterListener()
|
||||
*/
|
||||
protected function RegisterEventListeners()
|
||||
{
|
||||
EventService::RegisterListener(EVENT_DB_CHECK_TO_WRITE, [$this, 'CheckPortalProfiles']);
|
||||
}
|
||||
|
||||
abstract public function CheckCredentials($sPassword);
|
||||
abstract public function TrustWebServerContext();
|
||||
abstract public function CanChangePassword();
|
||||
@@ -504,31 +493,6 @@ abstract class User extends cmdbAbstractObject
|
||||
}
|
||||
}
|
||||
|
||||
public function CheckPortalProfiles(EventData $oEventData): void
|
||||
{
|
||||
if (false === array_key_exists('profile_list', $this->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
|
||||
|
||||
@@ -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).
|
||||
*/
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @license http://opensource.org/licenses/AGPL-3.0
|
||||
*/
|
||||
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\DBObject;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use URP_UserProfile;
|
||||
use User;
|
||||
use UserLocal;
|
||||
|
||||
class CustomCheckToWriteTest extends ItopDataTestCase
|
||||
{
|
||||
public function PortaPowerUserProvider()
|
||||
{
|
||||
return [
|
||||
'No profile' => [
|
||||
'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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user