N°6193 - Excel export of dependencies of a CI by class - WIP

This commit is contained in:
Anne-Cath
2025-05-14 11:41:17 +02:00
parent 6bd11fb9bf
commit 772a6a4907
5 changed files with 906 additions and 3 deletions

View File

@@ -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;

View File

@@ -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')),

View File

@@ -496,6 +496,7 @@ $(function()
{
sHtml += '<li><a href="#" id="'+sPopupMenuId+'_attachment">'+this.options.export_as_attachment.label+'</a></li>';
}
sHtml += '<li><a href="#" id="' + sPopupMenuId + '_bob" href="' + this.options.export_as_bob.url + '">' + this.options.export_as_bob.label + '</a></li>';
//sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
sHtml += '</ul></li></ul></div>';
sHtml += '</div>';
@@ -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)

View File

@@ -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
*

View File

@@ -0,0 +1,840 @@
<?php
declare(strict_types=1);
namespace Combodo\iTop\Service\Export;
use ArchivedObjectException;
use AttributeDateTime;
use BulkChange;
use BulkExport;
use CellChangeSpec;
use CMDBChange;
use CMDBObject;
use Combodo\iTop\Application\Helper\WebResourcesHelper;
use Combodo\iTop\Application\TwigBase\Controller\Controller;
use Combodo\iTop\Application\UI\Base\Component\Button\ButtonUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\DataTable\DataTableUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Field\FieldUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Form\Form;
use Combodo\iTop\Application\UI\Base\Component\Form\FormUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Html\Html;
use Combodo\iTop\Application\UI\Base\Component\Input\InputUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectOptionUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Input\Select\SelectUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Component\Panel\PanelUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\Column\ColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\MultiColumn\MultiColumnUIBlockFactory;
use Combodo\iTop\Application\UI\Base\Layout\UIContentBlockUIBlockFactory;
use Combodo\iTop\Application\WebPage\AjaxPage;
use Combodo\iTop\Application\WebPage\iTopWebPage;
use Combodo\iTop\Application\WebPage\WebPage;
use Combodo\iTop\Core\CMDBChange\CMDBChangeOrigin;
use Combodo\iTop\Service\Router\Router;
use CoreException;
use CSVParser;
use CSVParserException;
use DBObjectSearch;
use DBObjectSet;
use Dict;
use DictExceptionMissingString;
use ExcelBulkExport;
use Exception;
use MetaModel;
use MissingQueryArgument;
use MySQLException;
use MySQLHasGoneAwayException;
use OQLException;
use PDFBulkExport;
use ReflectionException;
use UserRights;
use utils;
/**
*
*/
class ExportController extends Controller
{
public const ROUTE_NAMESPACE = 'export';
/**
* @return AjaxPage
* @throws \ApplicationException
* @throws \CoreException
* @throws \OQLException
*/
public static function OperationChooseGlobalParams()
{
$sFormat = utils::ReadParam('format', '');
$sExportBtnLabel = json_encode(Dict::S('UI:Button:Export'));
$sJSTitle = json_encode(utils::EscapeHtml(utils::ReadParam('dialog_title', '', false, 'raw_data')));
$oP = new AjaxPage($sJSTitle);
$oP->add('<div id="interactive_export_dlg">');
$oP->add_ready_script(
<<<EOF
$('#interactive_export_dlg').dialog({
autoOpen: true,
modal: true,
width: '80%',
height: 'auto',
maxHeight: $(window).height() - 50,
title: $sJSTitle,
close: function() { $('#export-form').attr('data-state', 'cancelled'); $(this).remove(); },
buttons: [
{text: $sExportBtnLabel, id: 'export-dlg-submit', click: function() {} }
]
});
setTimeout(function() { $('#interactive_export_dlg').dialog('option', { position: { my: "center", at: "center", of: window }}); $('#export-btn').hide(); ExportImpactButton('#export-dlg-submit'); }, 100);
EOF
);
$oForm = self::GetFormWithHiddenParams($sFormat, $oP);
/*
A AJOUTER PEUT-ETRE PLUS TARD
sHtmlForm += '<tr><td>'+this.options.labels.title+'</td><td><input name="title" value="'+this.options.labels.untitled+'" style="width: 20em;"/></td></tr>';
sHtmlForm += '<tr><td>'+this.options.labels.comments+'</td><td><textarea style="width: 20em; height:5em;" name="comments"/></textarea></td></tr>';
*/
/* 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('</div>');
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('</div>');
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(
<<<EOF
function FormatDatesInPreview(sRadioSelector, sPreviewSelector)
{
if ($('#'+sRadioSelector+'_date_time_format_default').prop('checked'))
{
sPHPFormat = $sJSDefaultDateTimeFormat;
}
else
{
sPHPFormat = $('#'+sRadioSelector+'_custom_date_time_format').val();
}
$('#interactive_fields_'+sPreviewSelector+' .user-formatted-date-time').each(function() {
var val = $(this).attr('data-date');
var sDisplay = DateTimeFormatFromPHP(val, sPHPFormat);
$(this).html(sDisplay);
});
$('#interactive_fields_'+sPreviewSelector+' .user-formatted-date').each(function() {
var val = $(this).attr('data-date');
var sDisplay = DateFormatFromPHP(val, sPHPFormat);
$(this).html(sDisplay);
});
}
EOF
);
$oP->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('<p class="export-message" style="text-align:center;">'.Dict::S('ExcelExport:PreparingExport').'</p>'));
$oBlockExport->AddSubBlock(new Html('<div class="export-progress-bar" style="max-width:30em; margin-left:auto;margin-right:auto;"><div class="export-progress-message" style="text-align:center;"></div></div>'));
$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(
<<<EOF
window.aFormParts = $sJSParts;
$('#format_selector').on('change init', function() {
ExportToggleFormat($(this).val());
}).trigger('init');
$('.export-progress-bar').progressbar({
value: 0,
change: function() {
$('.export-progress-message').text( $(this).progressbar( "value" ) + "%" );
},
complete: function() {
$('.export-progress-message').text( '100 %' );
}
});
ExportInitButton('#export-btn');
EOF
);
}
/*_export_dlg: function(sTitle, sSubmitUrl, sOperation)
{
var sId = this.element.attr('id');
var me = this;
var oPositions = {};
for(k in this.aNodes)
{
oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y };
}
var sHtmlForm = '<div id="GraphExportDlg'+this.element.attr('id')+'"><form id="graph_'+this.element.attr('id')+'_export_dlg" target="_blank" action="'+sSubmitUrl+'" method="post">';
sHtmlForm += '<input type="hidden" name="g" value="'+this.options.grouping_threshold+'">';
sHtmlForm += '<input type="hidden" name="context_key" value="'+this.options.context_key+'">';
sHtmlForm += '<input type="hidden" name="transaction_id" value="'+this.options.transaction_id+'">';
$('#'+sId+'_contexts').multiselect('getChecked').each(function() {
sHtmlForm += '<input type="hidden" name="contexts['+$(this).val()+']" value="'+me.options.additional_contexts[$(this).val()].oql+'">';
});
sHtmlForm += '<input type="hidden" name="positions" value="">';
for(k in this.options.excluded_classes)
{
sHtmlForm += '<input type="hidden" name="excluded_classes[]" value="'+this.options.excluded_classes[k]+'">';
}
for(var k1 in this.options.sources)
{
for(var k2 in this.options.sources[k1])
{
sHtmlForm += '<input type="hidden" name="sources['+k1+'][]" value="'+this.options.sources[k1][k2]+'">';
}
}
for(var k1 in this.options.excluded)
{
for(var k2 in this.options.excluded[k1])
{
sHtmlForm += '<input type="hidden" name="excluded['+k1+'][]" value="'+this.options.excluded[k1][k2]+'">';
}
}
if (sOperation == 'attachment')
{
sHtmlForm += '<input type="hidden" name="obj_class" value="'+this.options.export_as_attachment.obj_class+'">';
sHtmlForm += '<input type="hidden" name="obj_key" value="'+this.options.export_as_attachment.obj_key+'">';
}
sHtmlForm += '<table>';
sHtmlForm += '<tr><td>'+this.options.page_format.label+'</td><td><select name="p">';
for(k in this.options.page_format.values)
{
var sSelected = (k == this.options.page_format['default']) ? ' selected' : '';
sHtmlForm += '<option value="'+k+'"'+sSelected+'>'+this.options.page_format.values[k]+'</option>';
}
sHtmlForm += '</select></td></tr>';
sHtmlForm += '<tr><td>'+this.options.page_orientation.label+'</td><td><select name="o">';
for(k in this.options.page_orientation.values)
{
var sSelected = (k == this.options.page_orientation['default']) ? ' selected' : '';
sHtmlForm += '<option value="'+k+'"'+sSelected+'>'+this.options.page_orientation.values[k]+'</option>';
}
sHtmlForm += '</select></td></tr>';
sHtmlForm += '<tr><td>'+this.options.labels.title+'</td><td><input name="title" value="'+this.options.labels.untitled+'" style="width: 20em;"/></td></tr>';
sHtmlForm += '<tr><td>'+this.options.labels.comments+'</td><td><textarea style="width: 20em; height:5em;" name="comments"/></textarea></td></tr>';
sHtmlForm += '<tr><td colspan=2><input type="checkbox" checked id="include_list_checkbox" name="include_list" value="1"><label for="include_list_checkbox">&nbsp;'+this.options.labels.include_list+'</label></td></tr>';
sHtmlForm += '<table>';
sHtmlForm += '</form></div>';
$('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(
<<<EOF
$('#$sWidgetId').tabularfieldsselector({fields: $JSAllFields, value_holder: '#tabular_fields', advanced_holder: '#tabular_advanced', sample_data: $sJSSampleData, total_count: $iCount, preview_limit: $iPreviewLimit, labels: $sJSLabels });
EOF
);
$oUIContentBlock = UIContentBlockUIBlockFactory::MakeStandard($sWidgetId);
$oUIContentBlock->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("<img src=\"$sIconUrl\" style=\"vertical-align:middle;width: 24px; height: 24px;\"/> ".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('</div>');
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(
<<<EOF
$('#$sWidgetId').tabularfieldsselector({fields: $JSAllFields, value_holder: '#tabular_fields', advanced_holder: '#tabular_advanced', sample_data: $sJSSampleData, total_count: $iCount, preview_limit: $iPreviewLimit, labels: $sJSLabels });
EOF
);
$oUIContentBlock = UIContentBlockUIBlockFactory::MakeStandard($sWidgetId);
$oUIContentBlock->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;
}
}