Przeglądaj źródła

Automatically save the PDF of the impact analysis as an attachement to the ticket.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3582 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 lat temu
rodzic
commit
badf45f3b5

+ 6 - 0
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');
+	}
 }

+ 52 - 20
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, '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'</p>', 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 = '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8'), $iNbLines).'</p>';
+			$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('<div id="'.$sId.'" class="simple-graph"></div>');
@@ -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)

+ 2 - 0
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~~',

+ 3 - 1
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',

+ 3 - 1
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',

+ 73 - 14
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 += '<li><a href="#" id="'+sPopupMenuId+'_pdf">'+this.options.export_as_pdf.label+'</a></li>';			
 			}
-			if (this.options.export_as_document != null)
+			if (this.options.export_as_attachment != null)
 			{
-				sHtml += '<li><a href="#" id="'+sPopupMenuId+'_document">'+this.options.export_as_document.label+'</a></li>';
+				sHtml += '<li><a href="#" id="'+sPopupMenuId+'_attachment">'+this.options.export_as_attachment.label+'</a></li>';
 			}
 			//sHtml += '<li><a href="#" id="'+sPopupMenuId+'_reload">Refresh</a></li>';
 			sHtml += '</ul></li></ul></div>';
@@ -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(); });
 		},
@@ -450,12 +452,16 @@ $(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 = '<div id="PDFExportDlg'+this.element.attr('id')+'"><form id="graph_'+this.element.attr('id')+'_export_as_pdf" target="_blank" action="'+this.options.export_as_pdf.url+'" method="post">';
+			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="positions" value="">';
 			for(k in this.options.excluded_classes)
@@ -476,6 +482,11 @@ $(function()
 					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)
@@ -491,26 +502,29 @@ $(function()
 				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"  style="width: 20em;"/></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_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+' <img style="vertical-align:bottom" src="../images/indicator.gif">');
+			}
+			$.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('<div class="attachment" id="display_attachment_'+data.att_id+'"><a data-preview="false" href="'+sDownloadLink+'"><img src="'+sIcon+'"><br/>'+sTitle+'.pdf<input id="attachment_'+data.att_id+'" type="hidden" name="attachments[]" value="'+data.att_id+'"/></a><br/><input type="button" class="btn_hidden" value="{$sDeleteBtn}" onClick="RemoveAttachment('+data.att_id+');"/></div>');
+				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()
 		{

+ 23 - 2
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: