فهرست منبع

A little bit of polishing on the export feature to be ready for the beta.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3640 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 سال پیش
والد
کامیت
388bfea437

+ 1 - 0
application/utils.inc.php

@@ -802,6 +802,7 @@ class utils
 				// Bulk export actions
 				$aResult[] = new JSPopupMenuItem('UI:Menu:CSVExport', Dict::S('UI:Menu:CSVExport'), "ExportListDlg('$sOQL', '$sDataTableId', 'csv', ".json_encode(Dict::S('UI:Menu:CSVExport')).")");
 				$aResult[] = new JSPopupMenuItem('UI:Menu:ExportXLSX', Dict::S('ExcelExporter:ExportMenu'), "ExportListDlg('$sOQL', '$sDataTableId', 'xlsx', ".json_encode(Dict::S('ExcelExporter:ExportMenu')).")");
+				$aResult[] = new JSPopupMenuItem('UI:Menu:ExportPDF', Dict::S('UI:Menu:ExportPDF'), "ExportListDlg('$sOQL', '$sDataTableId', 'pdf', ".json_encode(Dict::S('UI:Menu:ExportPDF')).")");
 			}	
 			$aResult[] = new JSPopupMenuItem('UI:Menu:AddToDashboard', Dict::S('UI:Menu:AddToDashboard'), "DashletCreationDlg('$sOQL')");
 			$aResult[] = new JSPopupMenuItem('UI:Menu:ShortcutList', Dict::S('UI:Menu:ShortcutList'), "ShortcutListDlg('$sOQL', '$sDataTableId', '$sContext')");

+ 43 - 3
core/attributedef.class.inc.php

@@ -728,7 +728,42 @@ class AttributeLinkedSet extends AttributeDefinition
 
 	public function GetAsXML($sValue, $oHostObject = null, $bLocalize = true)
 	{
-		return "Sorry, no yet implemented";
+		if (is_object($sValue) && ($sValue instanceof DBObjectSet))
+		{
+			$sValue->Rewind();
+			$sRes = "<Set>\n";
+			while ($oObj = $sValue->Fetch())
+			{
+				$sObjClass = get_class($oObj);
+				$sRes .= "<$sObjClass id=\"".$oObj->GetKey()."\">\n";
+				// Show only relevant information (hide the external key to the current object)
+				$aAttributes = array();
+				foreach(MetaModel::ListAttributeDefs($sObjClass) as $sAttCode => $oAttDef)
+				{
+					if ($sAttCode == 'finalclass')
+					{
+						if ($sObjClass == $this->GetLinkedClass())
+						{
+							// Simplify the output if the exact class could be determined implicitely
+							continue;
+						}
+					}
+					if ($sAttCode == $this->GetExtKeyToMe()) continue;
+					if ($oAttDef->IsExternalField()) continue;
+					if (!$oAttDef->IsDirectField()) continue;
+					if (!$oAttDef->IsScalar()) continue;
+					$sAttValue = $oObj->GetAsXML($sAttCode, $bLocalize);
+					$sRes .= "<$sAttCode>$sAttValue</$sAttCode>\n";
+				}
+				$sRes .= "</$sObjClass>\n";
+			}
+			$sRes .= "</Set>\n";
+		}
+		else
+		{
+			$sRes = '';
+		}
+		return $sRes;
 	}
 
 	public function GetAsCSV($sValue, $sSeparator = ',', $sTextQualifier = '"', $oHostObject = null, $bLocalize = true)
@@ -761,7 +796,7 @@ class AttributeLinkedSet extends AttributeDefinition
 					if ($oAttDef->IsExternalField()) continue;
 					if (!$oAttDef->IsDirectField()) continue;
 					if (!$oAttDef->IsScalar()) continue;
-					$sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '');
+					$sAttValue = $oObj->GetAsCSV($sAttCode, $sSepValue, '', $bLocalize);
 					if (strlen($sAttValue) > 0)
 					{
 						$sAttributeData = str_replace($sAttributeQualifier, $sAttributeQualifier.$sAttributeQualifier, $sAttCode.$sSepValue.$sAttValue);
@@ -897,7 +932,8 @@ class AttributeLinkedSet extends AttributeDefinition
 					{
 						throw new CoreException('Wrong attribute code for link attribute specification', array('class' => $sTargetClass, 'attcode' => $sAttCode));
 					}
-					$aValues[$sAttCode] = $sValue;
+					$oAttDef = MetaModel::GetAttributeDef($sTargetClass, $sAttCode);
+					$aValues[$sAttCode] = $oAttDef->MakeValueFromString($sValue, $bLocalizedValue, $sSepItem, $sSepAttribute, $sSepValue, $sAttributeQualifier);
 				}
 			}
 
@@ -4530,6 +4566,10 @@ class AttributeStopWatch extends AttributeDefinition
 					switch($sThresholdCode)
 					{
 					case 'deadline':
+						if ($value != '')
+						{
+							$sRet = $sTextQualifier.date(self::GetDateFormat(true /*full*/), $value).$sTextQualifier;
+						}
 						break;
 
 					case 'passed':

+ 54 - 31
core/csvbulkexport.class.inc.php

@@ -57,6 +57,26 @@ class CSVBulkExport extends TabularBulkExport
 		$this->aStatusInfo['charset'] = strtoupper(utils::ReadParam('character-set', 'UTF-8', true, 'raw_data'));
 	}
 
+
+	protected function SuggestField($aAliases, $sClass, $sAlias, $sAttCode)
+	{
+		switch($sAttCode)
+		{
+			case 'id': // replace 'id' by 'friendlyname'
+				$sAttCode = 'friendlyname';
+				break;
+					
+			default:
+				$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+				if ($oAttDef instanceof AttributeExternalKey)
+				{
+					$sAttCode .= '_friendlyname';
+				}
+		}
+
+		return parent::SuggestField($aAliases, $sClass, $sAlias, $sAttCode);
+	}
+
 	public function EnumFormParts()
 	{
 		return array_merge(parent::EnumFormParts(), array('csv_options' => array('separator', 'character-set', 'text-qualifier', 'no_localize') ,'interactive_fields_csv' => array('interactive_fields_csv')));
@@ -153,6 +173,7 @@ class CSVBulkExport extends TabularBulkExport
 				$aAuthorizedClasses[$sAlias] = $sClassName;
 			}
 		}
+		$aAliases = array_keys($aAuthorizedClasses);
 		$aData = array();
 		foreach($this->aStatusInfo['fields'] as $sExtendedAttCode)
 		{
@@ -163,46 +184,47 @@ class CSVBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aAuthorizedClasses);
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
-			if (!array_key_exists($sAlias, $aAuthorizedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aAuthorizedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 			$sClass = $aAuthorizedClasses[$sAlias];
 				
-			if ($this->aStatusInfo['localize'])
+			switch($sAttCode)
 			{
-				switch($sAttCode)
+				case 'id':
+				$sLabel = 'id';
+				break;
+						
+				default:
+				$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+				if ($oAttDef instanceof AttributeExternalField)
+				{
+					if ($this->aStatusInfo['localize'])
+					{
+						$sStar = $oAttDef->IsNullAllowed() ? '' : '*';
+						$sLabel = $oAttDef->GetKeyAttDef()->GetLabel().$sStar.'->'.$oAttDef->GetExtAttDef()->GetLabel();
+					}
+					else
+					{
+						$sLabel =  $oAttDef->GetKeyAttDef()->GetCode().'->'.$oAttDef->GetExtAttDef()->GetCode();
+					}						
+				}
+				else
 				{
-					case 'id':
-						if (count($aAuthorizedClasses) > 1)
-						{
-							$aData[] = $sAlias.'.id';
-						}
-						else
-						{
-							$aData[] = 'id';
-						}
-						break;
-							
-					default:
-						$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
-						$sLabel = $this->aStatusInfo['localize'] ? $oAttDef->GetLabel() : $sAttCode;
-						if (count($aAuthorizedClasses) > 1)
-						{
-							$aData[] = $sAlias.'.'.$sLabel;
-						}
-						else
-						{
-							$aData[] = $sLabel;
-						}
+					$sLabel = $this->aStatusInfo['localize'] ? $oAttDef->GetLabel() : $sAttCode;
 				}
 			}
+			if (count($aAuthorizedClasses) > 1)
+			{
+				$aData[] = $sAlias.'.'.$sLabel;
+			}
 			else
 			{
-				$aData[] = $sExtendedAttCode;
+				$aData[] = $sLabel;
 			}
 		}
 		$sFrom = array("\r\n", $this->aStatusInfo['text_qualifier']);
@@ -239,6 +261,7 @@ class CSVBulkExport extends TabularBulkExport
 				$aAuthorizedClasses[$sAlias] = $sClassName;
 			}
 		}
+		$aAliases = array_keys($aAuthorizedClasses);
 		$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
 
 		$aAliasByField = array();
@@ -254,13 +277,13 @@ class CSVBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aAuthorizedClasses);
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
 				
-			if (!array_key_exists($sAlias, $aAuthorizedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aAuthorizedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 				
 			if (!array_key_exists($sAlias, $aColumnsToLoad))

+ 31 - 7
core/excelbulkexport.class.inc.php

@@ -102,6 +102,14 @@ class ExcelBulkExport extends TabularBulkExport
 		$this->aStatusInfo['total'] = $oSet->Count();
 
 		$aSelectedClasses = $this->oSearch->GetSelectedClasses();
+		foreach($aSelectedClasses as $sAlias => $sClassName)
+		{
+			if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
+			{
+				$aAuthorizedClasses[$sAlias] = $sClassName;
+			}
+		}
+		$aAliases = array_keys($aAuthorizedClasses);
 		$aTableHeaders = array();
 		foreach($this->aStatusInfo['fields'] as $sExtendedAttCode)
 		{
@@ -112,12 +120,12 @@ class ExcelBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aSelectedClasses);
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
-			if (!array_key_exists($sAlias, $aSelectedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aSelectedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 			$sClass = $aSelectedClasses[$sAlias];
 
@@ -170,6 +178,7 @@ class ExcelBulkExport extends TabularBulkExport
 		$hFile = fopen($this->aStatusInfo['tmp_file'], 'ab');
 		$oSet = new DBObjectSet($this->oSearch);
 		$aSelectedClasses = $this->oSearch->GetSelectedClasses();
+		$aAliases = array_keys($aSelectedClasses);
 		$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
 
 		$aAliasByField = array();
@@ -185,13 +194,13 @@ class ExcelBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aSelectedClasses);
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
 
-			if (!array_key_exists($sAlias, $aSelectedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aSelectedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 
 			if (!array_key_exists($sAlias, $aColumnsToLoad))
@@ -224,7 +233,22 @@ class ExcelBulkExport extends TabularBulkExport
 						break;
 							
 					default:
-						$sField = $aRow[$aAttCode['alias']]->Get($aAttCode['attcode']);
+					$value = $aRow[$aAttCode['alias']]->Get($aAttCode['attcode']);
+					if ($value instanceOf ormCaseLog)
+					{
+						// Extract the case log as text and remove the "===" which make Excel think that the cell contains a formula the next time you edit it!
+						$sField = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText()));
+					}
+					else if ($value instanceOf DBObjectSet)
+					{
+						$oAttDef = MetaModel::GetAttributeDef(get_class($aRow[$aAttCode['alias']]), $aAttCode['attcode']);
+						$sField =  $oAttDef->GetAsCSV($value, '', '', $aRow[$aAttCode['alias']]);
+					}
+					else
+					{
+						$oAttDef = MetaModel::GetAttributeDef(get_class($aRow[$aAttCode['alias']]), $aAttCode['attcode']);
+						$sField =  $oAttDef->GetEditValue($value, $aRow[$aAttCode['alias']]);
+					}
 				}
 				$aData[] = $sField;
 			}

+ 20 - 9
core/htmlbulkexport.class.inc.php

@@ -56,12 +56,22 @@ class HTMLBulkExport extends TabularBulkExport
 
 	public function GetHeader()
 	{
+        $sData = '';
+		
 		$oSet = new DBObjectSet($this->oSearch);
 		$this->aStatusInfo['status'] = 'running';
 		$this->aStatusInfo['position'] = 0;
 		$this->aStatusInfo['total'] = $oSet->Count();
 
 		$aSelectedClasses = $this->oSearch->GetSelectedClasses();
+		foreach($aSelectedClasses as $sAlias => $sClassName)
+		{
+			if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) && (UR_ALLOWED_YES || UR_ALLOWED_DEPENDS))
+			{
+				$aAuthorizedClasses[$sAlias] = $sClassName;
+			}
+		}
+		$aAliases = array_keys($aAuthorizedClasses);
 		$aData = array();
 		foreach($this->aStatusInfo['fields'] as $sExtendedAttCode)
 		{
@@ -72,12 +82,13 @@ class HTMLBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aSelectedClasses);
+				
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
-			if (!array_key_exists($sAlias, $aSelectedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aSelectedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 			$sClass = $aSelectedClasses[$sAlias];
 				
@@ -106,7 +117,7 @@ class HTMLBulkExport extends TabularBulkExport
 					}
 			}
 		}
-		$sData = "<table class=\"listResults\">\n";
+		$sData .= "<table class=\"listResults\">\n";
 		$sData .= "<thead>\n";
 		$sData .= "<tr>\n";
 		foreach($aData as $sLabel)
@@ -126,6 +137,7 @@ class HTMLBulkExport extends TabularBulkExport
 
 		$oSet = new DBObjectSet($this->oSearch);
 		$aSelectedClasses = $this->oSearch->GetSelectedClasses();
+		$aAliases = array_keys($aSelectedClasses);
 		$oSet->SetLimit($this->iChunkSize, $this->aStatusInfo['position']);
 
 		$aAliasByField = array();
@@ -141,13 +153,13 @@ class HTMLBulkExport extends TabularBulkExport
 			}
 			else
 			{
-				$sAlias = reset($aSelectedClasses);
+				$sAlias = reset($aAliases);
 				$sAttCode = $sExtendedAttCode;
 			}
 				
-			if (!array_key_exists($sAlias, $aSelectedClasses))
+			if (!in_array($sAlias, $aAliases))
 			{
-				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", array_keys($aSelectedClasses))."'");
+				throw new Exception("Invalid alias '$sAlias' for the column '$sExtendedAttCode'. Availables aliases: '".implode("', '", $aAliases)."'");
 			}
 				
 			if (!array_key_exists($sAlias, $aColumnsToLoad))
@@ -170,7 +182,7 @@ class HTMLBulkExport extends TabularBulkExport
 		while($aRow = $oSet->FetchAssoc())
 		{
 			set_time_limit($iLoopTimeLimit);
-			$sFirstAlias = reset($aSelectedClasses);
+			$sFirstAlias = reset($aAliases);
 			$sHilightClass = $aRow[$sFirstAlias]->GetHilightClass();
 			if ($sHilightClass != '')
 			{
@@ -222,7 +234,6 @@ class HTMLBulkExport extends TabularBulkExport
 	{
 		$sData = "</tbody>\n";
 		$sData .= "</table>\n";
-
 		return $sData;
 	}
 

+ 14 - 16
core/tabularbulkexport.class.inc.php

@@ -266,18 +266,6 @@ EOF
 		return true; //$oAttDef->IsScalar();
 	}
 
-	/**
-	 * Tells if the specified field is part of the "advanced" fields
-	 * @param unknown $sClass
-	 * @param unknown $sAttCode
-	 * @param AttributeDefinition $oAttDef Can be null when $sAttCode == 'id'
-	 * @return boolean
-	 */
-	protected function IsAdvancedValidField($sClass, $sAttCode, $oAttDef = null)
-	{
-		return (($sAttCode == 'id') || ($oAttDef instanceof AttributeExternalKey));
-	}
-
 	protected function GetSampleData(DBObject $oObj, $sAttCode)
 	{
 		if ($oObj == null) return '';
@@ -306,10 +294,14 @@ EOF
 			if ($oQueries->Count() > 0)
 			{
 				$oQuery = $oQueries->Fetch();
-				$sFields = trim($oQuery->Get('fields'));
-				if ($sFields === '')
+				if (($sFields === null) || ($sFields === ''))
 				{
-					throw new BulkExportMissingParameterException('fields');
+					// No 'fields' parameter supplied, take the fields from the query phrasebook definition
+					$sFields = trim($oQuery->Get('fields'));
+					if ($sFields === '')
+					{
+						throw new BulkExportMissingParameterException('fields');
+					}
 				}
 			}
 			else
@@ -318,6 +310,12 @@ EOF
 			}
 		}
 
-		$this->aStatusInfo['fields'] = explode(',', $sFields);
+		$aFields = explode(',', $sFields);
+		$this->aStatusInfo['fields'] = array();
+		foreach($aFields as $sField)
+		{
+			// Trim the values since it's too temping to write: fields=name, first_name, org_name instead of fields=name,first_name,org_name
+			$this->aStatusInfo['fields'][] = trim($sField);
+		}
 	}
 }

+ 3 - 6
core/xmlbulkexport.class.inc.php

@@ -133,13 +133,10 @@ class XMLBulkExport extends BulkExport
 					}
 					else
 					{
-						if ($oAttDef->IsWritable())
+						if ($oAttDef->IsWritable() )
 						{
-							if (!$oAttDef->IsLinkSet())
-							{
-								$sValue = $oObj->GetAsXML($sAttCode, $bLocalize);
-								$sData .= "<$sAttCode>$sValue</$sAttCode>\n";
-							}
+							$sValue = $oObj->GetAsXML($sAttCode, $bLocalize);
+							$sData .= "<$sAttCode>$sValue</$sAttCode>\n";
 						}
 					}
 				}

+ 2 - 3
css/light-grey.css

@@ -1962,9 +1962,8 @@ table.export_parameters td {
 }
 
 
-.table_preview .nodragtable-sortable li {
-  border: 1px #555555 solid;
-  font-size: 10pt;
+.table_preview .drag-handle {
+  cursor: move;
 }
 
 

+ 2 - 3
css/light-grey.scss

@@ -1450,7 +1450,6 @@ table.export_parameters td {
 	padding-right: 0.25em;
 	font-size: 10pt;
 }
-.table_preview .nodragtable-sortable li {
-	border: 1px $grey-color solid;
-	font-size: 10pt;
+.table_preview .drag-handle {
+	cursor: move;	
 }

+ 3 - 0
dictionaries/de.dictionary.itop.core.php

@@ -582,4 +582,7 @@ Operatoren:<br/>
 	'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:~~',
 	'Core:BulkExportMessageEmptyOQL' => 'Please enter a valid OQL query.~~',
 	'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Please select a valid phrasebook entry.~~',	
+	'Core:BulkExportQueryPlaceholder' => 'Type an OQL query here...~~',
+	'Core:BulkExportCanRunNonInteractive' => 'Click here to run the export in non-interactive mode.~~',
+	'Core:BulkExportLegacyExport' => 'Click here to access the legacy export.~~',
 ));

+ 2 - 0
dictionaries/de.dictionary.itop.ui.php

@@ -1020,5 +1020,7 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
 	'UI:CurrentObjectLockExpired_Explanation' => 'The lock to prevent concurrent modifications of the object has expired. You can no longer submit your modification since other users are now allowed to modify this object.~~',
 	'UI:ConcurrentLockKilled' => 'The lock preventing modifications on the current object has been deleted.~~',
 	'UI:Menu:KillConcurrentLock' => 'Kill the Concurrent Modification Lock !~~',
+	
+	'UI:Menu:ExportPDF' => 'Export as PDF...~~',
 ));
 ?>

+ 3 - 1
dictionaries/dictionary.itop.core.php

@@ -831,5 +831,7 @@ Dict::Add('EN US', 'English', 'English', array(
 	'Core:BulkExportLabelPhrasebookEntry' => 'Query Phrasebook Entry:',
 	'Core:BulkExportMessageEmptyOQL' => 'Please enter a valid OQL query.',
 	'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Please select a valid phrasebook entry.',
-
+	'Core:BulkExportQueryPlaceholder' => 'Type an OQL query here...',
+	'Core:BulkExportCanRunNonInteractive' => 'Click here to run the export in non-interactive mode.',
+	'Core:BulkExportLegacyExport' => 'Click here to access the legacy export.',
 ));

+ 2 - 0
dictionaries/dictionary.itop.ui.php

@@ -1286,5 +1286,7 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:CurrentObjectLockExpired_Explanation' => 'The lock to prevent concurrent modifications of the object has expired. You can no longer submit your modification since other users are now allowed to modify this object.',
 	'UI:ConcurrentLockKilled' => 'The lock preventing modifications on the current object has been deleted.',
 	'UI:Menu:KillConcurrentLock' => 'Kill the Concurrent Modification Lock !',
+	
+	'UI:Menu:ExportPDF' => 'Export as PDF...',
 ));
 ?>

+ 4 - 1
dictionaries/fr.dictionary.itop.core.php

@@ -689,7 +689,10 @@ Opérateurs :<br/>
 	'Core:BulkExport:OptionNoLocalize' => 'Ne pas traduire les valeurs (pour les champs de type "Enum")',
 	'Core:BulkExport:ScopeDefinition' => 'Définition des objets à exporter',
 	'Core:BulkExportLabelOQLExpression' => 'Requête OQL:',
-	'Core:BulkExportLabelPhrasebookEntry' => 'Entrée dans le livre des requêtes:',
+	'Core:BulkExportLabelPhrasebookEntry' => 'Entrée du livre des requêtes:',
 	'Core:BulkExportMessageEmptyOQL' => 'Veuillez saisir une requête OQL valide.',
 	'Core:BulkExportMessageEmptyPhrasebookEntry' => 'Veuillez sélectionner une entrée dans le livre des requêtes.',
+	'Core:BulkExportQueryPlaceholder' => 'Saisissez une requête OQL...',
+	'Core:BulkExportCanRunNonInteractive' => 'Cliquez ici pour exécuter l\'export en mode non-interactif.',
+	'Core:BulkExportLegacyExport' => 'Cliquez ici pour exécuter l\'ancienne version de l\'export.',
 ));

+ 3 - 2
dictionaries/fr.dictionary.itop.ui.php

@@ -1128,5 +1128,6 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:CurrentObjectLockExpired_Explanation' => 'Le verrouillage interdisant les modifications concurrentes a expiré. Vos modifications ne peuvent pas être acceptées car d\'autres utilisateurs peuvent modifier cet objet.',
 	'UI:ConcurrentLockKilled' => 'Le verrouillage en édition de l\'objet courant a été supprimé.',
 	'UI:Menu:KillConcurrentLock' => 'Supprimer le verrouillage !',
-));
-?>
+	
+	'UI:Menu:ExportPDF' => 'Exporter en PDF...',
+));

+ 9 - 4
js/tabularfieldsselector.js

@@ -36,11 +36,10 @@ $(function()
 			this.element.parent().bind('validate', function() { me.validate(); });
 			
 			this.aSelected = [];
-			var sContent = '';
 			
 			for(var i in this.options.fields)
 			{
-				sContent += '<fieldset><legend>'+this._format(this.options.labels.columns_selection, i)+'</legend>';
+				var sContent = '<fieldset><legend>'+this._format(this.options.labels.columns_selection, i)+'</legend>';
 				sContent += '<div style="text-align:right"><button class="check_all" type="button">'+this.options.labels.check_all+'</button>&nbsp;<button class="uncheck_all" type="button">'+this.options.labels.uncheck_all+'</button></div>';
 				for(var j in this.options.fields[i])
 				{
@@ -409,6 +408,7 @@ $(function()
 					return sTooltipContent;
 				},
 				items: '.tfs_advanced',
+				tooltipClass: 'tooltip-tfs',
 				position: {
 					my: "center bottom-10",
 					at: "center  top",					
@@ -458,6 +458,9 @@ $(function()
 				var sDataId = $(this).attr('data-attcode');
 				$('#'+me.sId+' .tfs_advanced[data-attcode="'+sDataId+'"]').tooltip('close');
 			});
+			this.element.parent().on("click", ":not(.tooltip-tfs *,.tooltip-tfs)", function(){
+				me.close_all_tooltips();
+			});
 		},
 		_get_tooltip_content: function(sDataAttCode)
 		{
@@ -505,9 +508,11 @@ $(function()
 			}
 			return sContent;
 		},
-		_close_all_tooltips: function()
+		close_all_tooltips: function()
 		{
-			this.element.find('.tfs_item').tooltip('close');
+			$('.tfs_advanced').each(function (i) {  
+				$(this).tooltip("close"); 
+			});
 		}
 	});	
 });

+ 1 - 0
js/utils.js

@@ -483,6 +483,7 @@ function ExportStartExport()
 			}
 		}
 	});
+	$(':itop-tabularfieldsselector:visible').tabularfieldsselector('close_all_tooltips');
 	$('#export-form').hide();
 	$('#export-feedback').show();
 	oParams.operation = 'export_build';

+ 30 - 9
webservices/export-v2.php

@@ -123,7 +123,7 @@ function DisplayExpressionForm(WebPage $oP, $sAction, $sExpression = '', $sExcep
 	$oP->add('<table style="width:100%" class="export_parameters">');
 	$sExpressionHint = empty($sExceptionMessage) ? '' : '<tr><td colspan="2">'.htmlentities($sExceptionMessage, ENT_QUOTES, 'UTF-8').'</td></tr>';
 	$oP->add('<tr><td class="column-label"><span style="white-space: nowrap;"><input type="radio" name="query_mode" value="oql" id="radio_oql" checked><label for="radio_oql">'.Dict::S('Core:BulkExportLabelOQLExpression').'</label></span></td>');
-	$oP->add('<td><textarea style="width:100%" cols="70" rows="8" name="expression" id="textarea_oql" placeholder="SELECT Server">'.htmlentities($sExpression, ENT_QUOTES, 'UTF-8').'</textarea></td></tr>');
+	$oP->add('<td><textarea style="width:100%" cols="70" rows="8" name="expression" id="textarea_oql" placeholder="'.Dict::S('Core:BulkExportQueryPlaceholder').'">'.htmlentities($sExpression, ENT_QUOTES, 'UTF-8').'</textarea></td></tr>');
 	$oP->add($sExpressionHint);
 	$oP->add('<tr><td class="column-label"><span style="white-space: nowrap;"><input type="radio" name="query_mode" value="phrasebook" id="radio_phrasebook"><label for="radio_phrasebook">'.Dict::S('Core:BulkExportLabelPhrasebookEntry').'</label></span></td>');
 	$oP->add('<td><select name="query" id="select_phrasebook">');
@@ -139,6 +139,8 @@ function DisplayExpressionForm(WebPage $oP, $sAction, $sExpression = '', $sExcep
 	$oP->add('</table>');
 	$oP->add('</form>');
 	$oP->add('</fieldset>');
+	$oP->p('<a target="_blank" href="'.utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php">'.Dict::S('Core:BulkExportCanRunNonInteractive').'</a>');
+	$oP->p('<a target="_blank" href="'.utils::GetAbsoluteUrlAppRoot().'webservices/export.php">'.Dict::S('Core:BulkExportLegacyExport').'</a>');
 	$sJSEmptyOQL = json_encode(Dict::S('Core:BulkExportMessageEmptyOQL'));
 	$sJSEmptyQueryId = json_encode(Dict::S('Core:BulkExportMessageEmptyPhrasebookEntry'));
 	
@@ -232,12 +234,14 @@ function DisplayForm(WebPage $oP, $sAction = '', $sExpression = '', $sQueryId =
 	if ($sFormat == null)
 	{
 		// No specific format chosen
+		$sDefaultFormat = utils::ReadParam('format', 'xlsx');
 		$oP->add('<p>'.Dict::S('Core:BulkExport:ExportFormatPrompt').' <select name="format" id="format_selector">');
 		$aSupportedFormats = BulkExport::FindSupportedFormats();
 		asort($aSupportedFormats);
 		foreach($aSupportedFormats as $sFormatCode => $sLabel)
 		{
-			$oP->add('<option value="'.$sFormatCode.'">'.htmlentities($sLabel, ENT_QUOTES, 'UTF-8').'</option>');
+			$sSelected = ($sFormatCode == $sDefaultFormat) ? 'selected' : '';
+			$oP->add('<option value="'.$sFormatCode.'" '.$sSelected.'>'.htmlentities($sLabel, ENT_QUOTES, 'UTF-8').'</option>');
 			$oExporter = BulkExport::FindExporter($sFormatCode);
 			$oExporter->SetObjectList($oExportSearch);
 			$aParts = $oExporter->EnumFormParts();
@@ -319,7 +323,7 @@ function InteractiveShell($sExpression, $sQueryId, $sFormat, $sFileName, $sMode)
 			modal: true,
 			width: '80%',
 			title: $sJSTitle,
-			close: function() { $(this).remove(); },
+			close: function() { $('#export-form').attr('data-state', 'cancelled'); $(this).remove(); },
 			buttons: [
 				{text: $sExportBtnLabel, id: 'export-dlg-submit', click: function() {} }
 			]
@@ -345,10 +349,7 @@ EOF
 			{
 				$oQuery = $oQueries->Fetch();
 				$sExpression = $oQuery->Get('oql');
-				if (strlen($sFields) == 0)
-				{
-					$sFields = trim($oQuery->Get('fields'));
-				}
+				$sFields = trim($oQuery->Get('fields'));
 			}
 			else
 			{
@@ -396,6 +397,14 @@ EOF
 	$oP->output();	
 }
 
+/**
+ * Checks the parameters and returns the appropriate exporter (if any)
+ * @param string $sExpression The OQL query to export or null
+ * @param string $sQueryId The entry in the query phrasebook if $sExpression is null
+ * @param string $sFormat The code of export format: csv, pdf, html, xlsx
+ * @throws MissingQueryArgument
+ * @return Ambigous <iBulkExport, NULL>
+ */
 function CheckParameters($sExpression, $sQueryId, $sFormat)
 {
 	$oExporter  = null;	
@@ -660,7 +669,18 @@ try
 	else 
 	{
 		$oExporter = CheckParameters($sExpression, $sQueryId, $sFormat);
-		$oP = new WebPage('iTop export');
+		$sMimeType = $oExporter->GetMimeType();
+		if ($sMimeType == 'text/html')
+		{
+			$oP = new NiceWebPage('iTop export');
+			$oP->add_style("body { overflow: auto; }");
+			$oP->add_ready_script("$('table.listResults').tablesorter({widgets: ['MyZebra']});");
+		}
+		else
+		{
+			$oP = new ajax_page('iTop export');
+			$oP->SetContentType($oExporter->GetMimeType());
+		}
 		DoExport($oP, $oExporter, false);
 		$oP->output();
 	}
@@ -675,6 +695,7 @@ catch (BulkExportMissingParameterException $e)
 catch (Exception $e)
 {
 	$oP = new WebPage('iTop Export');
-	$oP->add($e->getMessage()."<br/>".$e->getTraceAsString());
+	$oP->add('Error: '.$e->getMessage());
+	IssueLog::Error($e->getMessage()."\n".$e->getTraceAsString());
 	$oP->output();
 }