diff --git a/application/pdfpage.class.inc.php b/application/pdfpage.class.inc.php index 84c58bfcc..e1c50c59e 100644 --- a/application/pdfpage.class.inc.php +++ b/application/pdfpage.class.inc.php @@ -102,9 +102,11 @@ class PDFPage extends WebPage { $this->add_style( <<'); fwrite($fd, ''); fwrite($fd, ''); - fwrite($fd, ''); + fwrite($fd, ''); fwrite($fd, ''); fwrite($fd, ''); fwrite($fd, ''); diff --git a/core/bulkexport.class.inc.php b/core/bulkexport.class.inc.php index 46710d04a..4b54bc73e 100644 --- a/core/bulkexport.class.inc.php +++ b/core/bulkexport.class.inc.php @@ -281,9 +281,13 @@ abstract class BulkExport return array(); // return array('csv' => Dict::S('UI:ExportFormatCSV')); } + + public function SetHttpHeaders(WebPage $oPage) + { + } + public function GetHeader() { - } abstract public function GetNextChunk(&$aStatus); public function GetFooter() diff --git a/core/csvbulkexport.class.inc.php b/core/csvbulkexport.class.inc.php index 1a55444c1..672b23729 100644 --- a/core/csvbulkexport.class.inc.php +++ b/core/csvbulkexport.class.inc.php @@ -58,7 +58,7 @@ class CSVBulkExport extends TabularBulkExport } - protected function SuggestField($aAliases, $sClass, $sAlias, $sAttCode) + protected function SuggestField($sClass, $sAttCode) { switch($sAttCode) { @@ -74,7 +74,7 @@ class CSVBulkExport extends TabularBulkExport } } - return parent::SuggestField($aAliases, $sClass, $sAlias, $sAttCode); + return parent::SuggestField($sClass, $sAttCode); } public function EnumFormParts() @@ -170,13 +170,19 @@ class CSVBulkExport extends TabularBulkExport protected function GetSampleData($oObj, $sAttCode) { - if ($oObj) + return '
'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'
'; + } + + protected function GetValue($oObj, $sAttCode) + { + switch($sAttCode) { - $sRet = trim($oObj->GetAsCSV($sAttCode), '"'); - } - else - { - $sRet = ''; + case 'id': + $sRet = $oObj->GetKey(); + break; + + default: + $sRet = trim($oObj->GetAsCSV($sAttCode), '"'); } return $sRet; } diff --git a/core/dbsearch.class.php b/core/dbsearch.class.php index 67ca84e78..28597d1b3 100644 --- a/core/dbsearch.class.php +++ b/core/dbsearch.class.php @@ -392,7 +392,7 @@ abstract class DBSearch $sRes = $oSQLQuery->RenderSelect($aOrderSpec, $aScalarArgs, $iLimitCount, $iLimitStart, $bGetCount, $bBeautifulSQL); if ($sClassAlias == '_itop_') { - echo $sRes."
\n"; + IssueLog::Info('SQL Query (_itop_): '.$sRes); } } catch (MissingQueryArgument $e) diff --git a/core/excelbulkexport.class.inc.php b/core/excelbulkexport.class.inc.php index 0b7ec92eb..223391bc3 100644 --- a/core/excelbulkexport.class.inc.php +++ b/core/excelbulkexport.class.inc.php @@ -67,7 +67,7 @@ class ExcelBulkExport extends TabularBulkExport } } - protected function SuggestField($aAliases, $sClass, $sAlias, $sAttCode) + protected function SuggestField($sClass, $sAttCode) { switch($sAttCode) { @@ -83,7 +83,41 @@ class ExcelBulkExport extends TabularBulkExport } } - return parent::SuggestField($aAliases, $sClass, $sAlias, $sAttCode); + return parent::SuggestField($sClass, $sAttCode); + } + + protected function GetSampleData($oObj, $sAttCode) + { + return '
'.htmlentities($this->GetValue($oObj, $sAttCode), ENT_QUOTES, 'UTF-8').'
'; + } + + protected function GetValue($oObj, $sAttCode) + { + switch($sAttCode) + { + case 'id': + $sRet = $oObj->GetKey(); + break; + + default: + $value = $oObj->Get($sAttCode); + 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! + $sRet = trim(preg_replace('/========== ([^=]+) ============/', '********** $1 ************', $value->GetText())); + } + else if ($value instanceOf DBObjectSet) + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetAsCSV($value, '', '', $oObj); + } + else + { + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + $sRet = $oAttDef->GetEditValue($value, $oObj); + } + } + return $sRet; } public function GetHeader() @@ -155,30 +189,7 @@ class ExcelBulkExport extends TabularBulkExport $sField = ''; if ($oObj) { - switch($sAttCode) - { - case 'id': - $sField = $oObj->GetKey(); - break; - - default: - $value = $oObj->Get($sAttCode); - 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($oObj), $sAttCode); - $sField = $oAttDef->GetAsCSV($value, '', '', $oObj); - } - else - { - $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); - $sField = $oAttDef->GetEditValue($value, $oObj); - } - } + $sField = $this->GetValue($oObj, $sAttCode); } $aData[] = $sField; } diff --git a/core/htmlbulkexport.class.inc.php b/core/htmlbulkexport.class.inc.php index d742beb14..9bc92ebda 100644 --- a/core/htmlbulkexport.class.inc.php +++ b/core/htmlbulkexport.class.inc.php @@ -51,13 +51,31 @@ class HTMLBulkExport extends TabularBulkExport protected function GetSampleData($oObj, $sAttCode) { - if ($oObj) + return $this->GetValue($oObj, $sAttCode); + } + + protected function GetValue($oObj, $sAttCode) + { + switch($sAttCode) { - $sRet = $oObj->GetAsHTML($sAttCode); - } - else - { - $sRet = ''; + case 'id': + $sRet = $oObj->GetHyperlink(); + break; + + default: + $value = $oObj->Get($sAttCode); + if ($value instanceof ormCaseLog) + { + $sRet = $value->GetAsSimpleHtml(); + } + elseif ($value instanceof ormStopWatch) + { + $sRet = $value->GetTimeSpent(); + } + else + { + $sRet = $oObj->GetAsHtml($sAttCode); + } } return $sRet; } @@ -125,15 +143,7 @@ class HTMLBulkExport extends TabularBulkExport $sField = ''; if ($oObj) { - switch($sAttCode) - { - case 'id': - $sField = $aRow[$sAlias]->GetHyperlink(); - break; - - default: - $sField = $aRow[$sAlias]->GetAsHtml($sAttCode); - } + $sField = $this->GetValue($oObj, $sAttCode); } $sValue = ($sField === '') ? ' ' : $sField; $sData .= "$sValue"; diff --git a/core/metamodel.class.php b/core/metamodel.class.php index 70a2150f4..8e26c5d6e 100644 --- a/core/metamodel.class.php +++ b/core/metamodel.class.php @@ -429,6 +429,25 @@ abstract class MetaModel return $oNameExpr; } + /** + * Returns the friendly name IIF it is equivalent to a single attribute + */ + final static public function GetFriendlyNameAttributeCode($sClass) + { + $aNameSpec = self::GetNameSpec($sClass); + $sFormat = trim($aNameSpec[0]); + $aAttributes = $aNameSpec[1]; + if (($sFormat != '') && ($sFormat != '%1$s')) + { + return null; + } + if (count($aAttributes) > 1) + { + return null; + } + return reset($aAttributes); + } + final static public function GetStateAttributeCode($sClass) { self::_check_subclass($sClass); diff --git a/core/ormcaselog.class.inc.php b/core/ormcaselog.class.inc.php index 9e5ed6c0b..7d7cf46e5 100644 --- a/core/ormcaselog.class.inc.php +++ b/core/ormcaselog.class.inc.php @@ -212,6 +212,81 @@ class ormCaseLog { return $sHtml; } + /** + * Produces an HTML representation, aimed at being used to produce a PDF with TCPDF (no table) + */ + public function GetAsSimpleHtml() + { + $sStyleCaseLogHeader = ''; + $sStyleCaseLogEntry = ''; + + $sHtml = '
    '; + $iPos = 0; + $aIndex = $this->m_aIndex; + for($index=count($aIndex)-1 ; $index >= 0 ; $index--) + { + $iPos += $aIndex[$index]['separator_length']; + $sTextEntry = substr($this->m_sLog, $iPos, $aIndex[$index]['text_length']); + $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8')); + $iPos += $aIndex[$index]['text_length']; + + $sEntry = '
  • '; + // Workaround: PHP < 5.3 cannot unserialize correctly DateTime objects, + // therefore we have changed the format. To preserve the compatibility with existing + // installations of iTop, both format are allowed: + // the 'date' item is either a DateTime object, or a unix timestamp + if (is_int($aIndex[$index]['date'])) + { + // Unix timestamp + $sDate = date(Dict::S('UI:CaseLog:DateFormat'),$aIndex[$index]['date']); + } + elseif (is_object($aIndex[$index]['date'])) + { + if (version_compare(phpversion(), '5.3.0', '>=')) + { + // DateTime + $sDate = $aIndex[$index]['date']->format(Dict::S('UI:CaseLog:DateFormat')); + } + else + { + // No Warning... but the date is unknown + $sDate = ''; + } + } + $sEntry .= sprintf(Dict::S('UI:CaseLog:Header_Date_UserName'), ''.$sDate.'', ''.$aIndex[$index]['user_name'].''); + $sEntry .= '
    '; + $sEntry .= $sTextEntry; + $sEntry .= '
    '; + $sEntry .= '
  • '; + $sHtml = $sHtml.$sEntry; + } + + // Process the case of an eventual remainder (quick migration of AttributeText fields) + if ($iPos < (strlen($this->m_sLog) - 1)) + { + $sTextEntry = substr($this->m_sLog, $iPos); + $sTextEntry = str_replace(array("\r\n", "\n", "\r"), "
    ", htmlentities($sTextEntry, ENT_QUOTES, 'UTF-8')); + + if (count($this->m_aIndex) == 0) + { + $sHtml .= '
  • '; + $sHtml .= $sTextEntry; + $sHtml .= '
  • '; + } + else + { + $sHtml .= '
  • '; + $sHtml .= Dict::S('UI:CaseLog:InitialValue'); + $sHtml .= '
    '; + $sHtml .= $sTextEntry; + $sHtml .= '
    '; + $sHtml .= '
  • '; + } + } + $sHtml .= '
'; + return $sHtml; + } + /** * Produces an HTML representation, aimed at being used within the iTop framework */ diff --git a/core/pdfbulkexport.class.inc.php b/core/pdfbulkexport.class.inc.php index 54f5feb4e..0b70f32d7 100644 --- a/core/pdfbulkexport.class.inc.php +++ b/core/pdfbulkexport.class.inc.php @@ -124,6 +124,9 @@ class PDFBulkExport extends HTMLBulkExport $sData = parent::GetFooter(); $oPage = new PDFPage(Dict::Format('Core:BulkExportOf_Class', MetaModel::GetName($this->oSearch->GetClass())), $this->aStatusInfo['page_size'], $this->aStatusInfo['page_orientation']); + $oPDF = $oPage->get_tcpdf(); + $oPDF->SetFont('dejavusans', '', 8, '', true); + $oPage->add(file_get_contents($this->aStatusInfo['tmp_file'])); $oPage->add($sData); diff --git a/core/spreadsheetbulkexport.class.inc.php b/core/spreadsheetbulkexport.class.inc.php index 5b8e1dd33..bc979a1ae 100644 --- a/core/spreadsheetbulkexport.class.inc.php +++ b/core/spreadsheetbulkexport.class.inc.php @@ -63,17 +63,57 @@ class SpreadsheetBulkExport extends TabularBulkExport protected function GetSampleData($oObj, $sAttCode) { - if ($oObj) + return $this->GetValue($oObj, $sAttCode); + } + + protected function GetValue($oObj, $sAttCode) + { + switch($sAttCode) { - $sRet = $oObj->GetAsHTML($sAttCode); - } - else - { - $sRet = ''; + case 'id': + $sRet = $oObj->GetKey(); + break; + + default: + $value = $oObj->Get($sAttCode); + $oAttDef = MetaModel::GetAttributeDef(get_class($oObj), $sAttCode); + if ($value instanceof ormCaseLog) + { + $sRet = str_replace("\n", "
", htmlentities($value->__toString(), ENT_QUOTES, 'UTF-8')); + } + elseif ($value instanceof ormStopWatch) + { + $sRet = $value->GetTimeSpent(); + } + elseif ($oAttDef instanceof AttributeString) + { + $sRet = $oObj->GetAsHTML($sAttCode); + } + else + { + if ($this->bLocalizeOutput) + { + $sRet = htmlentities($oObj->GetEditValue(), ENT_QUOTES, 'UTF-8'); + } + else + { + $sRet = htmlentities($value, ENT_QUOTES, 'UTF-8'); + } + } } + return $sRet; } + public function SetHttpHeaders(WebPage $oPage) + { + // Integration within MS-Excel web queries + HTTPS + IIS: + // MS-IIS set these header values with no-cache... while Excel fails to do the job if using HTTPS + // Then the fix is to force the reset of header values Pragma and Cache-control + $oPage->add_header("Pragma:", true); + $oPage->add_header("Cache-control:", true); + } + public function GetHeader() { $oSet = new DBObjectSet($this->oSearch); @@ -99,8 +139,14 @@ class SpreadsheetBulkExport extends TabularBulkExport $aData[] = $sColLabel; } } + else + { + $aData[] = $sColLabel; + } } - $sData = "\n"; + $sData = ''; + $sData .= ''; // Trick for Excel: keep line breaks inside the same cell ! + $sData .= "
\n"; $sData .= "\n"; foreach($aData as $sLabel) { @@ -137,14 +183,14 @@ class SpreadsheetBulkExport extends TabularBulkExport $oObj = $aRow[$sAlias]; if ($oObj == null) { - $sData .= ""; + $sData .= ""; continue; } switch($sAttCode) { case 'id': - $sField = $oObj->GetName(); + $sField = $oObj->GetKey(); $sData .= ""; break; @@ -165,22 +211,14 @@ class SpreadsheetBulkExport extends TabularBulkExport // Trick for Excel: treat the content as text even if it begins with an equal sign $sData .= ""; } + else if($oAttDef instanceof AttributeString) + { + $sField = $oObj->GetAsHTML($sAttCode); + $sData .= ""; + } else { $rawValue = $oObj->Get($sAttCode); - // Due to custom formatting rules, empty friendlynames may be rendered as non-empty strings - // let's fix this and make sure we render an empty string if the key == 0 - if ($oAttDef instanceof AttributeFriendlyName) - { - $sKeyAttCode = $oAttDef->GetKeyAttCode(); - if ($sKeyAttCode != 'id') - { - if ($oObj->Get($sKeyAttCode) == 0) - { - $sValue = ''; - } - } - } if ($this->bLocalizeOutput) { $sField = htmlentities($oFinalAttDef->GetEditValue($rawValue), ENT_QUOTES, 'UTF-8'); diff --git a/core/tabularbulkexport.class.inc.php b/core/tabularbulkexport.class.inc.php index 8a90b246e..1dd067f5f 100644 --- a/core/tabularbulkexport.class.inc.php +++ b/core/tabularbulkexport.class.inc.php @@ -47,12 +47,13 @@ abstract class TabularBulkExport extends BulkExport break; default: - return parent:: DisplayFormPart($oP, $sPartId); + return parent::DisplayFormPart($oP, $sPartId); } } protected function SuggestFields($aSuggestedFields) { + $aRet = array(); // By defaults all fields are Ok, nothing gets translated but // you can overload this method if some fields are better exported // (in a given format) by using an alternate field, for example id => friendlyname @@ -71,20 +72,75 @@ abstract class TabularBulkExport extends BulkExport $sAttCode = $sField; $sClass = reset($aAliases); } - $aSuggestedFields[$idx] = $this->SuggestField($aAliases, $sClass, $sAlias, $sAttCode); + $sMostRelevantField = $this->SuggestField($sClass, $sAttCode); + $sAttCodeEx = $this->NormalizeFieldSpec($sClass, $sMostRelevantField); + // Remove the aliases (if any) from the field names to make them compatible + // with the 'short' notation used in this case by the widget + if (count($aAliases) > 1) + { + $sAttCodeEx = $sAlias.'.'.$sAttCodeEx; + } + $aRet[] = $sAttCodeEx; } - return $aSuggestedFields; + return $aRet; } - protected function SuggestField($aAliases, $sClass, $sAlias, $sAttCode) + protected function SuggestField($sClass, $sAttCode) { - // Remove the aliases (if any) from the field names to make them compatible - // with the 'short' notation used in this case by the widget - if (count($aAliases) == 1) + return $sAttCode; + } + + /** + * Given a field spec, get the most relevant (unique) representation + * Examples for a user request: + * - friendlyname => ref + * - org_name => org_id->name + * - org_id_friendlyname => org_id=>name + * - caller_name => caller_id->name + * - caller_id_friendlyname => caller_id->friendlyname + */ + protected function NormalizeFieldSpec($sClass, $sField) + { + $sRet = $sField; + + if ($sField == 'id') { - return $sAttCode; + $sRet = 'id'; } - return $sAlias.'.'.$sAttCode; + elseif ($sField == 'friendlyname') + { + $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sClass); + if (!is_null($sFriendlyNameAttCode)) + { + // The friendly name is made of a single attribute + $sRet = $sFriendlyNameAttCode; + } + } + else + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sField); + if ($oAttDef instanceof AttributeFriendlyName) + { + $oKeyAttDef = MetaModel::GetAttributeDef($sClass, $oAttDef->GetKeyAttCode()); + $sRemoteClass = $oKeyAttDef->GetTargetClass(); + $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sRemoteClass); + if (is_null($sFriendlyNameAttCode)) + { + // The friendly name is made of several attributes + $sRet = $oAttDef->GetKeyAttCode().'->friendlyname'; + } + else + { + // The friendly name is made of a single attribute + $sRet = $oAttDef->GetKeyAttCode().'->'.$sFriendlyNameAttCode; + } + } + elseif ($oAttDef->IsExternalField()) + { + $sRet = $oAttDef->GetKeyAttCode().'->'.$oAttDef->GetExtAttCode(); + } + } + return $sRet; } protected function IsSubAttribute($sClass, $sAttCode, $oAttDef) @@ -99,19 +155,64 @@ abstract class TabularBulkExport extends BulkExport { case 'AttributeExternalKey': case 'AttributeHierarchicalKey': - $aResult[] = array('code' => $sAttCode, 'unique_label' => $oAttDef->GetLabel(), 'label' => Dict::S('Core:BulkExport:Identifier'), 'attdef' => $oAttDef); - $aResult[] = array('code' => $sAttCode.'_friendlyname', 'unique_label' => $oAttDef->GetLabel().'->'.Dict::S('Core:FriendlyName-Label'), 'label' => Dict::S('Core:FriendlyName-Label'), 'attdef' => MetaModel::GetAttributeDef($sClass, $sAttCode.'_friendlyname')); + + $bAddFriendlyName = true; + $oKeyAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + $sRemoteClass = $oKeyAttDef->GetTargetClass(); + $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sRemoteClass); + if (!is_null($sFriendlyNameAttCode)) + { + // The friendly name is made of a single attribute, check if that attribute is present as an external field + foreach(MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef) + { + if ($oSubAttDef instanceof AttributeExternalField) + { + if (($oSubAttDef->GetKeyAttCode() == $sAttCode) && ($oSubAttDef->GetExtAttCode() == $sFriendlyNameAttCode)) + { + $bAddFriendlyName = false; + } + } + } + } + + $aResult[$sAttCode] = array('code' => $sAttCode, 'unique_label' => $oAttDef->GetLabel(), 'label' => Dict::S('UI:CSVImport:idField'), 'attdef' => $oAttDef); + + if ($bAddFriendlyName) + { + if ($this->IsExportableField($sClass, $sAttCode.'->friendlyname')) + { + $aResult[$sAttCode.'->friendlyname'] = array('code' => $sAttCode.'->friendlyname', 'unique_label' => $oAttDef->GetLabel().'->'.Dict::S('Core:FriendlyName-Label'), 'label' => Dict::S('Core:FriendlyName-Label'), 'attdef' => MetaModel::GetAttributeDef($sClass, $sAttCode.'_friendlyname')); + } + } foreach(MetaModel::ListAttributeDefs($sClass) as $sSubAttCode => $oSubAttDef) { if ($oSubAttDef instanceof AttributeExternalField) { - if ($oSubAttDef->GetKeyAttCode() == $sAttCode) + if ($this->IsExportableField($sClass, $sSubAttCode, $oSubAttDef)) { - $aResult[] = array('code' => $sSubAttCode, 'unique_label' => $oAttDef->GetLabel().'->'.$oSubAttDef->GetExtAttDef()->GetLabel(), 'label' => $oSubAttDef->GetExtAttDef()->GetLabel(), 'attdef' => $oSubAttDef); + if ($oSubAttDef->GetKeyAttCode() == $sAttCode) + { + $sAttCodeEx = $sAttCode.'->'.$oSubAttDef->GetExtAttCode(); + $aResult[$sAttCodeEx] = array('code' => $sAttCodeEx, 'unique_label' => $oAttDef->GetLabel().'->'.$oSubAttDef->GetExtAttDef()->GetLabel(), 'label' => MetaModel::GetLabel($sRemoteClass, $oSubAttDef->GetExtAttCode()), 'attdef' => $oSubAttDef); + } } } } + + // Add the reconciliation keys + foreach(MetaModel::GetReconcKeys($sRemoteClass) as $sRemoteAttCode) + { + $sAttCodeEx = $sAttCode.'->'.$sRemoteAttCode; + if (!array_key_exists($sAttCodeEx, $aResult)) + { + $oRemoteAttDef = MetaModel::GetAttributeDef($sRemoteClass, $sRemoteAttCode); + if ($this->IsExportableField($sRemoteClass, $sRemoteAttCode, $oRemoteAttDef)) + { + $aResult[$sAttCodeEx] = array('code' => $sAttCodeEx, 'unique_label' => $oAttDef->GetLabel().'->'.$oRemoteAttDef->GetLabel(), 'label' => MetaModel::GetLabel($sRemoteClass, $sRemoteAttCode), 'attdef' => $oRemoteAttDef); + } + } + } break; case 'AttributeStopWatch': @@ -121,12 +222,14 @@ abstract class TabularBulkExport extends BulkExport { if ($oSubAttDef->GetParentAttCode() == $sAttCode) { - $aResult[] = array('code' => $sSubAttCode, 'unique_label' => $oSubAttDef->GetLabel(), 'label' => $oSubAttDef->GetLabel(), 'attdef' => $oSubAttDef); + if ($this->IsExportableField($sClass, $sSubAttCode, $oSubAttDef)) + { + $aResult[$sSubAttCode] = array('code' => $sSubAttCode, 'unique_label' => $oSubAttDef->GetLabel(), 'label' => $oSubAttDef->GetLabel(), 'attdef' => $oSubAttDef); + } } } } break; - } return $aResult; } @@ -144,6 +247,7 @@ abstract class TabularBulkExport extends BulkExport } } $aAllFieldsByAlias = array(); + $aAllAttCodes = array(); foreach($aAuthorizedClasses as $sAlias => $sClass) { $aAllFields = array(); @@ -155,18 +259,29 @@ abstract class TabularBulkExport extends BulkExport { $sShortAlias = ''; } - if ($this->IsValidField($sClass, 'id')) + if ($this->IsExportableField($sClass, 'id')) { - $aAllFields[] = array('code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('Core:BulkExport:Identifier'), 'label' => $sShortAlias.'id', 'subattr' => array( - array('code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('Core:BulkExport:Identifier'), 'label' => $sShortAlias.'id'), - array('code' => $sShortAlias.'friendlyname', 'unique_label' => $sShortAlias.Dict::S('Core:FriendlyName-Label'), 'label' => $sShortAlias.Dict::S('Core:FriendlyName-Label')), - )); + $sFriendlyNameAttCode = MetaModel::GetFriendlyNameAttributeCode($sClass); + if (is_null($sFriendlyNameAttCode)) + { + // The friendly name is made of several attribute + $aSubAttr = array( + array('attcodeex' => 'id', 'code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('UI:CSVImport:idField'), 'label' => $sShortAlias.'id'), + array('attcodeex' => 'friendlyname', 'code' => $sShortAlias.'friendlyname', 'unique_label' => $sShortAlias.Dict::S('Core:FriendlyName-Label'), 'label' => $sShortAlias.Dict::S('Core:FriendlyName-Label')), + ); } + else + { + // The friendly name has no added value + $aSubAttr = array(); + } + $aAllFields[] = array('attcodeex' => 'id', 'code' => $sShortAlias.'id', 'unique_label' => $sShortAlias.Dict::S('UI:CSVImport:idField'), 'label' => Dict::S('UI:CSVImport:idField'), 'subattr' => $aSubAttr); + } foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) { if($this->IsSubAttribute($sClass, $sAttCode, $oAttDef)) continue; - if ($this->IsValidField($sClass, $sAttCode, $oAttDef)) + if ($this->IsExportableField($sClass, $sAttCode, $oAttDef)) { $sShortLabel = $oAttDef->GetLabel(); $sLabel = $sShortAlias.$oAttDef->GetLabel(); @@ -174,12 +289,9 @@ abstract class TabularBulkExport extends BulkExport $aValidSubAttr = array(); foreach($aSubAttr as $aSubAttDef) { - if ($this->IsValidField($sClass, $aSubAttDef['code'], $aSubAttDef['attdef'])) - { - $aValidSubAttr[] = array('code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $aSubAttDef['unique_label']); - } + $aValidSubAttr[] = array('attcodeex' => $aSubAttDef['code'], 'code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $sShortAlias.$aSubAttDef['unique_label']); } - $aAllFields[] = array('code' => $sShortAlias.$sAttCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr); + $aAllFields[] = array('attcodeex' => $sAttCode, 'code' => $sShortAlias.$sAttCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr); } } usort($aAllFields, array(get_class($this), 'SortOnLabel')); @@ -192,12 +304,36 @@ abstract class TabularBulkExport extends BulkExport $sKey = MetaModel::GetName($sClass); } $aAllFieldsByAlias[$sKey] = $aAllFields; + + foreach ($aAllFields as $aFieldSpec) + { + $sAttCode = $aFieldSpec['attcodeex']; + if (count($aFieldSpec['subattr']) > 0) + { + foreach ($aFieldSpec['subattr'] as $aSubFieldSpec) + { + $aAllAttCodes[$sAlias][] = $aSubFieldSpec['attcodeex']; + } + } + else + { + $aAllAttCodes[$sAlias][] = $sAttCode; + } + } } $oP->add('
'); $JSAllFields = json_encode($aAllFieldsByAlias); + + // First, fetch only the ids - the rest will be fetched by an object reload $oSet = new DBObjectSet($this->oSearch); $iCount = $oSet->Count(); + + foreach ($this->oSearch->GetSelectedClasses() as $sAlias => $sClass) + { + $aColumns[$sAlias] = array(); + } + $oSet->OptimizeColumnLoad($aColumns); $iPreviewLimit = 3; $oSet->SetLimit($iPreviewLimit); $aSampleData = array(); @@ -215,16 +351,10 @@ abstract class TabularBulkExport extends BulkExport $sShortAlias = ''; } - if ($this->IsValidField($sClass, 'id')) + foreach ($aAllAttCodes[$sAlias] as $sAttCodeEx) { - $aSampleRow[$sShortAlias.'id'] = $this->GetSampleKey($aRow[$sAlias]); - } - foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef) - { - if ($this->IsValidField($sClass, $sAttCode, $oAttDef)) - { - $aSampleRow[$sShortAlias.$sAttCode] = $this->GetSampleData($aRow[$sAlias], $sAttCode); - } + $oObj = $aRow[$sAlias]; + $aSampleRow[$sShortAlias.$sAttCodeEx] = $oObj ? $this->GetSampleData($oObj, $sAttCodeEx) : ''; } } $aSampleData[] = $aSampleRow; @@ -256,28 +386,26 @@ EOF * Tells if the specified field can be exported * @param unknown $sClass * @param unknown $sAttCode - * @param AttributeDefinition $oAttDef Can be null when $sAttCode == 'id' + * @param AttributeDefinition $oAttDef Can be null in case the attribute definition has not been fetched by the caller * @return boolean */ - protected function IsValidField($sClass, $sAttCode, $oAttDef = null) + protected function IsExportableField($sClass, $sAttCode, $oAttDef = null) { if ($sAttCode == 'id') return true; + if (is_null($oAttDef)) + { + $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode); + } if ($oAttDef instanceof AttributeLinkedSet) return false; return true; //$oAttDef->IsScalar(); } protected function GetSampleData($oObj, $sAttCode) { - if ($oObj == null) return ''; + if ($sAttCode == 'id') return $oObj->GetKey(); return $oObj->GetEditValue($sAttCode); } - protected function GetSampleKey($oObj) - { - if ($oObj == null) return ''; - return $oObj->GetKey(); - } - public function ReadParameters() { parent::ReadParameters(); @@ -358,7 +486,7 @@ EOF catch (Exception $e) { throw new Exception("Wrong field specification '$sFieldSpec': ".$e->getMessage()); - } + } } else { diff --git a/core/xmlbulkexport.class.inc.php b/core/xmlbulkexport.class.inc.php index cdf821da6..353872295 100644 --- a/core/xmlbulkexport.class.inc.php +++ b/core/xmlbulkexport.class.inc.php @@ -70,14 +70,7 @@ class XMLBulkExport extends BulkExport protected function GetSampleData($oObj, $sAttCode) { - if ($oObj) - { - $sRet = $oObj->GetAsXML($sAttCode); - } - else - { - $sRet = ''; - } + $sRet = ($sAttCode == 'id') ? $oObj->GetKey() : $oObj->GetAsXML($sAttCode); return $sRet; } diff --git a/css/light-grey.css b/css/light-grey.css index 1e12b6ae5..095d6944d 100644 --- a/css/light-grey.css +++ b/css/light-grey.css @@ -1956,6 +1956,9 @@ table.export_parameters td { .table_preview > table { border-collapse: collapse; + max-height: 150px; + overflow: auto; + display: block; } @@ -1968,11 +1971,21 @@ table.export_parameters td { } +.table_preview > table > tbody > tr > td { + vertical-align: top; +} + + .table_preview .drag-handle { cursor: move; } +.table_preview div.text-preview { + white-space: pre-wrap; +} + + .graph_zoom { display: inline-block; float: right; diff --git a/css/light-grey.scss b/css/light-grey.scss index 283bec7c6..6e1126cc2 100644 --- a/css/light-grey.scss +++ b/css/light-grey.scss @@ -1445,8 +1445,12 @@ div.ui-dialog-header { table.export_parameters td { padding-right: 2em; } + .table_preview > table { border-collapse: collapse; + max-height: 150px; + overflow: auto; + display: block; } .table_preview > table > thead > tr > th, .table_preview > table > tbody > tr > td { border: 1px $grey-color solid; @@ -1455,9 +1459,15 @@ table.export_parameters td { padding-right: 0.25em; font-size: 10pt; } +.table_preview > table > tbody > tr > td { + vertical-align: top; +} .table_preview .drag-handle { cursor: move; } +.table_preview div.text-preview { + white-space: pre-wrap; +} .graph_zoom { display: inline-block; float: right; diff --git a/dictionaries/da.dictionary.itop.core.php b/dictionaries/da.dictionary.itop.core.php index 9eaf032a3..9e9312fcc 100644 --- a/dictionaries/da.dictionary.itop.core.php +++ b/dictionaries/da.dictionary.itop.core.php @@ -2446,7 +2446,6 @@ Operators:
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/de.dictionary.itop.core.php b/dictionaries/de.dictionary.itop.core.php index 6cbb2ab38..ff7f88f6e 100644 --- a/dictionaries/de.dictionary.itop.core.php +++ b/dictionaries/de.dictionary.itop.core.php @@ -558,7 +558,6 @@ Operatoren:
'Core:BulkExport:MissingParameter_Param' => 'Fehlender Parameter "%1$s"', 'Core:BulkExport:InvalidParameter_Query' => 'ungültiger Wert für den Paramter "query". Es gibt keinen Eintrag in der Query-Bibliothek, der zu der id "%1$s" korrespondiert.', 'Core:BulkExport:ExportFormatPrompt' => 'Exportformat:', - 'Core:BulkExport:Identifier' => 'Identifikator', 'Core:BulkExportOf_Class' => '%1$s-Export', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Klicken Sie hier um %1$s herunterzuladen', 'Core:BulkExport:ExportResult' => 'Ergebnis ses Exportvorgangs:', diff --git a/dictionaries/dictionary.itop.core.php b/dictionaries/dictionary.itop.core.php index f38449c18..26f35be17 100644 --- a/dictionaries/dictionary.itop.core.php +++ b/dictionaries/dictionary.itop.core.php @@ -810,7 +810,6 @@ Dict::Add('EN US', 'English', 'English', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter "%1$s"', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter "query". There is no Query Phrasebook corresponding to the id: "%1$s".', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:', - 'Core:BulkExport:Identifier' => 'Identifier', 'Core:BulkExportOf_Class' => '%1$s Export', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s', 'Core:BulkExport:ExportResult' => 'Result of the export:', diff --git a/dictionaries/es_cr.dictionary.itop.core.php b/dictionaries/es_cr.dictionary.itop.core.php index d4ed181ee..d6f08b373 100644 --- a/dictionaries/es_cr.dictionary.itop.core.php +++ b/dictionaries/es_cr.dictionary.itop.core.php @@ -804,7 +804,6 @@ Dict::Add('ES CR', 'Spanish', 'Español, Castellano', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/fr.dictionary.itop.core.php b/dictionaries/fr.dictionary.itop.core.php index 03bdd00d7..73f60ac99 100644 --- a/dictionaries/fr.dictionary.itop.core.php +++ b/dictionaries/fr.dictionary.itop.core.php @@ -667,7 +667,6 @@ Opérateurs :
'Core:BulkExport:MissingParameter_Param' => 'Il manque le paramètre "%1$s"', 'Core:BulkExport:InvalidParameter_Query' => 'Valeur incorrecte pour le paramètre "query". Il n\'existe aucune entrée dans le livre des requêtes pour l\'identifiant: "%1$s"', 'Core:BulkExport:ExportFormatPrompt' => 'Format d\'export:', - 'Core:BulkExport:Identifier' => 'Identifiant', 'Core:BulkExportOf_Class' => 'Export de: %1$s', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Cliquez ici pour télécharger %1$s', 'Core:BulkExport:ExportResult' => 'Résultat de l\'export:', diff --git a/dictionaries/hu.dictionary.itop.core.php b/dictionaries/hu.dictionary.itop.core.php index 37ed27085..e508d63d3 100755 --- a/dictionaries/hu.dictionary.itop.core.php +++ b/dictionaries/hu.dictionary.itop.core.php @@ -561,7 +561,6 @@ Operators:
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/it.dictionary.itop.core.php b/dictionaries/it.dictionary.itop.core.php index 15c3fa986..2818dd9fb 100644 --- a/dictionaries/it.dictionary.itop.core.php +++ b/dictionaries/it.dictionary.itop.core.php @@ -793,7 +793,6 @@ Dict::Add('IT IT', 'Italian', 'Italiano', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/ja.dictionary.itop.core.php b/dictionaries/ja.dictionary.itop.core.php index 57099f2df..3885587b0 100644 --- a/dictionaries/ja.dictionary.itop.core.php +++ b/dictionaries/ja.dictionary.itop.core.php @@ -583,7 +583,6 @@ Operators:
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/nl.dictionary.itop.core.php b/dictionaries/nl.dictionary.itop.core.php index 2123a3e04..315561dd8 100644 --- a/dictionaries/nl.dictionary.itop.core.php +++ b/dictionaries/nl.dictionary.itop.core.php @@ -813,7 +813,6 @@ Dict::Add('NL NL', 'Dutch', 'Nederlands', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/pt_br.dictionary.itop.core.php b/dictionaries/pt_br.dictionary.itop.core.php index daea98b1d..729c0b393 100644 --- a/dictionaries/pt_br.dictionary.itop.core.php +++ b/dictionaries/pt_br.dictionary.itop.core.php @@ -806,7 +806,6 @@ Dict::Add('PT BR', 'Brazilian', 'Brazilian', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/ru.dictionary.itop.core.php b/dictionaries/ru.dictionary.itop.core.php index 1934278e2..2108c883d 100644 --- a/dictionaries/ru.dictionary.itop.core.php +++ b/dictionaries/ru.dictionary.itop.core.php @@ -801,7 +801,6 @@ Dict::Add('RU RU', 'Russian', 'Русский', array( 'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/tr.dictionary.itop.core.php b/dictionaries/tr.dictionary.itop.core.php index df4ec632d..0e9eb1310 100644 --- a/dictionaries/tr.dictionary.itop.core.php +++ b/dictionaries/tr.dictionary.itop.core.php @@ -733,7 +733,6 @@ Operators:
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/dictionaries/zh.dictionary.itop.core.php b/dictionaries/zh.dictionary.itop.core.php index 0b836efd9..6f1315724 100644 --- a/dictionaries/zh.dictionary.itop.core.php +++ b/dictionaries/zh.dictionary.itop.core.php @@ -732,7 +732,6 @@ Operators:
'Core:BulkExport:MissingParameter_Param' => 'Missing parameter \"%1$s\"~~', 'Core:BulkExport:InvalidParameter_Query' => 'Invalid value for the parameter \"query\". There is no Query Phrasebook corresponding to the id: \"%1$s\".~~', 'Core:BulkExport:ExportFormatPrompt' => 'Export format:~~', - 'Core:BulkExport:Identifier' => 'Identifier~~', 'Core:BulkExportOf_Class' => '%1$s Export~~', 'Core:BulkExport:ClickHereToDownload_FileName' => 'Click here to download %1$s~~', 'Core:BulkExport:ExportResult' => 'Result of the export:~~', diff --git a/webservices/export-v2.php b/webservices/export-v2.php index 19769e2b6..c59dc4d8b 100644 --- a/webservices/export-v2.php +++ b/webservices/export-v2.php @@ -489,6 +489,7 @@ function CheckParameters($sExpression, $sQueryId, $sFormat) function DoExport(Page $oP, BulkExport $oExporter, $bInteractive = false) { + $oExporter->SetHttpHeaders($oP); $exportResult = $oExporter->GetHeader(); $aStatus = array(); do diff --git a/webservices/itoprest.examples.php b/webservices/itoprest.examples.php index 076e3d798..2480e647f 100644 --- a/webservices/itoprest.examples.php +++ b/webservices/itoprest.examples.php @@ -185,10 +185,15 @@ $aOperations = array( ); $aOperations = array( array( - 'operation' => 'core/get', // operation code - 'class' => 'PhysicalDevice', - 'key' => 'SELECT PhysicalDevice WHERE id < 3', - 'output_fields' => '*+', // list of fields to show in the results (* or a,b,c) + 'operation' => 'core/update', // operation code + 'comment' => 'Synchronization from blah...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server WHERE name="Server1"', + 'output_fields' => 'id, friendlyname, description', // list of fields to show in the results (* or a,b,c) + // Values for the object to create + 'fields' => array( + 'description' => 'Issue #'.time(), + ), ), ); $aOperations = array( @@ -213,6 +218,15 @@ $aXXXOperations = array( 'password' => 'admin', ), ); +$aOperations = array( + array( + 'operation' => 'core/delete', // operation code + 'comment' => 'Cleanup for synchro with...', // comment recorded in the change tracking log + 'class' => 'Server', + 'key' => 'SELECT Server', + 'simulate' => false, + ), +); if (false) { @@ -225,8 +239,11 @@ else } $aData = array(); -$aData['auth_user'] = 'admin'; -$aData['auth_pwd'] = 'admin'; +$aData['auth_user'] = 'no-export'; +$aData['auth_pwd'] = 'no-export'; +//$aData['auth_user'] = 'admin'; +//$aData['auth_pwd'] = 'admin'; + foreach ($aOperations as $iOp => $aOperation) {
$sField$sField$sField$sField