mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-12 23:14:18 +01:00
N°6228 - CheckToWrite() propagation to target objects based on with_php_constraint property
This commit is contained in:
committed by
Eric Espie
parent
9a59bc7890
commit
ea845dc6eb
@@ -5354,7 +5354,7 @@ EOF
|
||||
$aErrors = $oObj->UpdateObjectFromPostedForm('');
|
||||
$bResult = (count($aErrors) == 0);
|
||||
if ($bResult) {
|
||||
list($bResult, $aErrors) = $oObj->CheckToWrite();
|
||||
[$bResult, $aErrors] = $oObj->CheckToWrite();
|
||||
}
|
||||
if ($bPreview) {
|
||||
$sStatus = $bResult ? Dict::S('UI:BulkModifyStatusOk') : Dict::S('UI:BulkModifyStatusError');
|
||||
@@ -5958,42 +5958,62 @@ JS
|
||||
}
|
||||
|
||||
/**
|
||||
* If the passed object is an instance of a link class, then will register each remote object for modification using {@see static::RegisterObjectAwaitingEventDbLinksChanged()}
|
||||
* Possibility for linked classes to be notified of current class modification
|
||||
*
|
||||
* If an external key was modified, register also the previous object that was linked previously.
|
||||
*
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \Exception
|
||||
* @uses static::RegisterObjectAwaitingEventDbLinksChanged()
|
||||
*
|
||||
* @since 3.1.0 N°5906
|
||||
* @throws ArchivedObjectException
|
||||
* @throws CoreException
|
||||
* @throws Exception
|
||||
*
|
||||
* @since 3.1.0 N°5906 method creation
|
||||
* @since 3.1.1 3.2.0 N°6228 now just notify attributes having `with_php_computation`
|
||||
*/
|
||||
final protected function NotifyAttachedObjectsOnLinkClassModification(): void
|
||||
{
|
||||
$sClass = get_class($this);
|
||||
if (false === MetaModel::IsLinkClass($sClass)) {
|
||||
return;
|
||||
}
|
||||
// previous values in case of link change
|
||||
$aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes();
|
||||
$sClass = get_class($this);
|
||||
$aClassExtKeyAttCodes = MetaModel::GetAttributesList($sClass, [AttributeExternalKey::class]);
|
||||
foreach ($aClassExtKeyAttCodes as $sExternalKeyAttCode) {
|
||||
/** @var AttributeExternalKey $oAttDef */
|
||||
$oAttDef = MetaModel::GetAttributeDef($sClass, $sExternalKeyAttCode);
|
||||
|
||||
$aLnkClassExternalKeys = MetaModel::GetAttributesList($sClass, [AttributeExternalKey::class]);
|
||||
foreach ($aLnkClassExternalKeys as $sExternalKeyAttCode) {
|
||||
/** @var \AttributeExternalKey $oExternalKeyAttDef */
|
||||
$oExternalKeyAttDef = MetaModel::GetAttributeDef($sClass, $sExternalKeyAttCode);
|
||||
$sRemoteClassName = $oExternalKeyAttDef->GetTargetClass();
|
||||
if (false === $this->DoesRemoteObjectHavePhpComputation($oAttDef)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$sRemoteObjectId = $this->Get($sExternalKeyAttCode);
|
||||
if ($sRemoteObjectId > 0) {
|
||||
self::RegisterObjectAwaitingEventDbLinksChanged($sRemoteClassName, $sRemoteObjectId);
|
||||
self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sRemoteObjectId);
|
||||
}
|
||||
|
||||
$sPreviousRemoteObjectId = $aPreviousValues[$sExternalKeyAttCode] ?? 0;
|
||||
if ($sPreviousRemoteObjectId > 0) {
|
||||
self::RegisterObjectAwaitingEventDbLinksChanged($sRemoteClassName, $sPreviousRemoteObjectId);
|
||||
self::RegisterObjectAwaitingEventDbLinksChanged($oAttDef->GetTargetClass(), $sPreviousRemoteObjectId);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function DoesRemoteObjectHavePhpComputation(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()){
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register one object for later EVENT_DB_LINKS_CHANGED event.
|
||||
*
|
||||
|
||||
@@ -1744,6 +1744,15 @@ class AttributeLinkedSet extends AttributeDefinition
|
||||
return $this->GetOptional('with_php_constraint', false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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()
|
||||
{
|
||||
return $this->GetOptional('with_php_computation', false);
|
||||
}
|
||||
|
||||
public function GetLinkedClass()
|
||||
{
|
||||
return $this->Get('linked_class');
|
||||
|
||||
@@ -474,7 +474,7 @@ abstract class CMDBObject extends DBObject
|
||||
public function DBDelete(&$oDeletionPlan = null)
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
$oDeletionPlan = $this->DBDeleteTracked_Internal($oDeletionPlan);
|
||||
$oDeletionPlan = parent::DBDelete($oDeletionPlan);
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
@@ -502,6 +502,12 @@
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</property>
|
||||
<property id="with_php_computation">
|
||||
<php_param>with_php_computation</php_param>
|
||||
<mandatory>false</mandatory>
|
||||
<type>boolean</type>
|
||||
<default>false</default>
|
||||
</property>
|
||||
<property id="create_temporary_object">
|
||||
<php_param>create_temporary_object</php_param>
|
||||
<mandatory>false</mandatory>
|
||||
|
||||
@@ -187,12 +187,13 @@ abstract class DBObject implements iDisplay
|
||||
protected $m_oLinkHostObject = null;
|
||||
|
||||
/**
|
||||
* @var array List all the CRUD stack in progress
|
||||
*
|
||||
* The array contains instances of
|
||||
* ['type' => 'type of CRUD operation (INSERT, UPDATE, DELETE)',
|
||||
* 'class' => 'class of the object in the CRUD process',
|
||||
* 'id' => 'id of the object in the CRUD process']
|
||||
* @var array{array{
|
||||
* type: string,
|
||||
* class: string,
|
||||
* id: string,
|
||||
* }} List all the CRUD stack in progress, with :
|
||||
* - type: CRUD operation (INSERT, UPDATE, DELETE)',
|
||||
* - class: class of the object in the CRUD process, leaf (object finalclass) if we have a hierarchy
|
||||
*
|
||||
* @since 3.1.0 N°5906
|
||||
*/
|
||||
@@ -2461,6 +2462,110 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 3.1.1 3.2.0 N°6228 method creation
|
||||
*/
|
||||
final protected function CheckPhpConstraint(bool $bIsCheckToDelete = false): void
|
||||
{
|
||||
$aChanges = $this->ListChanges();
|
||||
|
||||
$aClassExtKeyAttCodes = MetaModel::GetAttributesList(get_class($this), [AttributeExternalKey::class]);
|
||||
foreach ($aClassExtKeyAttCodes as $sExtKeyWithMirrorLinkAttCode) {
|
||||
/** @var AttributeExternalKey $oExtKeyWithMirrorLinkAttDef */
|
||||
$oExtKeyWithMirrorLinkAttDef = MetaModel::GetAttributeDef(get_class($this), $sExtKeyWithMirrorLinkAttCode);
|
||||
|
||||
$oRemoteObject = $this->GetRemoteObjectWithPhpConstraint($oExtKeyWithMirrorLinkAttDef, $this->Get($sExtKeyWithMirrorLinkAttCode));
|
||||
if (is_null($oRemoteObject)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
/** @var AttributeLinkedSet $oAttDefMirrorLink */
|
||||
$oAttDefMirrorLink = $oExtKeyWithMirrorLinkAttDef->GetMirrorLinkAttribute();
|
||||
if (is_null($oAttDefMirrorLink)) {
|
||||
continue;
|
||||
}
|
||||
$sAttCodeMirrorLink = $oAttDefMirrorLink->GetCode();
|
||||
|
||||
if ($this->IsNew()) {
|
||||
$this->CheckRemotePhpConstraintOnObject('add', $oRemoteObject, $sAttCodeMirrorLink, false);
|
||||
} else if ($bIsCheckToDelete) {
|
||||
$this->CheckRemotePhpConstraintOnObject('remove', $oRemoteObject, $sAttCodeMirrorLink, true);
|
||||
} else {
|
||||
if (array_key_exists($sExtKeyWithMirrorLinkAttCode, $aChanges)) {
|
||||
// need to update remote old + new
|
||||
$aPreviousValues = $this->ListPreviousValuesForUpdatedAttributes();
|
||||
$sPreviousRemoteObjectKey = $aPreviousValues[$sExtKeyWithMirrorLinkAttCode];
|
||||
$oPreviousRemoteObject = $this->GetRemoteObjectWithPhpConstraint($oExtKeyWithMirrorLinkAttDef, $sPreviousRemoteObjectKey);
|
||||
if (false === is_null($oPreviousRemoteObject)) {
|
||||
$this->CheckRemotePhpConstraintOnObject('remove', $oPreviousRemoteObject, $sAttCodeMirrorLink, false);
|
||||
}
|
||||
$this->CheckRemotePhpConstraintOnObject('add', $oRemoteObject, $sAttCodeMirrorLink, false);
|
||||
} else {
|
||||
$this->CheckRemotePhpConstraintOnObject('modify', $oRemoteObject, $sAttCodeMirrorLink, false); // we need to update remote with current lnk instance
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function CheckRemotePhpConstraintOnObject(string $sAction, DBObject $oRemoteObject, string $sAttCodeMirrorLink, bool $bIsCheckToDelete): void
|
||||
{
|
||||
$this->LogCRUDDebug(__METHOD__, "action: $sAction ".get_class($oRemoteObject).'::'.$oRemoteObject->GetKey()." ($sAttCodeMirrorLink)");
|
||||
|
||||
/** @var \ormLinkSet $oRemoteValue */
|
||||
$oRemoteValue = $oRemoteObject->Get($sAttCodeMirrorLink);
|
||||
switch ($sAction) {
|
||||
case 'add':
|
||||
$oRemoteValue->AddItem($this);
|
||||
break;
|
||||
case 'remove':
|
||||
$oRemoteValue->RemoveItem($this->GetKey());
|
||||
break;
|
||||
case 'modify':
|
||||
$oRemoteValue->ModifyItem($this);
|
||||
break;
|
||||
}
|
||||
$oRemoteObject->Set($sAttCodeMirrorLink, $oRemoteValue);
|
||||
[$bCheckStatus, $aCheckIssues, $bSecurityIssue] = $oRemoteObject->CheckToWrite();
|
||||
if (false === $bCheckStatus) {
|
||||
if ($bIsCheckToDelete) {
|
||||
$this->m_aDeleteIssues = array_merge($this->m_aDeleteIssues ?? [], $aCheckIssues);
|
||||
} else {
|
||||
$this->m_aCheckIssues = array_merge($this->m_aCheckIssues ?? [], $aCheckIssues);
|
||||
}
|
||||
$this->m_bSecurityIssue = $this->m_bSecurityIssue || $bSecurityIssue;
|
||||
}
|
||||
$aRemoteCheckWarnings = $oRemoteObject->GetCheckWarnings();
|
||||
if (is_array($aRemoteCheckWarnings)) {
|
||||
$this->m_aCheckWarnings = array_merge($this->m_aCheckWarnings ?? [], $aRemoteCheckWarnings);
|
||||
}
|
||||
}
|
||||
|
||||
private function GetRemoteObjectWithPhpConstraint(AttributeExternalKey $oAttDef, $sRemoteObjectKey)
|
||||
{
|
||||
$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
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/** @var AttributeLinkedSet $oAttDefMirrorLink */
|
||||
$oAttDefMirrorLink = $oAttDef->GetMirrorLinkAttribute();
|
||||
if (is_null($oAttDefMirrorLink) || false === $oAttDefMirrorLink->GetHasConstraint()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (DBObject::IsObjectCurrentlyInCrud($sRemoteObjectClass, $sRemoteObjectKey)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return MetaModel::GetObject($sRemoteObjectClass, $sRemoteObjectKey, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
* @api-advanced
|
||||
@@ -2484,6 +2589,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
return array(true, array());
|
||||
}
|
||||
|
||||
if (is_null($this->m_bCheckStatus))
|
||||
{
|
||||
$this->m_aCheckIssues = array();
|
||||
@@ -2500,6 +2606,9 @@ abstract class DBObject implements iDisplay
|
||||
$oKPI = new ExecutionKPI();
|
||||
$this->DoCheckToWrite();
|
||||
$oKPI->ComputeStatsForExtension($this, 'DoCheckToWrite');
|
||||
|
||||
$this->CheckPhpConstraint();
|
||||
|
||||
if (count($this->m_aCheckIssues) == 0)
|
||||
{
|
||||
$this->m_bCheckStatus = true;
|
||||
@@ -2509,6 +2618,7 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_bCheckStatus = false;
|
||||
}
|
||||
}
|
||||
|
||||
return array($this->m_bCheckStatus, $this->m_aCheckIssues, $this->m_bSecurityIssue);
|
||||
}
|
||||
|
||||
@@ -2661,6 +2771,7 @@ abstract class DBObject implements iDisplay
|
||||
{
|
||||
$this->MakeDeletionPlan($oDeletionPlan);
|
||||
$oDeletionPlan->ComputeResults();
|
||||
|
||||
return (!$oDeletionPlan->FoundStopper());
|
||||
}
|
||||
|
||||
@@ -3214,7 +3325,7 @@ abstract class DBObject implements iDisplay
|
||||
}
|
||||
}
|
||||
|
||||
list($bRes, $aIssues) = $this->CheckToWrite(false);
|
||||
[$bRes, $aIssues] = $this->CheckToWrite(false);
|
||||
if (!$bRes) {
|
||||
throw new CoreCannotSaveObjectException(array('issues' => $aIssues, 'class' => get_class($this), 'id' => $this->GetKey()));
|
||||
}
|
||||
@@ -3454,7 +3565,7 @@ abstract class DBObject implements iDisplay
|
||||
return $this->m_iKey;
|
||||
}
|
||||
|
||||
list($bRes, $aIssues) = $this->CheckToWrite(false);
|
||||
[$bRes, $aIssues] = $this->CheckToWrite(false);
|
||||
if (!$bRes) {
|
||||
throw new CoreCannotSaveObjectException(['issues' => $aIssues, 'class' => $sClass, 'id' => $this->GetKey()]);
|
||||
}
|
||||
@@ -3921,6 +4032,8 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
protected function DBDeleteSingleObject()
|
||||
{
|
||||
$this->LogCRUDEnter(__METHOD__);
|
||||
|
||||
if (MetaModel::DBIsReadOnly())
|
||||
{
|
||||
$this->LogCRUDExit(__METHOD__, 'DB is read-only');
|
||||
@@ -4043,6 +4156,7 @@ abstract class DBObject implements iDisplay
|
||||
|
||||
|
||||
$this->m_bIsInDB = false;
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
// Fix for N°926: do NOT reset m_iKey as it can be used to have it for reporting purposes (see the REST service to delete
|
||||
// objects, reported as bug N°926)
|
||||
// Thought the key is not reset, using DBInsert or DBWrite will create an object having the same characteristics and a new ID. DBUpdate is protected
|
||||
@@ -4073,74 +4187,70 @@ 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');
|
||||
}
|
||||
if (is_null($oDeletionPlan))
|
||||
{
|
||||
$oDeletionPlan = new DeletionPlan();
|
||||
}
|
||||
$this->MakeDeletionPlan($oDeletionPlan);
|
||||
$oDeletionPlan->ComputeResults();
|
||||
static $iLoopTimeLimit = null;
|
||||
if ($iLoopTimeLimit == null) {
|
||||
$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
|
||||
}
|
||||
if (is_null($oDeletionPlan)) {
|
||||
$oDeletionPlan = new DeletionPlan();
|
||||
}
|
||||
$this->MakeDeletionPlan($oDeletionPlan);
|
||||
$oDeletionPlan->ComputeResults();
|
||||
|
||||
if ($oDeletionPlan->FoundStopper())
|
||||
{
|
||||
$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)));
|
||||
}
|
||||
if ($oDeletionPlan->FoundStopper()) {
|
||||
$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:
|
||||
// www.php.net/manual/fr/function.set-time-limit.php#72305
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
// Getting and setting time limit are not symetric:
|
||||
// www.php.net/manual/fr/function.set-time-limit.php#72305
|
||||
$iPreviousTimeLimit = ini_get('max_execution_time');
|
||||
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aToDelete)
|
||||
{
|
||||
foreach ($aToDelete as $iId => $aData)
|
||||
{
|
||||
/** @var \DBObject $oToDelete */
|
||||
$oToDelete = $aData['to_delete'];
|
||||
// The deletion based on a deletion plan should not be done for each object if the deletion plan is common (Trac #457)
|
||||
// because for each object we would try to update all the preceding ones... that are already deleted
|
||||
// A better approach would be to change the API to apply the DBDelete on the deletion plan itself... just once
|
||||
// As a temporary fix: delete only the objects that are still to be deleted...
|
||||
if ($oToDelete->m_bIsInDB)
|
||||
{
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
foreach ($oDeletionPlan->ListDeletes() as $sClass => $aToDelete) {
|
||||
foreach ($aToDelete as $iId => $aData) {
|
||||
/** @var \DBObject $oToDelete */
|
||||
$oToDelete = $aData['to_delete'];
|
||||
// The deletion based on a deletion plan should not be done for each object if the deletion plan is common (Trac #457)
|
||||
// because for each object we would try to update all the preceding ones... that are already deleted
|
||||
// A better approach would be to change the API to apply the DBDelete on the deletion plan itself... just once
|
||||
// As a temporary fix: delete only the objects that are still to be deleted...
|
||||
if ($oToDelete->m_bIsInDB) {
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
|
||||
$oToDelete->AddCurrentObjectInCrudStack('DELETE');
|
||||
try {
|
||||
$oToDelete->DBDeleteSingleObject();
|
||||
}
|
||||
finally {
|
||||
$oToDelete->RemoveCurrentObjectInCrudStack();
|
||||
$oToDelete->AddCurrentObjectInCrudStack('DELETE');
|
||||
try {
|
||||
$oToDelete->DBDeleteSingleObject();
|
||||
}
|
||||
finally {
|
||||
$oToDelete->RemoveCurrentObjectInCrudStack();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate)
|
||||
{
|
||||
foreach ($aToUpdate as $aData)
|
||||
{
|
||||
$oToUpdate = $aData['to_reset'];
|
||||
/** @var \DBObject $oToUpdate */
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef)
|
||||
{
|
||||
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
$oToUpdate->DBUpdate();
|
||||
foreach ($oDeletionPlan->ListUpdates() as $sClass => $aToUpdate) {
|
||||
foreach ($aToUpdate as $aData) {
|
||||
$oToUpdate = $aData['to_reset'];
|
||||
/** @var \DBObject $oToUpdate */
|
||||
foreach ($aData['attributes'] as $sRemoteExtKey => $aRemoteAttDef) {
|
||||
$oToUpdate->Set($sRemoteExtKey, $aData['values'][$sRemoteExtKey]);
|
||||
set_time_limit(intval($iLoopTimeLimit));
|
||||
$oToUpdate->DBUpdate();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
set_time_limit(intval($iPreviousTimeLimit));
|
||||
} finally {
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
$this->RemoveCurrentObjectInCrudStack();
|
||||
}
|
||||
|
||||
set_time_limit(intval($iPreviousTimeLimit));
|
||||
|
||||
$this->LogCRUDExit(__METHOD__);
|
||||
return $oDeletionPlan;
|
||||
}
|
||||
|
||||
@@ -5246,6 +5356,7 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aDeleteIssues = array(); // Ok
|
||||
$this->FireEventCheckToDelete($oDeletionPlan);
|
||||
$this->DoCheckToDelete($oDeletionPlan);
|
||||
$this->CheckPhpConstraint(true);
|
||||
$oDeletionPlan->SetDeletionIssues($this, $this->m_aDeleteIssues, $this->m_bSecurityIssue);
|
||||
|
||||
$aDependentObjects = $this->GetReferencingObjects(true /* allow all data */);
|
||||
@@ -6236,6 +6347,18 @@ abstract class DBObject implements iDisplay
|
||||
$this->m_aCheckWarnings[] = $sWarning;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @api
|
||||
*
|
||||
* @return string[]|null
|
||||
* @since 3.1.1 3.2.0
|
||||
*/
|
||||
public function GetCheckWarnings(): ?array
|
||||
{
|
||||
return $this->m_aCheckWarnings;
|
||||
}
|
||||
|
||||
/**
|
||||
* @api
|
||||
*
|
||||
@@ -6484,6 +6607,11 @@ abstract class DBObject implements iDisplay
|
||||
// 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);
|
||||
}
|
||||
|
||||
foreach (self::$m_aCrudStack as $aCrudStackEntry) {
|
||||
if (($sClass === $aCrudStackEntry['class'])
|
||||
&& ($sConvertedId === $aCrudStackEntry['id'])) {
|
||||
@@ -6523,6 +6651,7 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
private function AddCurrentObjectInCrudStack(string $sCrudType): void
|
||||
{
|
||||
$this->LogCRUDDebug(__METHOD__);
|
||||
self::$m_aCrudStack[] = [
|
||||
'type' => $sCrudType,
|
||||
'class' => get_class($this),
|
||||
@@ -6539,6 +6668,7 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
private function UpdateCurrentObjectInCrudStack(): void
|
||||
{
|
||||
$this->LogCRUDDebug(__METHOD__);
|
||||
$aCurrentCrudStack = array_pop(self::$m_aCrudStack);
|
||||
$aCurrentCrudStack['id'] = (string)$this->GetKey();
|
||||
self::$m_aCrudStack[] = $aCurrentCrudStack;
|
||||
@@ -6552,7 +6682,8 @@ abstract class DBObject implements iDisplay
|
||||
*/
|
||||
private function RemoveCurrentObjectInCrudStack(): void
|
||||
{
|
||||
array_pop(self::$m_aCrudStack);
|
||||
$aRemoved = array_pop(self::$m_aCrudStack);
|
||||
$this->LogCRUDDebug(__METHOD__, $aRemoved['class'].':'.$aRemoved['id']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -5155,7 +5155,7 @@ abstract class MetaModel
|
||||
*/
|
||||
protected static function DBCreateTables($aCallback = null)
|
||||
{
|
||||
list($aErrors, $aSugFix, $aCondensedQueries) = self::DBCheckFormat();
|
||||
[$aErrors, $aSugFix, $aCondensedQueries] = self::DBCheckFormat();
|
||||
|
||||
//$sSQL = implode('; ', $aCondensedQueries); Does not work - multiple queries not allowed
|
||||
foreach($aCondensedQueries as $sQuery)
|
||||
@@ -5177,7 +5177,7 @@ abstract class MetaModel
|
||||
*/
|
||||
protected static function DBCreateViews()
|
||||
{
|
||||
list($aErrors, $aSugFix) = self::DBCheckViews();
|
||||
[$aErrors, $aSugFix] = self::DBCheckViews();
|
||||
|
||||
foreach($aSugFix as $sClass => $aTarget)
|
||||
{
|
||||
@@ -6926,6 +6926,22 @@ abstract class MetaModel
|
||||
return $iCount === 1;
|
||||
}
|
||||
|
||||
public static function GetFinalClassName(string $sClass, int $iKey): string
|
||||
{
|
||||
$sFinalClassField = Metamodel::DBGetClassField($sClass);
|
||||
if (utils::IsNullOrEmptyString($sFinalClassField)) {
|
||||
return $sClass;
|
||||
}
|
||||
|
||||
$sRootClass = MetaModel::GetRootClass($sClass);
|
||||
$sTable = MetaModel::DBGetTable($sRootClass);
|
||||
$sKeyCol = MetaModel::DBGetKey($sRootClass);
|
||||
$sEscapedKey = CMDBSource::Quote($iKey);
|
||||
|
||||
$sQuery = "SELECT `{$sFinalClassField}` FROM `{$sTable}` WHERE `{$sKeyCol}` = {$sEscapedKey}";
|
||||
return CMDBSource::QueryToScalar($sQuery);
|
||||
}
|
||||
|
||||
/**
|
||||
* Search for the specified class and id. If the object is archived it will be returned anyway (this is for pre-2.4
|
||||
* module compatibility, see N.1108)
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
<?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);
|
||||
@@ -260,6 +262,15 @@ 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();
|
||||
@@ -493,6 +504,31 @@ 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
|
||||
|
||||
@@ -202,6 +202,7 @@
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
<ext_key_to_remote>contact_id</ext_key_to_remote>
|
||||
<with_php_computation>true</with_php_computation>
|
||||
<duplicates/>
|
||||
</field>
|
||||
<field id="functionalcis_list" xsi:type="AttributeLinkedSetIndirect">
|
||||
@@ -210,6 +211,7 @@
|
||||
<count_min>0</count_min>
|
||||
<count_max>0</count_max>
|
||||
<ext_key_to_remote>functionalci_id</ext_key_to_remote>
|
||||
<with_php_computation>true</with_php_computation>
|
||||
<duplicates/>
|
||||
</field>
|
||||
<field id="workorders_list" xsi:type="AttributeLinkedSet">
|
||||
|
||||
@@ -182,6 +182,7 @@ Dict::Add('EN US', 'English', 'English', array(
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Changing status is not allowed for your own User',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Allowed organizations must contain User organization',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'The current list of profiles does not give sufficient access rights (Users are not modifiable anymore)',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'The Portal power user profile does not give sufficient access rights (another profile must be added)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'At least one organization must be assigned to this user.',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organization not allowed.',
|
||||
'Class:User/Error:UserOrganizationNotAllowed' => 'The user account does not belong to your allowed organizations.',
|
||||
|
||||
@@ -166,6 +166,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
|
||||
'Class:User/Error:StatusChangeIsNotAllowed' => 'Impossible de changer l\'état de son propre utilisateur',
|
||||
'Class:User/Error:AllowedOrgsMustContainUserOrg' => 'Les organisations permises doivent contenir l\'organisation de l\'utilisateur',
|
||||
'Class:User/Error:CurrentProfilesHaveInsufficientRights' => 'Les profils existants ne permettent pas de modifier les utilisateurs',
|
||||
'Class:User/Error:PortalPowerUserHasInsufficientRights' => 'Le profil Portal power user ne donne pas suffisamment de droits à l\'utilisateur (un autre profil doit être ajouté)',
|
||||
'Class:User/Error:AtLeastOneOrganizationIsNeeded' => 'L\'utilisateur doit avoir au moins une organisation.',
|
||||
'Class:User/Error:OrganizationNotAllowed' => 'Organisation non autorisée.',
|
||||
'Class:User/Error:UserOrganizationNotAllowed' => 'L\'utilisateur n\'appartient pas à vos organisations.',
|
||||
|
||||
@@ -2081,6 +2081,7 @@ EOF
|
||||
$this->CompileCommonProperty('edit_when', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$this->CompileCommonProperty('with_php_computation', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
} elseif ($sAttType == 'AttributeLinkedSet') {
|
||||
$this->CompileCommonProperty('linked_class', $oField, $aParameters, $sModuleRelativeDir);
|
||||
@@ -2092,6 +2093,7 @@ EOF
|
||||
$this->CompileCommonProperty('edit_when', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('filter', $oField, $aParameters, $sModuleRelativeDir);
|
||||
$this->CompileCommonProperty('with_php_constraint', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$this->CompileCommonProperty('with_php_computation', $oField, $aParameters, $sModuleRelativeDir, false);
|
||||
$aParameters['depends_on'] = $sDependencies;
|
||||
} elseif ($sAttType == 'AttributeExternalKey') {
|
||||
$this->CompileCommonProperty('target_class', $oField, $aParameters, $sModuleRelativeDir);
|
||||
|
||||
@@ -1,82 +1,16 @@
|
||||
<?php
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
const USE_TRANSACTION = true;
|
||||
const CREATE_TEST_ORG = true;
|
||||
// Count the events by name
|
||||
private static array $aEventCalls = [];
|
||||
private static int $iEventCalls = 0;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
static::$aEventCalls = [];
|
||||
static::$iEventCalls = 0;
|
||||
parent::setUp();
|
||||
}
|
||||
|
||||
public static function IncrementCallCount(string $sEvent)
|
||||
{
|
||||
self::$aEventCalls[$sEvent] = (self::$aEventCalls[$sEvent] ?? 0) + 1;
|
||||
self::$iEventCalls++;
|
||||
}
|
||||
|
||||
public function testCheckLinkModifications() {
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
$this->assertSame([], $aLinkModificationsStack);
|
||||
|
||||
// retain events
|
||||
cmdbAbstractObject::SetEventDBLinksChangedBlocked(true);
|
||||
|
||||
// Create the person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
// Create the team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
// contact types
|
||||
$oContactType1 = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.rand(10000, 99999)]);
|
||||
$oContactType1->DBInsert();
|
||||
$oContactType2 = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.rand(10000, 99999)]);
|
||||
$oContactType2->DBInsert();
|
||||
|
||||
// Prepare the link for the insertion with the team
|
||||
|
||||
$aValues = [
|
||||
'person_id' => $oPerson->GetKey(),
|
||||
'role_id' => $oContactType1->GetKey(),
|
||||
'team_id' => $oTeam->GetKey(),
|
||||
];
|
||||
$oLinkPersonToTeam1 = MetaModel::NewObject(lnkPersonToTeam::class, $aValues);
|
||||
$oLinkPersonToTeam1->DBInsert();
|
||||
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
self::assertCount(3, $aLinkModificationsStack);
|
||||
$aExpectedLinkStack = [
|
||||
'Team' => [$oTeam->GetKey() => 1],
|
||||
'Person' => [$oPerson->GetKey() => 1],
|
||||
'ContactType' => [$oContactType1->GetKey() => 1],
|
||||
];
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
|
||||
$oLinkPersonToTeam1->Set('role_id', $oContactType2->GetKey());
|
||||
$oLinkPersonToTeam1->DBWrite();
|
||||
$aLinkModificationsStack = $this->GetObjectsAwaitingFireEventDbLinksChanged();
|
||||
self::assertCount(3, $aLinkModificationsStack);
|
||||
$aExpectedLinkStack = [
|
||||
'Team' => [$oTeam->GetKey() => 2],
|
||||
'Person' => [$oPerson->GetKey() => 2],
|
||||
'ContactType' => [
|
||||
$oContactType1->GetKey() => 2,
|
||||
$oContactType2->GetKey() => 1,
|
||||
],
|
||||
];
|
||||
self::assertSame($aExpectedLinkStack, $aLinkModificationsStack);
|
||||
}
|
||||
|
||||
public function testProcessClassIdDeferredUpdate()
|
||||
{
|
||||
// Create the team
|
||||
@@ -152,288 +86,4 @@ class cmdbAbstractObjectTest extends ItopDataTestCase {
|
||||
{
|
||||
$this->SetNonPublicStaticProperty(cmdbAbstractObject::class, 'aObjectsAwaitingEventDbLinksChanged', $aObjects);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are not sent to the current updated/created object (Team)
|
||||
* the events are sent to the other side (Person)
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testDBInsertTeam()
|
||||
{
|
||||
// Prepare the link set
|
||||
$sLinkedClass = lnkPersonToTeam::class;
|
||||
$aLinkedObjectsArray = [];
|
||||
$oSet = DBObjectSet::FromArray($sLinkedClass, $aLinkedObjectsArray);
|
||||
$oLinkSet = new ormLinkSet(Team::class, 'persons_list', $oSet);
|
||||
|
||||
// Create the 3 persons
|
||||
for ($i = 0; $i < 3; $i++) {
|
||||
$oPerson = $this->CreatePerson($i);
|
||||
$this->assertIsObject($oPerson);
|
||||
// Add the person to the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey()]);
|
||||
$oLinkSet->AddItem($oLink);
|
||||
}
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'persons_list' => $oLinkSet, 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// 3 links added to person + 1 for the Team
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when creating a new lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testAddLinkToTeam()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link creation will signal both the Person an the Team
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
// 2 events one for Person and One for Team
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when updating an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUpdateLinkRole()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Person, the Team and the ContactType
|
||||
// Change the role
|
||||
$oContactType = MetaModel::NewObject(ContactType::class, ['name' => 'test_'.$oLink->GetKey()]);
|
||||
$oContactType->DBInsert();
|
||||
$oLink->Set('role_id', $oContactType->GetKey());
|
||||
$oLink->DBUpdate();
|
||||
|
||||
// 3 events one for Person, one for Team and one for ContactType
|
||||
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that when a link changes from an object to another, then both objects are notified
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testUpdateLinkPerson()
|
||||
{
|
||||
// Create 2 person
|
||||
$oPerson1 = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson1);
|
||||
|
||||
$oPerson2 = $this->CreatePerson(2);
|
||||
$this->assertIsObject($oPerson2);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link between Person1 and Team
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson1->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link update will signal both the Persons and the Team
|
||||
// Change the person
|
||||
$oLink->Set('person_id', $oPerson2->GetKey());
|
||||
$oLink->DBUpdate();
|
||||
|
||||
// 3 events 2 for Person, one for Team
|
||||
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that EVENT_DB_LINKS_CHANGED events are sent to all the linked objects when deleting an existing lnk object
|
||||
*
|
||||
* @return void
|
||||
* @throws \ArchivedObjectException
|
||||
* @throws \CoreCannotSaveObjectException
|
||||
* @throws \CoreException
|
||||
* @throws \CoreUnexpectedValue
|
||||
* @throws \CoreWarning
|
||||
* @throws \MySQLException
|
||||
* @throws \OQLException
|
||||
*/
|
||||
public function testDeleteLink()
|
||||
{
|
||||
// Create a person
|
||||
$oPerson = $this->CreatePerson(1);
|
||||
$this->assertIsObject($oPerson);
|
||||
|
||||
// Create a Team
|
||||
$oTeam = MetaModel::NewObject(Team::class, ['name' => 'TestTeam1', 'org_id' => $this->getTestOrgId()]);
|
||||
$oTeam->DBInsert();
|
||||
$this->assertIsObject($oTeam);
|
||||
|
||||
// Create the link
|
||||
$oLink = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLink->DBInsert();
|
||||
|
||||
$this->debug("\n-------------> Test Starts HERE\n");
|
||||
$oEventReceiver = new LinksEventReceiver($this);
|
||||
$oEventReceiver->RegisterCRUDListeners();
|
||||
|
||||
// The link delete will signal both the Person an the Team
|
||||
$oLink->DBDelete();
|
||||
|
||||
// 3 events one for Person, one for Team
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Debug called by event receivers
|
||||
*
|
||||
* @param $sMsg
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function DebugStatic($sMsg)
|
||||
{
|
||||
if (static::$DEBUG_UNIT_TEST) {
|
||||
if (is_string($sMsg)) {
|
||||
echo "$sMsg\n";
|
||||
} else {
|
||||
print_r($sMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Count events received
|
||||
* And allow callbacks on events
|
||||
*/
|
||||
class LinksEventReceiver
|
||||
{
|
||||
private $oTestCase;
|
||||
private $aCallbacks = [];
|
||||
|
||||
public static $bIsObjectInCrudStack;
|
||||
|
||||
public function __construct(ItopDataTestCase $oTestCase)
|
||||
{
|
||||
$this->oTestCase = $oTestCase;
|
||||
}
|
||||
|
||||
public function AddCallback(string $sEvent, string $sClass, string $sFct, int $iCount = 1): void
|
||||
{
|
||||
$this->aCallbacks[$sEvent][$sClass] = [
|
||||
'callback' => [$this, $sFct],
|
||||
'count' => $iCount,
|
||||
];
|
||||
}
|
||||
|
||||
public function CleanCallbacks()
|
||||
{
|
||||
$this->aCallbacks = [];
|
||||
}
|
||||
|
||||
// Event callbacks
|
||||
public function OnEvent(EventData $oData)
|
||||
{
|
||||
$sEvent = $oData->GetEvent();
|
||||
$oObject = $oData->Get('object');
|
||||
$sClass = get_class($oObject);
|
||||
$iKey = $oObject->GetKey();
|
||||
$this->Debug(__METHOD__.": received event '$sEvent' for $sClass::$iKey");
|
||||
cmdbAbstractObjectTest::IncrementCallCount($sEvent);
|
||||
|
||||
if (isset($this->aCallbacks[$sEvent][$sClass])) {
|
||||
$aCallBack = $this->aCallbacks[$sEvent][$sClass];
|
||||
if ($aCallBack['count'] > 0) {
|
||||
$this->aCallbacks[$sEvent][$sClass]['count']--;
|
||||
call_user_func($this->aCallbacks[$sEvent][$sClass]['callback'], $oObject);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function RegisterCRUDListeners(string $sEvent = null, $mEventSource = null)
|
||||
{
|
||||
$this->Debug('Registering Test event listeners');
|
||||
if (is_null($sEvent)) {
|
||||
$this->oTestCase->EventService_RegisterListener(EVENT_DB_LINKS_CHANGED, [$this, 'OnEvent']);
|
||||
return;
|
||||
}
|
||||
$this->oTestCase->EventService_RegisterListener($sEvent, [$this, 'OnEvent'], $mEventSource);
|
||||
}
|
||||
|
||||
private function Debug($msg)
|
||||
{
|
||||
cmdbAbstractObjectTest::DebugStatic($msg);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,6 +82,7 @@ class QueryTest extends ItopDataTestCase
|
||||
}
|
||||
|
||||
$oQuery->DBInsert();
|
||||
$this->assertFalse($oQuery->IsNew());
|
||||
|
||||
return $oQuery;
|
||||
}
|
||||
|
||||
@@ -208,4 +208,33 @@ PHP
|
||||
$oFormFieldNoTouchedAtt = $oAttDef->MakeFormField($oPerson);
|
||||
$this->assertTrue($oFormFieldNoTouchedAtt->IsValidationDisabled(), 'email wasn\'t modified, we must not validate the corresponding field');
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithConstraintParameterProvider
|
||||
*
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param bool $bConstraintExpected
|
||||
* @param bool $bComputationExpected
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
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());
|
||||
}
|
||||
|
||||
public function WithConstraintParameterProvider()
|
||||
{
|
||||
return [
|
||||
['User', 'profile_list', true, false],
|
||||
['User', 'allowed_org_list', true, false],
|
||||
['Person', 'team_list', false, false],
|
||||
['Ticket', 'functionalcis_list', false, true],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,6 @@
|
||||
namespace Combodo\iTop\Test\UnitTest\Core\CRUD;
|
||||
|
||||
use Combodo\iTop\Service\Events\EventData;
|
||||
use Combodo\iTop\Service\Events\EventService;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
use ContactType;
|
||||
use CoreException;
|
||||
@@ -21,6 +20,7 @@ use ormLinkSet;
|
||||
use Person;
|
||||
use Team;
|
||||
use utils;
|
||||
use const EVENT_DB_LINKS_CHANGED;
|
||||
|
||||
class CRUDEventTest extends ItopDataTestCase
|
||||
{
|
||||
@@ -339,8 +339,8 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_WRITE]);
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_BEFORE_WRITE]);
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_WRITE]);
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertEquals(20, self::$iEventCalls);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
$this->assertEquals(16, self::$iEventCalls);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -388,8 +388,8 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_CHECK_TO_WRITE]);
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_BEFORE_WRITE]);
|
||||
$this->assertEquals(4, self::$aEventCalls[EVENT_DB_AFTER_WRITE]);
|
||||
$this->assertEquals(3, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertEquals(19, self::$iEventCalls);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
$this->assertEquals(16, self::$iEventCalls);
|
||||
|
||||
// Read the object explicitly from the DB to check that the role has been set
|
||||
$oSet = new DBObjectSet(DBSearch::FromOQL('SELECT Team WHERE id=:id'), [], ['id' => $oTeam->GetKey()]);
|
||||
@@ -495,7 +495,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
$oLnk = MetaModel::NewObject(lnkPersonToTeam::class, ['person_id' => $oPerson->GetKey(), 'team_id' => $oTeam->GetKey()]);
|
||||
$oLnk->DBInsert();
|
||||
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
}
|
||||
|
||||
public function testLinksDeleted()
|
||||
@@ -517,7 +517,7 @@ class CRUDEventTest extends ItopDataTestCase
|
||||
|
||||
$oLnk->DBDelete();
|
||||
|
||||
$this->assertEquals(2, self::$aEventCalls[EVENT_DB_LINKS_CHANGED]);
|
||||
$this->assertArrayNotHasKey(EVENT_DB_LINKS_CHANGED, self::$aEventCalls, 'no relation with the with_php_compute attribute !');
|
||||
}
|
||||
|
||||
// Tests with MockDBObject
|
||||
|
||||
@@ -0,0 +1,359 @@
|
||||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2023 Combodo SARL
|
||||
* @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\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()
|
||||
{
|
||||
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,
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PortaPowerUserProvider
|
||||
* @covers User::CheckPortalProfiles
|
||||
*/
|
||||
public function testUserLocalCreation($aProfilesBeforeUserCreation, $sWaitForException)
|
||||
{
|
||||
$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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider PortaPowerUserProvider
|
||||
* @covers User::CheckPortalProfiles
|
||||
*/
|
||||
public function testUserLocalDelete($aProfilesBeforeUserCreation, $sWaitForException)
|
||||
{
|
||||
$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();
|
||||
}
|
||||
|
||||
/**
|
||||
* @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',
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -488,29 +488,4 @@ class UserRightsTest extends ItopDataTestCase
|
||||
'with Admins hidden' => [true, 0],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider WithConstraintParameterProvider
|
||||
* @param string $sClass
|
||||
* @param string $sAttCode
|
||||
* @param bool $bExpected
|
||||
*
|
||||
* @return void
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function testWithConstraintParameter(string $sClass, string $sAttCode, bool $bExpected)
|
||||
{
|
||||
$oAttDef = \MetaModel::GetAttributeDef($sClass, $sAttCode);
|
||||
$this->assertTrue(method_exists($oAttDef, "GetHasConstraint"));
|
||||
$this->assertEquals($bExpected, $oAttDef->GetHasConstraint());
|
||||
}
|
||||
|
||||
public function WithConstraintParameterProvider()
|
||||
{
|
||||
return [
|
||||
['User', 'profile_list', true],
|
||||
['User', 'allowed_org_list', true],
|
||||
['Person', 'team_list', false],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user