mirror of
https://github.com/Combodo/iTop.git
synced 2026-02-22 03:44:12 +01:00
Compare commits
23 Commits
documentat
...
saas-1.0.7
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
163276a6c2 | ||
|
|
9b0c2f7324 | ||
|
|
e1807f598f | ||
|
|
02e63fff64 | ||
|
|
0864f05d9f | ||
|
|
1bbcd9656a | ||
|
|
34d8e52c22 | ||
|
|
ac7309e48c | ||
|
|
c8fade6013 | ||
|
|
ad052dd861 | ||
|
|
a5ea868609 | ||
|
|
0b03b3ef4d | ||
|
|
174cace20a | ||
|
|
e3f5dbfc80 | ||
|
|
40e24c25a2 | ||
|
|
c6e4466c53 | ||
|
|
6638eb4adc | ||
|
|
eb40968e34 | ||
|
|
766c9f0e7e | ||
|
|
2a064fd97d | ||
|
|
ca3c0cb163 | ||
|
|
44c0e236b0 | ||
|
|
6190429f51 |
@@ -62,6 +62,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -70,8 +71,7 @@ class LoginBasic extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'basic')
|
||||
{
|
||||
list($sAuthUser) = $this->GetAuthUserAndPassword();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -45,6 +45,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -53,8 +54,7 @@ class LoginExternal extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'external')
|
||||
{
|
||||
$sAuthUser = $this->GetAuthUser();
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'external', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'external', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -71,6 +71,7 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -82,17 +83,8 @@ class LoginForm extends AbstractLoginFSMExtension implements iLoginUIExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'form')
|
||||
{
|
||||
if (Session::IsSet('auth_user'))
|
||||
{
|
||||
// If FSM reenter this state (example 2FA) then the auth_user is not resubmitted
|
||||
$sAuthUser = Session::Get('auth_user');
|
||||
}
|
||||
else
|
||||
{
|
||||
$sAuthUser = utils::ReadPostedParam('auth_user', '', 'raw_data');
|
||||
}
|
||||
// Store 'auth_user' in session for further use
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
$iErrorCode = LoginWebPage::EXIT_CODE_WRONGCREDENTIALS;
|
||||
return LoginWebPage::LOGIN_FSM_ERROR;
|
||||
}
|
||||
Session::Set('auth_user', $sAuthUser);
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
@@ -68,8 +69,7 @@ class LoginURL extends AbstractLoginFSMExtension
|
||||
{
|
||||
if (Session::Get('login_mode') == 'url')
|
||||
{
|
||||
$sAuthUser = utils::ReadParam('auth_user', '', false, 'raw_data');
|
||||
LoginWebPage::OnLoginSuccess($sAuthUser, 'internal', Session::Get('login_mode'));
|
||||
LoginWebPage::OnLoginSuccess(Session::Get('auth_user'), 'internal', Session::Get('login_mode'));
|
||||
}
|
||||
return LoginWebPage::LOGIN_FSM_CONTINUE;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ class LoginWebPage extends NiceWebPage
|
||||
*/
|
||||
public static function SynchronizeProfiles(&$oUser, array $aProfiles, $sOrigin)
|
||||
{
|
||||
$oProfilesSet = $oUser->Get(‘profile_list’);
|
||||
$oProfilesSet = $oUser->Get('profile_list');
|
||||
//delete old profiles
|
||||
$aExistingProfiles = [];
|
||||
while ($oProfile = $oProfilesSet->Fetch())
|
||||
|
||||
@@ -11,7 +11,7 @@ define('UTF8_BOM', chr(239).chr(187).chr(191)); // 0xEF, 0xBB, 0xBF
|
||||
|
||||
/**
|
||||
* CellChangeSpec
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given cell: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -42,6 +42,17 @@ abstract class CellChangeSpec
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -86,26 +97,90 @@ class CellStatus_Issue extends CellStatus_Modify
|
||||
parent::__construct($proposedValue, $previousValue);
|
||||
}
|
||||
|
||||
public function GetDescription()
|
||||
public function GetDisplayableValue()
|
||||
{
|
||||
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
|
||||
{
|
||||
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()
|
||||
{
|
||||
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
|
||||
{
|
||||
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_sQuery = $sOql;
|
||||
$this->sSerializedSearch = $sSerializedSearch;
|
||||
parent::__construct(null, $previousValue, '');
|
||||
}
|
||||
|
||||
@@ -139,12 +227,23 @@ class CellStatus_Ambiguous extends CellStatus_Issue
|
||||
$sCount = $this->m_iCount;
|
||||
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)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* RowStatus
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
* A series of classes, keeping the information about a given row: could it be changed or not (and why)?
|
||||
*
|
||||
* @package iTopORM
|
||||
*/
|
||||
@@ -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
|
||||
@@ -220,17 +339,35 @@ class RowStatus_Issue extends RowStatus
|
||||
*/
|
||||
class BulkChange
|
||||
{
|
||||
protected $m_sClass;
|
||||
/** @var string */
|
||||
protected $m_sClass;
|
||||
protected $m_aData; // Note: hereafter, iCol maybe actually be any acceptable key (string)
|
||||
// #@# todo: rename the variables to sColIndex
|
||||
protected $m_aAttList; // attcode => iCol
|
||||
protected $m_aExtKeys; // aExtKeys[sExtKeyAttCode][sExtReconcKeyAttCode] = iCol;
|
||||
protected $m_aReconcilKeys; // attcode (attcode = 'id' for the pkey)
|
||||
protected $m_sSynchroScope; // OQL - if specified, then the missing items will be reported
|
||||
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)
|
||||
protected $m_sDateFormat; // Date format specification, see DateTime::createFromFormat
|
||||
protected $m_bLocalizedValues; // Values in the data set are localized (see AttributeEnum)
|
||||
protected $m_aExtKeysMappingCache; // Cache for resolving external keys based on the given search criterias
|
||||
/** @var array<string, string> attcode as key, iCol as value */
|
||||
protected $m_aAttList;
|
||||
/** @var array<string, array<string, string>> sExtKeyAttCode as key, array of sExtReconcKeyAttCode/iCol as value */
|
||||
protected $m_aExtKeys;
|
||||
/** @var string[] list of attcode (attcode = 'id' for the pkey) */
|
||||
protected $m_aReconcilKeys;
|
||||
/** @var string OQL - if specified, then the missing items will be reported */
|
||||
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)
|
||||
{
|
||||
@@ -261,30 +398,30 @@ class BulkChange
|
||||
$this->m_sReportCsvSep = $sSeparator;
|
||||
$this->m_sReportCsvDelimiter = $sDelimiter;
|
||||
}
|
||||
|
||||
|
||||
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
|
||||
{
|
||||
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
$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];
|
||||
}
|
||||
else
|
||||
{
|
||||
// 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);
|
||||
}
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$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
|
||||
@@ -318,10 +455,10 @@ class BulkChange
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// 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
|
||||
// if (!array_key_exists($sAttCode, $this->m_aAttList)) continue;
|
||||
@@ -330,7 +467,7 @@ class BulkChange
|
||||
|
||||
if ($this->IsNullExternalKeySpec($aRowData, $sAttCode))
|
||||
{
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// Default reporting
|
||||
// $aRowData[$iCol] is always null
|
||||
@@ -352,25 +489,24 @@ class BulkChange
|
||||
$oReconFilter = new DBObjectSearch($oExtKey->GetTargetClass());
|
||||
|
||||
$aCacheKeys = array();
|
||||
foreach ($aKeyConfig as $sForeignAttCode => $iCol)
|
||||
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
|
||||
{
|
||||
// The foreign attribute is one of our reconciliation key
|
||||
if ($sForeignAttCode == 'id')
|
||||
if ($sReconKeyAttCode == 'id')
|
||||
{
|
||||
$value = $aRowData[$iCol];
|
||||
}
|
||||
else
|
||||
{
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sForeignAttCode);
|
||||
$oForeignAtt = MetaModel::GetAttributeDef($oExtKey->GetTargetClass(), $sReconKeyAttCode);
|
||||
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
}
|
||||
$aCacheKeys[] = $value;
|
||||
$oReconFilter->AddCondition($sForeignAttCode, $value, '=');
|
||||
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
|
||||
$aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
|
||||
}
|
||||
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
|
||||
$iForeignKey = null;
|
||||
$sOQL = '';
|
||||
// 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))
|
||||
{
|
||||
@@ -379,9 +515,8 @@ class BulkChange
|
||||
if (array_key_exists($sCacheKey, $this->m_aExtKeysMappingCache[$sAttCode]))
|
||||
{
|
||||
// Cache hit
|
||||
$iCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iObjectFoundCount = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['c'];
|
||||
$iForeignKey = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['k'];
|
||||
$sOQL = $this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['oql'];
|
||||
// Record the hit
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey]['h']++;
|
||||
}
|
||||
@@ -389,34 +524,35 @@ class BulkChange
|
||||
{
|
||||
// Cache miss, let's initialize it
|
||||
$oExtObjects = new CMDBObjectSet($oReconFilter);
|
||||
$iCount = $oExtObjects->Count();
|
||||
if ($iCount == 1)
|
||||
$iObjectFoundCount = $oExtObjects->Count();
|
||||
if ($iObjectFoundCount == 1)
|
||||
{
|
||||
$oForeignObj = $oExtObjects->Fetch();
|
||||
$iForeignKey = $oForeignObj->GetKey();
|
||||
}
|
||||
$this->m_aExtKeysMappingCache[$sAttCode][$sCacheKey] = array(
|
||||
'c' => $iCount,
|
||||
'c' => $iObjectFoundCount,
|
||||
'k' => $iForeignKey,
|
||||
'oql' => $oReconFilter->ToOql(),
|
||||
'h' => 0, // number of hits on this cache entry
|
||||
);
|
||||
}
|
||||
switch($iCount)
|
||||
switch($iObjectFoundCount)
|
||||
{
|
||||
case 0:
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
$aResults[$sAttCode]= new CellStatus_SearchIssue();
|
||||
break;
|
||||
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResults[$sAttCode] = $oCellStatus_SearchIssue;
|
||||
$aErrors[$sAttCode] = Dict::S('UI:CSVReport-Value-Issue-NotFound');
|
||||
break;
|
||||
|
||||
case 1:
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
// Do change the external key attribute
|
||||
$oTargetObj->Set($sAttCode, $iForeignKey);
|
||||
break;
|
||||
|
||||
default:
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iCount, $sOQL);
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-FoundMany', $iObjectFoundCount);
|
||||
$aResults[$sAttCode]= new CellStatus_Ambiguous($oTargetObj->Get($sAttCode), $iObjectFoundCount, $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -433,7 +569,7 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
$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
|
||||
$aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
|
||||
@@ -446,7 +582,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Set the object attributes
|
||||
//
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
@@ -487,7 +623,13 @@ class BulkChange
|
||||
$value = $oAttDef->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
|
||||
if (is_null($value) && (strlen($aRowData[$iCol]) > 0))
|
||||
{
|
||||
$aErrors[$sAttCode] = Dict::Format('UI:CSVReport-Value-Issue-NoMatch', $sAttCode);
|
||||
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);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -504,7 +646,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -556,7 +698,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -567,12 +709,101 @@ class BulkChange
|
||||
}
|
||||
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)
|
||||
{
|
||||
$aResults = array();
|
||||
$aErrors = array();
|
||||
|
||||
|
||||
// External keys
|
||||
//
|
||||
foreach($this->m_aExtKeys as $sAttCode => $aKeyConfig)
|
||||
@@ -585,7 +816,7 @@ class BulkChange
|
||||
$aResults[$iCol] = new CellStatus_Void('?');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Update attributes
|
||||
//
|
||||
foreach($this->m_aOnDisappear as $sAttCode => $value)
|
||||
@@ -596,7 +827,7 @@ class BulkChange
|
||||
}
|
||||
$oTargetObj->Set($sAttCode, $value);
|
||||
}
|
||||
|
||||
|
||||
// Reporting on fields
|
||||
//
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
@@ -616,7 +847,7 @@ class BulkChange
|
||||
$aResults[$iCol]= new CellStatus_Void($oTargetObj->Get($sAttCode));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Checks
|
||||
//
|
||||
$res = $oTargetObj->CheckConsistency();
|
||||
@@ -674,14 +905,16 @@ class BulkChange
|
||||
}
|
||||
|
||||
$aResult[$iRow] = $this->PrepareObject($oTargetObj, $aRowData, $aErrors);
|
||||
|
||||
|
||||
if (count($aErrors) > 0)
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
// Check that any external key will have a value proposed
|
||||
$aMissingKeys = array();
|
||||
foreach (MetaModel::GetExternalKeys($this->m_sClass) as $sExtKeyAttCode => $oExtKey)
|
||||
@@ -689,7 +922,7 @@ class BulkChange
|
||||
if (!$oExtKey->IsNullAllowed())
|
||||
{
|
||||
if (!array_key_exists($sExtKeyAttCode, $this->m_aExtKeys) && !array_key_exists($sExtKeyAttCode, $this->m_aAttList))
|
||||
{
|
||||
{
|
||||
$aMissingKeys[] = $oExtKey->GetLabel();
|
||||
}
|
||||
}
|
||||
@@ -745,14 +978,16 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Modify(count($aChangedFields));
|
||||
|
||||
|
||||
// Optionaly record the results
|
||||
//
|
||||
if ($oChange)
|
||||
@@ -794,9 +1029,11 @@ class BulkChange
|
||||
{
|
||||
$sErrors = implode(', ', $aErrors);
|
||||
$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;
|
||||
}
|
||||
|
||||
|
||||
$aChangedFields = $oTargetObj->ListChanges();
|
||||
if (count($aChangedFields) > 0)
|
||||
{
|
||||
@@ -821,7 +1058,7 @@ class BulkChange
|
||||
$aResult[$iRow]["__STATUS__"] = new RowStatus_Disappeared(0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public function Process(CMDBChange $oChange = null)
|
||||
{
|
||||
if ($oChange)
|
||||
@@ -866,7 +1103,7 @@ class BulkChange
|
||||
foreach ($this->m_aAttList as $sAttCode => $iCol)
|
||||
{
|
||||
if ($sAttCode == 'id') continue;
|
||||
|
||||
|
||||
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
|
||||
if ($oAttDef instanceof AttributeDateTime) // AttributeDate is derived from AttributeDateTime
|
||||
{
|
||||
@@ -881,14 +1118,18 @@ class BulkChange
|
||||
$sFormat = $sDateFormat;
|
||||
}
|
||||
$oFormat = new DateTimeFormat($sFormat);
|
||||
$sDateExample = $oFormat->Format(new DateTime('2022-10-23 16:25:33'));
|
||||
$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][$iCol] = new CellStatus_Issue(utils::HtmlEntities($sValue), null, $sErrorMsg);
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
$oDate = DateTime::createFromFormat($sFormat, $this->m_aData[$iRow][$iCol]);
|
||||
$oDate = DateTime::createFromFormat($sFormat, $sValue);
|
||||
if ($oDate !== false)
|
||||
{
|
||||
$sNewDate = $oDate->format($oAttDef->GetInternalFormat());
|
||||
@@ -898,7 +1139,7 @@ class BulkChange
|
||||
{
|
||||
// Leave the cell unchanged
|
||||
$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,23 +1193,26 @@ class BulkChange
|
||||
else
|
||||
{
|
||||
// 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)
|
||||
{
|
||||
$oRemoteObj = reset($aMatches); // first item
|
||||
$valuecondition = $oRemoteObj->GetKey();
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Void($oRemoteObj->GetKey());
|
||||
}
|
||||
}
|
||||
elseif (count($aMatches) == 0)
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_SearchIssue();
|
||||
}
|
||||
$oCellStatus_SearchIssue = $this->GetCellSearchIssue($oReconFilter);
|
||||
$aResult[$iRow][$sAttCode] = $oCellStatus_SearchIssue;
|
||||
}
|
||||
else
|
||||
{
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $sQuery);
|
||||
$aResult[$iRow][$sAttCode] = new CellStatus_Ambiguous(null, count($aMatches), $oReconFilter->serialize());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1019,7 +1263,7 @@ class BulkChange
|
||||
default:
|
||||
// Found several matches, 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';
|
||||
}
|
||||
}
|
||||
@@ -1110,7 +1354,7 @@ class BulkChange
|
||||
}
|
||||
}
|
||||
$oBulkChanges->Seek(0);
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
while ($oChange = $oBulkChanges->Fetch())
|
||||
{
|
||||
@@ -1274,7 +1518,7 @@ EOF
|
||||
$oOldTarget = MetaModel::GetObject($oAttDef->GetTargetClass(), $oOperation->Get('oldvalue'));
|
||||
$sOldValue = $oOldTarget->GetHyperlink();
|
||||
}
|
||||
|
||||
|
||||
$sNewValue = Dict::S('UI:UndefinedObject');
|
||||
if ($oOperation->Get('newvalue') != 0)
|
||||
{
|
||||
@@ -1300,11 +1544,11 @@ EOF
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
$aAttributes[$sAttCode] = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$aDetails = array();
|
||||
foreach($aObjects as $iUId => $aObjData)
|
||||
{
|
||||
@@ -1356,6 +1600,6 @@ EOF
|
||||
$aConfig[$sAttCode] = array('label' => MetaModel::GetLabel($sClass, $sAttCode), 'description' => MetaModel::GetDescription($sClass, $sAttCode));
|
||||
}
|
||||
$oPage->table($aConfig, $aDetails);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1238,6 +1238,14 @@ class Config
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'navigation_menu.show_organization_filter' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Display organization filter in menu',
|
||||
'default' => true,
|
||||
'value' => true,
|
||||
'source_of_value' => '',
|
||||
'show_in_conf_sample' => false,
|
||||
],
|
||||
'quick_create.enabled' => [
|
||||
'type' => 'bool',
|
||||
'description' => 'Whether or not the quick create is enabled',
|
||||
@@ -1861,7 +1869,7 @@ class Config
|
||||
}
|
||||
if (strlen($sNoise) > 0)
|
||||
{
|
||||
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
|
||||
// Note: sNoise is an html output, but so far it was ok for me (e.g. showing the entire call stack)
|
||||
throw new ConfigException('Syntax error in configuration file',
|
||||
array('file' => $sConfigFile, 'error' => '<tt>'.htmlentities($sNoise, ENT_QUOTES, 'UTF-8').'</tt>'));
|
||||
}
|
||||
@@ -2671,7 +2679,7 @@ class ConfigPlaceholdersResolver
|
||||
}
|
||||
|
||||
$sPattern = '/\%(env|server)\((\w+)\)(?:\?:(\w*))?\%/'; //3 capturing groups, ie `%env(HTTP_PORT)?:8080%` produce: `env` `HTTP_PORT` and `8080`.
|
||||
|
||||
|
||||
if (! preg_match_all($sPattern, $rawValue, $aMatchesCollection, PREG_SET_ORDER))
|
||||
{
|
||||
return $rawValue;
|
||||
|
||||
7
css/backoffice/pages/_csv-import.scss
vendored
7
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;
|
||||
}
|
||||
|
||||
.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 {
|
||||
border-bottom: 1px $ibo-color-grey-400 solid;
|
||||
}
|
||||
@@ -44,4 +39,4 @@ tr.ibo-csv-import--row-added td {
|
||||
font-size: 4em;
|
||||
color: $ibo-color-primary-400;
|
||||
margin: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -612,7 +612,7 @@ $popover-arrow-color: $popover-bg !default;
|
||||
//** Popover outer arrow width
|
||||
$popover-arrow-outer-width: ($popover-arrow-width + 1) !default;
|
||||
//** Popover outer arrow color
|
||||
$popover-arrow-outer-color: fadein($popover-border-color, 5%) !default;
|
||||
$popover-arrow-outer-color: fade-in($popover-border-color, 0.05) !default;
|
||||
//** Popover outer arrow fallback color
|
||||
$popover-arrow-outer-fallback-color: darken($popover-fallback-border-color, 20%) !default;
|
||||
|
||||
|
||||
@@ -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:CSVReport-Value-Modified' => 'Upraveno',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nemůže být změněno - důvod: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemůže být změněno na %1$s - důvod: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Žádná shoda',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Chybí povinná hodnota',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nalezeno %1$s objektů',
|
||||
'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:CSVReport-Value-Modified' => 'Ændret',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Kunne ikke ændres - årsag: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Kunne ikke ændres til %1$s - årsag: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Mangler obligatorisk værdi',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Tvetydig: fandt %1$s objekter',
|
||||
'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:CSVReport-Value-Modified' => 'Modifiziert',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Konnte nicht geändert werden - Grund: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Konnte nicht zu %1$s geändert werden - Grund: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Kein Treffer',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Pflichtfeld fehlt',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Doppeldeutig: %1$s Objekte gefunden',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Unverändert',
|
||||
|
||||
@@ -650,9 +650,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:CSVReport-Value-Modified' => 'Modified',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Invalid value for attribute',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value',
|
||||
'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-Ambiguous' => 'Ambiguous: found %1$s objects',
|
||||
'UI:CSVReport-Row-Unchanged' => 'unchanged',
|
||||
@@ -666,11 +671,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-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-AllowedValues' => 'Allowed \'%1$s\' value(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-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-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-Ambiguous' => 'ambiguous reconciliation',
|
||||
'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:CSVReport-Value-Modified' => 'Modificado',
|
||||
'UI:CSVReport-Value-SetIssue' => 'No puede ser modificado - motivo: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'No puede ser cambiado a %1$s - motivo: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No hay Coincidencias',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Falta valor obligatorio',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambigüedad: encontrados %1$s objetos',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Sin Cambios',
|
||||
|
||||
@@ -633,9 +633,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:CSVReport-Value-Modified' => 'Modifié',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Modification impossible - cause : %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\' - cause : %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Pas de correspondance',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Valeur invalide',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Ne peut pas prendre la valeur \'%1$s\'',
|
||||
'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-Ambiguous' => 'Ambigüité: %1$d objets trouvés',
|
||||
'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:CSVReport-Value-Modified' => 'Modified~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||
'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:CSVReport-Value-Modified' => 'Modified~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Could not be changed - reason: %1$s~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Could not be changed to %1$s - reason: %2$s~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match~~',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Missing mandatory value~~',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects~~',
|
||||
'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:CSVReport-Value-Modified' => '修正済み',
|
||||
'UI:CSVReport-Value-SetIssue' => '変更出来ません - 理由: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s へ変更出来ません - 理由: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'マッチしません',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => '必須の値がありません',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'あいまいな値: %1$s オブジェクト',
|
||||
'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:CSVReport-Value-Modified' => 'Aangepast',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Kon niet worden aangepast - reden: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Kon niet worden aangepast naar %1$s - reden: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Geen match',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Ontbrekende verplichte waarde',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Onduidelijk: gevonden %1$s objecten',
|
||||
'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:CSVReport-Value-Modified' => 'Zmodyfikowano',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nie można było zmienić - powód: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nie można zmienić na %1$s - powód: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Nie pasuje',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Brak wymaganej wartości',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Uwaga: znaleziono %1$s obiektów',
|
||||
'UI:CSVReport-Row-Unchanged' => 'niezmieniony',
|
||||
|
||||
@@ -644,9 +644,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Selecione a classe para pesquisar: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Modificado',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Não pode ser modificado - razão: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Não pode ser modificado para %1$s - razão: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Não combina',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Faltando valor obrigatório',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Ambiguous: found %1$s objects',
|
||||
'UI:CSVReport-Row-Unchanged' => 'unchanged',
|
||||
|
||||
@@ -645,9 +645,9 @@ Dict::Add('RU RU', 'Russian', 'Русский', array(
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => 'Выбор класса для поиска: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => 'Изменен',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Не может быть изменен - причина: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Не может быть изменен %1$s - причина: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Нет совпадений',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Отсутствует обязательное значение',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Неоднозначное сопоставление: найдено %1$s объектов',
|
||||
'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:CSVReport-Value-Modified' => 'Upravený',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Nemožno zmeniť - dôvod: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => 'Nemožno zmeniť na %1$s - dôvod: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Žiadna zhoda',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Chýbajúca povinná hodnota',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Nejednoznačné: nájdených %1$s objektov',
|
||||
'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:CSVReport-Value-Modified' => 'Değiştiridi',
|
||||
'UI:CSVReport-Value-SetIssue' => 'Değiştirilemedi - Sebep: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '%1$s olarak değiştirilemedi - Sebep: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => 'Eşleşme yok',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => 'Eksik Zorunlu Değer',
|
||||
'UI:CSVReport-Value-Ambiguous' => 'Belirsiz: %1$s nesnelerini buldum',
|
||||
'UI:CSVReport-Row-Unchanged' => 'Değiştirilmedi',
|
||||
|
||||
@@ -649,9 +649,9 @@ We hope you’ll enjoy this version as much as we enjoyed imagining and creating
|
||||
'UI:UniversalSearch:LabelSelectTheClass' => '选择要搜索的类别: ',
|
||||
|
||||
'UI:CSVReport-Value-Modified' => '已修改',
|
||||
'UI:CSVReport-Value-SetIssue' => '无法修改 - 原因: %1$s',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '无法修改成 %1$s - 原因: %2$s',
|
||||
'UI:CSVReport-Value-NoMatch' => '不匹配',
|
||||
'UI:CSVReport-Value-SetIssue' => 'invalid value for attribute~~',
|
||||
'UI:CSVReport-Value-ChangeIssue' => '\'%1$s\' is an invalid value~~',
|
||||
'UI:CSVReport-Value-NoMatch' => 'No match for value \'%1$s\'~~',
|
||||
'UI:CSVReport-Value-Missing' => '缺少必填项',
|
||||
'UI:CSVReport-Value-Ambiguous' => '模糊匹配: 找到 %1$s 个对象',
|
||||
'UI:CSVReport-Row-Unchanged' => '保持不变',
|
||||
|
||||
@@ -139,7 +139,7 @@ try {
|
||||
}
|
||||
return $aResult;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return the most frequent (and regularly occuring) character among the given set, in the specified lines
|
||||
* @param array $aCSVData The input data, one entry per line
|
||||
@@ -175,7 +175,7 @@ try {
|
||||
}
|
||||
$iLine++;
|
||||
}
|
||||
|
||||
|
||||
$aScores = array();
|
||||
foreach($aGuesses as $sSep => $aData)
|
||||
{
|
||||
@@ -186,7 +186,7 @@ try {
|
||||
$sSeparator = $aKeys[0]; // Take the first key, the one with the best score
|
||||
return $sSeparator;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Try to predict the CSV parameters based on the input data
|
||||
* @param string $sCSVData The input data
|
||||
@@ -197,10 +197,10 @@ try {
|
||||
$aData = explode("\n", $sCSVData);
|
||||
$sSeparator = GuessFromFrequency($aData, array("\t", ',', ';', '|')); // Guess the most frequent (and regular) character on each line
|
||||
$sQualifier = GuessFromFrequency($aData, array('"', "'")); // Guess the most frequent (and regular) character on each line
|
||||
|
||||
|
||||
return array('separator' => $sSeparator, 'qualifier' => $sQualifier);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Display a banner for the special "synchro" mode
|
||||
* @param WebPage $oP The Page for the output
|
||||
@@ -216,6 +216,7 @@ try {
|
||||
* Add a paragraph to the body of the page
|
||||
*
|
||||
* @param string $s_html
|
||||
* @param ?string $sLinkUrl
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
@@ -260,9 +261,9 @@ try {
|
||||
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
|
||||
$sDateTimeFormat = utils::ReadParam('date_time_format', 'default');
|
||||
$sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data');
|
||||
|
||||
|
||||
$sChosenDateFormat = ($sDateTimeFormat == 'default') ? (string)AttributeDateTime::GetFormat() : $sCustomDateTimeFormat;
|
||||
|
||||
|
||||
if (!empty($sSynchroScope))
|
||||
{
|
||||
$oSearch = DBObjectSearch::FromOQL($sSynchroScope);
|
||||
@@ -277,7 +278,7 @@ try {
|
||||
$sSynchroScope = '';
|
||||
$aSynchroUpdate = null;
|
||||
}
|
||||
|
||||
|
||||
// Parse the data set
|
||||
$oCSVParser = new CSVParser($sCSVData, $sSeparator, $sTextQualifier, MetaModel::GetConfig()->Get('max_execution_time_per_loop'));
|
||||
$aData = $oCSVParser->ToArray($iSkippedLines);
|
||||
@@ -287,10 +288,10 @@ try {
|
||||
$aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, array_shift($aData)).$sTextQualifier; // Remove the first line and store it in case of error
|
||||
$iRealSkippedLines++;
|
||||
}
|
||||
|
||||
|
||||
// Format for the line numbers
|
||||
$sMaxLen = (strlen(''.count($aData)) < 3) ? 3 : strlen(''.count($aData)); // Pad line numbers to the appropriate number of chars, but at least 3
|
||||
|
||||
|
||||
// Compute the list of search/reconciliation criteria
|
||||
$aSearchKeys = array();
|
||||
foreach($aSearchFields as $index => $sDummy)
|
||||
@@ -304,16 +305,16 @@ try {
|
||||
}
|
||||
else
|
||||
{
|
||||
$aSearchKeys[$sSearchField] = '';
|
||||
$aSearchKeys[$sSearchField] = '';
|
||||
}
|
||||
if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField))
|
||||
{
|
||||
// Remove invalid or unmapped search fields
|
||||
$aSearchFields[$index] = null;
|
||||
unset($aSearchKeys[$sSearchField]);
|
||||
unset($aSearchKeys[$sSearchField]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Compute the list of fields and external keys to process
|
||||
$aExtKeys = array();
|
||||
$aAttributes = array();
|
||||
@@ -346,13 +347,13 @@ try {
|
||||
}
|
||||
else
|
||||
{
|
||||
$aAttributes[$sAttCode] = $iIndex;
|
||||
$aAttributes[$sAttCode] = $iIndex;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$oMyChange = null;
|
||||
if (!$bSimulate)
|
||||
{
|
||||
@@ -362,8 +363,6 @@ try {
|
||||
CMDBObject::SetTrackOrigin(CMDBChangeOrigin::CSV_INTERACTIVE);
|
||||
$oMyChange = CMDBObject::GetCurrentChange();
|
||||
}
|
||||
CMDBObject::SetTrackOrigin('csv-interactive');
|
||||
|
||||
$oBulk = new BulkChange(
|
||||
$sClassName,
|
||||
$aData,
|
||||
@@ -373,7 +372,7 @@ try {
|
||||
empty($sSynchroScope) ? null : $sSynchroScope,
|
||||
$aSynchroUpdate,
|
||||
$sChosenDateFormat, // date format
|
||||
true // localize
|
||||
true // localize
|
||||
);
|
||||
$oBulk->SetReportHtml();
|
||||
|
||||
@@ -440,7 +439,6 @@ try {
|
||||
|
||||
case 'RowStatus_NewObj':
|
||||
$iCreated++;
|
||||
$sFinalClass = $aResRow['finalclass'];
|
||||
$sStatus = '<img src="../images/added.png" title="'.Dict::S('UI:CSVReport-Icon-Created').'">';
|
||||
$sCSSRowClass = 'ibo-csv-import--row-added';
|
||||
if ($bSimulate) {
|
||||
@@ -456,7 +454,7 @@ try {
|
||||
case 'RowStatus_Issue':
|
||||
$iErrors++;
|
||||
$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';
|
||||
$sCSSRowClass = 'ibo-csv-import--row-error';
|
||||
if (array_key_exists($iLine, $aData)) {
|
||||
@@ -477,33 +475,36 @@ try {
|
||||
if (isset($aExternalKeysByColumn[$iNumber - 1])) {
|
||||
$sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
|
||||
$oExtKeyCellStatus = $aResRow[$sExtKeyName];
|
||||
switch (get_class($oExtKeyCellStatus)) {
|
||||
case 'CellStatus_Issue':
|
||||
case 'CellStatus_SearchIssue':
|
||||
case 'CellStatus_NullIssue':
|
||||
case 'CellStatus_Ambiguous':
|
||||
$sCellMessage .= GetDivAlert($oExtKeyCellStatus->GetDescription());
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
$oCellStatus = $oExtKeyCellStatus;
|
||||
}
|
||||
$sHtmlValue = $oCellStatus->GetDisplayableValue();
|
||||
switch (get_class($oCellStatus)) {
|
||||
case 'CellStatus_Issue':
|
||||
case 'CellStatus_NullIssue':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'</div>';
|
||||
break;
|
||||
|
||||
case 'CellStatus_SearchIssue':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error">ERROR: '.$sHtmlValue.$sCellMessage.'</div>';
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||
'<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;
|
||||
|
||||
case 'CellStatus_Ambiguous':
|
||||
$sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = '<div class="ibo-csv-import--cell-error" >'.Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue).$sCellMessage.'</div>';
|
||||
$aTableRow[$sClassName.'/'.$sAttCode] = sprintf("%s%s%s%s%s%s",
|
||||
'<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;
|
||||
|
||||
case 'CellStatus_Modify':
|
||||
@@ -592,7 +593,7 @@ try {
|
||||
$oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
|
||||
$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->SetBeforeInput(false);
|
||||
$oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
|
||||
@@ -679,7 +680,7 @@ try {
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
$sErrors = json_encode(Dict::Format('UI:CSVImportError_items', $iErrors));
|
||||
$sCreated = json_encode(Dict::Format('UI:CSVImportCreated_items', $iCreated));
|
||||
$sModified = json_encode(Dict::Format('UI:CSVImportModified_items', $iModified));
|
||||
@@ -774,7 +775,7 @@ EOF
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
/**
|
||||
* Perform the actual load of the CSV data and display the results
|
||||
@@ -798,7 +799,7 @@ EOF
|
||||
$oField->AddSubBlock($oText);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Simulate the load of the CSV data and display the results
|
||||
* @param WebPage $oPage The web page to display the wizard
|
||||
@@ -810,7 +811,7 @@ EOF
|
||||
$oPage->AddSubBlock($oPanel);
|
||||
ProcessCSVData($oPage, true /* simulate */);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the mapping between the CSV column and the fields of the objects
|
||||
* @param WebPage $oPage The web page to display the wizard
|
||||
@@ -923,10 +924,10 @@ EOF
|
||||
$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
|
||||
$sFieldsMapping = addslashes(json_encode($aFieldsMapping));
|
||||
$sSearchFields = addslashes(json_encode($aSearchFields));
|
||||
|
||||
|
||||
$oPage->add_ready_script("DoMapping('$sFieldsMapping', '$sSearchFields');"); // There is already a class selected, run the mapping
|
||||
}
|
||||
|
||||
|
||||
$oPage->add_script(
|
||||
<<<EOF
|
||||
var aDefaultKeys = new Array();
|
||||
@@ -1142,7 +1143,7 @@ EOF
|
||||
EOF
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Select the options of the CSV load and check for CSV parsing errors
|
||||
* @param WebPage $oPage The current web page
|
||||
@@ -1166,7 +1167,7 @@ EOF
|
||||
$sCSVData = utils::ReadPostedParam('csvdata', '', 'raw_data');
|
||||
}
|
||||
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
|
||||
|
||||
|
||||
// Compute a subset of the data set, now that we know the charset
|
||||
if ($sEncoding == 'UTF-8')
|
||||
{
|
||||
@@ -1183,7 +1184,7 @@ EOF
|
||||
{
|
||||
$sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData);
|
||||
}
|
||||
|
||||
|
||||
$aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data
|
||||
|
||||
$iSkippedLines = utils::ReadParam('nb_skipped_lines', '');
|
||||
@@ -1191,7 +1192,7 @@ EOF
|
||||
$sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data');
|
||||
if ($sTextQualifier == '') // May be set to an empty value by the previous page
|
||||
{
|
||||
$sTextQualifier = $aGuesses['qualifier'];
|
||||
$sTextQualifier = $aGuesses['qualifier'];
|
||||
}
|
||||
$sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier;
|
||||
$bHeaderLine = utils::ReadParam('header_line', 0);
|
||||
@@ -1610,7 +1611,7 @@ EOF
|
||||
null, AjaxTab::ENUM_TAB_PLACEHOLDER_MISC);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
switch($iStep)
|
||||
{
|
||||
case 11:
|
||||
@@ -1618,45 +1619,45 @@ EOF
|
||||
$oPage = new AjaxPage('');
|
||||
BulkChange::DisplayImportHistory($oPage);
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tableHover();');
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
|
||||
$oPage->add_ready_script('$("#CSVImportHistory table.listResults").tablesorter( { widgets: ["myZebra", "truncatedList"]} );');
|
||||
break;
|
||||
|
||||
|
||||
case 10:
|
||||
// Case generated by BulkChange::DisplayImportHistory
|
||||
$iChange = (int)utils::ReadParam('changeid', 0);
|
||||
BulkChange::DisplayImportHistoryDetails($oPage, $iChange);
|
||||
break;
|
||||
|
||||
|
||||
case 5:
|
||||
LoadData($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 4:
|
||||
Preview($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 3:
|
||||
SelectMapping($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 2:
|
||||
SelectOptions($oPage);
|
||||
break;
|
||||
|
||||
|
||||
case 1:
|
||||
case 6: // Loop back here when we are done
|
||||
default:
|
||||
Welcome($oPage);
|
||||
}
|
||||
|
||||
|
||||
$oPage->output();
|
||||
}
|
||||
catch(CoreException $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc()));
|
||||
$oP->output();
|
||||
|
||||
if (MetaModel::IsLogEnabledIssue())
|
||||
@@ -1684,8 +1685,8 @@ catch(Exception $e)
|
||||
{
|
||||
require_once(APPROOT.'/setup/setuppage.class.inc.php');
|
||||
$oP = new ErrorPage(Dict::S('UI:PageTitle:FatalError'));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
|
||||
$oP->add("<h1>".Dict::S('UI:FatalErrorMessage')."</h1>\n");
|
||||
$oP->error(Dict::Format('UI:Error_Details', $e->getMessage()));
|
||||
$oP->output();
|
||||
|
||||
if (MetaModel::IsLogEnabledIssue())
|
||||
@@ -1705,4 +1706,4 @@ catch(Exception $e)
|
||||
|
||||
IssueLog::Error($e->getMessage());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,16 +25,18 @@ class Session
|
||||
|
||||
public static function Start()
|
||||
{
|
||||
if (!self::$bIsInitialized) {
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
}
|
||||
self::$bIsInitialized = true;
|
||||
if (!self::$bSessionStarted) {
|
||||
session_name('itop-'.md5(APPROOT));
|
||||
if (!is_null(self::$iSessionId)) {
|
||||
session_id(self::$iSessionId);
|
||||
self::$bSessionStarted = session_start();
|
||||
} else {
|
||||
self::$bSessionStarted = session_start();
|
||||
self::$iSessionId = session_id();
|
||||
if (session_id(self::$iSessionId) === false) {
|
||||
session_regenerate_id();
|
||||
}
|
||||
}
|
||||
self::$bSessionStarted = session_start();
|
||||
self::$iSessionId = session_id();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -49,6 +49,16 @@ class NewsroomMenuFactory
|
||||
return $oMenu;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is any Newsroom provider configured
|
||||
* @return boolean
|
||||
*/
|
||||
public static function HasProviders()
|
||||
{
|
||||
$aProviders = MetaModel::EnumPlugins('iNewsroomProvider');
|
||||
return count($aProviders) > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare parameters for the newsroom JS widget
|
||||
*
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
|
||||
namespace Combodo\iTop\Application\UI\Base\Layout\NavigationMenu;
|
||||
|
||||
|
||||
use ApplicationContext;
|
||||
use ApplicationMenu;
|
||||
use appUserPreferences;
|
||||
@@ -35,6 +34,7 @@ use MetaModel;
|
||||
use UIExtKeyWidget;
|
||||
use UserRights;
|
||||
use utils;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\NewsroomMenu\NewsroomMenuFactory;
|
||||
|
||||
/**
|
||||
* Class NavigationMenu
|
||||
@@ -196,7 +196,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
@@ -269,7 +269,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
||||
*/
|
||||
public function IsNewsroomEnabled(): bool
|
||||
{
|
||||
return MetaModel::GetConfig()->Get('newsroom_enabled');
|
||||
return (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -290,6 +290,14 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return True if the silo selection is enabled, false otherwise
|
||||
* @since 3.1.0
|
||||
*/
|
||||
public function IsSiloSelectionEnabled() : bool {
|
||||
return MetaModel::GetConfig()->Get('navigation_menu.show_organization_filter');
|
||||
}
|
||||
|
||||
/**
|
||||
* @return void
|
||||
* @throws \CoreException
|
||||
@@ -301,6 +309,10 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
||||
$this->bHasSiloSelected = false;
|
||||
$this->sSiloLabel = null;
|
||||
|
||||
if (! $this->IsSiloSelectionEnabled()){
|
||||
return;
|
||||
}
|
||||
|
||||
//TODO 3.0 Use components if we have the time to build select/autocomplete components before release
|
||||
// List of visible Organizations
|
||||
$iCount = 0;
|
||||
@@ -343,7 +355,7 @@ class NavigationMenu extends UIBlock implements iKeyboardShortcut
|
||||
$this->aSiloSelection['html'] = '<form data-role="ibo-navigation-menu--silo-selection--form" action="'.utils::GetAbsoluteUrlAppRoot().'pages/UI.php">'; //<select class="org_combo" name="c[org_id]" title="Pick an organization" onChange="this.form.submit();">';
|
||||
|
||||
$oPage = new \CaptureWebPage();
|
||||
|
||||
|
||||
$oWidget = new UIExtKeyWidget('Organization', 'org_id', '', true /* search mode */);
|
||||
$iMaxComboLength = MetaModel::GetConfig()->Get('max_combo_length');
|
||||
$this->aSiloSelection['html'] .= $oWidget->DisplaySelect($oPage, $iMaxComboLength, false, Dict::S('UI:Layout:NavigationMenu:Silo:Label'), $oSet, $iCurrentOrganization, false, 'c[org_id]', '',
|
||||
@@ -376,7 +388,7 @@ $sAddClearButton
|
||||
JS;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Compute if the menu is expanded or collapsed
|
||||
*
|
||||
@@ -479,4 +491,4 @@ JS;
|
||||
{
|
||||
return "[data-role='".static::BLOCK_CODE."']";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class NavigationMenuFactory
|
||||
{
|
||||
|
||||
$oNewsroomMenu = null;
|
||||
if (MetaModel::GetConfig()->Get('newsroom_enabled'))
|
||||
if (MetaModel::GetConfig()->Get('newsroom_enabled') && NewsroomMenuFactory::HasProviders())
|
||||
{
|
||||
$oNewsroomMenu = NewsroomMenuFactory::MakeNewsroomMenuForNavigationMenu();
|
||||
}
|
||||
|
||||
@@ -70,6 +70,7 @@ define('TAG_ATTCODE', 'domains');
|
||||
class ItopDataTestCase extends ItopTestCase
|
||||
{
|
||||
private $iTestOrgId;
|
||||
|
||||
// For cleanup
|
||||
private $aCreatedObjects = array();
|
||||
|
||||
@@ -409,10 +410,25 @@ class ItopDataTestCase extends ItopTestCase
|
||||
* @param string $sLogin
|
||||
* @param int $iProfileId
|
||||
*
|
||||
* @return \DBObject
|
||||
* @return \UserLocal
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function CreateUser($sLogin, $iProfileId, $sPassword=null, $iContactid=2)
|
||||
{
|
||||
$oUser = $this->CreateContactlessUser($sLogin, $iProfileId, $sPassword);
|
||||
$oUser->Set('contactid', $iContactid);
|
||||
$oUser->DBWrite();
|
||||
return $oUser;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $sLogin
|
||||
* @param int $iProfileId
|
||||
*
|
||||
* @return \UserLocal
|
||||
* @throws Exception
|
||||
*/
|
||||
protected function CreateContactlessUser($sLogin, $iProfileId, $sPassword=null)
|
||||
{
|
||||
if (empty($sPassword)){
|
||||
$sPassword = $sLogin;
|
||||
@@ -422,8 +438,8 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oUserProfile->Set('profileid', $iProfileId);
|
||||
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||
$oSet = DBObjectSet::FromObject($oUserProfile);
|
||||
/** @var \UserLocal $oUser */
|
||||
$oUser = $this->createObject('UserLocal', array(
|
||||
'contactid' => $iContactid,
|
||||
'login' => $sLogin,
|
||||
'password' => $sPassword,
|
||||
'language' => 'EN US',
|
||||
@@ -448,7 +464,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
$oUserProfile->Set('reason', 'UNIT Tests');
|
||||
/** @var DBObjectSet $oSet */
|
||||
$oSet = $oUser->Get('profile_list');
|
||||
$oSet->AddObject($oUserProfile);
|
||||
$oSet->AddItem($oUserProfile);
|
||||
$oUser = $this->updateObject('UserLocal', $oUser->GetKey(), array(
|
||||
'profile_list' => $oSet,
|
||||
));
|
||||
@@ -835,7 +851,7 @@ class ItopDataTestCase extends ItopTestCase
|
||||
}
|
||||
|
||||
/**
|
||||
* Import a consistent set of iTop objects from the specified XML text string
|
||||
* Import a consistent set of iTop objects from the specified XML text string
|
||||
* @param string $sXmlDataset
|
||||
* @param boolean $bSearch If true, a search will be performed on each object (based on its reconciliation keys)
|
||||
* before trying to import it (existing objects will be updated)
|
||||
|
||||
67
test/application/UI/Base/Layout/NavigationMenuTest.php
Normal file
67
test/application/UI/Base/Layout/NavigationMenuTest.php
Normal file
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
namespace UI\Base\Layout;
|
||||
|
||||
use ApplicationContext;
|
||||
use Combodo\iTop\Application\UI\Base\Component\PopoverMenu\PopoverMenu;
|
||||
use Combodo\iTop\Application\UI\Base\Layout\NavigationMenu\NavigationMenu;
|
||||
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
|
||||
|
||||
/**
|
||||
*
|
||||
* @runTestsInSeparateProcesses
|
||||
* @preserveGlobalState disabled
|
||||
* @backupGlobals disabled
|
||||
* Class NavigationMenuTest
|
||||
*
|
||||
* @package UI\Base\Layout
|
||||
*/
|
||||
class NavigationMenuTest extends ItopDataTestCase {
|
||||
public function IsAllowedProvider(){
|
||||
return [
|
||||
'show menu' => [ true ],
|
||||
'hide menu' => [ false ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider IsAllowedProvider
|
||||
* test used to make sure backward compatibility is ensured
|
||||
*/
|
||||
public function testIsAllowed($bExpectedIsAllowed=true){
|
||||
\MetaModel::GetConfig()->Set('navigation_menu.show_organization_filter', $bExpectedIsAllowed);
|
||||
$oNavigationMenu = new NavigationMenu(
|
||||
$this->createMock(ApplicationContext::class),
|
||||
$this->createMock(PopoverMenu::class));
|
||||
|
||||
$isAllowed = $oNavigationMenu->IsSiloSelectionEnabled();
|
||||
$this->assertEquals($bExpectedIsAllowed, $isAllowed);
|
||||
}
|
||||
|
||||
public function testIsAllowed_BackwardCompatibility_NoVariableInConfFile(){
|
||||
\MetaModel::GetConfig()->Set('navigation_menu.show_organization_filter', false);
|
||||
|
||||
$sTmpFilePath = tempnam(sys_get_temp_dir(), 'test_');
|
||||
$oInitConfig = \MetaModel::GetConfig();
|
||||
$oInitConfig->WriteToFile($sTmpFilePath);
|
||||
|
||||
//remove variable for the test
|
||||
$aLines = file($sTmpFilePath);
|
||||
|
||||
$aRows = array();
|
||||
|
||||
foreach ($aLines as $key => $sLine) {
|
||||
if (!preg_match('/navigation_menu.show_organization_filter/', $sLine)) {
|
||||
$aRows[] = $sLine;
|
||||
}
|
||||
}
|
||||
|
||||
file_put_contents($sTmpFilePath, implode("\n", $aRows));
|
||||
$oTempConfig = new \Config($sTmpFilePath);
|
||||
|
||||
$isAllowed = $oTempConfig->Get('navigation_menu.show_organization_filter');
|
||||
|
||||
$this->assertEquals(true, $isAllowed);
|
||||
unlink($sTmpFilePath);
|
||||
}
|
||||
}
|
||||
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)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
//$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$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],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
[["Bad", "Server1", "1", "production", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
[["", "Server1", "1", "production", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
[["Demo", "Server1", "1", "<svg onclick\"alert(1)\">", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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,17 +181,20 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
/** @var Server $oServer */
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $aInitData[0],
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||
|
||||
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||
/** @var Server $oServer */
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $aInitData[0],
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
$this->debug("oServer->GetKey():".$oServer->GetKey());
|
||||
}
|
||||
$this->debug("aCsvData:".json_encode($aCsvData[0]));
|
||||
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
|
||||
$oBulk = new \BulkChange(
|
||||
@@ -207,13 +219,16 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
if (array_key_exists('__STATUS__', $aRow)) {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
$this->debug("sStatus:".$sStatus->GetDescription());
|
||||
$this->assertEquals($sStatus->GetDescription(), $aResult["__STATUS__"]);
|
||||
$this->assertEquals($aResult["__STATUS__"], $sStatus->GetDescription());
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$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());
|
||||
@@ -225,21 +240,58 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
|
||||
public function CSVImportProvider() {
|
||||
return [
|
||||
"Case 6 - 1 : Unexpected value" => [
|
||||
"Case 6 - 1 : Unexpected value (update)" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
[["Demo", "ServerTest", "key", "BadValue", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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", ""],
|
||||
[["Demo", "ServerTest", "key", "<svg onclick\"alert(1)\">", ""]],
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
["1", "<svg onclick\"alert(1)\">", "production", ""],
|
||||
@@ -263,7 +315,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -271,7 +323,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -279,7 +331,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -287,7 +341,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
["1", "ServerTest", "production", ""],
|
||||
@@ -295,7 +351,9 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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" => [
|
||||
@@ -304,7 +362,7 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
|
||||
["org_id" => ["name" => 0]],
|
||||
["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,20 +384,22 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
//change value during the test
|
||||
$db_core_transactions_enabled=MetaModel::GetConfig()->Get('db_core_transactions_enabled');
|
||||
MetaModel::GetConfig()->Set('db_core_transactions_enabled',false);
|
||||
/** @var Server $oServer */
|
||||
$oOrganisation = $this->createObject('Organization', array(
|
||||
'name' =>$aInitData[0]
|
||||
));
|
||||
$aResult["org_id"]=$oOrganisation->GetKey();
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $oOrganisation->GetKey(),
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
if (is_array($aInitData) && sizeof($aInitData) != 0) {
|
||||
/** @var Server $oServer */
|
||||
$oOrganisation = $this->createObject('Organization', array(
|
||||
'name' => $aInitData[0]
|
||||
));
|
||||
$aResult["org_id"] = $oOrganisation->GetKey();
|
||||
$oServer = $this->createObject('Server', array(
|
||||
'name' => $aInitData[1],
|
||||
'status' => $aInitData[2],
|
||||
'org_id' => $oOrganisation->GetKey(),
|
||||
'purchase_date' => $aInitData[3],
|
||||
));
|
||||
$aCsvData[0][2]=$oServer->GetKey();
|
||||
$aResult[2]=$oServer->GetKey();
|
||||
$aResult["id"]=$oServer->GetKey();
|
||||
}
|
||||
$oBulk = new \BulkChange(
|
||||
"Server",
|
||||
$aCsvData,
|
||||
@@ -356,15 +416,17 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
static::assertNotNull($aRes);
|
||||
foreach ($aRes as $aRow) {
|
||||
foreach ($aRow as $i => $oCell) {
|
||||
if ($i != "finalclass" && $i != "__STATUS__") {
|
||||
if ($i != "finalclass" && $i != "__STATUS__" && $i != "__ERRORS__") {
|
||||
$this->debug("i:".$i);
|
||||
$this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
|
||||
$this->debug("aResult:".$aResult[$i]);
|
||||
$this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
|
||||
}
|
||||
elseif ($i == "__STATUS__") {
|
||||
} elseif ($i == "__STATUS__") {
|
||||
$sStatus = $aRow['__STATUS__'];
|
||||
$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());
|
||||
@@ -402,4 +464,4 @@ class BulkChangeTest extends ItopDataTestCase {
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -108,8 +108,8 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
$sAdminLogin = "admin-user-".$sUid;
|
||||
$sImpersonatedLogin = "impersonated-user-".$sUid;
|
||||
|
||||
$iAdminUserId = $this->CreateUserForImpersonation($sAdminLogin, 'Administrator', 'AdminName', 'AdminSurName');
|
||||
$this->CreateUserForImpersonation($sImpersonatedLogin, 'Configuration Manager', 'ImpersonatedName', 'ImpersonatedSurName');
|
||||
$oAdminUser = $this->CreateUserForImpersonation($sAdminLogin, 'Administrator', 'AdminName', 'AdminSurName');
|
||||
$oImpersonatedUser = $this->CreateUserForImpersonation($sImpersonatedLogin, 'Configuration Manager', 'ImpersonatedName', 'ImpersonatedSurName');
|
||||
|
||||
$_SESSION = [];
|
||||
\UserRights::Login($sAdminLogin);
|
||||
@@ -124,28 +124,31 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
if (is_null($sTrackInfo)){
|
||||
CMDBObject::SetTrackInfo(null);
|
||||
} else {
|
||||
$sTrackInfo = $this->ReplaceByFriendlyNames($sTrackInfo, $oAdminUser, $oImpersonatedUser);
|
||||
CMDBObject::SetTrackInfo($sTrackInfo);
|
||||
}
|
||||
|
||||
$this->CreateSimpleObject();
|
||||
if (is_null($sTrackInfo)){
|
||||
self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
self::assertEquals($oAdminUser->GetFriendlyName(), CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : no impersonation');
|
||||
} else {
|
||||
self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : no impersonation');
|
||||
}
|
||||
self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'),
|
||||
self::assertEquals($oAdminUser->GetKey(), CMDBObject::GetCurrentChange()->Get('user_id'),
|
||||
'TrackInfo : admin userid');
|
||||
|
||||
\UserRights::Impersonate($sImpersonatedLogin);
|
||||
$this->CreateSimpleObject();
|
||||
|
||||
if (is_null($sExpectedChangeLogWhenImpersonation)){
|
||||
self::assertEquals("AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName", CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
$sExpectedMsg = $this->ReplaceByFriendlyNames("AdminSurName AdminName on behalf of ImpersonatedSurName ImpersonatedName", $oAdminUser, $oImpersonatedUser);
|
||||
self::assertEquals($sExpectedMsg, CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : impersonation');
|
||||
} else {
|
||||
self::assertEquals($sExpectedChangeLogWhenImpersonation, CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
$sExpectedMsg = $this->ReplaceByFriendlyNames($sExpectedChangeLogWhenImpersonation, $oAdminUser, $oImpersonatedUser);
|
||||
self::assertEquals($sExpectedMsg, CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : impersonation');
|
||||
}
|
||||
|
||||
@@ -155,13 +158,13 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
\UserRights::Deimpersonate();
|
||||
$this->CreateSimpleObject();
|
||||
if (is_null($sTrackInfo)){
|
||||
self::assertEquals("AdminSurName AdminName", CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
self::assertEquals($oAdminUser->GetFriendlyName(), CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : no impersonation');
|
||||
} else {
|
||||
self::assertEquals($sTrackInfo, CMDBObject::GetCurrentChange()->Get('userinfo'),
|
||||
'TrackInfo : no impersonation');
|
||||
}
|
||||
self::assertEquals($iAdminUserId, CMDBObject::GetCurrentChange()->Get('user_id'),
|
||||
self::assertEquals($oAdminUser->GetKey(), CMDBObject::GetCurrentChange()->Get('user_id'),
|
||||
'TrackInfo : admin userid');
|
||||
|
||||
// restore initial conditions
|
||||
@@ -169,6 +172,12 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
CMDBObject::SetTrackInfo($sInitialTrackInfo);
|
||||
}
|
||||
|
||||
private function ReplaceByFriendlyNames($sMessage, $oAdminUser, $oImpersonatedUser) : string {
|
||||
$sNewMessage = str_replace('AdminSurName AdminName', $oAdminUser->GetFriendlyName(), $sMessage);
|
||||
$sNewMessage = str_replace('ImpersonatedSurName ImpersonatedName', $oImpersonatedUser->GetFriendlyName(), $sNewMessage);
|
||||
return $sNewMessage;
|
||||
}
|
||||
|
||||
private function CreateSimpleObject(){
|
||||
/** @var \DocumentWeb $oTestObject */
|
||||
$oTestObject = MetaModel::NewObject('DocumentWeb');
|
||||
@@ -178,7 +187,7 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
$oTestObject->DBWrite();
|
||||
}
|
||||
|
||||
private function CreateUserForImpersonation($sLogin, $sProfileName, $sName, $sSurname): int {
|
||||
private function CreateUserForImpersonation($sLogin, $sProfileName, $sName, $sSurname): \UserLocal {
|
||||
/** @var \Person $oPerson */
|
||||
$oPerson = $this->createObject('Person', array(
|
||||
'name' => $sName,
|
||||
@@ -187,8 +196,9 @@ class CMDBObjectTest extends ItopDataTestCase
|
||||
));
|
||||
|
||||
$oProfile = \MetaModel::GetObjectFromOQL("SELECT URP_Profiles WHERE name = :name", array('name' => $sProfileName), true);
|
||||
/** @var \UserLocal $oUser */
|
||||
$oUser = $this->CreateUser($sLogin, $oProfile->GetKey(), "1234567Azert@", $oPerson->GetKey());
|
||||
|
||||
return $oUser->GetKey();
|
||||
return $oUser;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ namespace Combodo\iTop\Test\UnitTest\Integration;
|
||||
|
||||
use Combodo\iTop\Test\UnitTest\ItopTestCase;
|
||||
|
||||
use Dict;
|
||||
|
||||
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);
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -232,7 +232,7 @@ if (utils::IsModeCLI())
|
||||
{
|
||||
// Next steps:
|
||||
// specific arguments: 'csvfile'
|
||||
//
|
||||
//
|
||||
$sAuthUser = ReadMandatoryParam($oP, 'auth_user', 'raw_data');
|
||||
$sAuthPwd = ReadMandatoryParam($oP, 'auth_pwd', 'raw_data');
|
||||
$sCsvFile = ReadMandatoryParam($oP, 'csvfile', 'raw_data');
|
||||
@@ -273,7 +273,7 @@ try
|
||||
//
|
||||
// Read parameters
|
||||
//
|
||||
$sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves
|
||||
$sClass = ReadMandatoryParam($oP, 'class', 'raw_data'); // do not filter as a valid class, we want to produce the report "wrong class" ourselves
|
||||
$sSep = ReadParam($oP, 'separator', 'raw_data');
|
||||
$sQualifier = ReadParam($oP, 'qualifier', 'raw_data');
|
||||
$sCharSet = ReadParam($oP, 'charset', 'raw_data');
|
||||
@@ -326,7 +326,7 @@ try
|
||||
{
|
||||
$sDateFormat = null;
|
||||
}
|
||||
|
||||
|
||||
if ($sCharSet == '')
|
||||
{
|
||||
$sCharSet = MetaModel::GetConfig()->Get('csv_file_default_charset');
|
||||
@@ -444,7 +444,7 @@ try
|
||||
{
|
||||
$sUTF8Data = iconv($sCharSet, 'UTF-8//IGNORE//TRANSLIT', $sCSVData);
|
||||
}
|
||||
$oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier);
|
||||
$oCSVParser = new CSVParser($sUTF8Data, $sSep, $sQualifier);
|
||||
|
||||
// Limitation: as the attribute list is in the first line, we can not match external key by a third-party attribute
|
||||
$aRawFieldList = $oCSVParser->ListFields();
|
||||
@@ -466,7 +466,7 @@ try
|
||||
// Remove any trailing "star" character before the arrow (->)
|
||||
// A star character at the end can be used to indicate a mandatory field
|
||||
$sFieldName = $aMatches[1].'->'.$aMatches[2];
|
||||
}
|
||||
}
|
||||
if (array_key_exists(strtolower($sFieldName), $aKnownColumnNames))
|
||||
{
|
||||
$aColumns = $aKnownColumnNames[strtolower($sFieldName)];
|
||||
@@ -488,7 +488,7 @@ try
|
||||
throw new BulkLoadException("Unknown column: '$sSafeName'. Possible columns: ".implode(', ', array_keys($aKnownColumnNames)));
|
||||
}
|
||||
}
|
||||
// Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->')
|
||||
// Note: at this stage the list of fields is supposed to be made of attcodes (and the symbol '->')
|
||||
|
||||
$aAttList = array();
|
||||
$aExtKeys = array();
|
||||
@@ -723,7 +723,7 @@ try
|
||||
}
|
||||
CMDBObject::SetTrackInfo($sMoreInfo);
|
||||
CMDBObject::SetTrackOrigin('csv-import.php');
|
||||
|
||||
|
||||
$oMyChange = CMDBObject::GetCurrentChange();
|
||||
}
|
||||
|
||||
@@ -758,7 +758,7 @@ try
|
||||
break;
|
||||
case 'RowStatus_Issue':
|
||||
$iCountErrors++;
|
||||
break;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($bWritten)
|
||||
@@ -837,7 +837,7 @@ try
|
||||
$aDisplayConfig["$iCol"] = array("label"=>$sAttCode, "description"=>$sLabel);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
$aResultDisp = array(); // to be displayed
|
||||
foreach($aRes as $iRow => $aRowData)
|
||||
{
|
||||
@@ -864,14 +864,16 @@ try
|
||||
foreach($aRowData as $key => $value)
|
||||
{
|
||||
$sKey = (string) $key;
|
||||
|
||||
|
||||
if ($sKey == '__STATUS__') continue;
|
||||
//__ERRORS__ used by tests only
|
||||
if ($sKey == '__ERRORS__') continue;
|
||||
if ($sKey == 'finalclass') continue;
|
||||
if ($sKey == 'id') continue;
|
||||
|
||||
|
||||
if (is_object($value))
|
||||
{
|
||||
$aRowDisp["$sKey"] = $value->GetDisplayableValue().$value->GetDescription();
|
||||
$aRowDisp["$sKey"] = $value->GetDisplayableValueAndDescription();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -885,15 +887,15 @@ try
|
||||
}
|
||||
catch(BulkLoadException $e)
|
||||
{
|
||||
$oP->add_comment($e->getMessage());
|
||||
$oP->add_comment($e->getMessage());
|
||||
}
|
||||
catch(SecurityException $e)
|
||||
{
|
||||
$oP->add_comment($e->getMessage());
|
||||
$oP->add_comment($e->getMessage());
|
||||
}
|
||||
catch(Exception $e)
|
||||
{
|
||||
$oP->add_comment((string)$e);
|
||||
$oP->add_comment((string)$e);
|
||||
}
|
||||
|
||||
$oP->output();
|
||||
|
||||
Reference in New Issue
Block a user