mirror of
https://github.com/Combodo/iTop.git
synced 2026-05-22 00:32:16 +02:00
Merge branch 'saas/3.0' into develop
This commit is contained in:
@@ -42,6 +42,17 @@ abstract class CellChangeSpec
|
|||||||
return $this->m_sOql;
|
return $this->m_sOql;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*/
|
||||||
|
public function GetDisplayableValueAndDescription(): string
|
||||||
|
{
|
||||||
|
return sprintf("%s%s",
|
||||||
|
$this->GetDisplayableValue(),
|
||||||
|
$this->GetDescription()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
abstract public function GetDescription();
|
abstract public function GetDescription();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,26 +97,90 @@ class CellStatus_Issue extends CellStatus_Modify
|
|||||||
parent::__construct($proposedValue, $previousValue);
|
parent::__construct($proposedValue, $previousValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetDescription()
|
public function GetDisplayableValue()
|
||||||
{
|
{
|
||||||
if (is_null($this->m_proposedValue))
|
if (is_null($this->m_proposedValue))
|
||||||
{
|
{
|
||||||
return Dict::Format('UI:CSVReport-Value-SetIssue', $this->m_sReason);
|
return Dict::Format('UI:CSVReport-Value-SetIssue');
|
||||||
}
|
}
|
||||||
return Dict::Format('UI:CSVReport-Value-ChangeIssue', $this->m_proposedValue, $this->m_sReason);
|
return Dict::Format('UI:CSVReport-Value-ChangeIssue', \utils::EscapeHtml($this->m_proposedValue));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetDescription()
|
||||||
|
{
|
||||||
|
return $this->m_sReason;
|
||||||
|
}
|
||||||
|
/*
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*/
|
||||||
|
public function GetDisplayableValueAndDescription(): string
|
||||||
|
{
|
||||||
|
return sprintf("%s. %s",
|
||||||
|
$this->GetDisplayableValue(),
|
||||||
|
$this->GetDescription()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class CellStatus_SearchIssue extends CellStatus_Issue
|
class CellStatus_SearchIssue extends CellStatus_Issue
|
||||||
{
|
{
|
||||||
public function __construct()
|
/** @var string|null $m_sAllowedValues */
|
||||||
|
private $m_sAllowedValues;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
* @var string $sSerializedSearch
|
||||||
|
*/
|
||||||
|
private $sSerializedSearch;
|
||||||
|
|
||||||
|
/** @var string|null $m_sTargetClass */
|
||||||
|
private $m_sTargetClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CellStatus_SearchIssue constructor.
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*
|
||||||
|
* @param string $sOql : main message
|
||||||
|
* @param string $sReason : main message
|
||||||
|
* @param null $sClass : used for additional message that provides allowed values for current class $sClass
|
||||||
|
* @param null $sAllowedValues : used for additional message that provides allowed values $sAllowedValues for current class
|
||||||
|
*/
|
||||||
|
public function __construct($sSerializedSearch, $sReason, $sClass=null, $sAllowedValues=null)
|
||||||
{
|
{
|
||||||
parent::__construct(null, null, null);
|
parent::__construct(null, null, $sReason);
|
||||||
|
$this->sSerializedSearch = $sSerializedSearch;
|
||||||
|
$this->m_sAllowedValues = $sAllowedValues;
|
||||||
|
$this->m_sTargetClass = $sClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetDisplayableValue()
|
||||||
|
{
|
||||||
|
if (null === $this->m_sReason) {
|
||||||
|
return Dict::Format('UI:CSVReport-Value-NoMatch', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->m_sReason;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function GetDescription()
|
public function GetDescription()
|
||||||
{
|
{
|
||||||
return Dict::S('UI:CSVReport-Value-NoMatch');
|
if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) ||
|
||||||
|
\utils::IsNullOrEmptyString($this->m_sTargetClass)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Dict::Format('UI:CSVReport-Value-NoMatch-PossibleValues', $this->m_sTargetClass, $this->m_sAllowedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function GetSearchLinkUrl()
|
||||||
|
{
|
||||||
|
return sprintf("UI.php?operation=search&filter=%s",
|
||||||
|
rawurlencode($this->sSerializedSearch)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -126,11 +201,24 @@ class CellStatus_NullIssue extends CellStatus_Issue
|
|||||||
class CellStatus_Ambiguous extends CellStatus_Issue
|
class CellStatus_Ambiguous extends CellStatus_Issue
|
||||||
{
|
{
|
||||||
protected $m_iCount;
|
protected $m_iCount;
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $sSerializedSearch;
|
||||||
|
|
||||||
public function __construct($previousValue, $iCount, $sOql)
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*
|
||||||
|
* @param $previousValue
|
||||||
|
* @param int $iCount
|
||||||
|
* @param string $sSerializedSearch
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public function __construct($previousValue, $iCount, $sSerializedSearch)
|
||||||
{
|
{
|
||||||
$this->m_iCount = $iCount;
|
$this->m_iCount = $iCount;
|
||||||
$this->m_sQuery = $sOql;
|
$this->sSerializedSearch = $sSerializedSearch;
|
||||||
parent::__construct(null, $previousValue, '');
|
parent::__construct(null, $previousValue, '');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -139,6 +227,17 @@ class CellStatus_Ambiguous extends CellStatus_Issue
|
|||||||
$sCount = $this->m_iCount;
|
$sCount = $this->m_iCount;
|
||||||
return Dict::Format('UI:CSVReport-Value-Ambiguous', $sCount);
|
return Dict::Format('UI:CSVReport-Value-Ambiguous', $sCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public function GetSearchLinkUrl()
|
||||||
|
{
|
||||||
|
return sprintf("UI.php?operation=search&filter=%s",
|
||||||
|
rawurlencode($this->sSerializedSearch)
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -211,6 +310,26 @@ class RowStatus_Issue extends RowStatus
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* class dedicated to testability
|
||||||
|
* not used/ignored in csv imports UI/CLI
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*/
|
||||||
|
class RowStatus_Error extends RowStatus
|
||||||
|
{
|
||||||
|
/** @var string */
|
||||||
|
protected $m_sError;
|
||||||
|
|
||||||
|
public function __construct($sError)
|
||||||
|
{
|
||||||
|
$this->m_sError = $sError;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function GetDescription()
|
||||||
|
{
|
||||||
|
return $this->m_sError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* BulkChange
|
* BulkChange
|
||||||
@@ -220,17 +339,35 @@ class RowStatus_Issue extends RowStatus
|
|||||||
*/
|
*/
|
||||||
class BulkChange
|
class BulkChange
|
||||||
{
|
{
|
||||||
|
/** @var string */
|
||||||
protected $m_sClass;
|
protected $m_sClass;
|
||||||
protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string)
|
protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string)
|
||||||
// #@# todo: rename the variables to sColIndex
|
// #@# todo: rename the variables to sColIndex
|
||||||
protected $m_aAttList; // attcode => iCol
|
/** @var array<string, string> attcode as key, iCol as value */
|
||||||
protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
|
protected $m_aAttList;
|
||||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
/** @var array<string, array<string, string>> sExtKeyAttCode as key, array of sExtReconcKeyAttCode/iCol as value */
|
||||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
protected $m_aExtKeys;
|
||||||
protected $m_aOnDisappear; // array of attcode => value, values to be set when an object gets out of scope (ignored if no scope has been defined)
|
/** @var string[] list of attcode (attcode = 'id' for the pkey) */
|
||||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
protected $m_aReconcilKeys;
|
||||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
/** @var string OQL - if specified, then the missing items will be reported */
|
||||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
protected $m_sSynchroScope;
|
||||||
|
/**
|
||||||
|
* @var array<string, mixed> attcode as key, attvalue as value. Values to be set when an object gets out of scope
|
||||||
|
* (ignored if no scope has been defined)
|
||||||
|
*/
|
||||||
|
protected $m_aOnDisappear;
|
||||||
|
/**
|
||||||
|
* @see DateTime::createFromFormat
|
||||||
|
* @var string Date format specification
|
||||||
|
*/
|
||||||
|
protected $m_sDateFormat;
|
||||||
|
/**
|
||||||
|
* @see AttributeEnum
|
||||||
|
* @var boolean true if Values in the data set are localized
|
||||||
|
*/
|
||||||
|
protected $m_bLocalizedValues;
|
||||||
|
/** @var array Cache for resolving external keys based on the given search criterias */
|
||||||
|
protected $m_aExtKeysMappingCache;
|
||||||
|
|
||||||
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null, $bLocalize = false)
|
public function __construct($sClass, $aData, $aAttList, $aExtKeys, $aReconcilKeys, $sSynchroScope = null, $aOnDisappear = null, $sDateFormat = null, $bLocalize = false)
|
||||||
{
|
{
|
||||||
@@ -266,25 +403,25 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||||
foreach ($this->m_aExtKeys[$sAttCode] as $sForeignAttCode => $iCol)
|
foreach ($this->m_aExtKeys[$sAttCode] as $sReconKeyAttCode => $iCol)
|
||||||
{
|
{
|
||||||
if ($sForeignAttCode == 'id')
|
if ($sReconKeyAttCode == 'id')
|
||||||
{
|
{
|
||||||
$value = (int) $aRowData[$iCol];
|
$value = (int) $aRowData[$iCol];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The foreign attribute is one of our reconciliation key
|
// The foreign attribute is one of our reconciliation key
|
||||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||||
}
|
}
|
||||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||||
}
|
}
|
||||||
|
|
||||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||||
$aKeys = $oExtObjects->ToArray();
|
$aKeys = $oExtObjects->ToArray();
|
||||||
return array($oReconFilter->ToOql(), $aKeys);
|
return array($oReconFilter, $aKeys);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the CSV data specifies that the external key must be left undefined
|
// Returns true if the CSV data specifies that the external key must be left undefined
|
||||||
@@ -321,7 +458,7 @@ class BulkChange
|
|||||||
|
|
||||||
// External keys reconciliation
|
// External keys reconciliation
|
||||||
//
|
//
|
||||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
foreach($this->m_aExtKeys as $sAttCode => $aReconKeys)
|
||||||
{
|
{
|
||||||
// Skip external keys used for the reconciliation process
|
// Skip external keys used for the reconciliation process
|
||||||
// if (!array_key_exists($sAttCode, $this->m_aAttList)) continue;
|
// if (!array_key_exists($sAttCode, $this->m_aAttList)) continue;
|
||||||
@@ -330,7 +467,7 @@ class BulkChange
|
|||||||
|
|
||||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
||||||
{
|
{
|
||||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||||
{
|
{
|
||||||
// Default reporting
|
// Default reporting
|
||||||
// $aRowData[$iCol] is always null
|
// $aRowData[$iCol] is always null
|
||||||
@@ -352,25 +489,24 @@ class BulkChange
|
|||||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||||
|
|
||||||
$aCacheKeys = array();
|
$aCacheKeys = array();
|
||||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||||
{
|
{
|
||||||
// The foreign attribute is one of our reconciliation key
|
// The foreign attribute is one of our reconciliation key
|
||||||
if ($sForeignAttCode == 'id')
|
if ($sReconKeyAttCode == 'id')
|
||||||
{
|
{
|
||||||
$value = $aRowData[$iCol];
|
$value = $aRowData[$iCol];
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||||
}
|
}
|
||||||
$aCacheKeys[] = $value;
|
$aCacheKeys[] = $value;
|
||||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||||
}
|
}
|
||||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||||
$iForeignKey = null;
|
$iForeignKey = null;
|
||||||
$sOQL = '';
|
|
||||||
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
|
// TODO: check if *too long* keys can lead to collisions... and skip the cache in such a case...
|
||||||
if (!array_key_exists($sAttCode, $this->m_aExtKeysMappingCache))
|
if (!array_key_exists($sAttCode, $this->m_aExtKeysMappingCache))
|
||||||
{
|
{
|
||||||
@@ -379,9 +515,8 @@ class BulkChange
|
|||||||
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
|
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
|
||||||
{
|
{
|
||||||
// Cache hit
|
// Cache hit
|
||||||
$iCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
$iObjectFoundCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||||
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
|
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
|
||||||
$sOQL = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['oql'];
|
|
||||||
// Record the hit
|
// Record the hit
|
||||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
|
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
|
||||||
}
|
}
|
||||||
@@ -389,24 +524,25 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
// Cache miss, let's initialize it
|
// Cache miss, let's initialize it
|
||||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||||
$iCount = $oExtObjects->Count();
|
$iObjectFoundCount = $oExtObjects->Count();
|
||||||
if ($iCount == 1)
|
if ($iObjectFoundCount == 1)
|
||||||
{
|
{
|
||||||
$oForeignObj = $oExtObjects->Fetch();
|
$oForeignObj = $oExtObjects->Fetch();
|
||||||
$iForeignKey = $oForeignObj->GetKey();
|
$iForeignKey = $oForeignObj->GetKey();
|
||||||
}
|
}
|
||||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
||||||
'c' => $iCount,
|
'c' => $iObjectFoundCount,
|
||||||
'k' => $iForeignKey,
|
'k' => $iForeignKey,
|
||||||
'oql' => $oReconFilter->ToOql(),
|
'oql' => $oReconFilter->ToOql(),
|
||||||
'h' => 0, // number of hits on this cache entry
|
'h' => 0, // number of hits on this cache entry
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
switch($iCount)
|
switch($iObjectFoundCount)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
|
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||||
|
$aResults[$sAttCode] = $oCellStatus_SearchIssue;
|
||||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||||
$aResults[$sAttCode]= new CellStatus_SearchIssue();
|
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 1:
|
case 1:
|
||||||
@@ -415,8 +551,8 @@ class BulkChange
|
|||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
|
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iObjectFoundCount);
|
||||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
|
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iObjectFoundCount, $oReconFilter->serialize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -433,7 +569,7 @@ class BulkChange
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
$aResults[$sAttCode]= new CellStatus_Modify($iForeignObj, $oTargetObj->GetOriginal($sAttCode));
|
$aResults[$sAttCode]= new CellStatus_Modify($iForeignObj, $oTargetObj->GetOriginal($sAttCode));
|
||||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||||
{
|
{
|
||||||
// Report the change on reconciliation values as well
|
// Report the change on reconciliation values as well
|
||||||
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
||||||
@@ -487,8 +623,14 @@ class BulkChange
|
|||||||
$value = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
$value = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||||
if (is_null($value) && (strlen($aRowData[$iCol]) > 0))
|
if (is_null($value) && (strlen($aRowData[$iCol]) > 0))
|
||||||
{
|
{
|
||||||
|
if ($oAttDef instanceof AttributeEnum || $oAttDef instanceof AttributeTagSet){
|
||||||
|
/** @var AttributeDefinition $oAttributeDefinition */
|
||||||
|
$oAttributeDefinition = $oAttDef;
|
||||||
|
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-AllowedValues', $sAttCode, implode(',', $oAttributeDefinition->GetAllowedValues()));
|
||||||
|
} else {
|
||||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$res = $oTargetObj->CheckValue($sAttCode, $value);
|
$res = $oTargetObj->CheckValue($sAttCode, $value);
|
||||||
@@ -568,6 +710,95 @@ class BulkChange
|
|||||||
return $aResults;
|
return $aResults;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* search with current permissions did not match
|
||||||
|
* let's search why and give some more feedbacks to the user through proper labels
|
||||||
|
*
|
||||||
|
* @param DBObjectSearch $oDbSearchWithConditions search used to find external key
|
||||||
|
*
|
||||||
|
* @return \CellStatus_SearchIssue
|
||||||
|
* @throws \CoreException
|
||||||
|
* @throws \MissingQueryArgument
|
||||||
|
* @throws \MySQLException
|
||||||
|
* @throws \MySQLHasGoneAwayException
|
||||||
|
*
|
||||||
|
* @since 3.1.0 N°5305
|
||||||
|
*/
|
||||||
|
protected function GetCellSearchIssue($oDbSearchWithConditions) : CellStatus_SearchIssue {
|
||||||
|
//current search with current permissions did not match
|
||||||
|
//let's search why and give some more feedback to the user
|
||||||
|
|
||||||
|
$sSerializedSearch = $oDbSearchWithConditions->serialize();
|
||||||
|
|
||||||
|
// Count all objects with all permissions without any condition
|
||||||
|
$oDbSearchWithoutAnyCondition = new DBObjectSearch($oDbSearchWithConditions->GetClass());
|
||||||
|
$oDbSearchWithoutAnyCondition->AllowAllData(true);
|
||||||
|
$oExtObjectSet = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||||
|
$iAllowAllDataObjectCount = $oExtObjectSet->Count();
|
||||||
|
|
||||||
|
if ($iAllowAllDataObjectCount === 0) {
|
||||||
|
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject', $oDbSearchWithConditions->GetClass());
|
||||||
|
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count all objects with current user permissions
|
||||||
|
$oDbSearchWithoutAnyCondition->AllowAllData(false);
|
||||||
|
$oExtObjectSetWithCurrentUserPermissions = new CMDBObjectSet($oDbSearchWithoutAnyCondition);
|
||||||
|
$iCurrentUserRightsObjectCount = $oExtObjectSetWithCurrentUserPermissions->Count();
|
||||||
|
|
||||||
|
if ($iCurrentUserRightsObjectCount === 0){
|
||||||
|
// No objects visible by current user
|
||||||
|
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||||
|
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
try{
|
||||||
|
$aDisplayedAllowedValues = [];
|
||||||
|
// Possibles values are displayed to UI user. we have to limit the amount of displayed values
|
||||||
|
$oExtObjectSetWithCurrentUserPermissions->SetLimit(4);
|
||||||
|
for($i = 0; $i < 3; $i++){
|
||||||
|
/** @var \DBObject $oVisibleObject */
|
||||||
|
$oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch();
|
||||||
|
if (is_null($oVisibleObject)){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aCurrentAllowedValueFields = [];
|
||||||
|
foreach ($oDbSearchWithConditions->GetInternalParams() as $sForeignAttCode => $sValue){
|
||||||
|
$aCurrentAllowedValueFields[] = $oVisibleObject->Get($sForeignAttCode);
|
||||||
|
}
|
||||||
|
$aDisplayedAllowedValues[] = implode(" ", $aCurrentAllowedValueFields);
|
||||||
|
|
||||||
|
}
|
||||||
|
$allowedValues = implode(", ", $aDisplayedAllowedValues);
|
||||||
|
if ($oExtObjectSetWithCurrentUserPermissions->Count() > 3){
|
||||||
|
$allowedValues .= "...";
|
||||||
|
}
|
||||||
|
} catch(Exception $e) {
|
||||||
|
IssueLog::Error("failure during CSV import when fetching few visible objects: ", null,
|
||||||
|
[ 'target_class' => $oDbSearchWithConditions->GetClass(), 'criteria' => $oDbSearchWithConditions->GetCriteria(), 'message' => $e->getMessage()]
|
||||||
|
);
|
||||||
|
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||||
|
return new CellStatus_SearchIssue($sSerializedSearch, $sReason);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($iAllowAllDataObjectCount != $iCurrentUserRightsObjectCount) {
|
||||||
|
// No match and some objects NOT visible by current user. including current search maybe...
|
||||||
|
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser', $oDbSearchWithConditions->GetClass());
|
||||||
|
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// No match. This is not linked to any right issue
|
||||||
|
// Possible values: DD,DD
|
||||||
|
$aCurrentValueFields = [];
|
||||||
|
foreach ($oDbSearchWithConditions->GetInternalParams() as $sValue){
|
||||||
|
$aCurrentValueFields[] = $sValue;
|
||||||
|
}
|
||||||
|
$value =implode(" ", $aCurrentValueFields);
|
||||||
|
$sReason = Dict::Format('UI:CSVReport-Value-NoMatch', $value);
|
||||||
|
return new CellStatus_SearchIssue($sSerializedSearch, $sReason, $oDbSearchWithConditions->GetClass(), $allowedValues);
|
||||||
|
}
|
||||||
|
|
||||||
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
|
protected function PrepareMissingObject(&$oTargetObj, &$aErrors)
|
||||||
{
|
{
|
||||||
$aResults = array();
|
$aResults = array();
|
||||||
@@ -679,6 +910,8 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
$sErrors = implode(', ', $aErrors);
|
$sErrors = implode(', ', $aErrors);
|
||||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||||
|
//__ERRORS__ used by tests only
|
||||||
|
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||||
return $oTargetObj;
|
return $oTargetObj;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -745,6 +978,8 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
$sErrors = implode(', ', $aErrors);
|
$sErrors = implode(', ', $aErrors);
|
||||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||||
|
//__ERRORS__ used by tests only
|
||||||
|
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -794,6 +1029,8 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
$sErrors = implode(', ', $aErrors);
|
$sErrors = implode(', ', $aErrors);
|
||||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
$aResult[$iRow]["__STATUS__"] = new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Attribute'));
|
||||||
|
//__ERRORS__ used by tests only
|
||||||
|
$aResult[$iRow]["__ERRORS__"] = new RowStatus_Error($sErrors);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -881,14 +1118,18 @@ class BulkChange
|
|||||||
$sFormat = $sDateFormat;
|
$sFormat = $sDateFormat;
|
||||||
}
|
}
|
||||||
$oFormat = new DateTimeFormat($sFormat);
|
$oFormat = new DateTimeFormat($sFormat);
|
||||||
|
$sDateExample = $oFormat->Format(new DateTime('2022-10-23 16:25:33'));
|
||||||
$sRegExp = $oFormat->ToRegExpr('/');
|
$sRegExp = $oFormat->ToRegExpr('/');
|
||||||
if (!preg_match($sRegExp, $this->m_aData[$iRow][$iCol]))
|
$sErrorMsg = Dict::Format('UI:CSVReport-Row-Issue-ExpectedDateFormat', $sDateExample);
|
||||||
|
if (!preg_match($sRegExp, $sValue))
|
||||||
{
|
{
|
||||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||||
|
$aResult[$iRow][$iCol] = new CellStatus_Issue(utils::HtmlEntities($sValue), null, $sErrorMsg);
|
||||||
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
$oDate = DateTime::createFromFormat($sFormat, $sValue);
|
||||||
if ($oDate !== false)
|
if ($oDate !== false)
|
||||||
{
|
{
|
||||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||||
@@ -898,7 +1139,7 @@ class BulkChange
|
|||||||
{
|
{
|
||||||
// Leave the cell unchanged
|
// Leave the cell unchanged
|
||||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
||||||
$aResult[$iRow][$sAttCode] = new CellStatus_Issue(null, utils::HtmlEntities($this->m_aData[$iRow][$iCol]), Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
|
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -952,7 +1193,9 @@ class BulkChange
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// The value has to be found or verified
|
// The value has to be found or verified
|
||||||
list($sQuery, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
|
||||||
|
/** var DBObjectSearch $oReconFilter */
|
||||||
|
list($oReconFilter, $aMatches) = $this->ResolveExternalKey($aRowData, $sAttCode, $aResult[$iRow]);
|
||||||
|
|
||||||
if (count($aMatches) == 1)
|
if (count($aMatches) == 1)
|
||||||
{
|
{
|
||||||
@@ -962,11 +1205,12 @@ class BulkChange
|
|||||||
}
|
}
|
||||||
elseif (count($aMatches) == 0)
|
elseif (count($aMatches) == 0)
|
||||||
{
|
{
|
||||||
$aResult[$iRow][$sAttCode] = new CellStatus_SearchIssue();
|
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||||
|
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery);
|
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1019,7 +1263,7 @@ class BulkChange
|
|||||||
default:
|
default:
|
||||||
// Found several matches, ambiguous
|
// Found several matches, ambiguous
|
||||||
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
|
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-Ambiguous'));
|
||||||
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->ToOql());
|
$aResult[$iRow]["id"]= new CellStatus_Ambiguous(0, $oReconciliationSet->Count(), $oReconciliationFilter->serialize());
|
||||||
$aResult[$iRow]["finalclass"]= 'n/a';
|
$aResult[$iRow]["finalclass"]= 'n/a';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
5
css/backoffice/pages/_csv-import.scss
vendored
5
css/backoffice/pages/_csv-import.scss
vendored
@@ -27,11 +27,6 @@ tr.ibo-csv-import--row-unchanged td {
|
|||||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||||
}
|
}
|
||||||
|
|
||||||
.wizContainer table tr.ibo-csv-import--row-error td {
|
|
||||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
|
||||||
background-color: $ibo-color-red-200;
|
|
||||||
}
|
|
||||||
|
|
||||||
tr.ibo-csv-import--row-modified td {
|
tr.ibo-csv-import--row-modified td {
|
||||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -644,9 +644,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte třídu pro hledání: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte třídu pro hledání: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Upraveno',
|
'UI:CSVReport-Value-Modified' => 'Upraveno',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Nemůže být změněno - důvod: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemůže být změněno na %1$s - důvod: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Žádná shoda',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Chybí povinná hodnota',
|
'UI:CSVReport-Value-Missing' => 'Chybí povinná hodnota',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nalezeno %1$s objektů',
|
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nalezeno %1$s objektů',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'nezměněn',
|
'UI:CSVReport-Row-Unchanged' => 'nezměněn',
|
||||||
|
|||||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vælg klasse at søge efter: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Vælg klasse at søge efter: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Ændret',
|
'UI:CSVReport-Value-Modified' => 'Ændret',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Kunne ikke ændres - årsag: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Kunne ikke ændres til %1$s - årsag: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Mangler obligatorisk værdi',
|
'UI:CSVReport-Value-Missing' => 'Mangler obligatorisk værdi',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Tvetydig: fandt %1$s objekter',
|
'UI:CSVReport-Value-Ambiguous' => 'Tvetydig: fandt %1$s objekter',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'Uændret',
|
'UI:CSVReport-Row-Unchanged' => 'Uændret',
|
||||||
|
|||||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Wählen Sie für die Suche die Klasse aus: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Wählen Sie für die Suche die Klasse aus: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modifiziert',
|
'UI:CSVReport-Value-Modified' => 'Modifiziert',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Konnte nicht geändert werden - Grund: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Konnte nicht zu %1$s geändert werden - Grund: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Kein Treffer',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Pflichtfeld fehlt',
|
'UI:CSVReport-Value-Missing' => 'Pflichtfeld fehlt',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Doppeldeutig: %1$s Objekte gefunden',
|
'UI:CSVReport-Value-Ambiguous' => 'Doppeldeutig: %1$s Objekte gefunden',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'Unverändert',
|
'UI:CSVReport-Row-Unchanged' => 'Unverändert',
|
||||||
|
|||||||
@@ -656,9 +656,14 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Select the class to search: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modified',
|
'UI:CSVReport-Value-Modified' => 'Modified',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'Invalid value for attribute',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'',
|
||||||
|
'UI:CSVReport-Value-NoMatch-PossibleValues' => 'Some possible \'%1$s\' value(s): %2$s',
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject' => 'There are no \'%1$s\' objects',
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => 'There are no \'%1$s\' objects found with your current profile',
|
||||||
|
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => 'There are some \'%1$s\' objects not visible with your current profile',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value',
|
'UI:CSVReport-Value-Missing' => 'Missing mandatory value',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'unchanged',
|
'UI:CSVReport-Row-Unchanged' => 'unchanged',
|
||||||
@@ -672,11 +677,13 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:CSVReport-Value-Issue-Readonly' => 'The attribute \'%1$s\' is read-only and cannot be modified (current value: %2$s, proposed value: %3$s)',
|
'UI:CSVReport-Value-Issue-Readonly' => 'The attribute \'%1$s\' is read-only and cannot be modified (current value: %2$s, proposed value: %3$s)',
|
||||||
'UI:CSVReport-Value-Issue-Format' => 'Failed to process input: %1$s',
|
'UI:CSVReport-Value-Issue-Format' => 'Failed to process input: %1$s',
|
||||||
'UI:CSVReport-Value-Issue-NoMatch' => 'Unexpected value for attribute \'%1$s\': no match found, check spelling',
|
'UI:CSVReport-Value-Issue-NoMatch' => 'Unexpected value for attribute \'%1$s\': no match found, check spelling',
|
||||||
|
'UI:CSVReport-Value-Issue-AllowedValues' => 'Allowed \'%1$s\' value(s): %2$s',
|
||||||
'UI:CSVReport-Value-Issue-Unknown' => 'Unexpected value for attribute \'%1$s\': %2$s',
|
'UI:CSVReport-Value-Issue-Unknown' => 'Unexpected value for attribute \'%1$s\': %2$s',
|
||||||
'UI:CSVReport-Row-Issue-Inconsistent' => 'Attributes not consistent with each others: %1$s',
|
'UI:CSVReport-Row-Issue-Inconsistent' => 'Attributes not consistent with each others: %1$s',
|
||||||
'UI:CSVReport-Row-Issue-Attribute' => 'Unexpected attribute value(s)',
|
'UI:CSVReport-Row-Issue-Attribute' => 'Unexpected attribute value(s)',
|
||||||
'UI:CSVReport-Row-Issue-MissingExtKey' => 'Could not be created, due to missing external key(s): %1$s',
|
'UI:CSVReport-Row-Issue-MissingExtKey' => 'Could not be created, due to missing external key(s): %1$s',
|
||||||
'UI:CSVReport-Row-Issue-DateFormat' => 'wrong date format',
|
'UI:CSVReport-Row-Issue-DateFormat' => 'wrong date format',
|
||||||
|
'UI:CSVReport-Row-Issue-ExpectedDateFormat' => 'Expected format: %1$s',
|
||||||
'UI:CSVReport-Row-Issue-Reconciliation' => 'failed to reconcile',
|
'UI:CSVReport-Row-Issue-Reconciliation' => 'failed to reconcile',
|
||||||
'UI:CSVReport-Row-Issue-Ambiguous' => 'ambiguous reconciliation',
|
'UI:CSVReport-Row-Issue-Ambiguous' => 'ambiguous reconciliation',
|
||||||
'UI:CSVReport-Row-Issue-Internal' => 'Internal error: %1$s, %2$s',
|
'UI:CSVReport-Row-Issue-Internal' => 'Internal error: %1$s, %2$s',
|
||||||
|
|||||||
@@ -644,9 +644,9 @@ Esperamos distrute de esta versión tanto como nosotros la imaginamos y creamos.
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleccione la clase a buscar: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleccione la clase a buscar: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modificado',
|
'UI:CSVReport-Value-Modified' => 'Modificado',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'No puede ser modificado - motivo: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'No puede ser cambiado a %1$s - motivo: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'No hay Coincidencias',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Falta valor obligatorio',
|
'UI:CSVReport-Value-Missing' => 'Falta valor obligatorio',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambigüedad: encontrados %1$s objetos',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambigüedad: encontrados %1$s objetos',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'Sin Cambios',
|
'UI:CSVReport-Row-Unchanged' => 'Sin Cambios',
|
||||||
|
|||||||
@@ -639,9 +639,14 @@ Nous espérons que vous aimerez cette version autant que nous avons eu du plaisi
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Sélectionnez le type d\'objets à rechercher : ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Sélectionnez le type d\'objets à rechercher : ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modifié',
|
'UI:CSVReport-Value-Modified' => 'Modifié',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Modification impossible - cause : %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'Valeur invalide',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\' - cause : %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\'',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Pas de correspondance',
|
'UI:CSVReport-Value-NoMatch' => 'Pas de correspondance avec \'%1$s\'',
|
||||||
|
'UI:CSVReport-Value-NoMatch-PossibleValues' => 'Valeur(s) possible(s) pour l\'objet \'%1$s\' : %2$s',
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject' => 'Il n\'y a aucun objet \'%1$s\'',
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => 'Il n\'y a aucun objet \'%1$s\' visible par votre utilisateur',
|
||||||
|
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => 'Il existe des objet(s) \'%1$s\' non visible(s) par votre utilisateur',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Missing' => 'Absence de valeur obligatoire',
|
'UI:CSVReport-Value-Missing' => 'Absence de valeur obligatoire',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambigüité: %1$d objets trouvés',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambigüité: %1$d objets trouvés',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'inchangé',
|
'UI:CSVReport-Row-Unchanged' => 'inchangé',
|
||||||
|
|||||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Keresendő osztály kiválasztása:',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Keresendő osztály kiválasztása:',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
||||||
|
|||||||
@@ -644,9 +644,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleziona la classe per la ricerca: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Seleziona la classe per la ricerca: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
'UI:CSVReport-Value-Modified' => 'Modified~~',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
'UI:CSVReport-Row-Unchanged' => 'unchanged~~',
|
||||||
|
|||||||
@@ -633,9 +633,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => '検索するクラスを選択してください。',
|
'UI:UniversalSearch:LabelSelectTheClass' => '検索するクラスを選択してください。',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => '修正済み',
|
'UI:CSVReport-Value-Modified' => '修正済み',
|
||||||
'UI:CSVReport-Value-SetIssue' => '変更出来ません - 理由: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s へ変更出来ません - 理由: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'マッチしません',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => '必須の値がありません',
|
'UI:CSVReport-Value-Missing' => '必須の値がありません',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'あいまいな値: %1$s オブジェクト',
|
'UI:CSVReport-Value-Ambiguous' => 'あいまいな値: %1$s オブジェクト',
|
||||||
'UI:CSVReport-Row-Unchanged' => '未変更',
|
'UI:CSVReport-Row-Unchanged' => '未変更',
|
||||||
|
|||||||
@@ -644,9 +644,9 @@ We hopen dat je even hard van deze versie geniet als dat we zelf ervan hebben ge
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecteer de klasse om te zoeken: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecteer de klasse om te zoeken: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Aangepast',
|
'UI:CSVReport-Value-Modified' => 'Aangepast',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Kon niet worden aangepast - reden: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Kon niet worden aangepast naar %1$s - reden: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Geen match',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Ontbrekende verplichte waarde',
|
'UI:CSVReport-Value-Missing' => 'Ontbrekende verplichte waarde',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Onduidelijk: gevonden %1$s objecten',
|
'UI:CSVReport-Value-Ambiguous' => 'Onduidelijk: gevonden %1$s objecten',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'onveranderd',
|
'UI:CSVReport-Row-Unchanged' => 'onveranderd',
|
||||||
|
|||||||
@@ -643,9 +643,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Wybierz klasę do przeszukania: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Wybierz klasę do przeszukania: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Zmodyfikowano',
|
'UI:CSVReport-Value-Modified' => 'Zmodyfikowano',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Nie można było zmienić - powód: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Nie można zmienić na %1$s - powód: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Nie pasuje',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Brak wymaganej wartości',
|
'UI:CSVReport-Value-Missing' => 'Brak wymaganej wartości',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Uwaga: znaleziono %1$s obiektów',
|
'UI:CSVReport-Value-Ambiguous' => 'Uwaga: znaleziono %1$s obiektów',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'niezmieniony',
|
'UI:CSVReport-Row-Unchanged' => 'niezmieniony',
|
||||||
|
|||||||
@@ -644,9 +644,9 @@ Esperamos que você goste desta versão tanto quanto gostamos de imaginá-la e c
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecione a classe para pesquisar: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecione a classe para pesquisar: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Modificado',
|
'UI:CSVReport-Value-Modified' => 'Modificado',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Não pode ser modificado - motivo: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Não pode ser modificado para %1$s - motivo: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Não corresponde',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Faltando valor obrigatório',
|
'UI:CSVReport-Value-Missing' => 'Faltando valor obrigatório',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Ambíguo: encontrado %1$s objeto(s)',
|
'UI:CSVReport-Value-Ambiguous' => 'Ambíguo: encontrado %1$s objeto(s)',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'inalterado',
|
'UI:CSVReport-Row-Unchanged' => 'inalterado',
|
||||||
|
|||||||
@@ -645,9 +645,9 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Выбор класса для поиска: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Выбор класса для поиска: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Изменен',
|
'UI:CSVReport-Value-Modified' => 'Изменен',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Не может быть изменен - причина: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Не может быть изменен %1$s - причина: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Нет совпадений',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Отсутствует обязательное значение',
|
'UI:CSVReport-Value-Missing' => 'Отсутствует обязательное значение',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Неоднозначное сопоставление: найдено %1$s объектов',
|
'UI:CSVReport-Value-Ambiguous' => 'Неоднозначное сопоставление: найдено %1$s объектов',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'без изменений',
|
'UI:CSVReport-Row-Unchanged' => 'без изменений',
|
||||||
|
|||||||
@@ -634,9 +634,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte triedu na vyhľadávanie: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Vyberte triedu na vyhľadávanie: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Upravený',
|
'UI:CSVReport-Value-Modified' => 'Upravený',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Nemožno zmeniť - dôvod: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemožno zmeniť na %1$s - dôvod: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Žiadna zhoda',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Chýbajúca povinná hodnota',
|
'UI:CSVReport-Value-Missing' => 'Chýbajúca povinná hodnota',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nájdených %1$s objektov',
|
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nájdených %1$s objektov',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'Nezmený',
|
'UI:CSVReport-Row-Unchanged' => 'Nezmený',
|
||||||
|
|||||||
@@ -661,9 +661,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Aranacak sınıfı seçiniz: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => 'Aranacak sınıfı seçiniz: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => 'Değiştiridi',
|
'UI:CSVReport-Value-Modified' => 'Değiştiridi',
|
||||||
'UI:CSVReport-Value-SetIssue' => 'Değiştirilemedi - Sebep: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s olarak değiştirilemedi - Sebep: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => 'Eşleşme yok',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => 'Eksik Zorunlu Değer',
|
'UI:CSVReport-Value-Missing' => 'Eksik Zorunlu Değer',
|
||||||
'UI:CSVReport-Value-Ambiguous' => 'Belirsiz: %1$s nesnelerini buldum',
|
'UI:CSVReport-Value-Ambiguous' => 'Belirsiz: %1$s nesnelerini buldum',
|
||||||
'UI:CSVReport-Row-Unchanged' => 'Değiştirilmedi',
|
'UI:CSVReport-Row-Unchanged' => 'Değiştirilmedi',
|
||||||
|
|||||||
@@ -649,9 +649,9 @@ Dict::Add('ZH CN', 'Chinese', '简体中文', array(
|
|||||||
'UI:UniversalSearch:LabelSelectTheClass' => '选择要搜索的类别: ',
|
'UI:UniversalSearch:LabelSelectTheClass' => '选择要搜索的类别: ',
|
||||||
|
|
||||||
'UI:CSVReport-Value-Modified' => '已修改',
|
'UI:CSVReport-Value-Modified' => '已修改',
|
||||||
'UI:CSVReport-Value-SetIssue' => '无法修改 - 原因: %1$s',
|
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||||
'UI:CSVReport-Value-ChangeIssue' => '无法修改成 %1$s - 原因: %2$s',
|
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||||
'UI:CSVReport-Value-NoMatch' => '不匹配',
|
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||||
'UI:CSVReport-Value-Missing' => '缺少必填项',
|
'UI:CSVReport-Value-Missing' => '缺少必填项',
|
||||||
'UI:CSVReport-Value-Ambiguous' => '模糊匹配: 找到 %1$s 个对象',
|
'UI:CSVReport-Value-Ambiguous' => '模糊匹配: 找到 %1$s 个对象',
|
||||||
'UI:CSVReport-Row-Unchanged' => '保持不变',
|
'UI:CSVReport-Row-Unchanged' => '保持不变',
|
||||||
|
|||||||
@@ -215,6 +215,7 @@ try {
|
|||||||
* Add a paragraph to the body of the page
|
* Add a paragraph to the body of the page
|
||||||
*
|
*
|
||||||
* @param string $s_html
|
* @param string $s_html
|
||||||
|
* @param ?string $sLinkUrl
|
||||||
*
|
*
|
||||||
* @return string
|
* @return string
|
||||||
*/
|
*/
|
||||||
@@ -437,7 +438,6 @@ try {
|
|||||||
|
|
||||||
case 'RowStatus_NewObj':
|
case 'RowStatus_NewObj':
|
||||||
$iCreated++;
|
$iCreated++;
|
||||||
$sFinalClass = $aResRow['finalclass'];
|
|
||||||
$sStatus = '<img src="../images/added.png" title="'.Dict::S('UI:CSVReport-Icon-Created').'">';
|
$sStatus = '<img src="../images/added.png" title="'.Dict::S('UI:CSVReport-Icon-Created').'">';
|
||||||
$sCSSRowClass = 'ibo-csv-import--row-added';
|
$sCSSRowClass = 'ibo-csv-import--row-added';
|
||||||
if ($bSimulate) {
|
if ($bSimulate) {
|
||||||
@@ -453,7 +453,7 @@ try {
|
|||||||
case 'RowStatus_Issue':
|
case 'RowStatus_Issue':
|
||||||
$iErrors++;
|
$iErrors++;
|
||||||
$sMessage .= GetDivAlert($oStatus->GetDescription());
|
$sMessage .= GetDivAlert($oStatus->GetDescription());
|
||||||
$sStatus = '<img src="../images/error.png" title="'.Dict::S('UI:CSVReport-Icon-Error').'">';//translate
|
$sStatus = '<div class="ibo-csv-import--cell-error"><i class="fas fa-exclamation-triangle" title="'.Dict::S('UI:CSVReport-Icon-Error').'" /></div>';//translate
|
||||||
$sCSSMessageClass = 'ibo-csv-import--cell-error';
|
$sCSSMessageClass = 'ibo-csv-import--cell-error';
|
||||||
$sCSSRowClass = 'ibo-csv-import--row-error';
|
$sCSSRowClass = 'ibo-csv-import--row-error';
|
||||||
if (array_key_exists($iLine, $aData)) {
|
if (array_key_exists($iLine, $aData)) {
|
||||||
@@ -474,33 +474,36 @@ try {
|
|||||||
if (isset($aExternalKeysByColumn[$iNumber - 1])) {
|
if (isset($aExternalKeysByColumn[$iNumber - 1])) {
|
||||||
$sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
|
$sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
|
||||||
$oExtKeyCellStatus = $aResRow[$sExtKeyName];
|
$oExtKeyCellStatus = $aResRow[$sExtKeyName];
|
||||||
switch (get_class($oExtKeyCellStatus)) {
|
$oCellStatus = $oExtKeyCellStatus;
|
||||||
case 'CellStatus_Issue':
|
|
||||||
case 'CellStatus_SearchIssue':
|
|
||||||
case 'CellStatus_NullIssue':
|
|
||||||
case 'CellStatus_Ambiguous':
|
|
||||||
$sCellMessage .= GetDivAlert($oExtKeyCellStatus->GetDescription());
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Do nothing
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
$sHtmlValue = $oCellStatus->GetDisplayableValue();
|
$sHtmlValue = $oCellStatus->GetDisplayableValue();
|
||||||
switch (get_class($oCellStatus)) {
|
switch (get_class($oCellStatus)) {
|
||||||
case 'CellStatus_Issue':
|
case 'CellStatus_Issue':
|
||||||
|
case 'CellStatus_NullIssue':
|
||||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'</div>';
|
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'</div>';
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'CellStatus_SearchIssue':
|
case 'CellStatus_SearchIssue':
|
||||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">ERROR: '.$sHtmlValue.$sCellMessage.'</div>';
|
'<a href="',
|
||||||
|
$oCellStatus->GetSearchLinkUrl(),
|
||||||
|
'"><div class="ibo-csv-import--cell-error">',
|
||||||
|
Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue),
|
||||||
|
GetDivAlert($oCellStatus->GetDescription()),
|
||||||
|
'<i class="fas fa-search"></i></div><a/>'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'CellStatus_Ambiguous':
|
case 'CellStatus_Ambiguous':
|
||||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error" >'.Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue).$sCellMessage.'</div>';
|
'<a href="',
|
||||||
|
$oCellStatus->GetSearchLinkUrl(),
|
||||||
|
'"><i class="fas fa-search"/><div class="ibo-csv-import--cell-error">',
|
||||||
|
Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue),
|
||||||
|
GetDivAlert($oCellStatus->GetDescription()),
|
||||||
|
'<i class="fas fa-search"></i></div><a/>'
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case 'CellStatus_Modify':
|
case 'CellStatus_Modify':
|
||||||
@@ -589,7 +592,7 @@ try {
|
|||||||
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
|
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
|
||||||
$oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
|
$oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
|
||||||
|
|
||||||
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<img src="../images/error.png"> '.sprintf($aDisplayFilters['errors'], $iErrors), '', "1", "show_errors", "checkbox");
|
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<i class="fas fa-exclamation-triangle" style="color:#A33; background-color: #FFF0F0;"> '.sprintf($aDisplayFilters['errors'], $iErrors).'</i></i>', '', "1", "show_errors", "checkbox");
|
||||||
$oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
|
$oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
|
||||||
$oCheckBoxUnchanged->SetBeforeInput(false);
|
$oCheckBoxUnchanged->SetBeforeInput(false);
|
||||||
$oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
|
$oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||||
|
|||||||
@@ -72,6 +72,7 @@ define('TAG_ATTCODE', 'domains');
|
|||||||
class ItopDataTestCase extends ItopTestCase
|
class ItopDataTestCase extends ItopTestCase
|
||||||
{
|
{
|
||||||
private $iTestOrgId;
|
private $iTestOrgId;
|
||||||
|
|
||||||
// For cleanup
|
// For cleanup
|
||||||
private $aCreatedObjects = array();
|
private $aCreatedObjects = array();
|
||||||
|
|
||||||
|
|||||||
344
test/core/BulkChangeExtKeyTest.inc.php
Normal file
344
test/core/BulkChangeExtKeyTest.inc.php
Normal file
@@ -0,0 +1,344 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Core;
|
||||||
|
|
||||||
|
use CMDBSource;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use MetaModel;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*
|
||||||
|
* created a dedicated test for external keys imports.
|
||||||
|
*
|
||||||
|
* Class BulkChangeExtKeyTest
|
||||||
|
*
|
||||||
|
* @package Combodo\iTop\Test\UnitTest\Core
|
||||||
|
*/
|
||||||
|
class BulkChangeExtKeyTest extends ItopDataTestCase {
|
||||||
|
const CREATE_TEST_ORG = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* this test may delete Person objects to cover all usecases
|
||||||
|
* DO NOT CHANGE USE_TRANSACTION value to avoid any DB loss!
|
||||||
|
*/
|
||||||
|
const USE_TRANSACTION = true;
|
||||||
|
|
||||||
|
private $sUid;
|
||||||
|
|
||||||
|
protected function setUp() : void {
|
||||||
|
parent::setUp();
|
||||||
|
require_once(APPROOT.'core/bulkchange.class.inc.php');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function deleteAllRacks(){
|
||||||
|
$oSearch = \DBSearch::FromOQL("SELECT Rack");
|
||||||
|
$oSet = new \DBObjectSet($oSearch);
|
||||||
|
$iCount = $oSet->Count();
|
||||||
|
if ($iCount != 0){
|
||||||
|
while ($oRack = $oSet->Fetch()){
|
||||||
|
$oRack->DBDelete();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_NoObjectAtAll($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
'There are no \'Rack\' objects',
|
||||||
|
"",
|
||||||
|
null,
|
||||||
|
$bIsRackReconKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function createRackObjects($aRackDict) {
|
||||||
|
foreach ($aRackDict as $iOrgId => $aRackNames) {
|
||||||
|
foreach ($aRackNames as $sRackName) {
|
||||||
|
$this->createObject('Rack', ['name' => $sRackName, 'description' => "${sRackName}Desc", 'org_id' => $iOrgId]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function createAnotherUserInAnotherOrg() {
|
||||||
|
$oOrg2 = $this->CreateOrganization('UnitTestOrganization2');
|
||||||
|
$oProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Configuration Manager'), true);
|
||||||
|
|
||||||
|
$sUid = $this->GetUid();
|
||||||
|
|
||||||
|
$oUserProfile = new \URP_UserProfile();
|
||||||
|
$oUserProfile->Set('profileid', $oProfile->GetKey());
|
||||||
|
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||||
|
$oSet = \DBObjectSet::FromObject($oUserProfile);
|
||||||
|
|
||||||
|
$oPerson = $this->CreatePerson('666', $oOrg2->GetKey());
|
||||||
|
$oUser = $this->createObject('UserLocal', array(
|
||||||
|
'contactid' => $oPerson->GetKey(),
|
||||||
|
'login' => $sUid,
|
||||||
|
'password' => "ABCdef$sUid@12345",
|
||||||
|
'language' => 'EN US',
|
||||||
|
'profile_list' => $oSet,
|
||||||
|
));
|
||||||
|
|
||||||
|
$oAllowedOrgList = $oUser->Get('allowed_org_list');
|
||||||
|
/** @var \URP_UserOrg $oUserOrg */
|
||||||
|
$oUserOrg = \MetaModel::NewObject('URP_UserOrg', ['allowed_org_id' => $oOrg2->GetKey(),]);
|
||||||
|
$oAllowedOrgList->AddItem($oUserOrg);
|
||||||
|
$oUser->Set('allowed_org_list', $oAllowedOrgList);
|
||||||
|
$oUser->DBWrite();
|
||||||
|
return [$oOrg2, $oUser];
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ReconciliationKeyProvider(){
|
||||||
|
return [
|
||||||
|
'rack_id NOT a reconcilication key' => [ false ],
|
||||||
|
'rack_id reconcilication key' => [ true ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_NoObjectVisibleByCurrentUser($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
$this->createRackObjects(
|
||||||
|
[
|
||||||
|
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
list($oOrg2, $oUser) = $this->createAnotherUserInAnotherOrg();
|
||||||
|
\UserRights::Login($oUser->Get('login'));
|
||||||
|
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
"There are no 'Rack' objects found with your current profile",
|
||||||
|
"",
|
||||||
|
$oOrg2,
|
||||||
|
$bIsRackReconKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_SomeObjectVisibleByCurrentUser($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
list($oOrg2, $oUser) = $this->createAnotherUserInAnotherOrg();
|
||||||
|
$this->createRackObjects(
|
||||||
|
[
|
||||||
|
$this->getTestOrgId() => ['RackTest1', 'RackTest2'],
|
||||||
|
$oOrg2->GetKey() => ['RackTest3', 'RackTest4'],
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
\UserRights::Login($oUser->Get('login'));
|
||||||
|
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
"There are some 'Rack' objects not visible with your current profile",
|
||||||
|
"Some possible 'Rack' value(s): RackTest3, RackTest4",
|
||||||
|
$oOrg2,
|
||||||
|
$bIsRackReconKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
$this->createRackObjects(
|
||||||
|
[
|
||||||
|
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
"No match for value 'UnexistingRack'",
|
||||||
|
"Some possible 'Rack' value(s): RackTest1, RackTest2, RackTest3...",
|
||||||
|
null,
|
||||||
|
$bIsRackReconKey
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser_AmbigousMatch($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
$this->createRackObjects(
|
||||||
|
[
|
||||||
|
$this->getTestOrgId() => ['UnexistingRack', 'UnexistingRack']
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
"invalid value for attribute",
|
||||||
|
"Ambiguous: found 2 objects",
|
||||||
|
null,
|
||||||
|
$bIsRackReconKey,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
'Found 2 matches'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ReconciliationKeyProvider
|
||||||
|
*/
|
||||||
|
public function testExternalFieldIssueImportFail_AllObjectsVisibleByCurrentUser_FurtherExtKeyForRack($bIsRackReconKey){
|
||||||
|
$this->deleteAllRacks();
|
||||||
|
$this->createRackObjects(
|
||||||
|
[
|
||||||
|
$this->getTestOrgId() => ['RackTest1', 'RackTest2', 'RackTest3', 'RackTest4']
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
$aCsvData = [["UnexistingRackDescription"]];
|
||||||
|
$aExtKeys = ["org_id" => ["name" => 0], "rack_id" => ["name" => 1, "description" => 3]];
|
||||||
|
|
||||||
|
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%28%60Rack%60.%60name%60+%3D+%3Aname%29+AND+%28%60Rack%60.%60description%60+%3D+%3Adescription%29%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%2C%22description%22%3A%22UnexistingRackDescription%22%7D%2C%5B%5D%5D'
|
||||||
|
;
|
||||||
|
$this->performBulkChangeTest(
|
||||||
|
"No match for value 'UnexistingRack UnexistingRackDescription'",
|
||||||
|
"Some possible 'Rack' value(s): RackTest1 RackTest1Desc, RackTest2 RackTest2Desc, RackTest3 RackTest3Desc...",
|
||||||
|
null,
|
||||||
|
$bIsRackReconKey,
|
||||||
|
$aCsvData,
|
||||||
|
$aExtKeys,
|
||||||
|
$sSearchLinkUrl
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private function GetUid(){
|
||||||
|
if (is_null($this->sUid)){
|
||||||
|
$this->sUid = date('dmYHis');
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->sUid;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** *
|
||||||
|
* @param $aInitData
|
||||||
|
* @param $aCsvData
|
||||||
|
* @param $aAttributes
|
||||||
|
* @param $aExtKeys
|
||||||
|
* @param $aReconcilKeys
|
||||||
|
*/
|
||||||
|
public function performBulkChangeTest($sExpectedDisplayableValue, $sExpectedDescription, $oOrg, $bIsRackReconKey,
|
||||||
|
$aAdditionalCsvData=null, $aExtKeys=null, $sSearchLinkUrl=null, $sError="Object not found") {
|
||||||
|
if ($sSearchLinkUrl===null){
|
||||||
|
$sSearchLinkUrl = 'UI.php?operation=search&filter=%5B%22SELECT+%60Rack%60+FROM+Rack+AS+%60Rack%60+WHERE+%28%60Rack%60.%60name%60+%3D+%3Aname%29%22%2C%7B%22name%22%3A%22UnexistingRack%22%7D%2C%5B%5D%5D';
|
||||||
|
}
|
||||||
|
if (is_null($oOrg)){
|
||||||
|
$iOrgId = $this->getTestOrgId();
|
||||||
|
$sOrgName = "UnitTestOrganization";
|
||||||
|
}else{
|
||||||
|
$iOrgId = $oOrg->GetKey();
|
||||||
|
$sOrgName = $oOrg->Get('name');
|
||||||
|
}
|
||||||
|
|
||||||
|
$sUid = $this->GetUid();
|
||||||
|
|
||||||
|
$aCsvData = [[$sOrgName, "UnexistingRack", "$sUid"]];
|
||||||
|
if ($aAdditionalCsvData !== null){
|
||||||
|
foreach ($aAdditionalCsvData as $i => $aData){
|
||||||
|
foreach ($aData as $sData){
|
||||||
|
$aCsvData[$i][] = $sData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$aAttributes = ["name" => 2];
|
||||||
|
if ($aExtKeys == null){
|
||||||
|
$aExtKeys = ["org_id" => ["name" => 0], "rack_id" => ["name" => 1]];
|
||||||
|
}
|
||||||
|
$aReconcilKeys = [ "name" ];
|
||||||
|
|
||||||
|
$aResult = [
|
||||||
|
0 => $sOrgName,
|
||||||
|
"org_id" => $iOrgId,
|
||||||
|
1 => "UnexistingRack",
|
||||||
|
2 => "\"$sUid\"",
|
||||||
|
"rack_id" => [
|
||||||
|
$sExpectedDisplayableValue,
|
||||||
|
$sExpectedDescription
|
||||||
|
],
|
||||||
|
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => $sError,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($bIsRackReconKey){
|
||||||
|
$aReconcilKeys[] = "rack_id";
|
||||||
|
$aResult[2] = $sUid;
|
||||||
|
$aResult["__STATUS__"] = "Issue: failed to reconcile";
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
CMDBSource::Query('START TRANSACTION');
|
||||||
|
//change value during the test
|
||||||
|
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||||
|
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||||
|
|
||||||
|
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||||
|
$this->debug("aReconcilKeys:". var_export($aReconcilKeys));
|
||||||
|
$oBulk = new \BulkChange(
|
||||||
|
"Server",
|
||||||
|
$aCsvData,
|
||||||
|
$aAttributes,
|
||||||
|
$aExtKeys,
|
||||||
|
$aReconcilKeys,
|
||||||
|
null,
|
||||||
|
null,
|
||||||
|
"Y-m-d H:i:s", // date format
|
||||||
|
true // localize
|
||||||
|
);
|
||||||
|
$this->debug("BulkChange:");
|
||||||
|
$oChange = \CMDBObject::GetCurrentChange();
|
||||||
|
$this->debug("GetCurrentChange:");
|
||||||
|
$aRes = $oBulk->Process($oChange);
|
||||||
|
$this->debug("Process:");
|
||||||
|
static::assertNotNull($aRes);
|
||||||
|
$this->debug("assertNotNull:");
|
||||||
|
var_dump($aRes);
|
||||||
|
foreach ($aRes as $aRow) {
|
||||||
|
if (array_key_exists('__STATUS__', $aRow)) {
|
||||||
|
$sStatus = $aRow['__STATUS__'];
|
||||||
|
$this->debug("sStatus:".$sStatus->GetDescription());
|
||||||
|
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||||
|
foreach ($aRow as $i => $oCell) {
|
||||||
|
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||||
|
$this->debug("i:".$i);
|
||||||
|
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||||
|
if (array_key_exists($i,$aResult)) {
|
||||||
|
$this->debug("aResult:".var_export($aResult[$i]));
|
||||||
|
if ($oCell instanceof \CellStatus_SearchIssue ||
|
||||||
|
$oCell instanceof \CellStatus_Ambiguous) {
|
||||||
|
$this->assertEquals($aResult[$i][0], $oCell->GetDisplayableValue(),
|
||||||
|
"failure on ".get_class($oCell).' cell type');
|
||||||
|
$this->assertEquals($sSearchLinkUrl, $oCell->GetSearchLinkUrl(),
|
||||||
|
"failure on ".get_class($oCell).' cell type');
|
||||||
|
$this->assertEquals($aResult[$i][1], $oCell->GetDescription(),
|
||||||
|
"failure on ".get_class($oCell).' cell type');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if ($i === "__ERRORS__") {
|
||||||
|
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||||
|
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MetaModel::GetConfig()->Set('db_core_transactions_enabled',$db_core_transactions_enabled);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -104,13 +104,13 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
if (array_key_exists('__STATUS__', $aRow)) {
|
if (array_key_exists('__STATUS__', $aRow)) {
|
||||||
$sStatus = $aRow['__STATUS__'];
|
$sStatus = $aRow['__STATUS__'];
|
||||||
//$this->debug("sStatus:".$sStatus->GetDescription());
|
//$this->debug("sStatus:".$sStatus->GetDescription());
|
||||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||||
foreach ($aRow as $i => $oCell) {
|
foreach ($aRow as $i => $oCell) {
|
||||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||||
$this->debug("i:".$i);
|
$this->debug("i:".$i);
|
||||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||||
$this->debug("aResult:".$aResult[$i]);
|
$this->debug("aResult:".$aResult[$i]);
|
||||||
$this->assertEquals($oCell->GetDisplayableValue(), $aResult[$i]);
|
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -131,28 +131,37 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "Server1", 2 => "1", 3 => "production", 4 => "date", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
[ 0 => "Demo", "org_id" => "n/a", 1 => "Server1", 2 => "1", 3 => "production", 4 => "'date' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||||
],
|
],
|
||||||
"Case 1 : no match" => [
|
"Case 1 : no match" => [
|
||||||
[["Bad", "Server1", "1", "production", ""]],
|
[["Bad", "Server1", "1", "production", ""]],
|
||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
["org_id" => "",1 => "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
["org_id" => "No match for value 'Bad'",1 => "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||||
],
|
],
|
||||||
"Case 10 : Missing mandatory value" => [
|
"Case 10 : Missing mandatory value" => [
|
||||||
[["", "Server1", "1", "production", ""]],
|
[["", "Server1", "1", "production", ""]],
|
||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ "org_id" => "", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[ "org_id" => "invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
||||||
],
|
],
|
||||||
"Case 6 : Unexpected value" => [
|
"Case 6 : Unexpected value" => [
|
||||||
[["Demo", "Server1", "1", "<svg onclick\"alert(1)\">", ""]],
|
[["Demo", "Server1", "1", "<svg onclick\"alert(1)\">", ""]],
|
||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[0 => "Demo", "org_id" => "3", 1 => "Server1", 2 => "1", 3 => "<svg onclick"alert(1)">", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[
|
||||||
|
0 => "Demo",
|
||||||
|
"org_id" => "3",
|
||||||
|
1 => "Server1",
|
||||||
|
2 => "1",
|
||||||
|
3 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||||
|
4 => "",
|
||||||
|
"id" => 1,
|
||||||
|
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Unexpected value for attribute 'status': no match found, check spelling"],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -172,6 +181,8 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
//change value during the test
|
//change value during the test
|
||||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||||
|
|
||||||
|
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||||
/** @var Server $oServer */
|
/** @var Server $oServer */
|
||||||
$oServer = $this->createObject('Server', array(
|
$oServer = $this->createObject('Server', array(
|
||||||
'name' => $aInitData[1],
|
'name' => $aInitData[1],
|
||||||
@@ -183,6 +194,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
$aResult[2]=$oServer->GetKey();
|
$aResult[2]=$oServer->GetKey();
|
||||||
$aResult["id"]=$oServer->GetKey();
|
$aResult["id"]=$oServer->GetKey();
|
||||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||||
|
}
|
||||||
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||||
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
|
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
|
||||||
$oBulk = new \BulkChange(
|
$oBulk = new \BulkChange(
|
||||||
@@ -207,13 +219,16 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
if (array_key_exists('__STATUS__', $aRow)) {
|
if (array_key_exists('__STATUS__', $aRow)) {
|
||||||
$sStatus = $aRow['__STATUS__'];
|
$sStatus = $aRow['__STATUS__'];
|
||||||
$this->debug("sStatus:".$sStatus->GetDescription());
|
$this->debug("sStatus:".$sStatus->GetDescription());
|
||||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||||
foreach ($aRow as $i => $oCell) {
|
foreach ($aRow as $i => $oCell) {
|
||||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||||
$this->debug("i:".$i);
|
$this->debug("i:".$i);
|
||||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||||
$this->debug("aResult:".$aResult[$i]);
|
$this->debug("aResult:".$aResult[$i]);
|
||||||
$this->assertEquals( $aResult[$i], $oCell->GetDisplayableValue());
|
$this->assertEquals( $aResult[$i], $oCell->GetDisplayableValue(), "failure on " . get_class($oCell) . ' cell type');
|
||||||
|
} else if ($i === "__ERRORS__") {
|
||||||
|
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||||
|
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
$this->assertEquals( $aResult[0], $aRow[0]->GetDisplayableValue());
|
||||||
@@ -225,21 +240,58 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
|
|
||||||
public function CSVImportProvider() {
|
public function CSVImportProvider() {
|
||||||
return [
|
return [
|
||||||
"Case 6 - 1 : Unexpected value" => [
|
"Case 6 - 1 : Unexpected value (update)" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
[["Demo", "ServerTest", "key", "BadValue", ""]],
|
[["Demo", "ServerTest", "key", "BadValue", ""]],
|
||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[0 => "Demo", "org_id" => "3", 1 => "ServerTest", 2 => "1", 3 => "BadValue", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[
|
||||||
|
0 => "Demo",
|
||||||
|
"org_id" => "3",
|
||||||
|
1 => "ServerTest",
|
||||||
|
2 => "1",
|
||||||
|
3 => "'BadValue' is an invalid value",
|
||||||
|
4 => "",
|
||||||
|
"id" => 1,
|
||||||
|
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||||
],
|
],
|
||||||
"Case 6 - 2 : Unexpected value" => [
|
],
|
||||||
|
"Case 6 - 2 : Unexpected value (update)" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
[["Demo", "ServerTest", "key", "<svg onclick\"alert(1)\">", ""]],
|
[["Demo", "ServerTest", "key", "<svg onclick\"alert(1)\">", ""]],
|
||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[0 => "Demo", "org_id" => "3", 1 => "ServerTest", 2 => "1", 3 => "<svg onclick"alert(1)">", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[
|
||||||
|
0 => "Demo",
|
||||||
|
"org_id" => "3",
|
||||||
|
1 => "ServerTest",
|
||||||
|
2 => "1",
|
||||||
|
3 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||||
|
4 => "",
|
||||||
|
"id" => 1,
|
||||||
|
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||||
|
],
|
||||||
|
],
|
||||||
|
"Case 6 - 3 : Unexpected value (creation)" => [
|
||||||
|
[],
|
||||||
|
[["Demo", "ServerTest", "<svg onclick\"alert(1)\">", ""]],
|
||||||
|
["name" => 1, "status" => 2, "purchase_date" => 3],
|
||||||
|
["org_id" => ["name" => 0]],
|
||||||
|
["name"],
|
||||||
|
[
|
||||||
|
0 => "Demo",
|
||||||
|
"org_id" => "3",
|
||||||
|
1 => "\"ServerTest\"",
|
||||||
|
2 => "'<svg onclick"alert(1)">' is an invalid value",
|
||||||
|
3 => "",
|
||||||
|
"id" => 1,
|
||||||
|
"__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Allowed 'status' value(s): implementation,obsolete,production,stock",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
"Case 8 : unchanged name" => [
|
"Case 8 : unchanged name" => [
|
||||||
["1", "<svg onclick\"alert(1)\">", "production", ""],
|
["1", "<svg onclick\"alert(1)\">", "production", ""],
|
||||||
@@ -263,7 +315,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "date", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'date' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||||
],
|
],
|
||||||
"Case 9 - 2: wrong date format" => [
|
"Case 9 - 2: wrong date format" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
@@ -271,7 +323,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "<svg onclick"alert(1)">", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'<svg onclick"alert(1)">' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||||
],
|
],
|
||||||
"Case 1 - 1 : no match" => [
|
"Case 1 - 1 : no match" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
@@ -279,7 +331,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "Bad", "org_id" => "",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[ 0 => "Bad", "org_id" => "No match for value 'Bad'",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Object not found",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
"Case 1 - 2 : no match" => [
|
"Case 1 - 2 : no match" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
@@ -287,7 +341,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "<svg fonclick"alert(1)">", "org_id" => "",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[ 0 => "<svg fonclick"alert(1)">", "org_id" => "No match for value '<svg fonclick\"alert(1)\">'",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Object not found",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
"Case 10 : Missing mandatory value" => [
|
"Case 10 : Missing mandatory value" => [
|
||||||
["1", "ServerTest", "production", ""],
|
["1", "ServerTest", "production", ""],
|
||||||
@@ -295,7 +351,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "", "org_id" => "", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
|
[ 0 => "", "org_id" => "invalid value for attribute", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)",
|
||||||
|
"__ERRORS__" => "Null not allowed",
|
||||||
|
],
|
||||||
],
|
],
|
||||||
|
|
||||||
"Case 0 : Date format" => [
|
"Case 0 : Date format" => [
|
||||||
@@ -304,7 +362,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||||
["org_id" => ["name" => 0]],
|
["org_id" => ["name" => 0]],
|
||||||
["id"],
|
["id"],
|
||||||
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "2020-20-03", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'2020-20-03' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
|
||||||
],
|
],
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -326,11 +384,12 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
//change value during the test
|
//change value during the test
|
||||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||||
|
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||||
/** @var Server $oServer */
|
/** @var Server $oServer */
|
||||||
$oOrganisation = $this->createObject('Organization', array(
|
$oOrganisation = $this->createObject('Organization', array(
|
||||||
'name' =>$aInitData[0]
|
'name' => $aInitData[0]
|
||||||
));
|
));
|
||||||
$aResult["org_id"]=$oOrganisation->GetKey();
|
$aResult["org_id"] = $oOrganisation->GetKey();
|
||||||
$oServer = $this->createObject('Server', array(
|
$oServer = $this->createObject('Server', array(
|
||||||
'name' => $aInitData[1],
|
'name' => $aInitData[1],
|
||||||
'status' => $aInitData[2],
|
'status' => $aInitData[2],
|
||||||
@@ -340,6 +399,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
$aCsvData[0][2]=$oServer->GetKey();
|
$aCsvData[0][2]=$oServer->GetKey();
|
||||||
$aResult[2]=$oServer->GetKey();
|
$aResult[2]=$oServer->GetKey();
|
||||||
$aResult["id"]=$oServer->GetKey();
|
$aResult["id"]=$oServer->GetKey();
|
||||||
|
}
|
||||||
$oBulk = new \BulkChange(
|
$oBulk = new \BulkChange(
|
||||||
"Server",
|
"Server",
|
||||||
$aCsvData,
|
$aCsvData,
|
||||||
@@ -356,15 +416,17 @@ class BulkChangeTest extends ItopDataTestCase {
|
|||||||
static::assertNotNull($aRes);
|
static::assertNotNull($aRes);
|
||||||
foreach ($aRes as $aRow) {
|
foreach ($aRes as $aRow) {
|
||||||
foreach ($aRow as $i => $oCell) {
|
foreach ($aRow as $i => $oCell) {
|
||||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||||
$this->debug("i:".$i);
|
$this->debug("i:".$i);
|
||||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||||
$this->debug("aResult:".$aResult[$i]);
|
$this->debug("aResult:".$aResult[$i]);
|
||||||
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
||||||
}
|
} elseif ($i == "__STATUS__") {
|
||||||
elseif ($i == "__STATUS__") {
|
|
||||||
$sStatus = $aRow['__STATUS__'];
|
$sStatus = $aRow['__STATUS__'];
|
||||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||||
|
} else if ($i === "__ERRORS__") {
|
||||||
|
$sErrors = array_key_exists("__ERRORS__", $aResult) ? $aResult["__ERRORS__"] : "";
|
||||||
|
$this->assertEquals( $sErrors, $oCell->GetDescription());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
$this->assertEquals($aResult[0], $aRow[0]->GetDisplayableValue());
|
$this->assertEquals($aResult[0], $aRow[0]->GetDisplayableValue());
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ namespace Combodo\iTop\Test\UnitTest\Integration;
|
|||||||
|
|
||||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||||
|
|
||||||
|
use Dict;
|
||||||
|
|
||||||
class DictionariesConsistencyTest extends ItopTestCase
|
class DictionariesConsistencyTest extends ItopTestCase
|
||||||
{
|
{
|
||||||
@@ -148,4 +149,67 @@ class DictionariesConsistencyTest extends ItopTestCase
|
|||||||
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
$sMessage = "File `{$sDictFile}` syntax didn't matched expectations\nparsing results=".var_export($output, true);
|
||||||
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
self::assertEquals($bIsSyntaxValid, $bDictFileSyntaxOk, $sMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dataProvider ImBulChanportCsvMessageStillOkProvider
|
||||||
|
* make sure N°5305 dictionary changes are still here and UI remains unbroken for any lang
|
||||||
|
*/
|
||||||
|
public function testImportCsvMessageStillOk($sLangCode, $sDictFile)
|
||||||
|
{
|
||||||
|
$aFailedLabels = [];
|
||||||
|
$aLabelsToTest = [
|
||||||
|
'UI:CSVReport-Value-SetIssue' => [],
|
||||||
|
'UI:CSVReport-Value-ChangeIssue' => [ 'arg1' ],
|
||||||
|
'UI:CSVReport-Value-NoMatch' => [ 'arg1' ],
|
||||||
|
'UI:CSVReport-Value-NoMatch-PossibleValues' => [ 'arg1', 'arg2' ],
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject' => [ 'arg1' ],
|
||||||
|
'UI:CSVReport-Value-NoMatch-NoObject-ForCurrentUser' => [ 'arg1' ],
|
||||||
|
'UI:CSVReport-Value-NoMatch-SomeObjectNotVisibleForCurrentUser' => [ 'arg1' ],
|
||||||
|
];
|
||||||
|
|
||||||
|
$sLanguageCode = strtoupper(str_replace('-', ' ', $sLangCode));
|
||||||
|
require_once(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/languages.php');
|
||||||
|
Dict::SetUserLanguage($sLanguageCode);
|
||||||
|
foreach ($aLabelsToTest as $sLabelKey => $aLabelArgs){
|
||||||
|
try{
|
||||||
|
$sLabelValue = Dict::Format($sLabelKey, ...$aLabelArgs);
|
||||||
|
var_dump($sLabelValue);
|
||||||
|
} catch (\Exception $e){
|
||||||
|
$aFailedLabels[] = $sLabelKey;
|
||||||
|
|
||||||
|
var_dump([
|
||||||
|
'exception' => $e->getMessage(),
|
||||||
|
'trace' => $e->getTraceAsString(),
|
||||||
|
'label_name' => $sLabelKey,
|
||||||
|
'label_args' =>$aLabelArgs,
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$this->assertEquals([], $aFailedLabels, "test fail for lang $sLangCode and labels (" . implode(",", $aFailedLabels) . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ImportCsvMessageStillOkProvider(){
|
||||||
|
return $this->GetDictFiles();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* return a map linked to *.dict.php files that are generated after setup
|
||||||
|
* each entry key is lang code (example 'en')
|
||||||
|
* each value is an array with lang code (again) and dict file path
|
||||||
|
* @return array
|
||||||
|
*/
|
||||||
|
private function GetDictFiles() : array {
|
||||||
|
$aDictFiles = [];
|
||||||
|
|
||||||
|
foreach (glob(APPROOT.'env-'.\utils::GetCurrentEnvironment().'/dictionaries/*.dict.php') as $sDictFile){
|
||||||
|
if (preg_match('/.*\\/(.*).dict.php/', $sDictFile, $aMatches)){
|
||||||
|
$sLangCode = $aMatches[1];
|
||||||
|
$aDictFiles[$sLangCode] = [
|
||||||
|
'lang' => $sLangCode,
|
||||||
|
'file' => $sDictFile
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return $aDictFiles;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -848,89 +848,6 @@ $oSearch->AddCondition_PointingTo($oOrgSearch, "org_id");
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
// Test bulk load API
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
|
||||||
|
|
||||||
class TestItopBulkLoad extends TestBizModel
|
|
||||||
{
|
|
||||||
static public function GetName()
|
|
||||||
{
|
|
||||||
return 'Itop - test BulkChange class';
|
|
||||||
}
|
|
||||||
|
|
||||||
static public function GetDescription()
|
|
||||||
{
|
|
||||||
return 'Execute a bulk change at the Core API level';
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function DoExecute()
|
|
||||||
{
|
|
||||||
$sLogin = 'testbulkload_'.time();
|
|
||||||
|
|
||||||
$oParser = new CSVParser("login,contactid->name,password,profile_list
|
|
||||||
_1_$sLogin,Picasso,secret1,profileid:10;reason:service manager|profileid->name:Problem Manager;'reason:toto;problem manager'
|
|
||||||
_2_$sLogin,Picasso,secret2,
|
|
||||||
", ',', '"');
|
|
||||||
$aData = $oParser->ToArray(1, array('_login', '_contact_name', '_password', '_profiles'));
|
|
||||||
self::DumpVariable($aData);
|
|
||||||
|
|
||||||
$oUser = new UserLocal();
|
|
||||||
$oUser->Set('login', 'patator');
|
|
||||||
$oUser->Set('password', 'patator');
|
|
||||||
//$oUser->Set('contactid', 0);
|
|
||||||
//$oUser->Set('language', $sLanguage);
|
|
||||||
|
|
||||||
$aProfiles = array(
|
|
||||||
array(
|
|
||||||
'profileid' => 10, // Service Manager
|
|
||||||
'reason' => 'service manager',
|
|
||||||
),
|
|
||||||
array(
|
|
||||||
'profileid->name' => 'Problem Manager',
|
|
||||||
'reason' => 'problem manager',
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
$oBulk = new BulkChange(
|
|
||||||
'UserLocal',
|
|
||||||
$aData,
|
|
||||||
// attributes
|
|
||||||
array('login' => '_login', 'password' => '_password', 'profile_list' => '_profiles'),
|
|
||||||
// ext keys
|
|
||||||
array('contactid' => array('name' => '_contact_name')),
|
|
||||||
// reconciliation
|
|
||||||
array('login'),
|
|
||||||
// Synchro - scope
|
|
||||||
"SELECT UserLocal",
|
|
||||||
// Synchro - set attribute on missing objects
|
|
||||||
array ('password' => 'terminated', 'login' => 'terminated'.time())
|
|
||||||
);
|
|
||||||
|
|
||||||
if (false)
|
|
||||||
{
|
|
||||||
$oMyChange = MetaModel::NewObject("CMDBChange");
|
|
||||||
$oMyChange->Set("date", time());
|
|
||||||
$oMyChange->Set("userinfo", "Testor");
|
|
||||||
$iChangeId = $oMyChange->DBInsert();
|
|
||||||
// echo "Created new change: $iChangeId</br>";
|
|
||||||
}
|
|
||||||
|
|
||||||
echo "<h3>Planned for loading...</h3>";
|
|
||||||
$aRes = $oBulk->Process();
|
|
||||||
self::DumpVariable($aRes);
|
|
||||||
if (false)
|
|
||||||
{
|
|
||||||
echo "<h3>Go for loading...</h3>";
|
|
||||||
$aRes = $oBulk->Process($oMyChange);
|
|
||||||
self::DumpVariable($aRes);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
// Test data load
|
// Test data load
|
||||||
///////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////
|
||||||
|
|||||||
198
test/webservices/ImportTest.inc.php
Normal file
198
test/webservices/ImportTest.inc.php
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Combodo\iTop\Test\UnitTest\Webservices;
|
||||||
|
|
||||||
|
use CMDBSource;
|
||||||
|
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||||
|
use MetaModel;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @runTestsInSeparateProcesses
|
||||||
|
* @preserveGlobalState disabled
|
||||||
|
* @backupGlobals disabled
|
||||||
|
*/
|
||||||
|
class ImportTest extends ItopDataTestCase {
|
||||||
|
|
||||||
|
private $sUrl;
|
||||||
|
private $sUid;
|
||||||
|
private $sLogin;
|
||||||
|
private $sPassword = "Iuytrez9876543ç_è-(";
|
||||||
|
private $sTmpFile = "";
|
||||||
|
private $oOrg;
|
||||||
|
|
||||||
|
protected function tearDown() : void{
|
||||||
|
parent::tearDown();
|
||||||
|
if (!empty($this->sTmpFile) && is_file($this->sTmpFile)){
|
||||||
|
unlink($this->sTmpFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function setUp() : void{
|
||||||
|
parent::setUp();
|
||||||
|
|
||||||
|
$this->sTmpFile = tempnam(sys_get_temp_dir(), 'import_csv_');
|
||||||
|
|
||||||
|
require_once(APPROOT.'application/startup.inc.php');
|
||||||
|
$this->sUid = date('dmYHis');
|
||||||
|
$this->sLogin = "import-" .$this->sUid;
|
||||||
|
$this->oOrg = $this->CreateOrganization($this->sUid);
|
||||||
|
|
||||||
|
$sConfigFile = \utils::GetConfig()->GetLoadedFile();
|
||||||
|
@chmod($sConfigFile, 0770);
|
||||||
|
$this->sUrl = \MetaModel::GetConfig()->Get('app_root_url');
|
||||||
|
@chmod($sConfigFile, 0444); // Read-only
|
||||||
|
|
||||||
|
$oRestProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'REST Services User'), true);
|
||||||
|
$oAdminProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => 'Administrator'), true);
|
||||||
|
|
||||||
|
if (is_object($oRestProfile) && is_object($oAdminProfile))
|
||||||
|
{
|
||||||
|
$oUser = $this->CreateUser($this->sLogin, $oRestProfile->GetKey(), $this->sPassword);
|
||||||
|
$this->AddProfileToUser($oUser, $oAdminProfile->GetKey());
|
||||||
|
} else {
|
||||||
|
throw new \Exception("setup failed. test cannot work as usual");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ImportOkProvider(){
|
||||||
|
return [
|
||||||
|
'with reconciliation key' => [ "sReconciliationKeys" => "name,first_name,org_id->name" ],
|
||||||
|
'without reconciliation key' => [ "sReconciliationKeys" => null ],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dataProvider ImportOkProvider
|
||||||
|
*/
|
||||||
|
public function testImportOk($sReconciliationKeys){
|
||||||
|
$sFirstName = "firstname_UID";
|
||||||
|
$sLastName = "lastname_UID";
|
||||||
|
$sEmail = "email_UID@toto.fr";
|
||||||
|
|
||||||
|
$this->performImportTesting(
|
||||||
|
'"first_name","name", "email", "org_id->name"',
|
||||||
|
sprintf('"%s", "%s", "%s", UID', $sFirstName, $sLastName, $sEmail),
|
||||||
|
sprintf('ORGID;"%s";"%s";"%s"', $sFirstName, $sLastName, $sEmail),
|
||||||
|
$sReconciliationKeys,
|
||||||
|
0,
|
||||||
|
1
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ImportFailProvider(){
|
||||||
|
return [
|
||||||
|
'without reconciliation key' => [
|
||||||
|
"sReconciliationKeys" => null,
|
||||||
|
"sExpectedLastLineNeedle" => 'Issue: Unexpected attribute value(s);n/a;n/a;No match for value \'gabuzomeu\'. Some possible \'Organization\' value(s): '
|
||||||
|
],
|
||||||
|
'with reconciliation key' => [
|
||||||
|
"sReconciliationKeys" => "name,first_name,org_id->name",
|
||||||
|
"sExpectedLastLineNeedle" => 'Issue: failed to reconcile;n/a;n/a;No match for value \'gabuzomeu\'. Some possible \'Organization\' value(s): '
|
||||||
|
],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* @dataProvider ImportFailProvider
|
||||||
|
*/
|
||||||
|
|
||||||
|
public function testImportFail_ExternalKey($sReconciliationKeys, $sExpectedLastLineNeedle){
|
||||||
|
$sFirstName = "firstname_UID";
|
||||||
|
$sLastName = "lastname_UID";
|
||||||
|
$sEmail = "email_UID@toto.fr";
|
||||||
|
|
||||||
|
$this->performImportTesting(
|
||||||
|
'"first_name","name", "email", "org_id->name"',
|
||||||
|
sprintf('"%s", "%s", "%s", gabuzomeu', $sFirstName, $sLastName, $sEmail),
|
||||||
|
$sExpectedLastLineNeedle,
|
||||||
|
$sReconciliationKeys,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportFail_Enum(){
|
||||||
|
$sFirstName = "firstname_UID";
|
||||||
|
$sLastName = "lastname_UID";
|
||||||
|
$sEmail = "email_UID@toto.fr";
|
||||||
|
|
||||||
|
$this->performImportTesting(
|
||||||
|
'"first_name","name", "email", "org_id->name", status',
|
||||||
|
sprintf('"%s", "%s", "%s", UID, toto', $sFirstName, $sLastName, $sEmail),
|
||||||
|
sprintf(
|
||||||
|
'Issue: Unexpected attribute value(s);n/a;n/a;ORGID;"%s";"%s";"%s";\'toto\' is an invalid value. Unexpected value for attribute \'status\': Value not allowed [toto]', $sFirstName, $sLastName, $sEmail
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testImportFail_Date(){
|
||||||
|
$sFirstName = "firstname_UID";
|
||||||
|
$sLastName = "lastname_UID";
|
||||||
|
$sEmail = "email_UID@toto.fr";
|
||||||
|
|
||||||
|
$this->performImportTesting(
|
||||||
|
'"first_name","name", "email", "org_id->name", obsolescence_date',
|
||||||
|
sprintf('"%s", "%s", "%s", UID, toto', $sFirstName, $sLastName, $sEmail),
|
||||||
|
sprintf(
|
||||||
|
'Issue: Internal error: Exception, Wrong format for date attribute obsolescence_date, expecting "Y-m-d" and got "toto";n/a;n/a;n/a;%s;%s;%s;toto', $sFirstName, $sLastName, $sEmail
|
||||||
|
),
|
||||||
|
null,
|
||||||
|
1,
|
||||||
|
0
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function performImportTesting($sCsvHeaders, $sCsvFirstLineValues, $sExpectedLastLineNeedle, $sReconciliationKeys=null, $iExpectedIssue=1, $iExpectedCreated=0) {
|
||||||
|
$sContent = <<<CSVFILE
|
||||||
|
$sCsvHeaders
|
||||||
|
$sCsvFirstLineValues
|
||||||
|
CSVFILE;
|
||||||
|
file_put_contents($this->sTmpFile, str_replace("UID", $this->sUid, $sContent));
|
||||||
|
|
||||||
|
$aParams = [
|
||||||
|
'class' => 'Person',
|
||||||
|
'csvfile' => $this->sTmpFile,
|
||||||
|
'charset' => 'UTF-8',
|
||||||
|
'no_localize' => '1',
|
||||||
|
'output' => 'details',
|
||||||
|
];
|
||||||
|
|
||||||
|
if (null != $sReconciliationKeys){
|
||||||
|
$aParams["reconciliationkeys"] = $sReconciliationKeys;
|
||||||
|
}
|
||||||
|
|
||||||
|
$aRes = \utils::ExecITopScript('webservices/import.php', $aParams, $this->sLogin, $this->sPassword);
|
||||||
|
$aOutput = $aRes[1];
|
||||||
|
$sOutput = implode("\n", $aOutput);
|
||||||
|
$sLastline = $aOutput[sizeof($aOutput) - 1];
|
||||||
|
$iRes = $aRes[0];
|
||||||
|
$this->assertEquals(0, $iRes, $sOutput);
|
||||||
|
$this->assertContains("#Issues: $iExpectedIssue", $sOutput, $sOutput);
|
||||||
|
$this->assertContains("#Warnings: 0", $sOutput, $sOutput);
|
||||||
|
$this->assertContains("#Created: $iExpectedCreated", $sOutput, $sOutput);
|
||||||
|
$this->assertContains("#Updated: 0", $sOutput, $sOutput);
|
||||||
|
var_dump($sLastline);
|
||||||
|
if ($iExpectedCreated === 1) {
|
||||||
|
$this->assertContains("created;Person", $sLastline, $sLastline);
|
||||||
|
}
|
||||||
|
|
||||||
|
$iOrgId = $this->oOrg->GetKey();
|
||||||
|
$sLastLineNeedle = $sExpectedLastLineNeedle;
|
||||||
|
foreach (["ORGID" => $iOrgId, "UID" => $this->sUid] as $sSearch => $sReplace){
|
||||||
|
$sLastLineNeedle = str_replace($sSearch, $sReplace, $sLastLineNeedle);
|
||||||
|
}
|
||||||
|
$this->assertContains($sLastLineNeedle, $sLastline, $sLastline);
|
||||||
|
|
||||||
|
$sPattern = "/Person;(\d+);/";
|
||||||
|
if (preg_match($sPattern,$sLastline,$aMatches)){
|
||||||
|
var_dump($aMatches);
|
||||||
|
$iObjId = $aMatches[1];
|
||||||
|
$oObj = MetaModel::GetObject("Person", $iObjId);
|
||||||
|
$oObj->DBDelete();
|
||||||
|
}
|
||||||
|
|
||||||
|
//date
|
||||||
|
//ext key
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -861,12 +861,14 @@ try
|
|||||||
$sKey = (string) $key;
|
$sKey = (string) $key;
|
||||||
|
|
||||||
if ($sKey == '__STATUS__') continue;
|
if ($sKey == '__STATUS__') continue;
|
||||||
|
//__ERRORS__ used by tests only
|
||||||
|
if ($sKey == '__ERRORS__') continue;
|
||||||
if ($sKey == 'finalclass') continue;
|
if ($sKey == 'finalclass') continue;
|
||||||
if ($sKey == 'id') continue;
|
if ($sKey == 'id') continue;
|
||||||
|
|
||||||
if (is_object($value))
|
if (is_object($value))
|
||||||
{
|
{
|
||||||
$aRowDisp["$sKey"] = $value->GetDisplayableValue().$value->GetDescription();
|
$aRowDisp["$sKey"] = $value->GetDisplayableValueAndDescription();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user