diff --git a/core/bulkexport.class.inc.php b/core/bulkexport.class.inc.php index 01453616a..2faa4c699 100644 --- a/core/bulkexport.class.inc.php +++ b/core/bulkexport.class.inc.php @@ -25,7 +25,8 @@ define('EXPORTER_DEFAULT_CHUNK_SIZE', 1000); class BulkExportException extends Exception { protected $sLocalizedMessage; - public function __construct($message, $sLocalizedMessage, $code = null, $previous = null) + + public function __construct($message, $sLocalizedMessage, $code = 0, $previous = null) { parent::__construct($message, $code, $previous); $this->sLocalizedMessage = $sLocalizedMessage; diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index b7fbe7475..24557769d 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -25,6 +25,7 @@ use Combodo\iTop\Application\WebPage\iTopPDF; use Combodo\iTop\Application\WebPage\PDFPage; use Combodo\iTop\Application\WebPage\WebPage; use Combodo\iTop\Renderer\BlockRenderer; +use Combodo\iTop\Service\Router\Router; /** * Special kind of Graph for producing some nice output @@ -1470,6 +1471,7 @@ class DisplayableGraph extends SimpleGraph try { $this->InitFromGraphviz(); $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); + $sExportAsPdfURL2 = Router::GetInstance()->GenerateUrl('export.choose_global_params', ['format' => 'pdf']); $sContext = $oAppContext->GetForLink(); $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext; $sExportAsDocumentURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_attachment&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); @@ -1495,7 +1497,8 @@ class DisplayableGraph extends SimpleGraph 'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects), 'excluded' => $aExcludedByClass, 'grouping_threshold' => $iGroupingThreshold, - 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), + 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), + 'export_as_bob' => array('url' => $sExportAsPdfURL2, 'label' => Dict::S('UI:Relation:ExportAsBob')), 'transaction_id' => utils::GetNewTransactionId(), 'export_as_attachment' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsAttachment'), 'obj_class' => $sObjClass, 'obj_key' => $iObjKey), 'drill_down' => array('url' => $sDrillDownURL, 'label' => Dict::S('UI:Relation:DrillDown')), diff --git a/js/simple_graph.js b/js/simple_graph.js index 65ecedfe9..4d40dd4f8 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -496,6 +496,7 @@ $(function() { sHtml += '
  • '+this.options.export_as_attachment.label+'
  • '; } + sHtml += '
  • ' + this.options.export_as_bob.label + '
  • '; //sHtml += '
  • Refresh
  • '; sHtml += ''; sHtml += ''; @@ -507,6 +508,9 @@ $(function() var me = this; + $('#' + sPopupMenuId + '_bob').on('click', function () { + me.export_as_bob(); + }); $('#'+sPopupMenuId+'_pdf').on('click', function() { me.export_as_pdf(); }); $('#'+sPopupMenuId+'_attachment').on('click', function() { me.export_as_attachment(); }); $('#'+sId+'_zoom').slider({ min: 0, max: 5, value: 1, step: 0.25, change: function() { me._on_zoom_change( $(this).slider('value')); } }); @@ -575,8 +579,47 @@ $(function() }); }, - export_as_pdf: function() + export_as_bob: function () { + var sId = this.element.attr('id'); + var me = this; + var oParams = {}; + oParams.g = this.options.grouping_threshold; + oParams.context_key = this.options.context_key; + oParams.transaction_id = this.options.transaction_id; + oParams.contexts = {}; + $('#' + sId + '_contexts').multiselect('getChecked').each(function () { + oParams.contexts[$(this).val()] = me.options.additional_contexts[$(this).val()].oql; + }); + + oParams.excluded_classes = {}; + for (k in this.options.excluded_classes) { + oParams.excluded_classes[k] = this.options.excluded_classes[k]; + } + oParams.sources = {}; + for (var k1 in this.options.sources) { + oParams.sources[k1] = {}; + for (var k2 in this.options.sources[k1]) { + oParams.sources[k1][k2] = this.options.sources[k1][k2]; + } + } + oParams.excluded = {}; + for (var k1 in this.options.excluded) { + oParams.options.excluded[k1] = {}; + for (var k2 in this.options.excluded[k1]) { + oParams.excluded[k1][k2] = this.options.excluded[k1][k2]; + } + } + oParams.list_classes = {}; + $("#dh_flash_criterion_outer [name= 'excluded[]']").each(function (index, element) { + oParams.list_classes[index] = $(element).val(); + }); + + $.post(this.options.export_as_bob.url, oParams, function (data) { + $('body').append(data); + }); + }, + export_as_pdf: function () { this._export_dlg(this.options.labels.export_pdf_title, this.options.export_as_pdf.url, 'download_pdf'); }, _export_dlg: function(sTitle, sSubmitUrl, sOperation) diff --git a/js/utils.js b/js/utils.js index 354c975e2..ddf8ede56 100644 --- a/js/utils.js +++ b/js/utils.js @@ -595,6 +595,22 @@ function ExportInitButton(sSelector) { }); } +function ExportImpactButton(sSelector) { + $(sSelector).on('click', function () { + var form = $('#export-form'); + var actionUrl = form.attr('action'); + + $.ajax({ + type: "POST", + url: actionUrl, + data: form.serialize(), // serializes the form's elements. + success: function (data) { + $(sSelector).html(data); // show response from the php script. + } + }); + }); +} + /** * @deprecated 3.0.0 N°4367 deprecated, use {@see CombodoSanitizer.EscapeHtml} instead * diff --git a/sources/Controller/Export/ExportController.php b/sources/Controller/Export/ExportController.php new file mode 100644 index 000000000..f67ed3af3 --- /dev/null +++ b/sources/Controller/Export/ExportController.php @@ -0,0 +1,840 @@ +add('
    '); + $oP->add_ready_script( + <<'; + sHtmlForm += ''+this.options.labels.comments+''; +*/ + /* first select params specific to the export format */ + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter === null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + $oP->add("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + $oP->add('
    '); + return $oP; + } + $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sFormat)->AddCSSClass('form_part'); + $oForm->AddSubBlock($UIContentBlock); + $UIContentBlock->AddSubBlock($oExporter->GetFormPart($oP, $sFormat.'_options')); + + $aSelectedClasses = utils::ReadParam('list_classes', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + + $oPanel = PanelUIBlockFactory::MakeNeutral(Dict::S('UI:Export:Class:SelectedClasses')); + $oForm->AddSubBlock($oPanel); + $oMulticolumn = MultiColumnUIBlockFactory::MakeStandard('selected_classes'); + $oPanel->AddSubBlock($oMulticolumn); + $oMulticolumn->AddCSSClass('ibo-multi-column--export'); + $oColumn1 = ColumnUIBlockFactory::MakeStandard(); + $oMulticolumn->AddColumn($oColumn1); + $oColumn2 = ColumnUIBlockFactory::MakeStandard(); + $oMulticolumn->AddColumn($oColumn2); + foreach ($aSelectedClasses as $i => $sClass) { + $oBlock = FieldUIBlockFactory::MakeStandard(MetaModel::GetName($sClass)) ; + $oValue = SelectUIBlockFactory::MakeForSelect($sClass); + $oValue->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption('standard', Dict::S('UI:Export:Class:Standard'), true)); + $oValue->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption('user', Dict::S('UI:Export:Class:User'), false)); + $oValue->AddOption(SelectOptionUIBlockFactory::MakeForSelectOption('custom', Dict::S('UI:Export:Class:Custom'), false)); + $oBlock->AddSubBlock($oValue); + if ($i%2 == 0) { + $oColumn1->AddSubBlock($oBlock); + } else { + $oColumn2->AddSubBlock($oBlock); + } + } + + $oP->add(''); + + + return $oP; + } + +/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +/// +/// +/// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + /* function DisplayForm(WebPage $oP, $sAction = '', $sExpression = '', $sFormat = null) + { + $oExportSearch = null; + $oP->add_script(DateTimeFormat::GetJSSQLToCustomFormat()); + $sJSDefaultDateTimeFormat = json_encode((string)AttributeDateTime::GetFormat()); + $oP->add_script( + <<LinkScriptFromAppRoot('js/tabularfieldsselector.js'); + $oP->LinkScriptFromAppRoot('js/jquery.dragtable.js'); + $oP->LinkStylesheetFromAppRoot('css/dragtable.css'); + + /* $oForm = FormUIBlockFactory::MakeStandard("export-form"); + $oForm->SetAction($sAction); + $oForm->AddDataAttribute("state", "not-yet-started"); + $oP->AddSubBlock($oForm);* + + $bExpressionIsValid = true; + $sExpressionError = ''; + if ($sExpression === null) { + $bExpressionIsValid = false; + } else if ($sExpression !== '') { + try { + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + } + catch (OQLException $e) { + $bExpressionIsValid = false; + $sExpressionError = $e->getMessage(); + } + } + + if (!$bExpressionIsValid) { + DisplayExpressionForm($oP, $sAction, $sExpression, $sExpressionError,$oForm); + + return; + } + + + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden("expression", $sExpression)); + $oExportSearch = DBObjectSearch::FromOQL($sExpression); + $oExportSearch->UpdateContextFromUser(); + + $aFormPartsByFormat = array(); + $aAllFormParts = array(); + + // One specific format was chosen + $oSelect = InputUIBlockFactory::MakeForHidden("format", utils::EscapeHtml($sFormat)); + $oForm->AddSubBlock($oSelect); + + /* $oExporter = BulkExport::FindExporter($sFormat, $oExportSearch); + $aParts = $oExporter->EnumFormParts(); + foreach ($aParts as $sPartId => $void) { + $aAllFormParts[$sPartId] = $oExporter; + } + $aFormPartsByFormat[$sFormat] = array_keys($aAllFormParts); + + foreach ($aAllFormParts as $sPartId => $oExport) { + $UIContentBlock = UIContentBlockUIBlockFactory::MakeStandard('form_part_'.$sPartId)->AddCSSClass('form_part'); + $oForm->AddSubBlock($UIContentBlock); + $UIContentBlock->AddSubBlock($oExport->GetFormPart($oP, $sPartId)); + }* + //end of form + $oBlockExport = UIContentBlockUIBlockFactory::MakeStandard("export-feedback")->SetIsHidden(true); + $oBlockExport->AddSubBlock(new Html('

    '.Dict::S('ExcelExport:PreparingExport').'

    ')); + $oBlockExport->AddSubBlock(new Html('
    ')); + $oP->AddSubBlock($oBlockExport); + if ($sFormat == null) {//if it's global export + $oP->AddSubBlock(ButtonUIBlockFactory::MakeForPrimaryAction('export', Dict::S('UI:Button:Export'), 'export', false, 'export-btn')); + } + $oBlockResult = UIContentBlockUIBlockFactory::MakeStandard("export_text_result")->SetIsHidden(true); + $oBlockResult->AddSubBlock(new Html(Dict::S('Core:BulkExport:ExportResult'))); + + $oTextArea = new TextArea('export_content', '', 'export_content'); + $oTextArea->AddCSSClass('ibo-input-text--export'); + $oBlockResult->AddSubBlock($oTextArea); + $oP->AddSubBlock($oBlockResult); + + $sJSParts = json_encode($aFormPartsByFormat); + $oP->add_ready_script( + <<
    '; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + $('#'+sId+'_contexts').multiselect('getChecked').each(function() { + sHtmlForm += ''; + }); + + sHtmlForm += ''; + for(k in this.options.excluded_classes) + { + sHtmlForm += ''; + } + for(var k1 in this.options.sources) + { + for(var k2 in this.options.sources[k1]) + { + sHtmlForm += ''; + } + } + for(var k1 in this.options.excluded) + { + for(var k2 in this.options.excluded[k1]) + { + sHtmlForm += ''; + } + } + if (sOperation == 'attachment') + { + sHtmlForm += ''; + sHtmlForm += ''; + } + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += ''; + sHtmlForm += '
    '+this.options.page_format.label+'
    '+this.options.page_orientation.label+'
    '+this.options.labels.title+'
    '+this.options.labels.comments+'
    '; + sHtmlForm += ''; + + $('body').append(sHtmlForm); + $('#graph_'+this.element.attr('id')+'_export_dlg input[name="positions"]').val(JSON.stringify(oPositions)); + var me = this; + if (sOperation == 'attachment') + { + $('#GraphExportDlg'+this.element.attr('id')+' form').on('submit', function() { return me._on_export_as_attachment(); }); + } + $('#GraphExportDlg'+this.element.attr('id')).dialog({ + width: 'auto', + modal: true, + title: sTitle, + close: function() { $(this).remove(); }, + buttons: [ + {text: this.options.labels['cancel'], click: function() { $(this).dialog('close');} }, + {text: this.options.labels['export'], click: function() { $('#graph_'+me.element.attr('id')+'_export_dlg').submit(); $(this).dialog('close');} }, + ] + }); + }, +*/ + /* + private function SelectColumns($sOQL): array + { + $sWidgetId = 'tabular_fields_selector'; + $oSearch = DBObjectSearch::FromOQL($sOQL); + $oSet = new DBObjectSet($oSearch); + $aSelectedClasses = $oSearch->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aSelectedClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ, $oSet) != UR_ALLOWED_NO) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + $aAllFieldsByAlias = array(); + $aAllAttCodes = array(); + foreach($aAuthorizedClasses as $sAlias => $sClass) + { + $aAllFields = array(); + if (count($aAuthorizedClasses) > 1 ) + { + $sShortAlias = $sAlias.'.'; + } + else + { + $sShortAlias = ''; + } + if ($this->IsExportableField($sClass, 'id')) + { + $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->IsExportableField($sClass, $sAttCode, $oAttDef)) + { + $sShortLabel = $oAttDef->GetLabel(); + $sLabel = $sShortAlias.$oAttDef->GetLabel(); + $aSubAttr = $this->GetSubAttributes($sClass, $sAttCode, $oAttDef); + $aValidSubAttr = array(); + foreach($aSubAttr as $aSubAttDef) + { + $aValidSubAttr[] = array('attcodeex' => $aSubAttDef['code'], 'code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $sShortAlias.$aSubAttDef['unique_label']); + } + $aAllFields[] = array('attcodeex' => $sAttCode, 'code' => $sShortAlias.$sAttCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr); + } + } + usort($aAllFields, array(get_class($this), 'SortOnLabel')); + if (count($aAuthorizedClasses) > 1) + { + $sKey = MetaModel::GetName($sClass).' ('.$sAlias.')'; + } + else + { + $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; + } + } + } + + $JSAllFields = json_encode($aAllFieldsByAlias); + + // First, fetch only the ids - the rest will be fetched by an object reload + $oSet = new DBObjectSet($oSearch); + $iCount = $oSet->Count(); + + foreach ($oSearch->GetSelectedClasses() as $sAlias => $sClass) + { + $aColumns[$sAlias] = array(); + } + $oSet->OptimizeColumnLoad($aColumns); + $iPreviewLimit = 3; + $oSet->SetLimit($iPreviewLimit); + $aSampleData = array(); + while($aRow = $oSet->FetchAssoc()) + { + $aSampleRow = array(); + foreach($aAuthorizedClasses as $sAlias => $sClass) + { + if (count($aAuthorizedClasses) > 1) { + $sShortAlias = $sAlias.'.'; + } else { + $sShortAlias = ''; + } + if (isset($aAllAttCodes[$sAlias])) { + foreach ($aAllAttCodes[$sAlias] as $sAttCodeEx) { + $oObj = $aRow[$sAlias]; + $aSampleRow[$sShortAlias.$sAttCodeEx] = $oObj ? $this->GetSampleData($oObj, $sAttCodeEx) : ''; + } + } + } + $aSampleData[] = $aSampleRow; + } + $sJSSampleData = json_encode($aSampleData); + $aLabels = array( + 'preview_header' => Dict::S('Core:BulkExport:DragAndDropHelp'), + 'empty_preview' => Dict::S('Core:BulkExport:EmptyPreview'), + 'columns_order' => Dict::S('Core:BulkExport:ColumnsOrder'), + 'columns_selection' => Dict::S('Core:BulkExport:AvailableColumnsFrom_Class'), + 'check_all' => Dict::S('Core:BulkExport:CheckAll'), + 'uncheck_all' => Dict::S('Core:BulkExport:UncheckAll'), + 'no_field_selected' => Dict::S('Core:BulkExport:NoFieldSelected'), + ); + $sJSLabels = json_encode($aLabels); + $oP->add_ready_script( + <<AddCSSClass('ibo-tabularbulkexport'); + + return $oUIContentBlock; + } + + public static function operationGeneratePdf() + { + require_once(APPROOT.'core/simplegraph.class.inc.php'); + require_once(APPROOT.'core/relationgraph.class.inc.php'); + require_once(APPROOT.'core/displayablegraph.class.inc.php'); + $sRelation = utils::ReadParam('relation', 'impacts'); + $sDirection = utils::ReadParam('direction', 'down'); + + $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer'); + $sPageFormat = utils::ReadParam('p', 'A4'); + $sPageOrientation = utils::ReadParam('o', 'L'); + $sTitle = utils::ReadParam('title', '', false, 'raw_data'); + $sPositions = utils::ReadParam('positions', null, false, 'raw_data'); + $aExcludedClasses = utils::ReadParam('excluded_classes', array(), false, 'raw_data'); + $bIncludeList = (bool)utils::ReadParam('include_list', false); + $sComments = utils::ReadParam('comments', '', false, 'raw_data'); + $aContexts = utils::ReadParam('contexts', array(), false, 'raw_data'); + $sContextKey = utils::ReadParam('context_key', '', false, 'raw_data'); + $aPositions = null; + if ($sPositions != null) { + $aPositions = json_decode($sPositions, true); + } + + // Get the list of source objects + $aSources = utils::ReadParam('sources', array(), false, 'raw_data'); + $aSourceObjects = array(); + foreach ($aSources as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aSourceObjects[] = $oObj; + } + } + $sSourceClass = '*'; + if (count($aSourceObjects) == 1) { + $sSourceClass = get_class($aSourceObjects[0]); + } + + // Get the list of excluded objects + $aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data'); + $aExcludedObjects = array(); + foreach ($aExcluded as $sClass => $aIDs) { + $oSearch = new DBObjectSearch($sClass); + $oSearch->AddCondition('id', $aIDs, 'IN'); + $oSet = new DBObjectSet($oSearch); + while ($oObj = $oSet->Fetch()) { + $aExcludedObjects[] = $oObj; + } + } + + $iMaxRecursionDepth = MetaModel::GetConfig()->Get('relations_max_depth'); + if ($sDirection == 'up') { + $oRelGraph = MetaModel::GetRelatedObjectsUp($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aContexts); + } else { + $oRelGraph = MetaModel::GetRelatedObjectsDown($sRelation, $aSourceObjects, $iMaxRecursionDepth, true, $aExcludedObjects, $aContexts); + } + + // Remove excluded classes from the graph + if (count($aExcludedClasses) > 0) { + $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); + foreach ($oIterator as $oNode) { + $oObj = $oNode->GetProperty('object'); + if ($oObj && in_array(get_class($oObj), $aExcludedClasses)) { + $oRelGraph->FilterNode($oNode); + } + } + } + + $oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation); + $oPage->SetContentDisposition('attachment', $sTitle.'.pdf'); + + $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'), true); + $oGraph->InitFromGraphviz(); + if ($aPositions != null) { + $oGraph->UpdatePositions($aPositions); + } + + $aGroups = array(); + $oIterator = new RelationTypeIterator($oGraph, 'Node'); + foreach ($oIterator as $oNode) { + if ($oNode instanceof DisplayableGroupNode) { + $aGroups[$oNode->GetProperty('group_index')] = $oNode->GetObjects(); + } + } + // First page is the graph + $oGraph->RenderAsPDF($oPage, $sComments, $sContextKey); + + if ($bIncludeList) { + // Then the lists of objects (one table per finalclass) + $aResults = array(); + $oIterator = new RelationTypeIterator($oRelGraph, 'Node'); + foreach ($oIterator as $oNode) { + $oObj = $oNode->GetProperty('object'); // Some nodes (Redundancy Nodes and Group) do not contain an object + if ($oObj) { + $sObjClass = get_class($oObj); + if (!array_key_exists($sObjClass, $aResults)) { + $aResults[$sObjClass] = array(); + } + $aResults[$sObjClass][] = $oObj; + } + } + + $oPage->get_tcpdf()->AddPage(); + $oPage->get_tcpdf()->SetFontSize(10); // Reset the font size to its default + $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationshipList'))); + $iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop'); + foreach ($aResults as $sListClass => $aObjects) { + set_time_limit($iLoopTimeLimit * count($aObjects)); + $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); + $oSet->SetShowObsoleteData(utils::ShowObsoleteData()); + $oTitle = new Html(Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass))); + $oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2)); + $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet, array('table_id' => $sSourceClass.'_'.$sRelation.'_'.$sDirection.'_'.$sListClass))); + } + + // Then the content of the groups (one table per group) + if (count($aGroups) > 0) { + $oPage->get_tcpdf()->AddPage(); + $oPage->AddSubBlock(TitleUIBlockFactory::MakeNeutral(Dict::S('UI:RelationGroups'))); + foreach ($aGroups as $idx => $aObjects) { + set_time_limit($iLoopTimeLimit * count($aObjects)); + $sListClass = get_class(current($aObjects)); + $oSet = CMDBObjectSet::FromArray($sListClass, $aObjects); + $sIconUrl = MetaModel::GetClassIcon($sListClass, false); + $sIconUrl = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-'.utils::GetCurrentEnvironment().'/', $sIconUrl); + $oTitle = new Html(" ".Dict::Format('UI:RelationGroupNumber_N', (1 + $idx)), Metamodel::GetName($sListClass)); + $oPage->AddSubBlock(TitleUIBlockFactory::MakeStandard($oTitle, 2)); + $oPage->AddSubBlock(cmdbAbstractObject::GetDataTableFromDBObjectSet($oSet)); + + } + } + } + }*/ + public static function OperationSelectColumns() + { + $oP = new WebPage(); + $sFormat = utils::ReadParam('format', ''); + $oForm = self::GetFormWithHiddenParams($sFormat, $oP); + + $oExporter = BulkExport::FindExporter($sFormat); + if ($oExporter === null) { + $aSupportedFormats = BulkExport::FindSupportedFormats(); + $oP->add("Invalid output format: '$sFormat'. The supported formats are: ".implode(', ', array_keys($aSupportedFormats))); + $oP->add(''); + return $oP; + } + $oExporter->ReadParameters(); + foreach ($oExporter->GetStatusInfo() as $sKey => $sValue) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden($sKey, $sValue)); + } + + + $sWidgetId = 'tabular_fields_selector'; + $aSelectedClasses = utils::ReadParam('class', '', false, utils::ENUM_SANITIZATION_FILTER_CLASS); + // $oSet = new CMDBObjectSet(new DBObjectSearch($sClass)); + // $aSelectedClasses = $oSearch->GetSelectedClasses(); + $aAuthorizedClasses = array(); + foreach($aSelectedClasses as $sAlias => $sClassName) + { + if (UserRights::IsActionAllowed($sClassName, UR_ACTION_BULK_READ) != UR_ALLOWED_NO) + { + $aAuthorizedClasses[$sAlias] = $sClassName; + } + } + $aAllFieldsByAlias = array(); + $aAllAttCodes = array(); + /* foreach($aAuthorizedClasses as $sAlias => $sClass) + { + $aAllFields = array(); + if (count($aAuthorizedClasses) > 1 ) + { + $sShortAlias = $sAlias.'.'; + } + else + { + $sShortAlias = ''; + } + if ($this->IsExportableField($sClass, 'id')) + { + $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->IsExportableField($sClass, $sAttCode, $oAttDef)) + { + $sShortLabel = $oAttDef->GetLabel(); + $sLabel = $sShortAlias.$oAttDef->GetLabel(); + $aSubAttr = $this->GetSubAttributes($sClass, $sAttCode, $oAttDef); + $aValidSubAttr = array(); + foreach($aSubAttr as $aSubAttDef) + { + $aValidSubAttr[] = array('attcodeex' => $aSubAttDef['code'], 'code' => $sShortAlias.$aSubAttDef['code'], 'label' => $aSubAttDef['label'], 'unique_label' => $sShortAlias.$aSubAttDef['unique_label']); + } + $aAllFields[] = array('attcodeex' => $sAttCode, 'code' => $sShortAlias.$sAttCode, 'label' => $sShortLabel, 'unique_label' => $sLabel, 'subattr' => $aValidSubAttr); + } + } + usort($aAllFields, array(get_class($this), 'SortOnLabel')); + if (count($aAuthorizedClasses) > 1) + { + $sKey = MetaModel::GetName($sClass).' ('.$sAlias.')'; + } + else + { + $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; + } + } + } + + $JSAllFields = json_encode($aAllFieldsByAlias); + + // First, fetch only the ids - the rest will be fetched by an object reload + $oSearch = new DBObjectSearch($sSelectedClass); + $oSet = new CMDBObjectSet($oSearch); + $iCount = $oSet->Count(); + + foreach ($oSearch->GetSelectedClasses() as $sAlias => $sClass) + { + $aColumns[$sAlias] = array(); + } + $oSet->OptimizeColumnLoad($aColumns); + $iPreviewLimit = 3; + $oSet->SetLimit($iPreviewLimit); + $aSampleData = array(); + while($aRow = $oSet->FetchAssoc()) + { + $aSampleRow = array(); + foreach($aAuthorizedClasses as $sAlias => $sClass) + { + if (count($aAuthorizedClasses) > 1) { + $sShortAlias = $sAlias.'.'; + } else { + $sShortAlias = ''; + } + if (isset($aAllAttCodes[$sAlias])) { + foreach ($aAllAttCodes[$sAlias] as $sAttCodeEx) { + $oObj = $aRow[$sAlias]; + $aSampleRow[$sShortAlias.$sAttCodeEx] = $oObj ? $this->GetSampleData($oObj, $sAttCodeEx) : ''; + } + } + } + $aSampleData[] = $aSampleRow; + } + $sJSSampleData = json_encode($aSampleData); + $aLabels = array( + 'preview_header' => Dict::S('Core:BulkExport:DragAndDropHelp'), + 'empty_preview' => Dict::S('Core:BulkExport:EmptyPreview'), + 'columns_order' => Dict::S('Core:BulkExport:ColumnsOrder'), + 'columns_selection' => Dict::S('Core:BulkExport:AvailableColumnsFrom_Class'), + 'check_all' => Dict::S('Core:BulkExport:CheckAll'), + 'uncheck_all' => Dict::S('Core:BulkExport:UncheckAll'), + 'no_field_selected' => Dict::S('Core:BulkExport:NoFieldSelected'), + ); + $sJSLabels = json_encode($aLabels); + $oP->add_ready_script( + <<AddCSSClass('ibo-tabularbulkexport'); +*/ + return $oP; + } + + /** + * @param mixed $sFormat + * @param AjaxPage $oP + * @return Form + * @throws Exception + */ + public static function GetFormWithHiddenParams(mixed $sFormat, AjaxPage $oP): Form + { + $oForm = FormUIBlockFactory::MakeStandard("export-form"); + // $oForm->SetAction(utils::GetAbsoluteUrlAppRoot().'webservices/export-v2.php'); + $oForm->SetAction(Router::GetInstance()->GenerateUrl('export.select_columns', ['format' => $sFormat])); + $oForm->AddDataAttribute("state", "not-yet-started"); + $oP->AddSubBlock($oForm); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('format', $sFormat)); + + //Add params coming from the screen + $iTransactionId = isset($aExtraParams['transaction_id']) ? $aExtraParams['transaction_id'] : utils::GetNewTransactionId(); + $oP->SetTransactionId($iTransactionId); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('transaction_id', $iTransactionId)); + + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('context_key', utils::ReadParam('context_key', '', false, 'raw_data'))); + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('g', utils::ReadParam('g', ''))); + $aContexts = utils::ReadParam('contexts', ''); + foreach ($aContexts as $sContext) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('contexts', $sContext)); + } + $aExcludedClasses = utils::ReadParam('excluded_classes', ''); + foreach ($aExcludedClasses as $sExcludedClass) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('excluded_classes', $sExcludedClass)); + } + $aSources = utils::ReadParam('sources', ''); + foreach ($aSources as $sKey => $aSource) { + foreach ($aSource as $sSource) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('sources[' . $sKey . ']', $sSource)); + } + } + $aExcludeds = utils::ReadParam('excluded', ''); + foreach ($aExcludeds as $sKey => $aExcluded) { + foreach ($aExcluded as $sExcluded) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('excluded[' . $sKey . ']', $sExcluded)); + } + } + $aSelectedClasses = utils::ReadParam('list_classes', '', false, utils::ENUM_SANITIZATION_FILTER_RAW_DATA); + foreach ($aSelectedClasses as $sSelectedClass) { + $oForm->AddSubBlock(InputUIBlockFactory::MakeForHidden('list_classes', $sSelectedClass)); + } + return $oForm; + } +} \ No newline at end of file