Browse Source

More options for the PDF export of the 'impact' graph.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3577 a333f486-631f-4898-b8df-5754b55c2be0
dflaven 10 years ago
parent
commit
2147e65a32

+ 39 - 1
application/cmdbabstract.class.inc.php

@@ -705,7 +705,45 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 	
 	public static function DisplaySet(WebPage $oPage, CMDBObjectSet $oSet, $aExtraParams = array())
 	{
-		$oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams));
+		if ($oPage->is_pdf())
+		{
+			$oPage->add(self::DisplaySetForPrinting($oPage, $oSet, $aExtraParams));
+		}
+		else
+		{
+			$oPage->add(self::GetDisplaySet($oPage, $oSet, $aExtraParams));
+		}
+	}
+	
+	/**
+	 * Simplifed version of GetDisplaySet() with less "decoration" around the table (and no paging)
+	 * that fits better into a printed document (like a PDF)
+	 * @param PDFPage $oPage
+	 * @param DBObjectSet $oSet
+	 * @param hash $aExtraParams
+	 * @return string The HTML representation of the table
+	 */
+	public static function DisplaySetForPrinting(PDFPage $oPage, DBObjectSet $oSet, $aExtraParams = array())
+	{
+		$iListId = empty($aExtraParams['currentId']) ? $oPage->GetUniqueId() : $aExtraParams['currentId'];
+		$sTableId = isset($aExtraParams['table_id']) ? $aExtraParams['table_id'] : null;
+		
+		$bViewLink = true;
+		$sSelectMode = 'none';
+		$iListId = $sTableId;
+		$sClassAlias = $oSet->GetClassAlias();
+		$sClassName = $oSet->GetClass();
+		$sZListName = 'list';
+		$aClassAliases = array( $sClassAlias => $sClassName);
+		$aList = cmdbAbstractObject::FlattenZList(MetaModel::GetZListItems($sClassName, $sZListName));
+	
+		$oDataTable = new PrintableDataTable($iListId, $oSet, $aClassAliases, $sTableId);
+		$oSettings = DataTableSettings::GetDataModelSettings($aClassAliases, $bViewLink, array($sClassAlias => $aList));
+		$oSettings->iDefaultPageSize = 0;
+		$oSettings->aSortOrder = MetaModel::GetOrderByDefault($sClassName);
+	
+		return $oDataTable->Display($oPage, $oSettings, false /* $bDisplayMenu */, $sSelectMode, $bViewLink, $aExtraParams);
+	
 	}
 
 	/**

+ 28 - 0
application/datatable.class.inc.php

@@ -606,6 +606,34 @@ EOF
 	}
 }
 
+/**
+ * Simplified version of the data table with less "decoration" (and no paging)
+ * which is optimized for printing
+ */
+class PrintableDataTable extends DataTable
+{
+	public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
+	{
+		return $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, -1, $bViewLink, $aExtraParams);
+	}
+	
+	public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
+	{
+		$iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
+		if ($iPageSize < 1)
+		{
+			$iPageSize = -1; // convention: no pagination
+		}
+		$aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
+	
+		$aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
+	
+		$sHtml = $oPage->GetTable($aAttribs, $aValues);
+		
+		return $sHtml;
+	}
+}
+
 class DataTableSettings implements Serializable
 {
 	public $aClassAliases;

+ 190 - 0
application/pdfpage.class.inc.php

@@ -0,0 +1,190 @@
+<?php
+require_once(APPROOT.'lib/tcpdf/tcpdf.php');
+
+/**
+ * Custom class derived from TCPDF for providing custom headers and footers
+ * @author denis
+ *
+ */
+class iTopPDF extends TCPDF
+{
+	protected $sDocumentTitle;
+	
+	public function SetDocumentTitle($sDocumentTitle)
+	{
+		$this->sDocumentTitle = $sDocumentTitle;
+	}
+
+	/**
+	 * Builds the custom header. Called for each new page.
+	 * @see TCPDF::Header()
+	 */
+	public function Header()
+	{
+		// Title
+		// Set font
+		$this->SetFont('dejavusans', 'B', 10);
+		
+		$iPageNumberWidth = 25;
+		$aMargins = $this->getMargins();
+		
+		// Display the title (centered)
+		$this->SetY(0);
+		$this->MultiCell($this->getPageWidth() - $aMargins['left'] - $aMargins['right'] - $iPageNumberWidth, 15, $this->sDocumentTitle, 0, 'C', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
+		$this->SetFont('dejavusans', '', 10);
+		
+		// Display the page number (right aligned)
+		// Warning: the 'R'ight alignment does not work when using placeholders like $this->getAliasNumPage() or $this->getAliasNbPages()
+		$this->MultiCell($iPageNumberWidth, 15, 'Page '.$this->page, 0, 'R', false, 0 /* $ln */, '', '', true, 0, false, true, 15, 'M' /* $valign */);
+		
+		// Branding logo
+		$sBrandingIcon = APPROOT.'images/itop-logo.png';
+		if (file_exists(MODULESROOT.'branding/main-logo.png'))
+		{
+			$sBrandingIcon = MODULESROOT.'branding/main-logo.png';
+		}
+		$this->Image($sBrandingIcon, $aMargins['left'], 5, 0, 10);
+	}
+
+	// Page footer
+	public function Footer()
+	{
+		// No footer
+	}
+}
+
+/**
+ * Special class of WebPage for printing into a PDF document
+ */
+class PDFPage extends WebPage
+{
+	/**
+	 * Instance of the TCPDF object for creating the PDF
+	 * @var TCPDF
+	 */
+	protected $oPdf;
+	
+	public function __construct($s_title, $sPageFormat = 'A4', $sPageOrientation = 'L')
+	{
+		parent::__construct($s_title);
+		define(K_PATH_FONTS, APPROOT.'lib/tcpdf/fonts');
+		$this->oPdf = new iTopPDF($sPageOrientation, 'mm', $sPageFormat, true, 'UTF-8', false);
+		
+		// set document information
+		$this->oPdf->SetCreator(PDF_CREATOR);
+		$this->oPdf->SetAuthor('iTop');
+		$this->oPdf->SetTitle($s_title);
+		$this->oPdf->SetDocumentTitle($s_title);
+		
+		$this->oPdf->setFontSubsetting(true);
+		
+		// Set font
+		// dejavusans is a UTF-8 Unicode font. Standard PDF fonts like helvetica or times new roman are NOT UTF-8
+		$this->oPdf->SetFont('dejavusans', '', 10, '', true);
+		
+		// set auto page breaks
+		$this->oPdf->SetAutoPageBreak(true, 15); // 15 mm break margin at the bottom
+		$this->oPdf->SetTopMargin(15);
+		
+		// Add a page, we're ready to start
+		$this->oPdf->AddPage();
+		
+		$this->SetContentType('application/pdf');
+		$this->SetContentDisposition('inline', $s_title.'.pdf');
+		$this->SetDefaultStyle();
+		
+	}
+	
+	/**
+	 * Sets a default style (suitable for printing) to be included each time $this->oPdf->writeHTML() is called
+	 */
+	protected function SetDefaultStyle()
+	{
+		$this->add_style(
+<<<EOF
+table.listResults td {
+	border: 0.5pt solid #000 ;
+	padding: 0.5pt;
+}
+table.listResults th {
+	background-color: #eee;
+	border: 0.5pt solid #000 ;
+}
+a {
+	text-decoration: none;
+	color: #000;
+}
+table.section td {
+	vertical-align: middle;
+	font-size: 10pt;
+	background-color:#eee;
+}
+td.icon {
+	width: 30px;
+}
+EOF
+		);		
+	}
+	
+	/**
+	 * Get access to the underlying TCPDF object
+	 * @return TCPDF
+	 */
+	public function get_tcpdf()
+	{
+		$this->flush();
+		return $this->oPdf;
+	}
+	
+	/**
+	 * Writes the currently buffered HTML content into the PDF. This can be useful:
+	 * - to sync the flow in case you want to access the underlying TCPDF object for some specific/graphic output
+	 * - to process the HTML by smaller chunks instead of processing the whole page at once for performance reasons
+	 */
+	public function flush()
+	{
+		if (strlen($this->s_content) > 0)
+		{
+			$sHtml = '';
+			if (count($this->a_styles) > 0)
+			{
+				$sHtml .= "<style>\n".implode("\n", $this->a_styles)."\n</style>\n";
+			}
+			$sHtml .= $this->s_content;
+			$this->oPdf->writeHTML($sHtml); // The style(s) must be supplied each time we call writeHtml
+			$this->s_content = '';
+		}
+	}
+	
+	/**
+	 * Whether or not the page is a PDF page
+	 * @return boolean
+	 */
+	public function is_pdf()
+	{
+		return true;
+	}
+	
+	/**
+	 * Generates the PDF document and returns the PDF content as a string
+	 * @return string
+	 * @see WebPage::output()
+	 */
+	public function output()
+	{
+	    if (!empty($this->sContentType))
+    	{
+			$this->add_header('Content-type: '.$this->sContentType);
+    	}
+    	if (!empty($this->sContentDisposition))
+    	{
+			$this->add_header('Content-Disposition: '.$this->sContentDisposition.'; filename="'.$this->sContentFileName.'"');
+    	}
+    	foreach($this->a_headers as $s_header)
+        {
+            header($s_header);
+        }
+        $this->flush();
+		echo $this->oPdf->Output($this->s_title.'.pdf', 'S');
+	}
+}

+ 30 - 1
application/webpage.class.inc.php

@@ -268,7 +268,27 @@ class WebPage implements Page
     {
         $this->a_linked_stylesheets[] = array( 'link' => $s_linked_stylesheet, 'condition' => $s_condition);
     }
-
+    
+    public function add_saas($sSaasRelPath)
+    {
+    	$sSaasPath = APPROOT.$sSaasRelPath;
+    	$sCssRelPath = preg_replace('/\.scss$/', '.css', $sSaasRelPath);
+    	$sCssPath = APPROOT.$sCssRelPath;
+    	clearstatcache();
+    	if (!file_exists($sCssPath) || (is_writable($sCssPath) && (filemtime($sCssPath) < filemtime($sSaasPath))))
+    	{
+    		// Rebuild the CSS file from the Saas file
+    		if (file_exists(APPROOT.'lib/sass/sass/SassParser.php'))
+    		{
+    			require_once(APPROOT.'lib/sass/sass/SassParser.php'); //including Sass libary (Syntactically Awesome Stylesheets)
+    			$oParser = new SassParser(array('style'=>'expanded'));
+    			$sCss = $oParser->toCss($sSaasPath);
+    			file_put_contents($sCssPath, $sCss);
+    		}
+    	}
+    	$sCSSUrl = utils::GetAbsoluteUrlAppRoot().$sCssRelPath;
+    	$this->add_linked_stylesheet($sCSSUrl);
+    }
 	/**
 	 * Add some custom header to the page
 	 */
@@ -294,6 +314,15 @@ class WebPage implements Page
 
 		$this->add($this->GetDetails($aFields));
     }
+    
+    /**
+     * Whether or not the page is a PDF page
+     * @return boolean
+     */
+    public function is_pdf()
+    {
+    	return false;
+    }
 
 	/**
 	 * Records the current state of the 'html' part of the page output

+ 86 - 43
core/displayablegraph.class.inc.php

@@ -892,55 +892,49 @@ class DisplayableGraph extends SimpleGraph
 	}
 	
 	/**
-	 * Renders the graph as a PDF file
-	 * @param WebPage $oP The page for the ouput of the PDF
-	 * @param string $sTitle The title of the PDF
-	 * @param string $sPageFormat The page format: A4, A3, Letter...
-	 * @param string $sPageOrientation The orientation of the page (L = Landscape, P = Portrait)
+	 * Renders the graph in a PDF document: centered in the current page
+	 * @param PDFPage $oPage The PDFPage representing the PDF document to draw into
+	 * @param string $sComments An optional comment to  display next to the graph (HTML entities will be escaped, \n replaced by <br/>)
+	 * @param float $xMin Left coordinate of the bounding box to display the graph
+	 * @param float $xMax Right coordinate of the bounding box to display the graph
+	 * @param float $yMin Top coordinate of the bounding box to display the graph
+	 * @param float $yMax Bottom coordinate of the bounding box to display the graph
 	 */
-	function RenderAsPDF(WebPage $oP, $sTitle = 'Untitled', $sPageFormat = 'A4', $sPageOrientation = 'P')
+	function RenderAsPDF(PDFPage $oPage, $sComments = '', $xMin = -1, $xMax = -1, $yMin = -1, $yMax = -1)
 	{
-		require_once(APPROOT.'lib/tcpdf/tcpdf.php');
-		$oPdf = new TCPDF($sPageOrientation, 'mm', $sPageFormat, true, 'UTF-8', false);
-		
-		// set document information
-		$oPdf->SetCreator(PDF_CREATOR);
-		$oPdf->SetAuthor('iTop');
-		$oPdf->SetTitle($sTitle);
-		
-		$oPdf->setFontSubsetting(true);
-		
-		// Set font
-		// dejavusans is a UTF-8 Unicode font, if you only need to
-		// print standard ASCII chars, you can use core fonts like
-		// helvetica or times to reduce file size.
-		$oPdf->SetFont('dejavusans', '', 14, '', true);
-		
-		// set auto page breaks
-		$oPdf->SetAutoPageBreak(false);
-		
-		// Add a page
-		// This method has several options, check the source code documentation for more information.
-		$oPdf->AddPage();
-		
+		$oPdf = $oPage->get_tcpdf();
+				
 		$aBB = $this->GetBoundingBox();
 		$this->Translate(-$aBB['xmin'], -$aBB['ymin']);
 		
-		if ($sPageOrientation == 'P')
+		$aMargins = $oPdf->getMargins();
+		
+		if ($xMin == -1)
 		{
-			// Portrait mode
-			$fHMargin = 10; // mm
-			$fVMargin = 15; // mm
+			$xMin = $aMargins['left'];
 		}
-		else
+		if ($xMax == -1)
+		{
+			$xMax =  $oPdf->getPageWidth() - $aMargins['right'];
+		}
+		if ($yMin == -1)
 		{
-			// Landscape mode
-			$fHMargin = 15; // mm
-			$fVMargin = 10; // mm
+			$yMin = $aMargins['top'];
 		}
+		if ($yMax == -1)
+		{
+			$yMax = $oPdf->getPageHeight() - $aMargins['bottom'];
+		}
+		
+		$fBreakMargin = $oPdf->getBreakMargin();
+		$oPdf->SetAutoPageBreak(false);
+		$fKeyWidth = $this->RenderKey($oPdf, $sComments, $xMin, $yMin, $xMax, $yMax);
+		$xMin += + $fKeyWidth;
 		
-		$fPageW = $oPdf->getPageWidth() - 2 * $fHMargin;
-		$fPageH = $oPdf->getPageHeight() - 2 * $fVMargin;
+		//$oPdf->Rect($xMin, $yMin, $xMax - $xMin, $yMax - $yMin, 'D', array(), array(225, 225, 225));
+		
+		$fPageW = $xMax - $xMin;
+		$fPageH = $yMax - $yMin;
 		
 		$w = $aBB['xmax'] - $aBB['xmin']; 
 		$h = $aBB['ymax'] - $aBB['ymin'] + 10; // Extra space for the labels which may appear "below" the icons
@@ -949,21 +943,70 @@ class DisplayableGraph extends SimpleGraph
 		$dx = ($fPageW - $fScale * $w) / 2;
 		$dy = ($fPageH - $fScale * $h) / 2;
 		
-		$this->Translate(($fHMargin + $dx)/$fScale, ($fVMargin + $dy)/$fScale);
-		
+		$this->Translate(($xMin + $dx)/$fScale, ($yMin + $dy)/$fScale);
+
 		$oIterator = new RelationTypeIterator($this, 'Edge');
+		$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
 		foreach($oIterator as $sId => $oEdge)
 		{
+			set_time_limit($iLoopTimeLimit);
 			$oEdge->RenderAsPDF($oPdf, $this, $fScale);
 		}
 
 		$oIterator = new RelationTypeIterator($this, 'Node');
 		foreach($oIterator as $sId => $oNode)
 		{
+			set_time_limit($iLoopTimeLimit);
 			$oNode->RenderAsPDF($oPdf, $this, $fScale);
 		}
-		
-		$oP->add($oPdf->Output('iTop.pdf', 'S'));	
+		$oIterator = new RelationTypeIterator($this, 'Node');
+		$oPdf->SetAutoPageBreak(true, $fBreakMargin);
+		$oPdf->SetAlpha(1);
 	}
 	
+	protected function RenderKey(TCPDF $oPdf, $sComments, $xMin, $yMin, $xMax, $yMax)
+	{
+		$oIterator = new RelationTypeIterator($this, 'Node');
+		$fMaxWidth = max($oPdf->GetStringWidth(Dict::S('UI:Relation:Key')) - 6, $oPdf->GetStringWidth(Dict::S('UI:Relation:Comments')) - 6);
+		$aClasses = array();
+		$aIcons = array();
+		$oPdf->SetFont('dejavusans', '', 8, '', true);
+		foreach($oIterator as $sId => $oNode)
+		{
+			if ($sClass = $oNode->GetProperty('class'))
+			{
+				if (!array_key_exists($sClass, $aClasses))
+				{
+					$sClassLabel = MetaModel::GetName($sClass);
+					$width = $oPdf->GetStringWidth($sClassLabel);
+					$fMaxWidth = max($width, $fMaxWidth);
+					$aClasses[$sClass] = $sClassLabel;
+					$sIconUrl = $oNode->GetProperty('icon_url');
+					$sIconPath = str_replace(utils::GetAbsoluteUrlModulesRoot(), APPROOT.'env-production/', $sIconUrl);
+					$aIcons[$sClass] = $sIconPath;
+				}
+			}
+		}
+		$oPdf->SetXY($xMin + 1, $yMin +1);
+		$yPos = $yMin + 1;
+		$oPdf->SetFillColor(225, 225, 225);
+		$oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Key'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
+		$yPos += 8;
+		foreach($aClasses as $sClass => $sLabel)
+		{
+			$oPdf->SetX($xMin + 7);
+			$oPdf->Cell(0, 8, $sLabel, 0 /* border */, 1 /* ln */);
+			$oPdf->Image($aIcons[$sClass], $xMin+1, $yPos, 6, 6);
+			$yPos += 8; 
+		}
+		$oPdf->Rect($xMin, $yMin, $fMaxWidth+9, $yPos - $yMin, 'D');
+		$oPdf->Rect($xMin, $yPos, $fMaxWidth+9, $yMax - $yPos, 'D');
+		$yPos +=1;
+		$oPdf->SetXY($xMin + 1, $yPos);
+		$oPdf->Cell(7 + $fMaxWidth, 7, Dict::S('UI:Relation:Comments'), 0 /* border */, 1 /* ln */, 'C', true /* fill */);
+		$yPos += 8;
+		$oPdf->SetX($xMin);
+		$oPdf->writeHTMLCell(8 + $fMaxWidth, $yMax - $yPos, $xMin, $yPos, '<p>'.str_replace("\n", '<br/>', htmlentities($sComments, ENT_QUOTES, 'UTF-8')).'</p>', 0 /* border */, 1 /* ln */);
+		return $fMaxWidth + 10;
+	}
 }

+ 19 - 0
dictionaries/de.dictionary.itop.ui.php

@@ -774,6 +774,25 @@ Wenn Aktionen mit Trigger verknüpft sind, bekommt jede Aktion eine Auftragsnumm
 	'UI:DisplayThisMessageAtStartup' => 'Diese Meldung beim Start immer anzeigen',
 	'UI:RelationshipGraph' => 'Grafische Ansicht',
 	'UI:RelationshipList' => 'Liste',
+	'UI:RelationGroups' => 'Gruppen',
+	'UI:RelationGroupNumber_N' => 'Gruppe #%1$d~~',
+	'UI:Relation:ExportAsPDF' => 'Export as PDF...~~',
+	'UI:Relation:ExportAsDocument' => 'Export as Document...~~',
+	'UI:Relation:DrillDown' => 'Details...~~',
+	'UI:Relation:PDFExportOptions' => 'PDF Export Options~~',
+	'UI:Relation:Key' => 'Key~~',
+	'UI:Relation:Comments' => 'Comments~~',
+	'UI:RelationOption:Title' => 'Title~~',
+	'UI:RelationOption:IncludeList' => 'Include the list of objects~~',
+	'UI:RelationOption:Comments' => 'Comments~~',
+	'UI:Button:Export' => 'Export~~',
+	'UI:Relation:PDFExportPageFormat' => 'Page format~~',
+	'UI:PageFormat_A3' => 'A3~~',
+	'UI:PageFormat_A4' => 'A4~~',
+	'UI:PageFormat_Letter' => 'Letter~~',
+	'UI:Relation:PDFExportPageOrientation' => 'Page orientation~~',
+	'UI:PageOrientation_Portrait' => 'Portrait~~',
+	'UI:PageOrientation_Landscape' => 'Landscape~~',
 	'UI:OperationCancelled' => 'Operation abgebrochen',
 	'UI:ElementsDisplayed' => 'Filtere',
 	'Portal:Title' => 'iTop-Benutzerportal',

+ 5 - 0
dictionaries/dictionary.itop.ui.php

@@ -973,6 +973,11 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:Relation:ExportAsDocument' => 'Export as Document...',
 	'UI:Relation:DrillDown' => 'Details...',
 	'UI:Relation:PDFExportOptions' => 'PDF Export Options',
+	'UI:Relation:Key' => 'Key',
+	'UI:Relation:Comments' => 'Comments',
+	'UI:RelationOption:Title' => 'Title',
+	'UI:RelationOption:IncludeList' => 'Include the list of objects',
+	'UI:RelationOption:Comments' => 'Comments',
 	'UI:Button:Export' => 'Export',
 	'UI:Relation:PDFExportPageFormat' => 'Page format',
 	'UI:PageFormat_A3' => 'A3',

+ 5 - 0
dictionaries/fr.dictionary.itop.ui.php

@@ -816,6 +816,11 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:Relation:ExportAsDocument' => 'Exportation comme Document...',
 	'UI:Relation:DrillDown' => 'Détails...',
 	'UI:Relation:PDFExportOptions' => 'Options de l\'Exportation PDF',
+	'UI:Relation:Key' => 'Légende',
+	'UI:Relation:Comments' => 'Commentaires',
+	'UI:RelationOption:Title' => 'Titre',
+	'UI:RelationOption:IncludeList' => 'Inclure la liste des objets',
+	'UI:RelationOption:Comments' => 'Commentaires',
 	'UI:Button:Export' => 'Exporter',
 	'UI:Relation:PDFExportPageFormat' => 'Format de page',
 	'UI:PageFormat_A3' => 'A3',

+ 5 - 1
js/simple_graph.js

@@ -19,7 +19,7 @@ $(function()
 			export_as_pdf: null,
 			page_format: { label: 'Page Format:', values: { A3: 'A3', A4: 'A4', Letter: 'Letter' }, 'default': 'A4'},
 			page_orientation: { label: 'Page Orientation:', values: { P: 'Portait', L: 'Landscape' }, 'default': 'L' },
-			labels: { export_pdf_title: 'PDF Export Options', cancel: 'Cancel', 'export': 'Export' },
+			labels: { export_pdf_title: 'PDF Export Options', cancel: 'Cancel', 'export': 'Export', title: 'Document Title', include_list: 'Include the list of objects', comments: 'Comments' },
 			export_as_document: null,
 			drill_down: null,
 			excluded: []
@@ -458,6 +458,9 @@ $(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.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>';
 			
@@ -465,6 +468,7 @@ $(function()
 			$('#graph_'+this.element.attr('id')+'_export_as_pdf input[name="positions"]').val(JSON.stringify(oPositions));
 			var me = this;
 			$('#PDFExportDlg'+this.element.attr('id')).dialog({
+				width: 'auto',
 				modal: true,
 				title: this.options.labels.export_pdf_title,
 				close: function() { $(this).remove(); },

BIN
lib/tcpdf/fonts/dejavusans.z


BIN
lib/tcpdf/fonts/dejavusansb.ctg.z


File diff suppressed because it is too large
+ 13 - 0
lib/tcpdf/fonts/dejavusansb.php


BIN
lib/tcpdf/fonts/dejavusansb.z


BIN
lib/tcpdf/fonts/dejavusansbi.ctg.z


File diff suppressed because it is too large
+ 13 - 0
lib/tcpdf/fonts/dejavusansbi.php


BIN
lib/tcpdf/fonts/dejavusansbi.z


+ 4 - 1
pages/UI.php

@@ -330,6 +330,9 @@ EOF
 				'export_pdf_title' => Dict::S('UI:Relation:PDFExportOptions'),
 				'export' => Dict::S('UI:Button:Export'),
 				'cancel' => Dict::S('UI:Button:Cancel'),
+				'title' => Dict::S('UI:RelationOption:Title'),
+				'include_list' => Dict::S('UI:RelationOption:IncludeList'),
+				'comments' => Dict::S('UI:RelationOption:Comments'),
 			),
 			'page_format' => array(
 				'label' => Dict::S('UI:Relation:PDFExportPageFormat'),
@@ -1615,7 +1618,7 @@ EOF
 			if ($oObj)
 			{
 				$sObjClass  = get_class($oObj);
-				if (!array_key_exists($sClass, $aResults))
+				if (!array_key_exists($sObjClass, $aResults))
 				{
 					$aResults[$sObjClass] = array();
 				}

+ 80 - 5
pages/ajax.render.php

@@ -28,6 +28,7 @@ require_once('../approot.inc.php');
 require_once(APPROOT.'/application/application.inc.php');
 require_once(APPROOT.'/application/webpage.class.inc.php');
 require_once(APPROOT.'/application/ajaxwebpage.class.inc.php');
+require_once(APPROOT.'/application/pdfpage.class.inc.php');
 require_once(APPROOT.'/application/wizardhelper.class.inc.php');
 require_once(APPROOT.'/application/ui.linkswidget.class.inc.php');
 require_once(APPROOT.'/application/ui.extkeywidget.class.inc.php');
@@ -1731,7 +1732,7 @@ EOF
 		require_once(APPROOT.'core/relationgraph.class.inc.php');
 		require_once(APPROOT.'core/displayablegraph.class.inc.php');
 		$sClass = utils::ReadParam('class', '', false, 'class');
-		$id = utils::ReadParam('id', 0);
+		$id = (int)utils::ReadParam('id', 0);
 		$sRelation = utils::ReadParam('relation', 'impact');
 		$sDirection = utils::ReadParam('direction', 'down');
 		
@@ -1741,6 +1742,8 @@ EOF
 		$sTitle = utils::ReadParam('title', '', false, 'raw_data');
 		$sPositions = utils::ReadParam('positions', null, false, 'raw_data');
 		$aExcluded = utils::ReadParam('excluded', array(), false, 'raw_data');
+		$bIncludeList = (bool)utils::ReadParam('include_list', false);
+		$sComments = utils::ReadParam('comments', '', false, 'raw_data');
 		$aPositions = null;
 		if ($sPositions != null)
 		{
@@ -1773,16 +1776,88 @@ EOF
 			}
 		}
 		
+		$oPage = new PDFPage($sTitle, $sPageFormat, $sPageOrientation);
+		
 		$oGraph = DisplayableGraph::FromRelationGraph($oRelGraph, $iGroupingThreshold, ($sDirection == 'down'));
 		$oGraph->InitFromGraphviz();
 		if ($aPositions != null)
 		{
 			$oGraph->UpdatePositions($aPositions);
 		}
-		$oGraph->RenderAsPDF($oPage, $sTitle, $sPageFormat, $sPageOrientation);
-		
-		$oPage->SetContentType('application/pdf');
-		$oPage->SetContentDisposition('inline', 'iTop.pdf');
+		$iGroupIdx = 0;
+		$aGroups = array();
+		$oIterator = new RelationTypeIterator($oGraph, 'Node');
+		foreach($oIterator as $oNode)
+		{
+			if ($oNode instanceof DisplayableGroupNode)
+			{
+				$aGroups[] = $oNode->GetObjects();
+				$oNode->SetProperty('group_index', $iGroupIdx);
+				$iGroupIdx++;
+			}
+		}
+		// First page is the graph
+		$oGraph->RenderAsPDF($oPage, $sComments);
+		/*
+		// Experimental QR code at the bottom left of the page...
+		$sUrl = "r=$sRelation&d=$sDirection&c=$sClass&id=$id";
+		$oPdf = $oPage->get_tcpdf();
+		$aMargins = $oPdf->getMargins();
+		$oPdf->write2DBarcode($sUrl, 'QRCODE,H', $aMargins['left'], $oPdf->getPageHeight() - $aMargins['bottom'] - 35, 25, 25, array(), 'N');
+		*/
+		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->add('<div class="page_header"><h1>'.Dict::S('UI:RelationshipList').'</h1></div>');
+			$iLoopTimeLimit = MetaModel::GetConfig()->Get('max_execution_time_per_loop');
+			foreach($aResults as $sListClass => $aObjects)
+			{
+				set_time_limit($iLoopTimeLimit);
+				$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
+				$sHtml = "<div class=\"page_header\">\n";
+				$sHtml .= "<table class=\"section\"><tr><td>".MetaModel::GetClassIcon($sListClass, true, 'width: 24px; height: 24px;')." ".Dict::Format('UI:Search:Count_ObjectsOf_Class_Found', $oSet->Count(), Metamodel::GetName($sListClass))."</td></tr></table>\n";
+				$sHtml .= "</div>\n";
+				$oPage->add($sHtml);
+				cmdbAbstractObject::DisplaySet($oPage, $oSet);
+				$oPage->p(''); // Some space
+			}
+			
+			// Then the content of the groups (one table per group)
+			if (count($aGroups) > 0)
+			{
+				$oPage->get_tcpdf()->AddPage();
+				$oPage->add('<div class="page_header"><h1>'.Dict::S('UI:RelationGroups').'</h1></div>');
+				foreach($aGroups as $idx => $aObjects)
+				{
+					set_time_limit($iLoopTimeLimit);
+					$sListClass = get_class(current($aObjects));
+					$oSet = CMDBObjectSet::FromArray($sListClass, $aObjects);
+					$sHtml = "<div class=\"page_header\">\n";
+					$sHtml .= "<table class=\"section\"><tr><td>".MetaModel::GetClassIcon($sListClass, true, 'width: 24px; height: 24px;')." ".Dict::Format('UI:RelationGroupNumber_N', (1+$idx))."</td></tr></table>\n";
+					$sHtml .= "</div>\n";
+					$oPage->add($sHtml);
+					cmdbAbstractObject::DisplaySet($oPage, $oSet);
+					$oPage->p(''); // Some space
+				}
+			}
+		}
 		break;
 		
 		case 'relation_json':

Some files were not shown because too many files changed in this diff