/**
* Specific to the interactive csv import
*
* @copyright Copyright (C) 2010-2012 Combodo SARL
* @license http://opensource.org/licenses/AGPL-3.0
*/
require_once('../approot.inc.php');
require_once(APPROOT.'/application/application.inc.php');
require_once(APPROOT.'/application/webpage.class.inc.php');
require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
require_once(APPROOT.'/application/wizardhelper.class.inc.php');
require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
require_once(APPROOT.'/application/csvpage.class.inc.php');
/**
* Determines if the name of the field to be mapped correspond
* to the name of an external key or an Id of the given class
* @param string $sClassName The name of the class
* @param string $sFieldCode The attribute code of the field , or empty if no match
* @return bool true if the field corresponds to an id/External key, false otherwise
*/
function IsIdField($sClassName, $sFieldCode)
{
$bResult = false;
if (!empty($sFieldCode))
{
if ($sFieldCode == 'id')
{
$bResult = true;
}
else if (strpos($sFieldCode, '->') === false)
{
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sFieldCode);
$bResult = $oAttDef->IsExternalKey();
}
}
return $bResult;
}
/**
* Get all the fields xxx->yyy based on the field xxx which is an external key
* @param string $sExtKeyAttCode Attribute code of the external key
* @param AttributeDefinition $oExtKeyAttDef Attribute definition of the external key
* @param bool $bAdvanced True if advanced mode
* @return Ash List of codes=>display name: xxx->yyy where yyy are the reconciliation keys for the object xxx
*/
function GetMappingsForExtKey($sAttCode, AttributeDefinition $oExtKeyAttDef, $bAdvanced)
{
$aResult = array();
$sTargetClass = $oExtKeyAttDef->GetTargetClass();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
{
if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
{
$bExtKey = $oTargetAttDef->IsExternalKey();
$sSuffix = '';
if ($bExtKey)
{
$sSuffix = '->id';
}
if ($bAdvanced || !$bExtKey)
{
// When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys !
$aResult[$sAttCode.'->'.$sTargetAttCode] = $oExtKeyAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix;
}
}
}
return $aResult;
}
/**
* Helper function to build the mapping drop-down list for a field
* Spec: Possible choices are "writable" fields in this class plus external fields that are listed as reconciliation keys
* for any class pointed to by an external key in the current class.
* If not in advanced mode, all "id" fields (id and external keys) must be mapped to ":none:" (i.e -- ignore this field --)
* External fields that do not correspond to a reconciliation key must be mapped to ":none:"
* Otherwise, if a field equals either the 'code' or the 'label' (translated) of a field, then it's mapped automatically
* @param string $sClassName Name of the class used for the mapping
* @param string $sFieldName Name of the field, as it comes from the data file (header line)
* @param integer $iFieldIndex Number of the field in the sequence
* @param bool $bAdvancedMode Whether or not advanced mode was chosen
* @param string $sDefaultChoice If set, this will be the item selected by default
* @return string The HTML code corresponding to the drop-down list for this field
*/
function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMode, $sDefaultChoice)
{
$aChoices = array('' => Dict::S('UI:CSVImport:MappingSelectOne'));
$aChoices[':none:'] = Dict::S('UI:CSVImport:MappingNotApplicable');
$sFieldCode = ''; // Code of the attribute, if there is a match
$aMatches = array();
if (preg_match('/^(.+)\*$/', $sFieldName, $aMatches))
{
// Remove any trailing "star" character.
// A star character at the end can be used to indicate a mandatory field
$sFieldName = $aMatches[1];
}
if ($sFieldName == 'id')
{
$sFieldCode = 'id';
}
if ($bAdvancedMode)
{
$aChoices['id'] = Dict::S('UI:CSVImport:idField');
}
foreach(MetaModel::ListAttributeDefs($sClassName) as $sAttCode => $oAttDef)
{
$sStar = '';
if ($oAttDef->IsExternalKey())
{
if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
{
$sFieldCode = $sAttCode;
}
if ($bAdvancedMode)
{
$aChoices[$sAttCode] = $oAttDef->GetLabel();
}
$oExtKeyAttDef = MetaModel::GetAttributeDef($sClassName, $oAttDef->GetKeyAttCode());
if (!$oExtKeyAttDef->IsNullAllowed())
{
$sStar = '*';
}
// Get fields of the external class that are considered as reconciliation keys
$sTargetClass = $oAttDef->GetTargetClass();
foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
{
if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
{
$bExtKey = $oTargetAttDef->IsExternalKey();
$sSuffix = '';
if ($bExtKey)
{
$sSuffix = '->id';
}
if ($bAdvancedMode || !$bExtKey)
{
// When not in advanced mode do not allow to use reconciliation keys (on external keys) if they are themselves external keys !
$aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix.$sStar;
if ((strcasecmp($sFieldName, $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel().$sSuffix) == 0) || (strcasecmp($sFieldName, ($sAttCode.'->'.$sTargetAttCode.$sSuffix)) == 0) )
{
$sFieldCode = $sAttCode.'->'.$sTargetAttCode;
}
}
}
}
}
else if ($oAttDef->IsWritable() && (!$oAttDef->IsLinkset() || ($bAdvancedMode && $oAttDef->IsIndirect())))
{
if (!$oAttDef->IsNullAllowed())
{
$sStar = '*';
}
$aChoices[$sAttCode] = $oAttDef->GetLabel().$sStar;
if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
{
$sFieldCode = $sAttCode;
}
}
}
asort($aChoices);
$sHtml = "\n";
return $sHtml;
}
try
{
require_once(APPROOT.'/application/startup.inc.php');
require_once(APPROOT.'/application/loginwebpage.class.inc.php');
LoginWebPage::DoLogin(); // Check user rights and prompt if needed
$sOperation = utils::ReadParam('operation', '');
switch($sOperation)
{
case 'parser_preview':
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->SetContentType('text/html');
$sSeparator = utils::ReadParam('separator', ',', false, 'raw_data');
if ($sSeparator == 'tab') $sSeparator = "\t";
$sTextQualifier = utils::ReadParam('qualifier', '"', false, 'raw_data');
$iLinesToSkip = utils::ReadParam('do_skip_lines', 0);
$bFirstLineAsHeader = utils::ReadParam('header_line', true);
$sEncoding = utils::ReadParam('encoding', 'UTF-8');
$sData = stripslashes(utils::ReadParam('csvdata', true, false, 'raw_data'));
$oCSVParser = new CSVParser($sData, $sSeparator, $sTextQualifier);
$aData = $oCSVParser->ToArray($iLinesToSkip);
$iTarget = count($aData);
if ($iTarget == 0)
{
$oPage->p(Dict::S('UI:CSVImport:NoData'));
}
else
{
$sMaxLen = (strlen(''.$iTarget) < 3) ? 3 : strlen(''.$iTarget); // Pad line numbers to the appropriate number of chars, but at least 3
$sFormat = '%0'.$sMaxLen.'d';
$oPage->p("
".Dict::S('UI:Title:DataPreview')."
\n");
$oPage->p("
\n");
$oPage->add("
");
$iMaxIndex= 10; // Display maximum 10 lines for the preview
$index = 1;
foreach($aData as $aRow)
{
$sCSSClass = 'csv_row'.($index % 2);
if ( ($bFirstLineAsHeader) && ($index == 1))
{
$oPage->add("
");
$aFirstLine = $aData[0]; // Use the first row to determine the number of columns
$iStartLine = 0;
$iNbColumns = count($aFirstLine);
if ($bFirstLineAsHeader)
{
$iStartLine = 1;
foreach($aFirstLine as $sField)
{
$aHeader[] = $sField;
}
}
else
{
// Build some conventional name for the fields: field1...fieldn
$index= 1;
foreach($aFirstLine as $sField)
{
$aHeader[] = Dict::Format('UI:CSVImport:FieldName', $index);
$index++;
}
}
$oPage->add("
\n");
$oPage->add('
');
$oPage->add('
'.Dict::S('UI:CSVImport:HeaderFields').'
'.Dict::S('UI:CSVImport:HeaderMappings').'
'.Dict::S('UI:CSVImport:HeaderSearch').'
'.Dict::S('UI:CSVImport:DataLine1').'
'.Dict::S('UI:CSVImport:DataLine2').'
');
$oPage->add('
');
$index = 1;
foreach($aHeader as $sField)
{
$sDefaultChoice = null;
if (isset($aInitFieldMapping[$index]))
{
$sDefaultChoice = $aInitFieldMapping[$index];
}
$oPage->add('
\n");
if (empty($sInitSearchField))
{
// Propose a reconciliation scheme
//
$aReconciliationKeys = MetaModel::GetReconcKeys($sClassName);
$aMoreReconciliationKeys = array(); // Store: key => void to automatically remove duplicates
foreach($aReconciliationKeys as $sAttCode)
{
if (!MetaModel::IsValidAttCode($sClassName, $sAttCode)) continue;
$oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
if ($oAttDef->IsExternalKey())
{
// An external key is specified as a reconciliation key: this means that all the reconciliation
// keys of this class are proposed to identify the target object
$aMoreReconciliationKeys = array_merge($aMoreReconciliationKeys, GetMappingsForExtKey($sAttCode, $oAttDef, $bAdvanced));
}
elseif($oAttDef->IsExternalField())
{
// An external field is specified as a reconciliation key, translate the field into a field on the target class
// since external fields are not writable, and thus never appears in the mapping form
$sKeyAttCode = $oAttDef->GetKeyAttCode();
$sTargetAttCode = $oAttDef->GetExtAttCode();
$aMoreReconciliationKeys[$sKeyAttCode.'->'.$sTargetAttCode] = '';
}
}
$sDefaultKeys = '"'.implode('", "',array_merge($aReconciliationKeys, array_keys($aMoreReconciliationKeys))).'"';
}
else
{
// The reconciliation scheme is given (navigating back in the wizard)
//
$aDefaultKeys = array();
foreach ($aInitSearchField as $iSearchField => $void)
{
$sAttCodeEx = $aInitFieldMapping[$iSearchField];
$aDefaultKeys[] = $sAttCodeEx;
}
$sDefaultKeys = '"'.implode('", "', $aDefaultKeys).'"';
}
$oPage->add_ready_script(
<<AddCondition('id', 0, '='); // Make sure we create an empty set
$oSet = new CMDBObjectSet($oSearch);
$sResult = cmdbAbstractObject::GetSetAsCSV($oSet, array('showMandatoryFields' => true));
$sClassDisplayName = MetaModel::GetName($sClassName);
$sDisposition = utils::ReadParam('disposition', 'inline');
if ($sDisposition == 'attachment')
{
$oPage = new CSVPage("");
$oPage->add_header("Content-type: text/csv; charset=utf-8");
$oPage->add_header("Content-disposition: attachment; filename=\"{$sClassDisplayName}.csv\"");
$oPage->no_cache();
$oPage->add($sResult);
}
else
{
$oPage = new ajax_page("");
$oPage->no_cache();
$oPage->add('