diff --git a/core/apc-emulation.php b/core/apc-emulation.php
index 7ccde34b2..6690ad0be 100644
--- a/core/apc-emulation.php
+++ b/core/apc-emulation.php
@@ -60,14 +60,15 @@ function apc_store($key, $var = NULL, $ttl = 0)
*/
function apc_fetch($key)
{
- if (is_array($key))
- {
- $aResult = array();
- foreach($key as $sKey)
- {
+ if (is_array($key)) {
+ $aResult = [];
+ foreach ($key as $sKey) {
$aResult[$sKey] = apcFile::FetchOneFile($sKey);
}
+
return $aResult;
+ } elseif (is_null($key)) {
+ return false;
}
return apcFile::FetchOneFile($key);
}
@@ -246,21 +247,16 @@ class apcFile
*/
static public function StoreOneFile($sKey, $value, $iTTL)
{
- if (empty($sKey))
- {
+ if (empty($sKey)) {
return false;
}
-
- if (is_file(self::GetCacheFileName($sKey)))
- {
+ if (is_file(self::GetCacheFileName($sKey))) {
@unlink(self::GetCacheFileName($sKey));
}
- if (is_file(self::GetCacheFileName('-'.$sKey)))
- {
+ if (is_file(self::GetCacheFileName('-'.$sKey))) {
@unlink(self::GetCacheFileName('-'.$sKey));
}
- if ($iTTL > 0)
- {
+ if ($iTTL > 0) {
// hint for ttl management
$sKey = '-'.$sKey;
}
@@ -268,15 +264,14 @@ class apcFile
$sFilename = self::GetCacheFileName($sKey);
// try to create the folder
$sDirname = dirname($sFilename);
- if (!file_exists($sDirname))
- {
- if (!@mkdir($sDirname, 0755, true))
- {
+ if (!is_dir($sDirname)) {
+ if (!@mkdir($sDirname, 0755, true)) {
return false;
}
}
$bRes = !(@file_put_contents($sFilename, serialize($value), LOCK_EX) === false);
self::AddFile($sFilename);
+
return $bRes;
}
@@ -360,19 +355,15 @@ class apcFile
*/
static protected function ReadCacheLocked($sFilename)
{
- if (!is_file($sFilename))
- {
- return false;
- }
+ $sContent = false;
$file = @fopen($sFilename, 'r');
- if ($file === false)
- {
- return false;
+ if ($file !== false) {
+ if (flock($file, LOCK_SH)) {
+ $sContent = file_get_contents($sFilename);
+ flock($file, LOCK_UN);
+ }
+ fclose($file);
}
- flock($file, LOCK_SH);
- $sContent = @fread($file, @filesize($sFilename));
- flock($file, LOCK_UN);
- fclose($file);
return $sContent;
}
diff --git a/core/bulkchange.class.inc.php b/core/bulkchange.class.inc.php
index ada67eb26..2e4e60945 100644
--- a/core/bulkchange.class.inc.php
+++ b/core/bulkchange.class.inc.php
@@ -35,11 +35,37 @@ abstract class CellChangeSpec
return $this->m_proposedValue;
}
- public function GetDisplayableValue()
+ /**
+ * @throws \Exception
+ * @since 3.2.0
+ */
+ public function GetCLIValue(bool $bLocalizedValues = false): string
{
+ if (is_object($this->m_proposedValue)) {
+ if ($this->m_proposedValue instanceof ReportValue) {
+ return $this->m_proposedValue->GetAsCSV($bLocalizedValues, ',', '"');
+ }
+ throw new Exception('Unexpected class : '. get_class($this->m_proposedValue));
+ }
return $this->m_proposedValue;
}
+ /**
+ * @throws \Exception
+ * @since 3.2.0
+ */
+ public function GetHTMLValue(bool $bLocalizedValues = false): string
+ {
+ if (is_object($this->m_proposedValue)) {
+ if ($this->m_proposedValue instanceof ReportValue) {
+ return $this->m_proposedValue->GetAsHTML($bLocalizedValues);
+ }
+ throw new Exception('Unexpected class : '. get_class($this->m_proposedValue));
+ }
+ return utils::EscapeHtml($this->m_proposedValue);
+ }
+
+
/**
* @since 3.1.0 N°5305
*/
@@ -54,12 +80,12 @@ abstract class CellChangeSpec
}
/**
- * @since 3.1.0 N°5305
+ * @since 3.2.0
*/
- public function GetDisplayableValueAndDescription(): string
+ public function GetCLIValueAndDescription(): string
{
return sprintf("%s%s",
- $this->GetDisplayableValue(),
+ $this->GetCLIValue(),
$this->GetDescription()
);
}
@@ -108,13 +134,25 @@ class CellStatus_Issue extends CellStatus_Modify
parent::__construct($proposedValue, $previousValue);
}
- public function GetDisplayableValue()
+ public function GetCLIValue(bool $bLocalizedValues = false): string
+ {
+ if (is_null($this->m_proposedValue)) {
+ return Dict::Format('UI:CSVReport-Value-SetIssue');
+ }
+ return Dict::Format('UI:CSVReport-Value-ChangeIssue',$this->m_proposedValue);
+ }
+
+ public function GetHTMLValue(bool $bLocalizedValues = false): string
{
if (is_null($this->m_proposedValue))
{
return Dict::Format('UI:CSVReport-Value-SetIssue');
}
- return Dict::Format('UI:CSVReport-Value-ChangeIssue', \utils::EscapeHtml($this->m_proposedValue));
+ if ($this->m_proposedValue instanceof ReportValue)
+ {
+ return Dict::Format('UI:CSVReport-Value-ChangeIssue', $this->m_proposedValue->GetAsHTML($bLocalizedValues));
+ }
+ return Dict::Format('UI:CSVReport-Value-ChangeIssue',utils::EscapeHtml($this->m_proposedValue));
}
public function GetDescription()
@@ -122,12 +160,12 @@ class CellStatus_Issue extends CellStatus_Modify
return $this->m_sReason;
}
/*
- * @since 3.1.0 N°5305
+ * @since 3.2.0
*/
- public function GetDisplayableValueAndDescription(): string
+ public function GetCLIValueAndDescription(): string
{
return sprintf("%s. %s",
- $this->GetDisplayableValue(),
+ $this->GetCLIValue(),
$this->GetDescription()
);
}
@@ -172,7 +210,7 @@ class CellStatus_SearchIssue extends CellStatus_Issue
$this->sAllowedValuesSearch = $sAllowedValuesSearch;
}
- public function GetDisplayableValue()
+ public function GetCLIValue(bool $bLocalizedValues = false): string
{
if (null === $this->m_sReason) {
return Dict::Format('UI:CSVReport-Value-NoMatch', '');
@@ -181,6 +219,15 @@ class CellStatus_SearchIssue extends CellStatus_Issue
return $this->m_sReason;
}
+ public function GetHTMLValue(bool $bLocalizedValues = false): string
+ {
+ if (null === $this->m_sReason) {
+ return Dict::Format('UI:CSVReport-Value-NoMatch', '');
+ }
+
+ return utils::EscapeHtml($this->m_sReason);
+ }
+
public function GetDescription()
{
if (\utils::IsNullOrEmptyString($this->m_sAllowedValues) ||
@@ -227,6 +274,33 @@ class CellStatus_NullIssue extends CellStatus_Issue
}
}
+/**
+ * Class to differ formatting depending on the caller
+ */
+class ReportValue
+{
+ /**
+ * @param DBObject $oObject
+ * @param string $sAttCode
+ * @param bool $bOriginal
+ */
+ public function __construct(protected DBObject $oObject, protected string $sAttCode, protected bool $bOriginal){}
+
+ public function GetAsHTML(bool $bLocalizedValues)
+ {
+ if ($this->bOriginal) {
+ return $this->oObject->GetOriginalAsHTML($this->sAttCode, $bLocalizedValues);
+ }
+ return $this->oObject->GetAsHTML($this->sAttCode, $bLocalizedValues);
+ }
+ public function GetAsCSV (bool $bLocalizedValues, string $sCsvSep, string $sCsvDelimiter) {
+ if ($this->bOriginal) {
+ return $this->oObject->GetOriginalAsCSV($this->sAttCode, $sCsvSep, $sCsvDelimiter, $bLocalizedValues);
+ }
+ return $this->oObject->GetAsCSV($this->sAttCode, $sCsvSep, $sCsvDelimiter, $bLocalizedValues);
+ }
+}
+
class CellStatus_Ambiguous extends CellStatus_Issue
{
@@ -413,22 +487,6 @@ class BulkChange
$this->m_aExtKeysMappingCache = array();
}
- protected $m_bReportHtml = false;
- protected $m_sReportCsvSep = ',';
- protected $m_sReportCsvDelimiter = '"';
-
- public function SetReportHtml()
- {
- $this->m_bReportHtml = true;
- }
-
- public function SetReportCsv($sSeparator = ',', $sDelimiter = '"')
- {
- $this->m_bReportHtml = false;
- $this->m_sReportCsvSep = $sSeparator;
- $this->m_sReportCsvDelimiter = $sDelimiter;
- }
-
protected function ResolveExternalKey($aRowData, $sAttCode, &$aResults)
{
$oExtKey = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
@@ -446,7 +504,7 @@ class BulkChange
$value = $oForeignAtt->MakeValueFromString($aRowData[$iCol], $this->m_bLocalizedValues);
}
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
- $aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ $aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
$oExtObjects = new CMDBObjectSet($oReconFilter);
@@ -470,7 +528,7 @@ class BulkChange
}
/**
- * @param \DBObject $oTargetObj
+ * @param DBObject $oTargetObj
* @param array $aRowData
* @param array $aErrors
*
@@ -533,7 +591,7 @@ class BulkChange
}
$aCacheKeys[] = $value;
$oReconFilter->AddCondition($sReconKeyAttCode, $value, '=');
- $aResults[$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ $aResults[$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
$sCacheKey = implode('_|_', $aCacheKeys); // Unique key for this query...
$iForeignKey = null;
@@ -602,7 +660,7 @@ class BulkChange
foreach ($aReconKeys as $sReconKeyAttCode => $iCol)
{
// Report the change on reconciliation values as well
- $aResults[$iCol] = new CellStatus_Modify(utils::HtmlEntities($aRowData[$iCol]));
+ $aResults[$iCol] = new CellStatus_Modify($aRowData[$iCol]);
}
}
}
@@ -680,50 +738,32 @@ class BulkChange
// Reporting on fields
//
$aChangedFields = $oTargetObj->ListChanges();
- foreach ($this->m_aAttList as $sAttCode => $iCol)
- {
- if ($sAttCode == 'id')
- {
- $aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ foreach ($this->m_aAttList as $sAttCode => $iCol) {
+ if ($sAttCode == 'id') {
+ $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
}
- else
- {
- if ($this->m_bReportHtml)
- {
- $sCurValue = $oTargetObj->GetAsHTML($sAttCode, $this->m_bLocalizedValues);
- $sOrigValue = $oTargetObj->GetOriginalAsHTML($sAttCode, $this->m_bLocalizedValues);
+ else {
+ $sCurValue = new ReportValue($oTargetObj, $sAttCode, false);
+ $sOrigValue = new ReportValue($oTargetObj, $sAttCode, true);
+ if (isset($aErrors[$sAttCode])) {
+ $aResults[$iCol]= new CellStatus_Issue($aRowData[$iCol], $sOrigValue, $aErrors[$sAttCode]);
}
- else
- {
- $sCurValue = $oTargetObj->GetAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
- $sOrigValue = $oTargetObj->GetOriginalAsCSV($sAttCode, $this->m_sReportCsvSep, $this->m_sReportCsvDelimiter, $this->m_bLocalizedValues);
- }
- if (isset($aErrors[$sAttCode]))
- {
- $aResults[$iCol]= new CellStatus_Issue(utils::HtmlEntities($aRowData[$iCol]), $sOrigValue, $aErrors[$sAttCode]);
- }
- elseif (array_key_exists($sAttCode, $aChangedFields))
- {
- if ($oTargetObj->IsNew())
- {
+ elseif (array_key_exists($sAttCode, $aChangedFields)){
+ if ($oTargetObj->IsNew()) {
$aResults[$iCol]= new CellStatus_Void($sCurValue);
}
- else
- {
+ else {
$aResults[$iCol]= new CellStatus_Modify($sCurValue, $sOrigValue);
}
}
- else
- {
+ else {
// By default... nothing happens
$oAttDef = MetaModel::GetAttributeDef($this->m_sClass, $sAttCode);
- if ($oAttDef instanceof AttributeDateTime)
- {
+ if ($oAttDef instanceof AttributeDateTime) {
$aResults[$iCol]= new CellStatus_Void($oAttDef->GetFormat()->Format($aRowData[$iCol]));
}
- else
- {
- $aResults[$iCol]= new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ else {
+ $aResults[$iCol]= new CellStatus_Void($aRowData[$iCol]);
}
}
}
@@ -788,7 +828,7 @@ class BulkChange
// 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 */
+ /** @var DBObject $oVisibleObject */
$oVisibleObject = $oExtObjectSetWithCurrentUserPermissions->Fetch();
if (is_null($oVisibleObject)){
break;
@@ -1155,7 +1195,7 @@ class BulkChange
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);
+ $aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
}
else
@@ -1168,6 +1208,7 @@ class BulkChange
}
else
{
+ // almost impossible ti reproduce since even incorrect dates with correct formats are formated and $oDate will not be false
// Leave the cell unchanged
$aResult[$iRow]["__STATUS__"]= new RowStatus_Issue(Dict::S('UI:CSVReport-Row-Issue-DateFormat'));
$aResult[$iRow][$iCol] = new CellStatus_Issue($sValue, null, $sErrorMsg);
@@ -1307,7 +1348,7 @@ class BulkChange
{
if (!array_key_exists($iCol, $aResult[$iRow]))
{
- $aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
}
foreach($this->m_aExtKeys as $sAttCode => $aForeignAtts)
@@ -1321,7 +1362,7 @@ class BulkChange
if (!array_key_exists($iCol, $aResult[$iRow]))
{
// The foreign attribute is one of our reconciliation key
- $aResult[$iRow][$iCol] = new CellStatus_Void(utils::HtmlEntities($aRowData[$iCol]));
+ $aResult[$iRow][$iCol] = new CellStatus_Void($aRowData[$iCol]);
}
}
}
diff --git a/lib/composer/autoload_classmap.php b/lib/composer/autoload_classmap.php
index c5bc07a67..6e7dc505f 100644
--- a/lib/composer/autoload_classmap.php
+++ b/lib/composer/autoload_classmap.php
@@ -493,6 +493,7 @@ return array(
'Combodo\\iTop\\Service\\Events\\EventService' => $baseDir . '/sources/Service/Events/EventService.php',
'Combodo\\iTop\\Service\\Events\\EventServiceLog' => $baseDir . '/sources/Service/Events/EventServiceLog.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => $baseDir . '/sources/Service/Events/iEventServiceSetup.php',
+ 'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => $baseDir . '/sources/Service/Import/CSVImportPageProcessor.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => $baseDir . '/sources/Service/Links/LinkSetDataTransformer.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => $baseDir . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => $baseDir . '/sources/Service/Links/LinkSetRepository.php',
@@ -1489,6 +1490,7 @@ return array(
'RelationObjectNode' => $baseDir . '/core/relationgraph.class.inc.php',
'RelationRedundancyNode' => $baseDir . '/core/relationgraph.class.inc.php',
'RelationTypeIterator' => $baseDir . '/core/simplegraph.class.inc.php',
+ 'ReportValue' => $baseDir . '/core/bulkchange.class.inc.php',
'RestDelete' => $baseDir . '/core/restservices.class.inc.php',
'RestResult' => $baseDir . '/application/applicationextension.inc.php',
'RestResultWithObjects' => $baseDir . '/core/restservices.class.inc.php',
diff --git a/lib/composer/autoload_static.php b/lib/composer/autoload_static.php
index ce35cd24a..1841222f1 100644
--- a/lib/composer/autoload_static.php
+++ b/lib/composer/autoload_static.php
@@ -868,6 +868,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'Combodo\\iTop\\Service\\Events\\EventService' => __DIR__ . '/../..' . '/sources/Service/Events/EventService.php',
'Combodo\\iTop\\Service\\Events\\EventServiceLog' => __DIR__ . '/../..' . '/sources/Service/Events/EventServiceLog.php',
'Combodo\\iTop\\Service\\Events\\iEventServiceSetup' => __DIR__ . '/../..' . '/sources/Service/Events/iEventServiceSetup.php',
+ 'Combodo\\iTop\\Service\\Import\\CSVImportPageProcessor' => __DIR__ . '/../..' . '/sources/Service/Import/CSVImportPageProcessor.php',
'Combodo\\iTop\\Service\\Links\\LinkSetDataTransformer' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetDataTransformer.php',
'Combodo\\iTop\\Service\\Links\\LinkSetModel' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetModel.php',
'Combodo\\iTop\\Service\\Links\\LinkSetRepository' => __DIR__ . '/../..' . '/sources/Service/Links/LinkSetRepository.php',
@@ -1864,6 +1865,7 @@ class ComposerStaticInit7f81b4a2a468a061c306af5e447a9a9f
'RelationObjectNode' => __DIR__ . '/../..' . '/core/relationgraph.class.inc.php',
'RelationRedundancyNode' => __DIR__ . '/../..' . '/core/relationgraph.class.inc.php',
'RelationTypeIterator' => __DIR__ . '/../..' . '/core/simplegraph.class.inc.php',
+ 'ReportValue' => __DIR__ . '/../..' . '/core/bulkchange.class.inc.php',
'RestDelete' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
'RestResult' => __DIR__ . '/../..' . '/application/applicationextension.inc.php',
'RestResultWithObjects' => __DIR__ . '/../..' . '/core/restservices.class.inc.php',
diff --git a/pages/csvimport.php b/pages/csvimport.php
index d064b7291..b131087cc 100644
--- a/pages/csvimport.php
+++ b/pages/csvimport.php
@@ -4,11 +4,9 @@
* @license http://opensource.org/licenses/AGPL-3.0
*/
-use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\UI\Base\Component\Alert\AlertUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\CollapsibleSection\CollapsibleSectionUIBlockFactory;
-use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\FieldSet\FieldSetUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
@@ -31,6 +29,7 @@ use Combodo\iTop\Application\WebPage\AjaxPage;
use Combodo\iTop\Application\WebPage\ErrorPage;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\WebPage;
+use Combodo\iTop\Service\Import\CSVImportPageProcessor;
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
use Combodo\iTop\Renderer\BlockRenderer;
@@ -216,18 +215,6 @@ try {
$oP->AddSubBlock(AlertUIBlockFactory::MakeForInformation(MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Title:BulkSynchro_nbItem_ofClass_class', $iCount, MetaModel::GetName($sClass))));
}
- /**
- * Add a paragraph to the body of the page
- *
- * @param string $s_html
- * @param ?string $sLinkUrl
- *
- * @return string
- */
- function GetDivAlert($s_html)
- {
- return "
$s_html
\n";
- }
/**
* Process the CSV data, for real or as a simulation
* @param WebPage $oPage The page used to display the wizard
@@ -253,544 +240,25 @@ try {
$sSeparator = utils::ReadParam('separator', ',', false, 'raw_data');
$sTextQualifier = utils::ReadParam('text_qualifier', '"', false, 'raw_data');
$bHeaderLine = (utils::ReadParam('header_line', '0') == 1);
- $iSkippedLines = 0;
- if (utils::ReadParam('box_skiplines', '0') == 1) {
- $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0');
- }
+ $iNbSkippedLines = utils::ReadParam('nb_skipped_lines', '0');
+ $iBoxSkipLines = utils::ReadParam('box_skiplines', '0');
$aFieldsMapping = utils::ReadParam('field', array(), false, 'raw_data');
$aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name');
$iCurrentStep = $bSimulate ? 4 : 5;
$bAdvanced = utils::ReadParam('advanced', 0);
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
$sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data');
+ $aSynchroUpdate = utils::ReadParam('synchro_update', array());
$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);
- $sClassName = $oSearch->GetClass(); // If a synchronization scope is set, then the class is fixed !
- $oSet = new DBObjectSet($oSearch);
- $iCount = $oSet->Count();
- DisplaySynchroBanner($oPage, $sClassName, $iCount);
- $aSynchroUpdate = utils::ReadParam('synchro_update', array());
- }
- else
- {
- $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);
- $iRealSkippedLines = $iSkippedLines;
- if ($bHeaderLine)
- {
- $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)
- {
- $sSearchField = $aFieldsMapping[$index];
- $aMatches = array();
- if (preg_match('/(.+)->(.+)/', $sSearchField, $aMatches) > 0)
- {
- $sSearchField = $aMatches[1];
- $aSearchKeys[$aMatches[1]] = '';
- }
- else
- {
- $aSearchKeys[$sSearchField] = '';
- }
- if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField))
- {
- // Remove invalid or unmapped search fields
- $aSearchFields[$index] = null;
- unset($aSearchKeys[$sSearchField]);
- }
- }
-
- // Compute the list of fields and external keys to process
- $aExtKeys = array();
- $aAttributes = array();
- $aExternalKeysByColumn = array();
- foreach($aFieldsMapping as $iNumber => $sAttCode)
- {
- $iIndex = $iNumber-1;
- if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass'))
- {
- if (preg_match('/(.+)->(.+)/', $sAttCode, $aMatches) > 0)
- {
- $sAttribute = $aMatches[1];
- $sField = $aMatches[2];
- $aExtKeys[$sAttribute][$sField] = $iIndex;
- $aExternalKeysByColumn[$iIndex] = $sAttribute;
- }
- else
- {
- if ($sAttCode == 'id')
- {
- $aAttributes['id'] = $iIndex;
- }
- else
- {
- $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
- if ($oAttDef->IsExternalKey())
- {
- $aExtKeys[$sAttCode]['id'] = $iIndex;
- $aExternalKeysByColumn[$iIndex] = $sAttCode;
- }
- else
- {
- $aAttributes[$sAttCode] = $iIndex;
- }
- }
- }
- }
- }
-
- $oMyChange = null;
- if (!$bSimulate)
- {
- // We're doing it for real, let's create a change
- $sUserString = CMDBChange::GetCurrentUserName().' (CSV)';
- CMDBObject::SetCurrentChangeFromParams($sUserString, CMDBChangeOrigin::CSV_INTERACTIVE);
- $oMyChange = CMDBObject::GetCurrentChange();
- }
-
- $oBulk = new BulkChange(
- $sClassName,
- $aData,
- $aAttributes,
- $aExtKeys,
- array_keys($aSearchKeys),
- empty($sSynchroScope) ? null : $sSynchroScope,
- $aSynchroUpdate,
- $sChosenDateFormat, // date format
- true // localize
- );
- $oBulk->SetReportHtml();
-
- $oPage->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", $sCSVDataTruncated, "csvdata_truncated"));
- $aRes = $oBulk->Process($oMyChange);
-
- $aColumns = [];
- $aColumns ["line"] = ["label" => "Line"];
- $aColumns ["status"] = ["label" => "Status"];
- $aColumns ["object"] = ["label" => "Object"];
- foreach ($aFieldsMapping as $iNumber => $sAttCode) {
- if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) {
- $aColumns[$sClassName.'/'.$sAttCode] = ["label" => MetaModel::GetLabel($sClassName, $sAttCode)];
- }
- }
- $aColumns["message"] = ["label" => "Message"];
-
- $iErrors = 0;
- $iCreated = 0;
- $iModified = 0;
- $iUnchanged = 0;
-
- $aTableData = [];
- $sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
-
- foreach ($aRes as $iLine => $aResRow) {
- $aTableRow = [];
- $oStatus = $aResRow['__STATUS__'];
- $sUrl = '';
- $sMessage = '';
- $sCSSRowClass = '';
- $sCSSMessageClass = 'cell_ok';
- switch (get_class($oStatus)) {
- case 'RowStatus_NoChange':
- $iUnchanged++;
- $sFinalClass = $aResRow['finalclass'];
- $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
- $sUrl = $oObj->GetHyperlink();
- $sStatus = '
';
- $sCSSRowClass = 'ibo-csv-import--row-unchanged';
- break;
-
- case 'RowStatus_Modify':
- $iModified++;
- $sFinalClass = $aResRow['finalclass'];
- $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
- $sUrl = $oObj->GetHyperlink();
- $sStatus = '
';
- $sCSSRowClass = 'ibo-csv-import--row-modified';
- break;
-
- case 'RowStatus_Disappeared':
- $iModified++;
- $sFinalClass = $aResRow['finalclass'];
- $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
- $sUrl = $oObj->GetHyperlink();
- $sStatus = '
';
- $sCSSRowClass = 'ibo-csv-import--row-modified';
- if ($bSimulate) {
- $sMessage = Dict::S('UI:CSVReport-Object-MissingToUpdate');
- } else {
- $sMessage = Dict::S('UI:CSVReport-Object-MissingUpdated');
- }
- break;
-
- case 'RowStatus_NewObj':
- $iCreated++;
- $sStatus = '
';
- $sCSSRowClass = 'ibo-csv-import--row-added';
- if ($bSimulate) {
- $sMessage = Dict::S('UI:CSVReport-Object-ToCreate');
- } else {
- $sFinalClass = $aResRow['finalclass'];
- $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
- $sUrl = $oObj->GetHyperlink();
- $sMessage = Dict::S('UI:CSVReport-Object-Created');
- }
- break;
-
- case 'RowStatus_Issue':
- $iErrors++;
- $sMessage .= GetDivAlert($oStatus->GetDescription());
- $sStatus = '
';//translate
- $sCSSMessageClass = 'ibo-csv-import--cell-error';
- $sCSSRowClass = 'ibo-csv-import--row-error';
- if (array_key_exists($iLine, $aData)) {
- $aRow = $aData[$iLine];
- $aResult[] = $sTextQualifier.implode($sTextQualifier.$sSeparator.$sTextQualifier, $aRow).$sTextQualifier; // Remove the first line and store it in case of error
- }
- break;
- }
- $aTableRow['@class'] = $sCSSRowClass;
- $aTableRow['line'] = sprintf("%0{$sMaxLen}d", 1 + $iLine + $iRealSkippedLines);
- $aTableRow['status'] = $sStatus;
- $aTableRow['object'] = $sUrl;
-
- foreach ($aFieldsMapping as $iNumber => $sAttCode) {
- if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) {
- $oCellStatus = $aResRow[$iNumber - 1];
- $sCellMessage = '';
- if (isset($aExternalKeysByColumn[$iNumber - 1])) {
- $sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
- $oExtKeyCellStatus = $aResRow[$sExtKeyName];
- $oExtKeyCellStatus->SetDisplayableValue($oCellStatus->GetDisplayableValue());
- $oCellStatus = $oExtKeyCellStatus;
- }
- $sHtmlValue = $oCellStatus->GetDisplayableValue();
- switch (get_class($oCellStatus)) {
- case 'CellStatus_Issue':
- case 'CellStatus_NullIssue':
- $sCellMessage .= GetDivAlert($oCellStatus->GetDescription());
- $aTableRow[$sClassName.'/'.$sAttCode] = ''.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'
';
- break;
-
- case 'CellStatus_SearchIssue':
- $sMessage = Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue);
- $sDivAlert = GetDivAlert($oCellStatus->GetDescription());
- $sAllowedValuesLinkUrl = $oCellStatus->GetAllowedValuesLinkUrl();
- $sAllowedValuesLinkLabel = Dict::S('UI:CSVImport:ViewAllPossibleValues');
- $aTableRow[$sClassName.'/'.$sAttCode] =
- <<
- $sMessage
- $sDivAlert
- $sAllowedValuesLinkLabel
-
-HTML;
- break;
-
- case 'CellStatus_Ambiguous':
- $sMessage = Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue);
- $sDivAlert = GetDivAlert($oCellStatus->GetDescription());
- $sSearchLinkUrl = $oCellStatus->GetSearchLinkUrl();
- $sSearchLinkLabel = Dict::S('UI:CSVImport:ViewAllAmbiguousValues');
- $aTableRow[$sClassName.'/'.$sAttCode] =
- <<
- $sMessage
- $sDivAlert
- $sSearchLinkLabel
-
-HTML;
- break;
-
- case 'CellStatus_Modify':
- $aTableRow[$sClassName.'/'.$sAttCode] = ''.$sHtmlValue.'
';
- break;
-
- default:
- $aTableRow[$sClassName.'/'.$sAttCode] = $sHtmlValue.$sCellMessage;
- }
- }
- }
- $aTableRow['message'] = "$sMessage
";
-
- $aTableData[] = $aTableRow;
- }
-
- $iUnchanged = count($aRes) - $iErrors - $iModified - $iCreated;
- $oContainer = UIContentBlockUIBlockFactory::MakeStandard();
- $oContainer->AddCSSClass("wizContainer");
- $oPage->AddSubBlock($oContainer);
-
- $oForm = FormUIBlockFactory::MakeStandard('wizForm');
- $oContainer->AddSubBlock($oForm);
-
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("transaction_id", utils::GetNewTransactionId()));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("step", ($iCurrentStep + 1)));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("separator", $sSeparator));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("text_qualifier", $sTextQualifier));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("header_line", $bHeaderLine));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("nb_skipped_lines", utils::ReadParam('nb_skipped_lines', '0')));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("box_skiplines", utils::ReadParam('box_skiplines', '0')));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", $sCSVDataTruncated, "csvdata_truncated"));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata", $sCSVData));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("encoding", $sEncoding));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("synchro_scope", $sSynchroScope));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("class_name", $sClassName));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("advanced", $bAdvanced));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("date_time_format", $sDateTimeFormat));
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("custom_date_time_format", $sCustomDateTimeFormat));
-
- if (!empty($sSynchroScope)) {
- foreach ($aSynchroUpdate as $sKey => $value) {
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("synchro_update[$sKey]", $value));
- }
- }
- foreach ($aFieldsMapping as $iNumber => $sAttCode) {
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("field[$iNumber]", $sAttCode));
- }
- foreach ($aSearchFields as $index => $sDummy) {
- $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("search_field[$index]", "1"));
- }
- $aDisplayFilters = array();
- if ($bSimulate) {
- $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsWillStayUnchanged');
- $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWillBeModified');
- $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWillBeAdded');
- $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsWillHaveErrors');
- } else {
- $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsRemainedUnchanged');
- $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWereModified');
- $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWereAdded');
- $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsHadErrors');
- }
- $oMulticolumn = MultiColumnUIBlockFactory::MakeStandard();
- $oMulticolumn->AddCSSClass('ml-1');
- $oForm->AddSubBlock($oMulticolumn);
-
- $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
'.sprintf($aDisplayFilters['unchanged'], $iUnchanged), '', "1", "show_unchanged", "checkbox");
- $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
- $oCheckBoxUnchanged->SetBeforeInput(false);
- $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
- $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
- $oPage->add_ready_script("$('#show_unchanged').on('click', function(){ToggleRows('ibo-csv-import--row-unchanged')})");
-
- $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
'.sprintf($aDisplayFilters['modified'], $iModified), '', "1", "show_modified", "checkbox");
- $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
- $oCheckBoxUnchanged->SetBeforeInput(false);
- $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
- $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
- $oPage->add_ready_script("$('#show_modified').on('click', function(){ToggleRows('ibo-csv-import--row-modified')})");
-
- $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
'.sprintf($aDisplayFilters['added'], $iCreated), '', "1", "show_created", "checkbox");
- $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
- $oCheckBoxUnchanged->SetBeforeInput(false);
- $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
- $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
- $oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
-
- $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' '.sprintf($aDisplayFilters['errors'], $iErrors) . '', '', "1", "show_errors", "checkbox");
- $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
- $oCheckBoxUnchanged->SetBeforeInput(false);
- $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
- $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
- $oPage->add_ready_script("$('#show_errors').on('click', function(){ToggleRows('ibo-csv-import--row-error')})");
-
- $oPanel = PanelUIBlockFactory::MakeNeutral('');
- $oPanel->AddCSSClasses(['ibo-datatable-panel', 'mb-5']);
- $oForm->AddSubBlock($oPanel);
-
- $oTable = DataTableUIBlockFactory::MakeForForm("csvImport", $aColumns, $aTableData);
- $oTable->AddOption('bFullscreen', true);
- $oPanel->AddSubBlock($oTable);
-
-
- if ($bSimulate) {
- $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Restart'))->SetOnClickJsCode("CSVRestart()"));
- }
- $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Back'))->SetOnClickJsCode("CSVGoBack()"));
-
- $bShouldConfirm = false;
- if ($bSimulate) {
- // if there are *too many* changes, we should ask the user for a confirmation
- if (count($aRes) >= MetaModel::GetConfig()->Get('csv_import_min_object_confirmation')) {
- $fErrorsPercentage = (100.0 * $iErrors) / count($aRes);
- if ($fErrorsPercentage >= MetaModel::GetConfig()->Get('csv_import_errors_percentage')) {
- $sMessage = Dict::Format('UI:CSVReport-Stats-Errors', $fErrorsPercentage);
- $bShouldConfirm = true;
- }
- $fCreatedPercentage = (100.0 * $iCreated) / count($aRes);
- if ($fCreatedPercentage >= MetaModel::GetConfig()->Get('csv_import_creations_percentage')) {
- $sMessage = Dict::Format('UI:CSVReport-Stats-Created', $fCreatedPercentage);
- $bShouldConfirm = true;
- }
- $fModifiedPercentage = (100.0 * $iModified) / count($aRes);
- if ($fModifiedPercentage >= MetaModel::GetConfig()->Get('csv_import_modifications_percentage')) {
- $sMessage = Dict::Format('UI:CSVReport-Stats-Modified', $fModifiedPercentage);
- $bShouldConfirm = true;
- }
-
- }
- $sConfirm = $bShouldConfirm ? 'true' : 'false';
- $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:DoImport'))->SetOnClickJsCode("return DoSubmit({$sConfirm})"));
-
- } else {
- $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Done'), "", "", true));
- }
-
- if ($bShouldConfirm) {
- $sYesButton = Dict::S('UI:Button:Ok');
- $sNoButton = Dict::S('UI:Button:Cancel');
- $oDlg = UIContentBlockUIBlockFactory::MakeStandard("dlg_confirmation")->SetHasForcedDiv(true);
- $oPage->AddSubBlock($oDlg);
- $oDlg->AddSubBlock(new Html($sMessage));
- $oDlg->AddSubBlock(new Html(utils::EscapeHtml(Dict::S('UI:CSVImportConfirmMessage'))));
-
- $oDlgConfirm = UIContentBlockUIBlockFactory::MakeStandard("confirmation_chart")->SetHasForcedDiv(true);
- $oDlg->AddSubBlock($oDlgConfirm);
-
- $sDlgTitle = Dict::S('UI:CSVImportConfirmTitle');
-
- $oPage->add_ready_script(
- <<add_script(
-<<< EOF
-function CSVGoBack()
-{
- $('input[name=step]').val($iCurrentStep-1);
- $('#wizForm').submit();
-
-}
-
-function CSVRestart()
-{
- $('input[name=step]').val(1);
- $('#wizForm').submit();
-
-}
-
-function ToggleRows(sCSSClass)
-{
- $('.'+sCSSClass).toggle();
-}
-
-function DoSubmit(bConfirm)
-{
- if (bConfirm) //Ask for a confirmation
- {
- $('#dlg_confirmation').dialog('open');
-
- var chart = c3.generate({
- bindto: '#confirmation_chart',
- data: {
- columns: [
- ['errors', $iErrors],
- ['created', $iCreated],
- ['modified', $iModified],
- ['unchanged', $iUnchanged]
- ],
- colors: {
- errors: '#FF6666',
- created: '#66FF66',
- modified: '#6666FF',
- unchanged: '#666666'
- },
- names: {
- errors: $sErrors,
- created: $sCreated,
- modified: $sModified,
- unchanged: $sUnchanged
- },
- type: 'donut'
- },
- legend: {
- show: true,
- }
- });
- }
- else
- {
- // Submit the form
- $('#wizForm').block();
- $('#wizForm').submit();
- }
- return false;
-}
-
-function CancelImport()
-{
- $('#dlg_confirmation').dialog('close');
-}
-
-function RunImport()
-{
- $('#dlg_confirmation').dialog('close');
- // Submit the form
- $('#wizForm').block();
- $('#wizForm').submit();
-}
-EOF
- );
- if ($iErrors > 0)
- {
- return $aResult;
- }
- else
- {
- return null;
- }
+ return CSVImportPageProcessor::ProcessData($iBoxSkipLines, $iNbSkippedLines, $sDateTimeFormat, $sCustomDateTimeFormat, $sClassName, $oPage, $aSynchroUpdate, $sCSVData, $sSeparator, $sTextQualifier, $bHeaderLine, $aResult, $aSearchFields, $aFieldsMapping, $bSimulate, $sCSVDataTruncated,
+ $iCurrentStep, $sEncoding,
+ $bAdvanced, $sSynchroScope);
}
+
+
/**
* Perform the actual load of the CSV data and display the results
* @param WebPage $oPage The web page to display the wizard
diff --git a/sources/Service/Import/CSVImportPageProcessor.php b/sources/Service/Import/CSVImportPageProcessor.php
new file mode 100644
index 000000000..0089a110f
--- /dev/null
+++ b/sources/Service/Import/CSVImportPageProcessor.php
@@ -0,0 +1,610 @@
+GetClass(); // If a synchronization scope is set, then the class is fixed !
+ $oSet = new DBObjectSet($oSearch);
+ $iCount = $oSet->Count();
+ DisplaySynchroBanner($oPage, $sClassName, $iCount);
+ } else {
+ $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);
+ $iRealSkippedLines = $iSkippedLines;
+ if ($bHeaderLine) {
+ $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 = [];
+ foreach ($aSearchFields as $index => $sDummy) {
+ $sSearchField = $aFieldsMapping[$index];
+ $aMatches = [];
+ if (preg_match('/(.+)->(.+)/', $sSearchField, $aMatches) > 0) {
+ $sSearchField = $aMatches[1];
+ $aSearchKeys[$aMatches[1]] = '';
+ } else {
+ $aSearchKeys[$sSearchField] = '';
+ }
+ if (!MetaModel::IsValidFilterCode($sClassName, $sSearchField)) {
+ // Remove invalid or unmapped search fields
+ $aSearchFields[$index] = null;
+ unset($aSearchKeys[$sSearchField]);
+ }
+ }
+
+ // Compute the list of fields and external keys to process
+ $aExtKeys = [];
+ $aAttributes = [];
+ $aExternalKeysByColumn = [];
+ foreach ($aFieldsMapping as $iNumber => $sAttCode) {
+ $iIndex = $iNumber - 1;
+ if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) {
+ if (preg_match('/(.+)->(.+)/', $sAttCode, $aMatches) > 0) {
+ $sAttribute = $aMatches[1];
+ $sField = $aMatches[2];
+ $aExtKeys[$sAttribute][$sField] = $iIndex;
+ $aExternalKeysByColumn[$iIndex] = $sAttribute;
+ } else {
+ if ($sAttCode == 'id') {
+ $aAttributes['id'] = $iIndex;
+ } else {
+ $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
+ if ($oAttDef->IsExternalKey()) {
+ $aExtKeys[$sAttCode]['id'] = $iIndex;
+ $aExternalKeysByColumn[$iIndex] = $sAttCode;
+ } else {
+ $aAttributes[$sAttCode] = $iIndex;
+ }
+ }
+ }
+ }
+ }
+
+ $oMyChange = null;
+ if (!$bSimulate) {
+ // We're doing it for real, let's create a change
+ $sUserString = CMDBChange::GetCurrentUserName() . ' (CSV)';
+ CMDBObject::SetCurrentChangeFromParams($sUserString, CMDBChangeOrigin::CSV_INTERACTIVE);
+ $oMyChange = CMDBObject::GetCurrentChange();
+ }
+
+ $oBulk = new BulkChange(
+ $sClassName,
+ $aData,
+ $aAttributes,
+ $aExtKeys,
+ array_keys($aSearchKeys),
+ empty($sSynchroScope) ? null : $sSynchroScope,
+ $aSynchroUpdate,
+ $sChosenDateFormat, // date format
+ true // localize
+ );
+
+ $oPage->AddSubBlock(InputUIBlockFactory::MakeForHidden("csvdata_truncated", $sCSVDataTruncated, "csvdata_truncated"));
+ $aRes = $oBulk->Process($oMyChange);
+
+ $aColumns = [];
+ $aColumns ["line"] = ["label" => "Line"];
+ $aColumns ["status"] = ["label" => "Status"];
+ $aColumns ["object"] = ["label" => "Object"];
+ foreach ($aFieldsMapping as $sAttCode) {
+ if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) {
+ $aColumns[$sClassName . '/' . $sAttCode] = ["label" => MetaModel::GetLabel($sClassName, $sAttCode)];
+ }
+ }
+ $aColumns["message"] = ["label" => "Message"];
+
+ $iErrors = 0;
+ $iCreated = 0;
+ $iModified = 0;
+
+ $aTableData = [];
+ $sAppRootUrl = utils::GetAbsoluteUrlAppRoot();
+
+ foreach ($aRes as $iLine => $aResRow) {
+ /** @var string[]|CellChangeSpec[] $aResRow */
+ $aTableRow = [];
+ $oStatus = $aResRow['__STATUS__'];
+ $sUrl = '';
+ $sMessage = '';
+ $sCSSRowClass = '';
+ $sCSSMessageClass = 'cell_ok';
+ switch (get_class($oStatus)) {
+ case 'RowStatus_NoChange':
+ $sFinalClass = $aResRow['finalclass'];
+ $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
+ $sUrl = $oObj->GetHyperlink();
+ $sStatus = '
';
+ $sCSSRowClass = 'ibo-csv-import--row-unchanged';
+ break;
+
+ case 'RowStatus_Modify':
+ $iModified++;
+ $sFinalClass = $aResRow['finalclass'];
+ $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
+ $sUrl = $oObj->GetHyperlink();
+ $sStatus = '
';
+ $sCSSRowClass = 'ibo-csv-import--row-modified';
+ break;
+
+ case 'RowStatus_Disappeared':
+ $iModified++;
+ $sFinalClass = $aResRow['finalclass'];
+ $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
+ $sUrl = $oObj->GetHyperlink();
+ $sStatus = '
';
+ $sCSSRowClass = 'ibo-csv-import--row-modified';
+ if ($bSimulate) {
+ $sMessage = Dict::S('UI:CSVReport-Object-MissingToUpdate');
+ } else {
+ $sMessage = Dict::S('UI:CSVReport-Object-MissingUpdated');
+ }
+ break;
+
+ case 'RowStatus_NewObj':
+ $iCreated++;
+ $sStatus = '
';
+ $sCSSRowClass = 'ibo-csv-import--row-added';
+ if ($bSimulate) {
+ $sMessage = Dict::S('UI:CSVReport-Object-ToCreate');
+ } else {
+ $sFinalClass = $aResRow['finalclass'];
+ $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue());
+ $sUrl = $oObj->GetHyperlink();
+ $sMessage = Dict::S('UI:CSVReport-Object-Created');
+ }
+ break;
+
+ case 'RowStatus_Issue':
+ $iErrors++;
+ $sMessage = self::GetDivAlert($oStatus->GetDescription());
+ $sStatus = '
';//translate
+ $sCSSMessageClass = 'ibo-csv-import--cell-error';
+ $sCSSRowClass = 'ibo-csv-import--row-error';
+ if (array_key_exists($iLine, $aData)) {
+ $aRow = $aData[$iLine];
+ $aResult[] = $sTextQualifier . implode($sTextQualifier . $sSeparator . $sTextQualifier, $aRow) . $sTextQualifier; // Remove the first line and store it in case of error
+ }
+ break;
+ }
+ $aTableRow['@class'] = $sCSSRowClass;
+ $aTableRow['line'] = sprintf("%0{$sMaxLen}d", 1 + $iLine + $iRealSkippedLines);
+ $aTableRow['status'] = $sStatus;
+ $aTableRow['object'] = $sUrl;
+
+ foreach ($aFieldsMapping as $iNumber => $sAttCode) {
+ if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) {
+ $oCellStatus = $aResRow[$iNumber - 1];
+ $sCellMessage = '';
+ if (isset($aExternalKeysByColumn[$iNumber - 1])) {
+ $sExtKeyName = $aExternalKeysByColumn[$iNumber - 1];
+ $oExtKeyCellStatus = $aResRow[$sExtKeyName];
+ $oExtKeyCellStatus->SetDisplayableValue($oCellStatus->GetCLIValue());
+ $oCellStatus = $oExtKeyCellStatus;
+ }
+ $sHtmlValue = $oCellStatus->GetHTMLValue();
+ switch (get_class($oCellStatus)) {
+ case 'CellStatus_Issue':
+ case 'CellStatus_NullIssue':
+ $sCellMessage .= self::GetDivAlert($oCellStatus->GetDescription());
+ $aTableRow[$sClassName . '/' . $sAttCode] = '' . Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue) . $sCellMessage . '
';
+ break;
+
+ case 'CellStatus_SearchIssue':
+ $sMessage = Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue);
+ $sDivAlert = self::GetDivAlert($oCellStatus->GetDescription());
+ $sAllowedValuesLinkUrl = $oCellStatus->GetAllowedValuesLinkUrl();
+ $sAllowedValuesLinkLabel = Dict::S('UI:CSVImport:ViewAllPossibleValues');
+ $aTableRow[$sClassName . '/' . $sAttCode] =
+ <<
+ $sMessage
+ $sDivAlert
+ $sAllowedValuesLinkLabel
+
+HTML;
+ break;
+
+ case 'CellStatus_Ambiguous':
+ $sMessage = Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue);
+ $sDivAlert = self::GetDivAlert($oCellStatus->GetDescription());
+ $sSearchLinkUrl = $oCellStatus->GetSearchLinkUrl();
+ $sSearchLinkLabel = Dict::S('UI:CSVImport:ViewAllAmbiguousValues');
+ $aTableRow[$sClassName . '/' . $sAttCode] =
+ <<
+ $sMessage
+ $sDivAlert
+ $sSearchLinkLabel
+
+HTML;
+ break;
+
+ case 'CellStatus_Modify':
+ $aTableRow[$sClassName . '/' . $sAttCode] = '' . $sHtmlValue . '
';
+ break;
+
+ default:
+ $aTableRow[$sClassName . '/' . $sAttCode] = $sHtmlValue . $sCellMessage;
+ }
+ }
+ }
+ $aTableRow['message'] = "$sMessage
";
+
+ $aTableData[] = $aTableRow;
+ }
+
+ $iUnchanged = count($aRes) - $iErrors - $iModified - $iCreated;
+ $oContainer = UIContentBlockUIBlockFactory::MakeStandard();
+ $oContainer->AddCSSClass("wizContainer");
+ $oPage->AddSubBlock($oContainer);
+
+ $oForm = FormUIBlockFactory::MakeStandard('wizForm');
+ $oContainer->AddSubBlock($oForm);
+
+ self::addHiddenInputToForm($oForm, "transaction_id", utils::GetNewTransactionId());
+ self::addHiddenInputToForm($oForm, "step", ($iCurrentStep + 1));
+ self::addHiddenInputToForm($oForm, "separator", $sSeparator);
+ self::addHiddenInputToForm($oForm, "text_qualifier", $sTextQualifier);
+ self::addHiddenInputToForm($oForm, "header_line", $bHeaderLine);
+ self::addHiddenInputToForm($oForm, "nb_skipped_lines", $iNbSkippedLines);
+ self::addHiddenInputToForm($oForm, "box_skiplines", $iBoxSkipLines);
+ self::addHiddenInputToForm($oForm, "csvdata_truncated", $sCSVDataTruncated, "csvdata_truncated");
+ self::addHiddenInputToForm($oForm, "csvdata", $sCSVData);
+ self::addHiddenInputToForm($oForm, "encoding", $sEncoding);
+ self::addHiddenInputToForm($oForm, "synchro_scope", $sSynchroScope);
+ self::addHiddenInputToForm($oForm, "class_name", $sClassName);
+ self::addHiddenInputToForm($oForm, "advanced", $bAdvanced);
+ self::addHiddenInputToForm($oForm, "date_time_format", $sDateTimeFormat);
+ self::addHiddenInputToForm($oForm, "custom_date_time_format", $sCustomDateTimeFormat);
+ if (!empty($sSynchroScope)) {
+ foreach ($aSynchroUpdate as $sKey => $value) {
+ $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("synchro_update[$sKey]", $value));
+ }
+ }
+ foreach ($aFieldsMapping as $iNumber => $sAttCode) {
+ $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("field[$iNumber]", $sAttCode));
+ }
+ foreach ($aSearchFields as $index => $sDummy) {
+ $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("search_field[$index]", "1"));
+ }
+ $aDisplayFilters = [];
+ if ($bSimulate) {
+ $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsWillStayUnchanged');
+ $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWillBeModified');
+ $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWillBeAdded');
+ $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsWillHaveErrors');
+ } else {
+ $aDisplayFilters['unchanged'] = Dict::S('UI:CSVImport:ObjectsRemainedUnchanged');
+ $aDisplayFilters['modified'] = Dict::S('UI:CSVImport:ObjectsWereModified');
+ $aDisplayFilters['added'] = Dict::S('UI:CSVImport:ObjectsWereAdded');
+ $aDisplayFilters['errors'] = Dict::S('UI:CSVImport:ObjectsHadErrors');
+ }
+ $oMulticolumn = MultiColumnUIBlockFactory::MakeStandard();
+ $oMulticolumn->AddCSSClass('ml-1');
+ $oForm->AddSubBlock($oMulticolumn);
+
+ $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
' . sprintf($aDisplayFilters['unchanged'], $iUnchanged), '', "1", "show_unchanged", "checkbox");
+ $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
+ $oCheckBoxUnchanged->SetBeforeInput(false);
+ $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
+ $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
+ $oPage->add_ready_script("$('#show_unchanged').on('click', function(){ToggleRows('ibo-csv-import--row-unchanged')})");
+
+ $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
' . sprintf($aDisplayFilters['modified'], $iModified), '', "1", "show_modified", "checkbox");
+ $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
+ $oCheckBoxUnchanged->SetBeforeInput(false);
+ $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
+ $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
+ $oPage->add_ready_script("$('#show_modified').on('click', function(){ToggleRows('ibo-csv-import--row-modified')})");
+
+ $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel('
' . sprintf($aDisplayFilters['added'], $iCreated), '', "1", "show_created", "checkbox");
+ $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
+ $oCheckBoxUnchanged->SetBeforeInput(false);
+ $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
+ $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
+ $oPage->add_ready_script("$('#show_created').on('click', function(){ToggleRows('ibo-csv-import--row-added')})");
+
+ $oCheckBoxUnchanged = InputUIBlockFactory::MakeForInputWithLabel(' ' . sprintf($aDisplayFilters['errors'], $iErrors) . '', '', "1", "show_errors", "checkbox");
+ $oCheckBoxUnchanged->GetInput()->SetIsChecked(true);
+ $oCheckBoxUnchanged->SetBeforeInput(false);
+ $oCheckBoxUnchanged->GetInput()->AddCSSClass('ibo-input-checkbox');
+ $oMulticolumn->AddColumn(ColumnUIBlockFactory::MakeForBlock($oCheckBoxUnchanged));
+ $oPage->add_ready_script("$('#show_errors').on('click', function(){ToggleRows('ibo-csv-import--row-error')})");
+
+ $oPanel = PanelUIBlockFactory::MakeNeutral('');
+ $oPanel->AddCSSClasses(['ibo-datatable-panel', 'mb-5']);
+ $oForm->AddSubBlock($oPanel);
+
+ $oTable = DataTableUIBlockFactory::MakeForForm("csvImport", $aColumns, $aTableData);
+ $oTable->AddOption('bFullscreen', true);
+ $oPanel->AddSubBlock($oTable);
+
+
+ if ($bSimulate) {
+ $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForCancel(Dict::S('UI:Button:Restart'))->SetOnClickJsCode("CSVRestart()"));
+ }
+ $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForSecondaryAction(Dict::S('UI:Button:Back'))->SetOnClickJsCode("CSVGoBack()"));
+
+ $bShouldConfirm = false;
+ if ($bSimulate) {
+ // if there are *too many* changes, we should ask the user for a confirmation
+ if (count($aRes) >= MetaModel::GetConfig()->Get('csv_import_min_object_confirmation')) {
+ $fErrorsPercentage = (100.0 * $iErrors) / count($aRes);
+ if ($fErrorsPercentage >= MetaModel::GetConfig()->Get('csv_import_errors_percentage')) {
+ $sConfirmationMessage = Dict::Format('UI:CSVReport-Stats-Errors', $fErrorsPercentage);
+ $bShouldConfirm = true;
+ }
+ $fCreatedPercentage = (100.0 * $iCreated) / count($aRes);
+ if ($fCreatedPercentage >= MetaModel::GetConfig()->Get('csv_import_creations_percentage')) {
+ $sConfirmationMessage = Dict::Format('UI:CSVReport-Stats-Created', $fCreatedPercentage);
+ $bShouldConfirm = true;
+ }
+ $fModifiedPercentage = (100.0 * $iModified) / count($aRes);
+ if ($fModifiedPercentage >= MetaModel::GetConfig()->Get('csv_import_modifications_percentage')) {
+ $sConfirmationMessage = Dict::Format('UI:CSVReport-Stats-Modified', $fModifiedPercentage);
+ $bShouldConfirm = true;
+ }
+
+ }
+ $sConfirm = $bShouldConfirm ? 'true' : 'false';
+ $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:DoImport'))->SetOnClickJsCode("return DoSubmit($sConfirm)"));
+
+ } else {
+ $oForm->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction(Dict::S('UI:Button:Done'), "", "", true));
+ }
+
+ if ($bShouldConfirm) {
+ $sYesButton = Dict::S('UI:Button:Ok');
+ $sNoButton = Dict::S('UI:Button:Cancel');
+ $oDlg = UIContentBlockUIBlockFactory::MakeStandard("dlg_confirmation")->SetHasForcedDiv(true);
+ $oPage->AddSubBlock($oDlg);
+ $oDlg->AddSubBlock(new Html($sConfirmationMessage));
+ $oDlg->AddSubBlock(new Html(utils::EscapeHtml(Dict::S('UI:CSVImportConfirmMessage'))));
+
+ $oDlgConfirm = UIContentBlockUIBlockFactory::MakeStandard("confirmation_chart")->SetHasForcedDiv(true);
+ $oDlg->AddSubBlock($oDlgConfirm);
+
+ $sDlgTitle = Dict::S('UI:CSVImportConfirmTitle');
+
+ $oPage->add_ready_script(
+ <<add_script(
+ <<< EOF
+function CSVGoBack()
+{
+ $('input[name=step]').val($iCurrentStep-1);
+ $('#wizForm').submit();
+
+}
+
+function CSVRestart()
+{
+ $('input[name=step]').val(1);
+ $('#wizForm').submit();
+
+}
+
+function ToggleRows(sCSSClass)
+{
+ $('.'+sCSSClass).toggle();
+}
+
+function DoSubmit(bConfirm)
+{
+ if (bConfirm) //Ask for a confirmation
+ {
+ $('#dlg_confirmation').dialog('open');
+
+ var chart = c3.generate({
+ bindto: '#confirmation_chart',
+ data: {
+ columns: [
+ ['errors', $iErrors],
+ ['created', $iCreated],
+ ['modified', $iModified],
+ ['unchanged', $iUnchanged]
+ ],
+ colors: {
+ errors: '#FF6666',
+ created: '#66FF66',
+ modified: '#6666FF',
+ unchanged: '#666666'
+ },
+ names: {
+ errors: $sErrors,
+ created: $sCreated,
+ modified: $sModified,
+ unchanged: $sUnchanged
+ },
+ type: 'donut'
+ },
+ legend: {
+ show: true,
+ }
+ });
+ }
+ else
+ {
+ // Submit the form
+ $('#wizForm').block();
+ $('#wizForm').submit();
+ }
+ return false;
+}
+
+function CancelImport()
+{
+ $('#dlg_confirmation').dialog('close');
+}
+
+function RunImport()
+{
+ $('#dlg_confirmation').dialog('close');
+ // Submit the form
+ $('#wizForm').block();
+ $('#wizForm').submit();
+}
+EOF
+ );
+ if ($iErrors > 0) {
+ return $aResult;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @param string $message
+ *
+ * @return string
+ */
+ private static function GetDivAlert(string $message): string
+ {
+ return "$message
\n";
+ }
+
+ /**
+ * @param Form $oForm
+ * @param string $name
+ * @param mixed $value
+ * @param null $id
+ *
+ * @return void
+ */
+ public static function AddHiddenInputToForm(Form $oForm, string $name, mixed $value, $id = null): void
+ {
+ $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($name, (string)$value, $id));
+ }
+}
\ No newline at end of file
diff --git a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
index a288a404d..a4df1d4b7 100644
--- a/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
+++ b/tests/php-unit-tests/src/BaseTestCase/ItopTestCase.php
@@ -10,6 +10,7 @@ use CMDBSource;
use DeprecatedCallsLog;
use MySQLTransactionNotClosedException;
use PHPUnit\Framework\TestCase;
+use ReflectionMethod;
use SetupUtils;
use const DEBUG_BACKTRACE_IGNORE_ARGS;
@@ -77,7 +78,54 @@ abstract class ItopTestCase extends TestCase
}
}
- protected function setUp(): void {
+ /**
+ * @param array $args
+ * @param string $sExportFileName relative to log folder
+ * @param array $aExcludedParams
+ * Function that aims to export the values of the parameters of a function in a file
+ * You can call the function anywhere like following :
+ * ```
+ * require __DIR__ . '/../../../tests/php-unit-tests/vendor/autoload.php'; // required to include phpunit autoload
+ * ItopTestCase::ExportFunctionParameterValues(func_get_args(), "parameters.txt");
+ * ```
+ * Useful to generate realistic data for tests providers
+ *
+ * @return string
+ * @throws \ReflectionException
+ */
+ public static function ExportFunctionParameterValues(array $args, string $sExportFileName, array $aExcludedParams = []): string
+ {
+ // get sclass et function dans la callstrack
+
+ // in the callstack get the call function name
+ $aCallStack = debug_backtrace();
+ $sCallFunction = $aCallStack[1]['function'];
+ // in the casll stack get the call class name
+ $sCallClass = $aCallStack[1]['class'];
+ $reflectionFunc = new ReflectionMethod($sCallClass, $sCallFunction);
+ $parameters = $reflectionFunc->getParameters();
+
+ $aParamValues = [];
+ foreach ($parameters as $index => $param) {
+ $aParamValues[$param->getName()] = $args[$index] ?? null;
+ }
+
+ $paramValues = $aParamValues;
+ foreach ($aExcludedParams as $sExcludedParam) {
+ unset($paramValues[$sExcludedParam]);
+ }
+
+ // extract oPage from the array in parameters and make a foreach on exlucded parameters
+ foreach ($aExcludedParams as $sExcludedParam) {
+ unset($paramValues[$sExcludedParam]);
+ }
+
+ $var_export = var_export($paramValues, true);
+ file_put_contents(APPROOT.'/log/' .$sExportFileName, $var_export);
+ return $var_export;
+ }
+
+ protected function setUp(): void {
parent::setUp();
$this->debug("\n----------\n---------- ".$this->getName()."\n----------\n");
diff --git a/tests/php-unit-tests/unitary-tests/core/BulkChangeTest.php b/tests/php-unit-tests/unitary-tests/core/BulkChangeTest.php
index a5630ae49..7ca006bfa 100644
--- a/tests/php-unit-tests/unitary-tests/core/BulkChangeTest.php
+++ b/tests/php-unit-tests/unitary-tests/core/BulkChangeTest.php
@@ -2,7 +2,7 @@
namespace Combodo\iTop\Test\UnitTest\Core;
-use CMDBSource;
+use BulkChange;
use Combodo\iTop\Test\UnitTest\ItopDataTestCase;
use MetaModel;
@@ -43,7 +43,7 @@ class BulkChangeTest extends ItopDataTestCase
"contactid" =>
array("first_name" => 0, "name" => 1, "email" => 2),
);
- $oBulk = new \BulkChange(
+ $oBulk = new BulkChange(
"UserLocal",
$aData,
$aAttributes,
@@ -70,18 +70,19 @@ class BulkChangeTest extends ItopDataTestCase
/**
* test $oBulk->Process with server 1 from demo datas
- * @dataProvider BulkChangeProvider
*
- * @param $aData
+ * @dataProvider bulkChangeWithoutInitDataProvider
+ *
+ * @param $aCSVData
* @param $aAttributes
* @param $aExtKeys
* @param $aReconcilKeys
*/
- public function testBulkChangeIssue($aData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult) {
+ public function testBulkChangeWithoutInitData($aCSVData, $aAttributes, $aExtKeys, $aReconcilKeys, $aResult, array $aResultHTML = null) {
$this->debug("aReconcilKeys:".$aReconcilKeys[0]);
- $oBulk = new \BulkChange(
+ $oBulk = new BulkChange(
"Server",
- $aData,
+ $aCSVData,
$aAttributes,
$aExtKeys,
$aReconcilKeys,
@@ -102,67 +103,117 @@ class BulkChangeTest extends ItopDataTestCase
foreach ($aRow as $i => $oCell) {
if ($i !== "finalclass" && $i !== "__STATUS__" && $i !== "__ERRORS__" && array_key_exists($i, $aResult)) {
$this->debug("i:".$i);
- $this->debug('GetDisplayableValue:'.$oCell->GetDisplayableValue());
+ $this->debug('GetCLIValue:'.$oCell->GetCLIValue());
$this->debug("aResult:".$aResult[$i]);
- $this->assertEquals($aResult[$i], $oCell->GetDisplayableValue());
+ $this->assertEquals($aResult[$i], $oCell->GetCLIValue());
+ if (null !== $aResultHTML) {
+ $this->assertEquals($aResultHTML[$i], $oCell->GetHTMLValue());
+ }
}
}
}
}
}
- public function BulkChangeProvider() {
+ public function bulkChangeWithoutInitDataProvider() {
return [
"Case 3, 5 et 8 : unchanged" => [
- [["Demo", "Server1", "1", "production", ""]],
- ["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
- ["org_id" => ["name" => 0]],
- ["id"],
- [0 => "Demo", "org_id" => "3", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "unchanged"],
+ "csvData" =>
+ [["Demo", "Server1", "1", "production", ""]],
+ "attributes"=>
+ ["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
+ "extKeys"=>
+ ["org_id" => ["name" => 0]],
+ "reconciliation Keys"=>
+ ["id"],
+ "expectedResult"=>
+ [0 => "Demo", "org_id" => "3", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "unchanged"],
+ "expectedResultHTML"=>
+ [0 => "Demo", "org_id" => "3", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "unchanged"],
],
"Case 9 : wrong date format" => [
- [["Demo", "Server1", "1", "production", "date"]],
- ["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' is an invalid value", "id" => 1, "__STATUS__" => "Issue: wrong date format"],
- ],
+ "csvData" =>
+ [["Demo", "Server1", "1", "production", "
+ ["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
+ "extKeys"=>
+ ["org_id" => ["name" => 0]],
+ "reconciliation Keys"=>
+ ["id"],
+ "expectedResult"=>
+ [ 0 => "Demo", "org_id" => "n/a", 1 => "Server1", 2 => "1", 3 => "production", 4 => "' 1, "__STATUS__" => "Issue: wrong date format"],
+ "expectedResultHTML"=>
+ [ 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"],
- [0 => 'Bad', "org_id" => "No match for value 'Bad'",1 => "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
+ "csvData" =>
+ [["
+ ["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
+ "extKeys"=>
+ ["org_id" => ["name" => 0]],
+ "reconciliation Keys"=>
+ ["id"],
+ "expectedResult"=>
+ [0 => ' "No match for value ' "Server1",2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
+ "expectedResultHTML"=>
+ [0 => '<Bad', "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"],
- [0 => null, "org_id" => "Invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
- ],
+ "csvData" =>
+ [["", "Server1", "1", "production", ""]],
+ "attributes"=>
+ ["name" => 1, "id" => 2, "status" => 3, "purchase_date" => 4],
+ "extKeys"=>
+ ["org_id" => ["name" => 0]],
+ "reconciliation Keys"=>
+ ["id"],
+ "expectedResult"=>
+ [0 => null, "org_id" => "Invalid value for attribute", 1 => "Server1", 2 => "1", 3 => "production", 4 => "", "id" => 1, "__STATUS__" => "Issue: Unexpected attribute value(s)"],
+ "expectedResultHTML"=>
+ [0 => null, "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", "