* @author Romain Quetiez * @author Denis Flaven * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL */ 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/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')); /** * 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); $iRealSkippedLines = $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'); 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 = 'Missing object: will be updated'; } else { $sMessage = 'Missing object: updated'; } break; case 'RowStatus_NewObj': $iCreated++; $sFinalClass = $aResRow['finalclass']; $sStatus = ''; $sCSSRowClass = 'row_added'; if ($bSimulate) { $sMessage = 'Object will be created'; } else { $sFinalClass = $aResRow['finalclass']; $oObj = MetaModel::GetObject($sFinalClass, $aResRow['id']->GetPureValue()); $sUrl = $oObj->GetHyperlink(); $sMessage = 'Object created'; } break; case 'RowStatus_Issue': $iErrors++; $sMessage .= $oPage->GetP($oStatus->GetDescription()); $sStatus = ''; $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".BulkChange::GetFriendlyAttCodeName($sClassName, $sAttCode)."Message
".sprintf("%0{$sMaxLen}d", 1+$iLine+$iRealSkippedLines)."$sStatus$sUrlERROR: '.$sHtmlValue.$sCellMessage.'ERROR: '.$sHtmlValue.$sCellMessage.'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(''); 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 = sprintf("%.0f %% of the loaded objects have errors and will be ignored.", $fErrorsPercentage); $bShouldConfirm = true; } $fCreatedPercentage = (100.0*$iCreated)/count($aRes); if ($fCreatedPercentage >= MetaModel::GetConfig()->Get('csv_import_creations_percentage')) { $sMessage = sprintf("%.0f %% of the loaded objects will be created.", $fCreatedPercentage); $bShouldConfirm = true; } $fModifiedPercentage = (100.0*$iModified)/count($aRes); if ($fModifiedPercentage >= MetaModel::GetConfig()->Get('csv_import_modifications_percentage')) { $sMessage = sprintf("%.0f %% of the loaded objects will be modified.", $fModifiedPercentage); $bShouldConfirm = true; } } $iCount = count($aRes); //$oPage->add('

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

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

'); } $oPage->add('
'); $oPage->add('
'); if ($bShouldConfirm) { $oPage->add('
'); $oPage->add('

'.$sMessage.'

'); $oPage->add('

Are you sure you want to do this ?

'); $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'); } 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(); } function open_flash_chart_data() { var iErrors = $iErrors; var iModified = $iModified; var iCreated = $iCreated; var iUnchanged = $iUnchanged; var fAlpha = 0.9; var oResult = { "elements": [ { "type": "pie", "tip": "#label# (#percent#)", "gradient-fill": true, "font-size": 14, "colours":[], "values": [], "animate":[ { "type": "fade" } ] } ], "x_axis": null, "font-size": 14, "bg_colour": "#EEEEEE" }; if (iErrors > 0) { var oErrors = { "value": iErrors, "label": "Errors: "+iErrors, "alpha": fAlpha, "label-colour": "#CC3333", }; oResult.elements[0].values.push(oErrors); oResult.elements[0].colours.push('#FF6666'); } if (iModified > 0) { var oModified = { "value": iModified, "label": "Modified: "+iModified, "alpha": fAlpha, "label-colour": "#3333CC", }; oResult.elements[0].values.push(oModified); oResult.elements[0].colours.push('#6666FF'); } if (iCreated > 0) { var oCreated = { "value": iCreated, "label": "Created: "+iCreated, "alpha": fAlpha, "label-colour": "#33CC33", }; oResult.elements[0].values.push(oCreated); oResult.elements[0].colours.push('#66FF66'); } if (iUnchanged > 0) { var oUnchanged = { "value": iUnchanged, "label": "Unchanged: "+iUnchanged, "alpha": fAlpha, "label-colour": "#333333", }; oResult.elements[0].values.push(oUnchanged); oResult.elements[0].colours.push('#666666'); } return JSON.stringify(oResult); } 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); $iSkippedLines = 0; if (utils::ReadParam('box_skiplines', '0') == 1) { $iSkippedLines = utils::ReadParam('nb_skipped_lines', '0'); } $sClassName = utils::ReadParam('class_name', '', false, 'class'); $bAdvanced = utils::ReadParam('advanced', 0); $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $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(''); if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $oPage->add(''); } } $oPage->add('

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

'); $oPage->add(''); $oPage->add(''); $sAlertIncompleteMapping = Dict::S('UI:CSVImport:AlertIncompleteMapping'); $sAlertNoSearchCriteria = Dict::S('UI:CSVImport:AlertNoSearchCriteria'); $oPage->add_ready_script( <<add_ready_script("DoMapping();"); // There is already a class selected, run the mapping } $oPage->add_script( << 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') { $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); // 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(''); $oPage->add(''); $oPage->add(''); $oPage->add(''); $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( <<add_ready_script('DoPreview();'); } /** * Prompt for the data to be loaded (either via a file or a copy/paste) * @param WebPage $oPage The current web page * @return void */ function Welcome(iTopWebPage $oPage) { // Encodings supported: // ICONV_CODE => Display Name // Each iconv installation supports different encodings // Some reasonably common and useful encodnings are listed here $aPossibleEncodings = array( 'UTF-8' => 'Unicode (UTF-8)', 'ISO-8859-1' => 'Western (ISO-8859-1)', 'WINDOWS-1251' => 'Cyrilic (Windows 1251)', 'WINDOWS-1252' => 'Western (Windows 1252)', 'ISO-8859-15' => 'Western (ISO-8859-15)', ); // Some more encodings can be specified in the config file $aExtraCharsets = MetaModel::GetConfig()->GetCSVImportCharsets(); $aPossibleEncodings = array_merge($aPossibleEncodings, $aExtraCharsets); asort($aPossibleEncodings); $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()); } 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); $iSkippedLines = utils::ReadParam('nb_skipped_lines', ''); $sClassName = utils::ReadParam('class_name', ''); $bAdvanced = utils::ReadParam('advanced', 0); $sEncoding = utils::ReadParam('encoding', 'UTF-8'); $sFileLoadHtml = '

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

'. '

'; $sFileLoadHtml .= '

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

'; $sFileLoadHtml .= '

'. ''. ''. ''. ''. ''. ''. ''; if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $sFileLoadHtml .= ''; } } $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 .= '

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

'. '

'. ''. ''. ''. ''. ''. ''. ''. ''. ''; if (!empty($sSynchroScope)) { foreach($aSynchroUpdate as $sKey => $value) { $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( <<SetCurrentTabContainer('tabs1'); $oPage->SetCurrentTab(Dict::S('UI:History:BulkImports')); BulkChange::DisplayImportHistory($oPage); } switch($iStep) { case 10: $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()); } } ?>