Explorar o código

Fixing Trac #91, finializing the subtle behavior of the "smart" mapping of fields in the CSV import.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@456 a333f486-631f-4898-b8df-5754b55c2be0
dflaven %!s(int64=15) %!d(string=hai) anos
pai
achega
80a3627811

+ 3 - 0
css/light-grey.css

@@ -699,4 +699,7 @@ td.dashboard {
 	-moz-border-radius: 10px;
 	-moz-border-radius: 10px;
 	padding: 10px;
 	padding: 10px;
 	width: 50%;
 	width: 50%;
+}
+.white {
+	background-color: #fff;
 }
 }

+ 1 - 1
dictionaries/dictionary.itop.ui.php

@@ -436,7 +436,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'UI:Button:Login' => 'Enter iTop',
 	'UI:Button:Login' => 'Enter iTop',
 	'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.',
 	'UI:Login:Error:AccessRestricted' => 'iTop access is restricted. Please, contact an iTop administrator.',
 	'UI:CSVImport:MappingSelectOne' => '-- select one --',
 	'UI:CSVImport:MappingSelectOne' => '-- select one --',
-	'UI:CSVImport:MappingNotApplicable' => '------ n/a ------',
+	'UI:CSVImport:MappingNotApplicable' => '-- ignore this field --',
 	'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!',
 	'UI:CSVImport:NoData' => 'Empty data set..., please provide some data!',
 	'UI:Title:DataPreview' => 'Data Preview',
 	'UI:Title:DataPreview' => 'Data Preview',
 	'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: The data contains only one column. Did you select the appropriate separator character?',
 	'UI:CSVImport:ErrorOnlyOneColumn' => 'Error: The data contains only one column. Did you select the appropriate separator character?',

+ 1 - 1
dictionaries/fr.dictionary.itop.ui.php

@@ -431,7 +431,7 @@ Dict::Add('FR FR', 'French', 'Français', array(
 	'UI:Button:Login' => 'Entrer dans iTop',
 	'UI:Button:Login' => 'Entrer dans iTop',
 	'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.',
 	'UI:Login:Error:AccessRestricted' => 'L\'accès à iTop est soumis à autorisation. Merci de contacter votre administrateur iTop.',
 	'UI:CSVImport:MappingSelectOne' => '-- choisir une valeur --',
 	'UI:CSVImport:MappingSelectOne' => '-- choisir une valeur --',
-	'UI:CSVImport:MappingNotApplicable' => '------ n/a ------',
+	'UI:CSVImport:MappingNotApplicable' => '-- ignorer ce champ --',
 	'UI:CSVImport:NoData' => 'Aucune donnée... merci de fournir des données !',
 	'UI:CSVImport:NoData' => 'Aucune donnée... merci de fournir des données !',
 	'UI:Title:DataPreview' => 'Aperçu des données',
 	'UI:Title:DataPreview' => 'Aperçu des données',
 	'UI:CSVImport:ErrorOnlyOneColumn' => 'Erreur: Les données semblent ne contenir qu\'une seule colonne. Avez-vous choisi le bon séparateur ?',
 	'UI:CSVImport:ErrorOnlyOneColumn' => 'Erreur: Les données semblent ne contenir qu\'une seule colonne. Avez-vous choisi le bon séparateur ?',

+ 73 - 6
pages/ajax.csvimport.php

@@ -31,12 +31,52 @@ require_once('../application/ui.linkswidget.class.inc.php');
 require_once('../application/csvpage.class.inc.php');
 require_once('../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;
+}
+
+/**
  * Helper function to build the mapping drop-down list for a field
  * 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
+ * @return string The HTML code corresponding to the drop-down list for this field
  */
  */
 function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMode = false)
 function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMode = false)
 {
 {
 	$aChoices = array('' => Dict::S('UI:CSVImport:MappingSelectOne'));
 	$aChoices = array('' => Dict::S('UI:CSVImport:MappingSelectOne'));
 	$aChoices[':none:'] = Dict::S('UI:CSVImport:MappingNotApplicable');
 	$aChoices[':none:'] = Dict::S('UI:CSVImport:MappingNotApplicable');
+	$sFieldCode = ''; // Code of the attribute, if there is a match
+	if ($sFieldName == 'id')
+	{
+		$sFieldCode = 'id';
+	}
 	if ($bAdvancedMode)
 	if ($bAdvancedMode)
 	{
 	{
 		$aChoices['id'] = Dict::S('UI:CSVImport:idField');
 		$aChoices['id'] = Dict::S('UI:CSVImport:idField');
@@ -45,7 +85,14 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo
 	{
 	{
 		if ($oAttDef->IsExternalKey())
 		if ($oAttDef->IsExternalKey())
 		{
 		{
-			$aChoices[$sAttCode] = $oAttDef->GetLabel();
+			if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
+			{
+				$sFieldCode = $sAttCode;
+			}
+			if ($bAdvancedMode)
+			{
+				$aChoices[$sAttCode] = $oAttDef->GetLabel();
+			}
 			// Get fields of the external class that are considered as reconciliation keys
 			// Get fields of the external class that are considered as reconciliation keys
 			$sTargetClass = $oAttDef->GetTargetClass();
 			$sTargetClass = $oAttDef->GetTargetClass();
 			foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
 			foreach(MetaModel::ListAttributeDefs($sTargetClass) as $sTargetAttCode => $oTargetAttDef)
@@ -53,28 +100,48 @@ function GetMappingForField($sClassName, $sFieldName, $iFieldIndex, $bAdvancedMo
 				if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
 				if (MetaModel::IsReconcKey($sTargetClass, $sTargetAttCode))
 				{
 				{
 					$aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel();
 					$aChoices[$sAttCode.'->'.$sTargetAttCode] = $oAttDef->GetLabel().'->'.$oTargetAttDef->GetLabel();
+					if (($sFieldName == $aChoices[$sAttCode.'->'.$sTargetAttCode]) || ($sFieldName == ($sAttCode.'->'.$sTargetAttCode)) )
+					{
+						$sFieldCode = $sAttCode.'->'.$sTargetAttCode;
+					}
 				}
 				}
 			}
 			}
 		}
 		}
 		else if ($oAttDef->IsWritable())
 		else if ($oAttDef->IsWritable())
 		{
 		{
 			$aChoices[$sAttCode] = $oAttDef->GetLabel();
 			$aChoices[$sAttCode] = $oAttDef->GetLabel();
-		}
+			if ( ($sFieldName == $oAttDef->GetLabel()) || ($sFieldName == $sAttCode))
+			{
+				$sFieldCode = $sAttCode;
+			}
+		}		
 	}
 	}
 	asort($aChoices);
 	asort($aChoices);
 	
 	
 	$sHtml = "<select id=\"mapping_{$iFieldIndex}\" name=\"field[$iFieldIndex]\">\n";
 	$sHtml = "<select id=\"mapping_{$iFieldIndex}\" name=\"field[$iFieldIndex]\">\n";
+	$bIsIdField = IsIdField($sClassName, $sFieldCode);
 	foreach($aChoices as $sAttCode => $sLabel)
 	foreach($aChoices as $sAttCode => $sLabel)
 	{
 	{
 		$sSelected = '';
 		$sSelected = '';
-		if ( ($sFieldName == $sAttCode) || ($sFieldName == $sLabel))
+		if ($bIsIdField && (!$bAdvancedMode)) // When not in advanced mode, ID are mapped to n/a
 		{
 		{
-			$sSelected = ' selected';
+			if ($sAttCode == ':none:')
+			{
+				$sSelected = ' selected';
+			}
 		}
 		}
-		if ((!$bAdvancedMode) && ($sAttCode == ':none:') && ($sFieldName == 'id'))
+		else if (empty($sFieldCode) && (strpos($sFieldName, '->') !== false))
+		{
+			if ($sAttCode == ':none:')
+			{
+				$sSelected = ' selected';
+			}
+		}
+		else if ($sFieldCode == $sAttCode) // Otherwise map by default if there is a match
 		{
 		{
 			$sSelected = ' selected';
 			$sSelected = ' selected';
 		}
 		}
+
 		$sHtml .= "<option value=\"$sAttCode\"$sSelected>$sLabel</option>\n";
 		$sHtml .= "<option value=\"$sAttCode\"$sSelected>$sLabel</option>\n";
 	}
 	}
 	$sHtml .= "</select>\n";
 	$sHtml .= "</select>\n";
@@ -123,7 +190,7 @@ switch($sOperation)
 		$sMaxLen = (strlen(''.$iTarget) < 3) ? 3 : strlen(''.$iTarget); // Pad line numbers to the appropriate number of chars, but at least 3
 		$sMaxLen = (strlen(''.$iTarget) < 3) ? 3 : strlen(''.$iTarget); // Pad line numbers to the appropriate number of chars, but at least 3
 		$sFormat = '%0'.$sMaxLen.'d';
 		$sFormat = '%0'.$sMaxLen.'d';
 		$oPage->p("<h3>".Dict::S('UI:Title:DataPreview')."</h3>\n");
 		$oPage->p("<h3>".Dict::S('UI:Title:DataPreview')."</h3>\n");
-		$oPage->p("<div style=\"overflow-y:auto\">\n");
+		$oPage->p("<div style=\"overflow-y:auto\" class=\"white\">\n");
 		$oPage->add("<table cellspacing=\"0\" style=\"overflow-y:auto\">");
 		$oPage->add("<table cellspacing=\"0\" style=\"overflow-y:auto\">");
 		$iMaxIndex= 10; // Display maximum 10 lines for the preview
 		$iMaxIndex= 10; // Display maximum 10 lines for the preview
 		$index = 1;
 		$index = 1;

+ 2 - 2
pages/csvimport.php

@@ -499,7 +499,7 @@ function ProcessCSVData(WebPage $oPage, UserContext $oContext, $bSimulate = true
 	$oPage->add('<input type="checkbox" checked id="show_modified" onClick="ToggleRows(\'row_modified\')"/>&nbsp;<img src="../images/modified.png">&nbsp;'.sprintf($aDisplayFilters['modified'], $iModified).'&nbsp&nbsp;');
 	$oPage->add('<input type="checkbox" checked id="show_modified" onClick="ToggleRows(\'row_modified\')"/>&nbsp;<img src="../images/modified.png">&nbsp;'.sprintf($aDisplayFilters['modified'], $iModified).'&nbsp&nbsp;');
 	$oPage->add('<input type="checkbox" checked id="show_created" onClick="ToggleRows(\'row_added\')"/>&nbsp;<img src="../images/added.png">&nbsp;'.sprintf($aDisplayFilters['added'], $iCreated).'&nbsp&nbsp;');
 	$oPage->add('<input type="checkbox" checked id="show_created" onClick="ToggleRows(\'row_added\')"/>&nbsp;<img src="../images/added.png">&nbsp;'.sprintf($aDisplayFilters['added'], $iCreated).'&nbsp&nbsp;');
 	$oPage->add('<input type="checkbox" checked id="show_errors" onClick="ToggleRows(\'row_error\')"/>&nbsp;<img src="../images/error.png">&nbsp;'.sprintf($aDisplayFilters['errors'], $iErrors).'</p>');
 	$oPage->add('<input type="checkbox" checked id="show_errors" onClick="ToggleRows(\'row_error\')"/>&nbsp;<img src="../images/error.png">&nbsp;'.sprintf($aDisplayFilters['errors'], $iErrors).'</p>');
-	$oPage->add('<div style="overflow-y:auto">');
+	$oPage->add('<div style="overflow-y:auto" class="white">');
 	$oPage->add($sHtml);
 	$oPage->add($sHtml);
 	$oPage->add('</div> <!-- end of preview -->');
 	$oPage->add('</div> <!-- end of preview -->');
 	$oPage->add('<p><input type="button" value="'.Dict::S('UI:Button:Back').'" onClick="CSVGoBack()"/>&nbsp;&nbsp;');
 	$oPage->add('<p><input type="button" value="'.Dict::S('UI:Button:Back').'" onClick="CSVGoBack()"/>&nbsp;&nbsp;');
@@ -606,7 +606,7 @@ function SelectMapping(WebPage $oPage)
 	$oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY));
 	$oPage->add(GetClassesSelect('class_name', $sClassName, 300, UR_ACTION_BULK_MODIFY));
 	$oPage->add('</td><td style="text-align:right"><input type="checkbox" name="advanced" value="1" '.IsChecked($bAdvanced, 1).' onChange="DoMapping()">&nbsp;'.Dict::S('UI:CSVImport:AdvancedMode').'</td></tr></table>');
 	$oPage->add('</td><td style="text-align:right"><input type="checkbox" name="advanced" value="1" '.IsChecked($bAdvanced, 1).' onChange="DoMapping()">&nbsp;'.Dict::S('UI:CSVImport:AdvancedMode').'</td></tr></table>');
 	$oPage->add('<div style="padding:1em;display:none" id="advanced_help" style="display:none">'.Dict::S('UI:CSVImport:AdvancedMode+').'</div>');
 	$oPage->add('<div style="padding:1em;display:none" id="advanced_help" style="display:none">'.Dict::S('UI:CSVImport:AdvancedMode+').'</div>');
-	$oPage->add('<div id="mapping"><p><br/>'.Dict::S('UI:CSVImport:SelectAClassFirst').'<br/></p></div>');
+	$oPage->add('<div id="mapping" class="white"><p style="text-align:center;width:100%;font-size:1.5em;padding:1em;">'.Dict::S('UI:CSVImport:SelectAClassFirst').'<br/></p></div>');
 	$oPage->add('<input type="hidden" name="step" value="4"/>');
 	$oPage->add('<input type="hidden" name="step" value="4"/>');
 	$oPage->add('<input type="hidden" name="separator" value="'.htmlentities($sSeparator, ENT_QUOTES, 'UTF-8').'"/>');
 	$oPage->add('<input type="hidden" name="separator" value="'.htmlentities($sSeparator, ENT_QUOTES, 'UTF-8').'"/>');
 	$oPage->add('<input type="hidden" name="text_qualifier" value="'.htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8').'"/>');
 	$oPage->add('<input type="hidden" name="text_qualifier" value="'.htmlentities($sTextQualifier, ENT_QUOTES, 'UTF-8').'"/>');