/** * CSV Import Page * Wizard to import CSV (or TSV) data into the database * * @copyright Copyright (C) 2010-2016 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ try { ini_set('memory_limit', '256M'); require_once('../approot.inc.php'); require_once(APPROOT.'/application/application.inc.php'); require_once(APPROOT.'/application/itopwebpage.class.inc.php'); require_once(APPROOT.'/application/ajaxwebpage.class.inc.php'); require_once(APPROOT.'/application/startup.inc.php'); require_once(APPROOT.'/application/loginwebpage.class.inc.php'); LoginWebPage::DoLogin(); // Check user rights and prompt if needed $iStep = utils::ReadParam('step', 1); $oPage = new iTopWebPage(Dict::S('UI:Title:BulkImport')); $oPage->SetBreadCrumbEntry('ui-tool-bulkimport', Dict::S('Menu:CSVImportMenu'), Dict::S('UI:Title:BulkImport+'), '', utils::GetAbsoluteUrlAppRoot().'images/wrench.png'); /** * Helper function to build a select from the list of valid classes for a given action * @param string $sName The name of the select in the HTML form * @param string $sDefaulfValue The defaut value (i.e the value selected by default) * @param integer $iWidthPx The width (in pixels) of the drop-down list * @param integer $iActionCode The ActionCode (from UserRights) to check for authorization for the classes * @return string The HTML fragment corresponding to the select tag */ function GetClassesSelect($sName, $sDefaultValue, $iWidthPx, $iActionCode = null) { $sHtml = ""; return $sHtml; } /** * Helper to 'check' an input in an HTML form if the current value equals the value given * @param mixed $sCurrentValue The current value to be chacked against the value of the input * @param mixed $sProposedValue The value of the input * @param bool $bInverseCondition Set to true to perform the reversed comparison * @return string Either ' checked' or an empty string */ function IsChecked($sCurrentValue, $sProposedValue, $bInverseCondition = false) { $bCondition = ($sCurrentValue == $sProposedValue); return ($bCondition xor $bInverseCondition) ? ' checked' : ''; } /** * Returns the number of occurences of each char from the set in the specified string * @param string $sString The input data * @param array $aSet The set of characters to count * @return hash 'char' => nb of occurences */ function CountCharsFromSet($sString, $aSet) { $aResult = array(); $aCount = count_chars($sString); foreach($aSet as $sChar) { $aResult[$sChar] = isset($aCount[ord($sChar)]) ? $aCount[ord($sChar)] : 0; } 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 * @param array $aPossibleSeparators The list of characters to count * @return string The most frequent character from the set */ function GuessFromFrequency($aCSVData, $aPossibleSeparators) { $iLine = 0; $iMaxLine = 20; // Process max 20 lines to guess the parameters foreach($aPossibleSeparators as $sSep) { $aGuesses[$sSep]['total'] = $aGuesses[$sSep]['max'] = 0; $aGuesses[$sSep]['min'] = 999; } $aStats = array(); while(($iLine < count($aCSVData)) && ($iLine < $iMaxLine) ) { if (strlen($aCSVData[$iLine]) > 0) { $aStats[$iLine] = CountCharsFromSet($aCSVData[$iLine], $aPossibleSeparators); } $iLine++; } $iLine = 1; foreach($aStats as $aLineStats) { foreach($aPossibleSeparators as $sSep) { $aGuesses[$sSep]['total'] += $aLineStats[$sSep]; if ($aLineStats[$sSep] > $aGuesses[$sSep]['max']) $aGuesses[$sSep]['max'] = $aLineStats[$sSep]; if ($aLineStats[$sSep] < $aGuesses[$sSep]['min']) $aGuesses[$sSep]['min'] = $aLineStats[$sSep]; } $iLine++; } $aScores = array(); foreach($aGuesses as $sSep => $aData) { $aScores[$sSep] = $aData['total'] + $aData['max'] - $aData['min']; } arsort($aScores, SORT_NUMERIC); // Sort the array, higher scores first $aKeys = array_keys($aScores); $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 * @return hash 'separator' => the_guessed_separator, 'qualifier' => the_guessed_text_qualifier */ function GuessParameters($sCSVData) { $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 * @param string $sClass The class of objects to synchronize * @param integer $iCount The number of objects to synchronize * @return none */ function DisplaySynchroBanner(WebPage $oP, $sClass, $iCount) { $oP->add("

".MetaModel::GetClassIcon($sClass)." ".Dict::Format('UI:Title:BulkSynchro_nbItem_ofClass_class', $iCount, MetaModel::GetName($sClass))."

\n"); } /** * Process the CSV data, for real or as a simulation * @param WebPage $oPage The page used to display the wizard * @param bool $bSimulate Whether or not to simulate the data load * @return array The CSV lines in error that were rejected from the load (with the header line - if any) or null */ function ProcessCSVData(WebPage $oPage, $bSimulate = true) { $aResult = array(); $sCSVData = utils::ReadParam('csvdata', '', false, 'raw_data'); $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '', false, 'raw_data'); $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'); } $sClassName = utils::ReadParam('class_name', '', false, 'class'); $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'); $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); $sClassesSelect = "'); $aRes = $oBulk->Process($oMyChange); $sHtml = ''; $sHtml .= ''; $sHtml .= ''; $sHtml .= ''; foreach($aFieldsMapping as $iNumber => $sAttCode) { if (!empty($sAttCode) && ($sAttCode != ':none:') && ($sAttCode != 'finalclass')) { $sHtml .= ""; } } $sHtml .= ''; $sHtml .= ''; $iErrors = 0; $iCreated = 0; $iModified = 0; $iUnchanged = 0; foreach($aRes as $iLine => $aResRow) { $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 = 'row_unchanged'; break; case 'RowStatus_Modify': $iModified++; $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_modified'; break; case 'RowStatus_Disappeared': $iModified++; $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); $sStatus = ''; $sCSSRowClass = 'row_modified'; if ($bSimulate) { $sMessage = Dict::S('UI:CSVReport-Object-MissingToUpdate'); } else { $sMessage = Dict::S('UI:CSVReport-Object-MissingUpdated'); } break; case 'RowStatus_NewObj': $iCreated++; $sFinalClass = $aResRow['finalclass']; $sStatus = ''; $sCSSRowClass = '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 .= $oPage->GetP($oStatus->GetDescription()); $sStatus = '';//translate $sCSSMessageClass = 'cell_error'; $sCSSRowClass = '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; } $sHtml .= ''; $sHtml .= ""; $sHtml .= ""; $sHtml .= ""; 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]; switch(get_class($oExtKeyCellStatus)) { case 'CellStatus_Issue': case 'CellStatus_SearchIssue': case 'CellStatus_NullIssue': $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); break; case 'CellStatus_Ambiguous': $sCellMessage .= $oPage->GetP($oExtKeyCellStatus->GetDescription()); break; default: // Do nothing } } $sHtmlValue = $oCellStatus->GetDisplayableValue(); switch(get_class($oCellStatus)) { case 'CellStatus_Issue': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sHtml .= ''; break; case 'CellStatus_SearchIssue': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sHtml .= ''; break; case 'CellStatus_Ambiguous': $sCellMessage .= $oPage->GetP($oCellStatus->GetDescription()); $sHtml .= ''; break; case 'CellStatus_Modify': $sHtml .= ''; break; default: $sHtml .= ''; } } } $sHtml .= ""; $sHtml .= ''; } $iUnchanged = count($aRes) - $iErrors - $iModified - $iCreated; $sHtml .= '
LineStatusObject".MetaModel::GetLabel($sClassName, $sAttCode)."Message
".sprintf("%0{$sMaxLen}d", 1+$iLine+$iRealSkippedLines)."$sStatus$sUrl'.Dict::Format('UI:CSVReport-Object-Error', $sHtmlValue).$sCellMessage.'ERROR: '.$sHtmlValue.$sCellMessage.''.Dict::Format('UI:CSVReport-Object-Ambiguous', $sHtmlValue).$sCellMessage.''.$sHtmlValue.''.$sHtmlValue.$sCellMessage.'$sMessage
'; $oPage->add('
'); $oPage->add('
'); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $oPage->add(''); } } foreach($aFieldsMapping as $iNumber => $sAttCode) { $oPage->add(''); } foreach($aSearchFields as $index => $sDummy) { $oPage->add(''); } $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'); } $oPage->add('

  '.sprintf($aDisplayFilters['unchanged'], $iUnchanged).'  '); $oPage->add('  '.sprintf($aDisplayFilters['modified'], $iModified).'  '); $oPage->add('  '.sprintf($aDisplayFilters['added'], $iCreated).'  '); $oPage->add('  '.sprintf($aDisplayFilters['errors'], $iErrors).'

'); $oPage->add('
'); $oPage->add($sHtml); $oPage->add('
'); $oPage->add('

'); if($bSimulate) { $oPage->add('  '); } $oPage->add('  '); $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; } } $iCount = count($aRes); //$oPage->add('

'); $sConfirm = $bShouldConfirm ? 'true' : 'false'; $oPage->add('

"); } else { $oPage->add('

'); } $oPage->add('
'); $oPage->add('
'); if ($bShouldConfirm) { $sYesButton = Dict::S('UI:Button:Ok'); $sNoButton = Dict::S('UI:Button:Cancel'); $oPage->add('
'); $oPage->add('

'.$sMessage.'

'); $oPage->add('

'.htmlentities(Dict::S('UI:CSVImportConfirmMessage'), ENT_QUOTES, 'UTF-8').'

'); $oPage->add('
'); $oPage->add('
'); $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; } } /** * Perform the actual load of the CSV data and display the results * @param WebPage $oPage The web page to display the wizard * @return void */ function LoadData(WebPage $oPage) { $oPage->add('

'.Dict::S('UI:Title:CSVImportStep5').'

'); $aResult = ProcessCSVData($oPage, false /* simulate = false */); if (is_array($aResult)) { $oPage->StartCollapsibleSection(Dict::S('UI:CSVImport:LinesNotImported'), false); $oPage->p(Dict::S('UI:CSVImport:LinesNotImported+')); $oPage->add(''); $oPage->EndCollapsibleSection(); } } /** * Simulate the load of the CSV data and display the results * @param WebPage $oPage The web page to display the wizard * @return void */ function Preview(WebPage $oPage) { $oPage->add('

'.Dict::S('UI:Title:CSVImportStep4').'

'); 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 * @return void */ function SelectMapping(WebPage $oPage) { $sCSVData = utils::ReadParam('csvdata', '', false, 'raw_data'); $sCSVDataTruncated = utils::ReadParam('csvdata_truncated', '', false, 'raw_data'); $sSeparator = utils::ReadParam('separator', ',', false, 'raw_data'); if ($sSeparator == 'tab') $sSeparator = "\t"; if ($sSeparator == 'other') { $sSeparator = utils::ReadParam('other_separator', ',', false, 'raw_data'); } $sTextQualifier = utils::ReadParam('text_qualifier', '"', false, 'raw_data'); if ($sTextQualifier == 'other') { $sTextQualifier = utils::ReadParam('other_qualifier', '"', false, 'raw_data'); } $bHeaderLine = (utils::ReadParam('header_line', '0') == 1); $sClassName = utils::ReadParam('class_name', '', false, 'class'); $bAdvanced = utils::ReadParam('advanced', 0); $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $sDateTimeFormat = utils::ReadParam('date_time_format', 'default'); $sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data'); $sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data'); 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); $sClassesSelect = " '.Dict::S('UI:CSVImport:AdvancedMode').''); $oPage->add(''); $oPage->add('

'.Dict::S('UI:CSVImport:SelectAClassFirst').'

'); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $oPage->add(''); } } $oPage->add('

  '); $oPage->add('  '); $oPage->add('

'); $oPage->add(''); $oPage->add(''); $sAlertIncompleteMapping = addslashes(Dict::S('UI:CSVImport:AlertIncompleteMapping')); $sAlertMultipleMapping = addslashes(Dict::S('UI:CSVImport:AlertMultipleMapping')); $sAlertNoSearchCriteria = addslashes(Dict::S('UI:CSVImport:AlertNoSearchCriteria')); $oPage->add_ready_script( <<add_ready_script("DoMapping('$sFieldsMapping', '$sSearchFields');"); // There is already a class selected, run the mapping } $oPage->add_script( << 1) && (sRefValue != ':none:')) { $(this).parent().css({'border': '2px #D81515 solid'}); bResult = false; bMultipleMapping = true; } } }); // At least one search field must be checked $('input[name^=search]:checked').each( function() { bSearchOk = true; }); if (!bMappingOk) { alert("$sAlertIncompleteMapping"); } if (bMultipleMapping) { alert("$sAlertMultipleMapping"); } if (!bSearchOk) { bResult = false; alert("$sAlertNoSearchCriteria"); } if (bResult) { $('#mapping').block(); // Re-enable all search_xxx checkboxes so that their value gets posted $('input[name^=search]').each(function() { $(this).attr('disabled', false); }); } return bResult; } function DoCheckMapping() { // Check if there is a field mapped to 'id' // In which case, it's the only possible search key var idSelected = 0; var nbSearchKeys = $('input[name^=search]:checked').length; var nbMappings = $('select[name^=field]').length; for(index=1; index <= nbMappings; index++) { var selectedValue = $('#mapping_'+index).val(); if (selectedValue == 'id') { idSelected = index; } } for (index=1; index <= nbMappings; index++) { sMappingValue = $('#mapping_'+index).val(); if ((sMappingValue == '') || (sMappingValue == ':none:')) { // Non-mapped field, uncheck and disabled $('#search_'+index).attr('checked', false); $('#search_'+index).attr('disabled', true); } else if (index == idSelected) { // The 'id' field was mapped, it's the only possible reconciliation key $('#search_'+index).attr('checked', true); $('#search_'+index).attr('disabled', true); } else { if (idSelected > 0) { // The 'id' field was mapped, it's the only possible reconciliation key $('#search_'+index).attr('checked', false); $('#search_'+index).attr('disabled', true); } else { $('#search_'+index).attr('disabled', false); if (nbSearchKeys == 0) { // No search key was selected, select the default ones for(j =0; j < aDefaultKeys.length; j++) { if (sMappingValue == aDefaultKeys[j]) { $('#search_'+index).attr('checked', true); } } } } } } } EOF ); } /** * Select the options of the CSV load and check for CSV parsing errors * @param WebPage $oPage The current web page * @return void */ function SelectOptions(WebPage $oPage) { $sOperation = utils::ReadParam('operation', 'csv_data'); $sCSVData = ''; switch($sOperation) { case 'file_upload': $oDocument = utils::ReadPostedDocument('csvdata'); if (!$oDocument->IsEmpty()) { $sCSVData = $oDocument->GetData(); } break; default: $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') { // Remove the BOM if any if (substr($sCSVData, 0, 3) == UTF8_BOM) { $sCSVData = substr($sCSVData, 3); } // Clean the input // Todo: warn the user if some characters are lost/substituted $sUTF8Data = iconv('UTF-8', 'UTF-8//IGNORE//TRANSLIT', $sCSVData); } else { $sUTF8Data = iconv($sEncoding, 'UTF-8//IGNORE//TRANSLIT', $sCSVData); } $aGuesses = GuessParameters($sUTF8Data); // Try to predict the parameters, based on the input data $sSeparator = utils::ReadParam('separator', '', false, 'raw_data'); if ($sSeparator == '') // May be set to an empty value by the previous page { $sSeparator = $aGuesses['separator']; } $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $bBoxSkipLines = utils::ReadParam('box_skiplines', 0); if ($sSeparator == 'tab') $sSeparator = "\t"; $sOtherSeparator = in_array($sSeparator, array(',', ';', "\t")) ? '' : $sSeparator; $sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data'); if ($sTextQualifier == '') // May be set to an empty value by the previous page { $sTextQualifier = $aGuesses['qualifier']; } $sOtherTextQualifier = in_array($sTextQualifier, array('"', "'")) ? '' : $sTextQualifier; $bHeaderLine = utils::ReadParam('header_line', 0); $sClassName = utils::ReadParam('class_name', '', false, 'class'); $bAdvanced = utils::ReadParam('advanced', 0); $aFieldsMapping = utils::ReadParam('field', array(), false, 'raw_data'); $aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name'); $sDateTimeFormat = utils::ReadParam('date_time_format', 'default'); $sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data'); // Create a truncated version of the data used for the fast preview // Take about 20 lines of data... knowing that some lines may contain carriage returns $iMaxLen = strlen($sUTF8Data); if ($iMaxLen > 0) { $iMaxLines = 20; $iCurPos = true; while ( ($iCurPos > 0) && ($iMaxLines > 0)) { $pos = strpos($sUTF8Data, "\n", $iCurPos); if ($pos !== false) { $iCurPos = 1+$pos; } else { $iCurPos = strlen($sUTF8Data); $iMaxLines = 1; } $iMaxLines--; } $sCSVDataTruncated = substr($sUTF8Data, 0, $iCurPos); } else { $sCSVDataTruncated = ''; } $sSynchroScope = utils::ReadParam('synchro_scope', '', false, 'raw_data'); if (!empty($sSynchroScope)) { $oSearch = DBObjectSearch::FromOQL($sSynchroScope); $sClassName = $oSearch->GetClass(); $oSet = new DBObjectSet($oSearch); $iCount = $oSet->Count(); DisplaySynchroBanner($oPage, $sClassName, $iCount); $aSynchroUpdate = utils::ReadParam('synchro_update', array()); } $oPage->add('

'.Dict::S('UI:Title:CSVImportStep2').'

'); $oPage->add('
'); $oPage->add('
'); $oPage->add('
'); $oPage->add('

'.Dict::S('UI:CSVImport:SeparatorCharacter').'

'); $oPage->add('

'.Dict::S('UI:CSVImport:SeparatorComma+').'
'); $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorSemicolon+').'
'); $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorTab+').'
'); $oPage->add(' '.Dict::S('UI:CSVImport:SeparatorOther').' '); $oPage->add('

'); $oPage->add('
'); $oPage->add('

'.Dict::S('UI:CSVImport:TextQualifierCharacter').'

'); $oPage->add('

'.Dict::S('UI:CSVImport:QualifierDoubleQuote+').'
'); $oPage->add(' '.Dict::S('UI:CSVImport:QualifierSimpleQuote+').'
'); $oPage->add(' '.Dict::S('UI:CSVImport:QualifierOther').' '); $oPage->add('

'); $oPage->add('
'); $oPage->add('

'.Dict::S('UI:CSVImport:CommentsAndHeader').'

'); $oPage->add('

'.Dict::S('UI:CSVImport:TreatFirstLineAsHeader').'

'); $oPage->add('

'.Dict::Format('UI:CSVImport:Skip_N_LinesAtTheBeginning', '').'

'); $oPage->add('

'); $oPage->add('

'.Dict::S('UI:CSVImport:DateAndTimeFormats').'

'); $oPage->add('

'); $oPage->add('

'); $oPage->add('

'); $oPage->add(''); $oPage->add(''); // The encoding has changed, keep that information within the wizard $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); foreach($aFieldsMapping as $iNumber => $sAttCode) { $oPage->add(''); } foreach($aSearchFields as $index => $sDummy) { $oPage->add(''); } $oPage->add(''); if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $oPage->add(''); } } $oPage->add('
'); $oPage->add('

'.Dict::S('UI:CSVImport:CSVDataPreview').'

'); $oPage->add('
'); $oPage->add(''); $oPage->add(''); $oPage->add(''); $oPage->add('
'); $oPage->add_script( <<'.Dict::S('UI:CSVImport:CustomDateTimeFormatTooltip').''); $oPage->add_ready_script( <<GetClass(); $oSet = new DBObjectSet($oSearch); $iCount = $oSet->Count(); DisplaySynchroBanner($oPage, $sClassName, $iCount); $aSynchroUpdate = utils::ReadParam('synchro_update', array()); } else { $aSynchroUpdate = null; } $oPage->add("

".Dict::S('UI:Title:BulkImport+')."

\n"); $oPage->AddTabContainer('tabs1'); $sSeparator = utils::ReadParam('separator', '', false, 'raw_data'); $sTextQualifier = utils::ReadParam('text_qualifier', '', false, 'raw_data'); $bHeaderLine = utils::ReadParam('header_line', true); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', 0); $sEncoding = utils::ReadParam('encoding', ''); $sDateTimeFormat = utils::ReadParam('date_time_format', 'default'); $sCustomDateTimeFormat = utils::ReadParam('custom_date_time_format', (string)AttributeDateTime::GetFormat(), false, 'raw_data'); if ($sEncoding == '') { $sEncoding = MetaModel::GetConfig()->Get('csv_file_default_charset'); } $aFieldsMapping = utils::ReadParam('field', array(), false, 'raw_data'); $aSearchFields = utils::ReadParam('search_field', array(), false, 'field_name'); $sFileLoadHtml = '

'.Dict::S('UI:CSVImport:SelectFile').'

'. '

'; $sFileLoadHtml .= '

'.Dict::S('UI:CSVImport:Encoding').': '; $sFileLoadHtml .= '

'; $sFileLoadHtml .= '

'. ''. ''. ''. ''. ''. ''. ''. ''; if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $sFileLoadHtml .= ''; } } foreach($aFieldsMapping as $iNumber => $sAttCode) { $oPage->add(''); } foreach($aSearchFields as $index => $sDummy) { $oPage->add(''); } $sFileLoadHtml .= '
'; $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:LoadFromFile'), $sFileLoadHtml); $sCSVData = utils::ReadParam('csvdata', '', false, 'raw_data'); $sPasteDataHtml = '

'.Dict::S('UI:CSVImport:PasteData').'

'. '

'; $sPasteDataHtml .= ''; $sPasteDataHtml .= '

'. ''. ''. ''. ''. ''. ''. ''. ''. ''. ''. ''. ''; if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $sPasteDataHtml .= ''; } } foreach($aFieldsMapping as $iNumber => $sAttCode) { $sPasteDataHtml .= ''; } foreach($aSearchFields as $index => $sDummy) { $sPasteDataHtml .= ''; } $sPasteDataHtml .= '
'; $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste'), $sPasteDataHtml); if (!empty($sCSVData)) { // When there are some data, activate the 'copy & paste' tab by default $oPage->SelectTab('tabs1', Dict::S('UI:CSVImport:Tab:CopyPaste')); } $sTemplateHtml = '

'.Dict::S('UI:CSVImport:PickClassForTemplate').' '; $sTemplateHtml .= GetClassesSelect('template_class', '', 300, UR_ACTION_BULK_MODIFY); $sTemplateHtml .= '

'; $sTemplateHtml .= '
'; $sTemplateHtml .= '
'; $oPage->AddToTab('tabs1', Dict::S('UI:CSVImport:Tab:Templates'), $sTemplateHtml); $oPage->add_script( <<add_ready_script( <<Get('csv_import_history_display')) { $oPage->SetCurrentTabContainer('tabs1'); $oPage->AddAjaxTab(Dict::S('UI:History:BulkImports'), utils::GetAbsoluteUrlAppRoot().'pages/csvimport.php?step=11', true /* bCache */); } } switch($iStep) { case 11: // Asynchronous tab $oPage = new ajax_page(''); BulkChange::DisplayImportHistory($oPage); $oPage->add_ready_script('$("#CSVImportHistory table.listResults").tableHover();'); $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 SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); $oP->error(Dict::Format('UI:Error_Details', $e->getHtmlDesc())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) { if (MetaModel::IsValidClass('EventIssue')) { $oLog = new EventIssue(); $oLog->Set('message', $e->getMessage()); $oLog->Set('userinfo', ''); $oLog->Set('issue', $e->GetIssue()); $oLog->Set('impact', 'Page could not be displayed'); $oLog->Set('callstack', $e->getTrace()); $oLog->Set('data', $e->getContextData()); $oLog->DBInsertNoReload(); } IssueLog::Error($e->getMessage()); } // For debugging only //throw $e; } catch(Exception $e) { require_once(APPROOT.'/setup/setuppage.class.inc.php'); $oP = new SetupPage(Dict::S('UI:PageTitle:FatalError')); $oP->add("

".Dict::S('UI:FatalErrorMessage')."

\n"); $oP->error(Dict::Format('UI:Error_Details', $e->getMessage())); $oP->output(); if (MetaModel::IsLogEnabledIssue()) { if (MetaModel::IsValidClass('EventIssue')) { $oLog = new EventIssue(); $oLog->Set('message', $e->getMessage()); $oLog->Set('userinfo', ''); $oLog->Set('issue', 'PHP Exception'); $oLog->Set('impact', 'Page could not be displayed'); $oLog->Set('callstack', $e->getTrace()); $oLog->Set('data', array()); $oLog->DBInsertNoReload(); } IssueLog::Error($e->getMessage()); } } ?>