Compare commits

...

23 Commits

Author SHA1 Message Date
odain
163276a6c2 N°5620-merge fix 2022-11-23 09:24:21 +01:00
odain
9b0c2f7324 Merge branch 'feature/5620-hide-org-filter-menu' into saas/3.0 2022-11-22 13:47:33 +01:00
odain
e1807f598f N°5620-fix ci 2022-11-22 13:46:33 +01:00
Molkobain
02e63fff64 Add PHPDoc 2022-11-22 13:28:02 +01:00
odain
0864f05d9f N°5620 - conf param renaming + backward compatibility test 2022-11-22 08:25:43 +01:00
denis.flaven@combodo.com
1bbcd9656a N°5619 - fixed crash when no provider at all! 2022-11-22 08:25:43 +01:00
odain
34d8e52c22 N°5620 - remove debug log 2022-11-22 08:25:27 +01:00
odain
ac7309e48c N°5620 - Hide the organization filter with a conf parameter 2022-11-22 08:25:27 +01:00
odain
c8fade6013 5620-simplify test to avoid regression in other test sections linked to MetaModel use 2022-11-22 07:42:03 +01:00
odain
ad052dd861 N°5620-renaming IsOrgMenuFilterAllowed<-IsSiloSelectionEnabled and made public 2022-11-22 07:31:12 +01:00
odain
a5ea868609 fix test 2022-11-21 10:41:03 +01:00
odain
0b03b3ef4d N°5620 - conf param renaming + backward compatibility test 2022-11-21 09:56:56 +01:00
odain
174cace20a N°5620 - remove debug log 2022-11-15 09:55:12 +01:00
odain
e3f5dbfc80 N°5620 - Hide the organization filter with a conf parameter 2022-11-15 09:49:36 +01:00
denis.flaven@combodo.com
40e24c25a2 N°5619 - hide newsroom menu when no provider 2022-11-15 09:17:27 +01:00
odain
c6e4466c53 ci: fix ItopDataTestCase CreateUser contactid unset 2022-10-26 14:03:11 +02:00
odain
6638eb4adc ci: adapt impersonate test to any friendlyname output 2022-10-26 09:50:56 +02:00
odain
eb40968e34 ci: add CreateContactlessUser method in test framework 2022-10-25 09:26:57 +02:00
odain-cbd
766c9f0e7e N°5305 - CSV import ergonomy PR (#332)
Reworked UI feedbacks on following attributes:
- enum
- date
- external key
2022-09-20 16:00:33 +02:00
Eric Espie
2a064fd97d N°5394 - use session for the FSM 2022-09-12 10:56:25 +02:00
Eric Espie
ca3c0cb163 N°5510 - Exception "$amount: Expected 5% to be within 0% and 1%" when compiling a theme 2022-09-09 09:20:52 +02:00
Eric Espie
44c0e236b0 N°5509 - User Provisioning Issue 2022-09-08 09:58:29 +02:00
Eric Espie
6190429f51 N°5394 - Rework session start 2022-09-01 16:08:16 +02:00
39 changed files with 1348 additions and 392 deletions

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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())

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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;
}
}

View File

@@ -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;

View File

@@ -644,9 +644,9 @@ We hope youll 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',

View File

@@ -633,9 +633,9 @@ We hope youll 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',

View File

@@ -633,9 +633,9 @@ We hope youll 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',

View File

@@ -650,9 +650,14 @@ We hope youll 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 youll 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',

View File

@@ -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',

View File

@@ -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é',

View File

@@ -633,9 +633,9 @@ We hope youll 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~~',

View File

@@ -644,9 +644,9 @@ We hope youll 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~~',

View File

@@ -633,9 +633,9 @@ We hope youll 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' => '未変更',

View File

@@ -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',

View File

@@ -643,9 +643,9 @@ We hope youll 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',

View File

@@ -644,9 +644,9 @@ We hope youll 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',

View File

@@ -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' => 'без изменений',

View File

@@ -634,9 +634,9 @@ We hope youll 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ý',

View File

@@ -661,9 +661,9 @@ We hope youll 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',

View File

@@ -649,9 +649,9 @@ We hope youll 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' => '保持不变',

View File

@@ -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">&nbsp;'.sprintf($aDisplayFilters['errors'], $iErrors), '', "1", "show_errors", "checkbox");
$oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('<i class="fas fa-exclamation-triangle" style="color:#A33; background-color: #FFF0F0;">&nbsp;'.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());
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
*

View File

@@ -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."']";
}
}
}

View File

@@ -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();
}

View File

@@ -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)

View 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);
}
}

View 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);
}
}

View File

@@ -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 => "&lt;svg onclick&quot;alert(1)&quot;&gt;", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
[
0 => "Demo",
"org_id" => "3",
1 => "Server1",
2 => "1",
3 => "'&lt;svg onclick&quot;alert(1)&quot;&gt;' 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 => "&lt;svg onclick&quot;alert(1)&quot;&gt;", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
[
0 => "Demo",
"org_id" => "3",
1 => "ServerTest",
2 => "1",
3 => "'&lt;svg onclick&quot;alert(1)&quot;&gt;' 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 => "'&lt;svg onclick&quot;alert(1)&quot;&gt;' 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 => "&lt;svg onclick&quot;alert(1)&quot;&gt;", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
[ 0 => "Demo", "org_id" => "n/a", 1 => "ServerTest", 2 => "1", 3 => "production", 4 => "'&lt;svg onclick&quot;alert(1)&quot;&gt;' 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 => "&lt;svg fonclick&quot;alert(1)&quot;&gt;", "org_id" => "",1 => "ServerTest",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
[ 0 => "&lt;svg fonclick&quot;alert(1)&quot;&gt;", "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 {
];
}
}
}

View File

@@ -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;
}
}

View File

@@ -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;
}
}

View File

@@ -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
///////////////////////////////////////////////////////////////////////////

View 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
}
}

View File

@@ -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();