From 80c031221993aea85bff7751f5db0448ae4fb1de Mon Sep 17 00:00:00 2001 From: Denis Flaven Date: Wed, 20 May 2015 09:33:42 +0000 Subject: [PATCH] Automatically save the PDF of the impact analysis as an attachement to the ticket. SVN:trunk[3582] --- application/pdfpage.class.inc.php | 6 ++ core/displayablegraph.class.inc.php | 72 +++++++++++++++------ dictionaries/de.dictionary.itop.ui.php | 2 + dictionaries/dictionary.itop.ui.php | 4 +- dictionaries/fr.dictionary.itop.ui.php | 4 +- js/simple_graph.js | 87 +++++++++++++++++++++----- pages/ajax.render.php | 25 +++++++- 7 files changed, 162 insertions(+), 38 deletions(-) diff --git a/application/pdfpage.class.inc.php b/application/pdfpage.class.inc.php index 56430f285..84c58bfcc 100644 --- a/application/pdfpage.class.inc.php +++ b/application/pdfpage.class.inc.php @@ -187,4 +187,10 @@ EOF $this->flush(); echo $this->oPdf->Output($this->s_title.'.pdf', 'S'); } + + public function get_pdf() + { + $this->flush(); + return $this->oPdf->Output($this->s_title.'.pdf', 'S'); + } } \ No newline at end of file diff --git a/core/displayablegraph.class.inc.php b/core/displayablegraph.class.inc.php index fa205fdc6..19d96b5da 100644 --- a/core/displayablegraph.class.inc.php +++ b/core/displayablegraph.class.inc.php @@ -879,8 +879,11 @@ class DisplayableGraph extends SimpleGraph $fBreakMargin = $oPdf->getBreakMargin(); $oPdf->SetAutoPageBreak(false); - $fKeyWidth = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax); - $xMin += + $fKeyWidth; + $aRemainingArea = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax); + $xMin = $aRemainingArea['xmin']; + $xMax = $aRemainingArea['xmax']; + $yMin = $aRemainingArea['ymin']; + $yMax = $aRemainingArea['ymax']; //$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 225, 225)); @@ -916,14 +919,15 @@ class DisplayableGraph extends SimpleGraph } /** - * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax). Returns the width used by the legend. + * Renders (in PDF) the key (legend) of the graphics vertically to the left of the specified zone (xmin,ymin, xmax,ymax), + * and the comment (if any) at the bottom of the page. Returns the position of remaining area. * @param TCPDF $oPdf * @param string $sComments * @param float $xMin * @param float $yMin * @param float $xMax * @param float $yMax - * @return number The width used by the legend + * @return hash An array ('xmin' => , 'xmax' => ,'ymin' => , 'ymax' => ) of the remaining available area to paint the graph */ protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax) { @@ -963,15 +967,24 @@ class DisplayableGraph extends SimpleGraph $oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, $fIconSize, $fIconSize); $yPos += $fIconSize + 2*$fPadding; } - $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yPos - $yMin, 'D'); - $oPdf->Rect($xMin, $yPos, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yPos, 'D'); - $yPos +=1; - $oPdf->SetXY($xMin + $fPadding, $yPos); - $oPdf->Cell($fIconSize + $fPadding + $fMaxWidth, $fIconSize + $fPadding, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */); - $yPos += $fIconSize + 2*$fPadding; - $oPdf->SetX($xMin); - $oPdf->writeHTMLCell($fIconSize + 2*$fPadding + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '

'.str_replace("\n", '
', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'

', 0 /* border */, 1 /* ln */); - return $fMaxWidth + $fIconSize + 4*$fPadding; + $oPdf->Rect($xMin, $yMin, $fMaxWidth + $fIconSize + 3*$fPadding, $yMax - $yMin, 'D'); + + if ($sComments != '') + { + // Draw the comment text (surrounded by a rectangle) + $xPos = $xMin + $fMaxWidth + $fIconSize + 4*$fPadding; + $w = $xMax - $xPos - 2*$fPadding; + $iNbLines = 1; + $sText = '

'.str_replace("\n", '
', htmlentities($sComments, ENT_QUOTES, 'UTF-8'), $iNbLines).'

'; + $fLineHeight = $oPdf->getStringHeight($w, $sText); + $h = (1+$iNbLines) * $fLineHeight; + $yPos = $yMax - 2*$fPadding - $h; + $oPdf->writeHTMLCell($w, $h, $xPos + $fPadding, $yPos + $fPadding, $sText, 0 /* border */, 1 /* ln */); + $oPdf->Rect($xPos, $yPos, $w + 2*$fPadding, $h + 2*$fPadding, 'D'); + $yMax = $yPos - $fPadding; + } + + return array('xmin' => $fMaxWidth + $fIconSize + 4*$fPadding, 'xmax' => $xMax, 'ymin' => $yMin, 'ymax' => $yMax); } /** @@ -982,7 +995,7 @@ class DisplayableGraph extends SimpleGraph * @param ApplicationContext $oAppContext * @param array $aExcludedObjects */ - function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array()) + function Display(WebPage $oP, $aResults, $sRelation, ApplicationContext $oAppContext, $aExcludedObjects = array(), $sObjClass = null, $iObjKey = null) { $aExcludedByClass = array(); foreach($aExcludedObjects as $oObj) @@ -1037,15 +1050,21 @@ EOF { $this->InitFromGraphviz(); $sExportAsPdfURL = ''; - if (extension_loaded('gd')) - { - $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); - } + $sExportAsPdfURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_pdf&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); $oAppcontext = new ApplicationContext(); $sContext = $oAppContext->GetForLink(); $sDrillDownURL = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=details&class=%1$s&id=%2$s&'.$sContext; - $sExportAsDocumentURL = ''; + $sExportAsDocumentURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_attachment&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); $sLoadFromURL = utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php?operation=relation_json&relation='.$sRelation.'&direction='.($this->bDirectionDown ? 'down' : 'up'); + $sAttachmentExportTitle = ''; + if (($sObjClass != null) && ($iObjKey != null)) + { + $oTargetObj = MetaModel::GetObject($sObjClass, $iObjKey, false); + if ($oTargetObj) + { + $sAttachmentExportTitle = Dict::Format('UI:Relation:AttachmentExportOptions_Name', $oTargetObj->GetName()); + } + } $sId = 'graph'; $oP->add('
'); @@ -1055,13 +1074,15 @@ EOF 'excluded' => $aExcludedByClass, 'grouping_threshold' => $iGroupingThreshold, 'export_as_pdf' => array('url' => $sExportAsPdfURL, 'label' => Dict::S('UI:Relation:ExportAsPDF')), - //'export_as_document' => array('url' => $sExportAsDocumentURL, 'label' => Dict::S('UI:Relation:ExportAsDocument')), + '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')), 'labels' => array( 'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'), + 'export_as_attachment_title' => $sAttachmentExportTitle, 'export' => Dict::S('UI:Button:Export'), 'cancel' => Dict::S('UI:Button:Cancel'), 'title' => Dict::S('UI:RelationOption:Title'), + 'untitled' => Dict::S('UI:RelationOption:Untitled'), 'include_list' => Dict::S('UI:RelationOption:IncludeList'), 'comments' => Dict::S('UI:RelationOption:Comments'), 'grouping_threshold' => Dict::S('UI:RelationOption:GroupingThreshold'), @@ -1083,6 +1104,17 @@ EOF ), ), ); + if (!extension_loaded('gd')) + { + // PDF export requires GD + unset($aParams['export_as_pdf']); + } + if (!extension_loaded('gd') || is_null($sObjClass) || is_null($iObjKey)) + { + // PDF export requires GD AND a valid objclass/objkey couple + unset($aParams['export_as_pdf']); + unset($aParams['export_as_attachment']); + } $oP->add_ready_script("$('#$sId').simple_graph(".json_encode($aParams).");"); } catch(Exception $e) diff --git a/dictionaries/de.dictionary.itop.ui.php b/dictionaries/de.dictionary.itop.ui.php index 8df4bf7ca..6e3e9bdd9 100644 --- a/dictionaries/de.dictionary.itop.ui.php +++ b/dictionaries/de.dictionary.itop.ui.php @@ -781,6 +781,8 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm 'UI:Relation:ExportAsDocument' => 'Export as Document...~~', 'UI:Relation:DrillDown' => 'Details...~~', 'UI:Relation:PDFExportOptions' => 'PDF Export Options~~', + 'UI:Relation:AttachmentExportOptions_Name' => 'Options for Attachment to %1$s~~', + 'UI:RelationOption:Untitled' => 'Untitled~~', 'UI:Relation:Key' => 'Key~~', 'UI:Relation:Comments' => 'Comments~~', 'UI:RelationOption:Title' => 'Title~~', diff --git a/dictionaries/dictionary.itop.ui.php b/dictionaries/dictionary.itop.ui.php index 427e87f77..1be80e823 100644 --- a/dictionaries/dictionary.itop.ui.php +++ b/dictionaries/dictionary.itop.ui.php @@ -971,9 +971,11 @@ When associated with a trigger, each action is given an "order" number, specifyi 'UI:RelationGroupNumber_N' => 'Group #%1$d', 'UI:Relation:ExportAsPDF' => 'Export as PDF...', 'UI:RelationOption:GroupingThreshold' => 'Grouping threshold', - 'UI:Relation:ExportAsDocument' => 'Export as Document...', + 'UI:Relation:ExportAsAttachment' => 'Export as Attachment...', 'UI:Relation:DrillDown' => 'Details...', 'UI:Relation:PDFExportOptions' => 'PDF Export Options', + 'UI:Relation:AttachmentExportOptions_Name' => 'Options for Attachment to %1$s', + 'UI:RelationOption:Untitled' => 'Untitled', 'UI:Relation:Key' => 'Key', 'UI:Relation:Comments' => 'Comments', 'UI:RelationOption:Title' => 'Title', diff --git a/dictionaries/fr.dictionary.itop.ui.php b/dictionaries/fr.dictionary.itop.ui.php index aa95460f4..4a7d1ac3e 100644 --- a/dictionaries/fr.dictionary.itop.ui.php +++ b/dictionaries/fr.dictionary.itop.ui.php @@ -814,9 +814,11 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé 'UI:RelationGroupNumber_N' => 'Groupe n°%1$d', 'UI:Relation:ExportAsPDF' => 'Exporter en PDF...', 'UI:RelationOption:GroupingThreshold' => 'Seuil de groupage', - 'UI:Relation:ExportAsDocument' => 'Exporter comme Document...', + 'UI:Relation:ExportAsAttachment' => 'Exporter comme une Pièce Jointe...', 'UI:Relation:DrillDown' => 'Détails...', 'UI:Relation:PDFExportOptions' => 'Options de l\'export en PDF', + 'UI:Relation:AttachmentExportOptions_Name' => 'Options pour la Pièce Jointe à %1$s', + 'UI:RelationOption:Untitled' => 'Sans Titre', 'UI:Relation:Key' => 'Légende', 'UI:Relation:Comments' => 'Commentaires', 'UI:RelationOption:Title' => 'Titre', diff --git a/js/simple_graph.js b/js/simple_graph.js index c60d77443..3f4589d8c 100644 --- a/js/simple_graph.js +++ b/js/simple_graph.js @@ -33,7 +33,9 @@ $(function() export_as_document: null, drill_down: null, grouping_threshold: 10, - excluded_classes: [] + excluded_classes: [], + attachment_obj_class: null, + attachment_obj_key: null }, // the constructor @@ -371,9 +373,9 @@ $(function() { sHtml += '
  • '+this.options.export_as_pdf.label+'
  • '; } - if (this.options.export_as_document != null) + if (this.options.export_as_attachment != null) { - sHtml += '
  • '+this.options.export_as_document.label+'
  • '; + sHtml += '
  • '+this.options.export_as_attachment.label+'
  • '; } //sHtml += '
  • Refresh
  • '; sHtml += ''; @@ -385,7 +387,7 @@ $(function() var me = this; $('#'+sPopupMenuId+'_pdf').click(function() { me.export_as_pdf(); }); - $('#'+sPopupMenuId+'_document').click(function() { me.export_as_document(); }); + $('#'+sPopupMenuId+'_attachment').click(function() { me.export_as_attachment(); }); $('#'+sId+'_grouping_threshold').spinner({ min: 2}); $('#'+sId+'_refresh_btn').button().click(function() { me.reload(); }); }, @@ -449,13 +451,17 @@ $(function() }, 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) { var oPositions = {}; for(k in this.aNodes) { oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; } - var sHtmlForm = '
    '; + var sHtmlForm = '
    '; sHtmlForm += ''; sHtmlForm += ''; for(k in this.options.excluded_classes) @@ -476,6 +482,11 @@ $(function() sHtmlForm += ''; } } + if (sOperation == 'attachment') + { + sHtmlForm += ''; + sHtmlForm += ''; + } sHtmlForm += ''; sHtmlForm += ''; - sHtmlForm += ''; + sHtmlForm += ''; sHtmlForm += ''; sHtmlForm += ''; sHtmlForm += '
    '+this.options.page_format.label+'
    '+this.options.labels.title+'
    '+this.options.labels.title+'
    '+this.options.labels.comments+'
    '; sHtmlForm += ''; $('body').append(sHtmlForm); - $('#graph_'+this.element.attr('id')+'_export_as_pdf input[name="positions"]').val(JSON.stringify(oPositions)); + $('#graph_'+this.element.attr('id')+'_export_dlg input[name="positions"]').val(JSON.stringify(oPositions)); var me = this; - $('#PDFExportDlg'+this.element.attr('id')).dialog({ + if (sOperation == 'attachment') + { + $('#GraphExportDlg'+this.element.attr('id')+' form').submit(function() { return me._on_export_as_attachment(); }); + } + $('#GraphExportDlg'+this.element.attr('id')).dialog({ width: 'auto', modal: true, - title: this.options.labels.export_pdf_title, + 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_as_pdf').submit(); $(this).dialog('close');} }, + {text: this.options.labels['export'], click: function() { $('#graph_'+me.element.attr('id')+'_export_dlg').submit(); $(this).dialog('close');} }, ] - }); - //$('#graph_'+this.element.attr('id')+'_export_as_pdf').submit(); + }); }, _on_resize: function() { @@ -553,9 +567,54 @@ $(function() me.load(data); }, 'json'); }, - export_as_document: function() + export_as_attachment: function() { - alert('Export as document: not yet implemented'); + this._export_dlg(this.options.labels.export_as_attachment_title, this.options.export_as_attachment.url, 'attachment'); + }, + _on_export_as_attachment: function() + { + var oParams = {}; + var oPositions = {}; + var jForm = $('#GraphExportDlg'+this.element.attr('id')+' form'); + for(k in this.aNodes) + { + oPositions[this.aNodes[k].id] = {x: this.aNodes[k].x, y: this.aNodes[k].y }; + } + oParams.positions = JSON.stringify(oPositions); + oParams.sources = this.options.sources; + oParams.excluded_classes = this.options.excluded_classes; + oParams.title = jForm.find(':input[name="title"]').val(); + oParams.comments = jForm.find(':input[name="comments"]').val(); + oParams.include_list = jForm.find(':input[name="include_list"]:checked').length; + oParams.o = jForm.find(':input[name="o"]').val(); + oParams.p = jForm.find(':input[name="p"]').val(); + oParams.obj_class = this.options.export_as_attachment.obj_class; + oParams.obj_key = this.options.export_as_attachment.obj_key; + var sUrl = jForm.attr('action'); + var sTitle = oParams.title; + var jPanel = $('#attachments').closest('.ui-tabs-panel'); + var jTab = null; + var sTabText = null; + if (jPanel.length > 0) + { + var sTabId = jPanel.attr('id'); + jTab = $('li[aria-controls='+sTabId+']'); + sTabText = jTab.find('span').html(); + jTab.find('span').html(sTabText+' '); + } + $.post(sUrl, oParams, function(data) { + var sDownloadLink = GetAbsoluteUrlAppRoot()+'pages/ajax.render.php?operation=download_document&class=Attachment&id='+data.att_id+'&field=contents'; + var sIcon = GetAbsoluteUrlModulesRoot()+'itop-attachments/icons/pdf.png'; + $('#attachments').append(''); + if (jTab != null) + { + var re = /^([^(]+)\(([0-9]+)\)(.*)$/; + var aParts = re.exec(sTabText); + var iPrevCount = parseInt(aParts[2], 10); + jTab.find('span').html(aParts[1]+'('+(1 + iPrevCount)+')'+aParts[3]); + } + }, 'json'); + return false; }, reload: function() { diff --git a/pages/ajax.render.php b/pages/ajax.render.php index a279f21c7..163bd6820 100644 --- a/pages/ajax.render.php +++ b/pages/ajax.render.php @@ -1728,13 +1728,14 @@ EOF break; case 'relation_pdf': + case 'relation_attachment': 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); + $iGroupingThreshold = utils::ReadParam('g', 5, false, 'integer'); $sPageFormat = utils::ReadParam('p', 'A4'); $sPageOrientation = utils::ReadParam('o', 'L'); $sTitle = utils::ReadParam('title', '', false, 'raw_data'); @@ -1876,6 +1877,26 @@ EOF } } } + if ($operation == 'relation_attachment') + { + $sObjClass = utils::ReadParam('obj_class', '', false, 'class'); + $iObjKey = (int)utils::ReadParam('obj_key', 0, false, 'integer'); + + // Save the generated PDF as an attachment + $sPDF = $oPage->get_pdf(); + $oPage = new ajax_page(''); + $oAttachment = new Attachment(); + $oAttachment->Set('item_class', $sObjClass); + $oAttachment->Set('item_id', $iObjKey); + $oDoc = new ormDocument($sPDF, 'application/pdf', $sTitle.'.pdf'); + $oAttachment->Set('contents', $oDoc); + $iAttachmentId = $oAttachment->DBInsert(); + $aRet = array( + 'status' => 'ok', + 'att_id' => $iAttachmentId, + ); + $oPage->add(json_encode($aRet)); + } break; case 'relation_json': @@ -2004,7 +2025,7 @@ EOF $aResults = $oRelGraph->GetObjectsByClass(); $oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down')); $oAppContext = new ApplicationContext(); - $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects); + $oGraph->Display($oPage, $aResults, $sRelation, $oAppContext, $aExcludedObjects, $sClass, $iId); break; default: