Browse Source

#576 Printable view for object details. Possibility to hide/show chapters (the equivalent of tabs in the UI) or any fieldset. Requires testing and comments.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@3679 a333f486-631f-4898-b8df-5754b55c2be0
romainq 9 years ago
parent
commit
a84684630b

+ 5 - 2
application/ajaxwebpage.class.inc.php

@@ -42,7 +42,10 @@ class ajax_page extends WebPage implements iTabbedPage
      */	  
 	function __construct($s_title)
     {
-        parent::__construct($s_title);
+		$sPrintable = utils::ReadParam('printable', '0');
+		$bPrintable = ($sPrintable == '1');
+
+        parent::__construct($s_title, $bPrintable);
         $this->m_sReadyScript = "";
 		//$this->add_header("Content-type: text/html; charset=utf-8");
 		$this->add_header("Cache-control: no-cache");
@@ -197,7 +200,7 @@ EOF
 );
 		}
 		// Render the tabs in the page (if any)
-		$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content);
+		$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
 		
 		// Additional UI widgets to be activated inside the ajax fragment ??
     	if (($this->sContentType == 'text/html') && (preg_match('/class="date-pick"/', $this->s_content) || preg_match('/class="datetime-pick"/', $this->s_content)) )

+ 118 - 103
application/cmdbabstract.class.inc.php

@@ -132,143 +132,158 @@ EOF
 		// Standard Header with name, actions menu and history block
 		//
 		
-		// Is there a message for this object ??
-		$aMessages = array();
-		$aRanks = array();
-		if (MetaModel::GetConfig()->Get('concurrent_lock_enabled'))
+		if (!$oPage->IsPrintableVersion())
 		{
-			$aLockInfo = iTopOwnershipLock::IsLocked(get_class($this), $this->GetKey());
-			if ($aLockInfo['locked'])
+			// Is there a message for this object ??
+			$aMessages = array();
+			$aRanks = array();
+			if (MetaModel::GetConfig()->Get('concurrent_lock_enabled'))
 			{
-				$aRanks[] = 0;
-				$sName =  $aLockInfo['owner']->GetName();
-				if ($aLockInfo['owner']->Get('contactid') != 0)
+				$aLockInfo = iTopOwnershipLock::IsLocked(get_class($this), $this->GetKey());
+				if ($aLockInfo['locked'])
 				{
-					$sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')';
+					$aRanks[] = 0;
+					$sName =  $aLockInfo['owner']->GetName();
+					if ($aLockInfo['owner']->Get('contactid') != 0)
+					{
+						$sName .= ' ('.$aLockInfo['owner']->Get('contactid_friendlyname').')';
+					}
+					$aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName);			$aMessages[] = "<div class=\"header_message message_error\">".Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName)."</div>";
 				}
-				$aResult['message'] = Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName);			$aMessages[] = "<div class=\"header_message message_error\">".Dict::Format('UI:CurrentObjectIsLockedBy_User', $sName)."</div>";
 			}
-		}
-		$sMessageKey = get_class($this).'::'.$this->GetKey();
-		if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages']))
-		{
-			foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData)
+			$sMessageKey = get_class($this).'::'.$this->GetKey();
+			if (array_key_exists('obj_messages', $_SESSION) && array_key_exists($sMessageKey, $_SESSION['obj_messages']))
+			{
+				foreach ($_SESSION['obj_messages'][$sMessageKey] as $sMessageId => $aMessageData)
+				{
+					$sMsgClass = 'message_'.$aMessageData['severity'];
+					$aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>";
+					$aRanks[] = $aMessageData['rank'];
+				}
+				unset($_SESSION['obj_messages'][$sMessageKey]);
+			}
+			array_multisort($aRanks, $aMessages);
+			foreach ($aMessages as $sMessage)
 			{
-				$sMsgClass = 'message_'.$aMessageData['severity'];
-				$aMessages[] = "<div class=\"header_message $sMsgClass\">".$aMessageData['message']."</div>";
-				$aRanks[] = $aMessageData['rank'];
+				$oPage->add($sMessage);
 			}
-			unset($_SESSION['obj_messages'][$sMessageKey]);
 		}
-		array_multisort($aRanks, $aMessages);
-		foreach ($aMessages as $sMessage)
+				
+		if (!$oPage->IsPrintableVersion())
 		{
-			$oPage->add($sMessage);
+			// action menu
+			$oSingletonFilter = new DBObjectSearch(get_class($this));
+			$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
+			$oBlock = new MenuBlock($oSingletonFilter, 'details', false);
+			$oBlock->Display($oPage, -1);
 		}
-				
-		// action menu
-		$oSingletonFilter = new DBObjectSearch(get_class($this));
-		$oSingletonFilter->AddCondition('id', $this->GetKey(), '=');
-		$oBlock = new MenuBlock($oSingletonFilter, 'details', false);
-		$oBlock->Display($oPage, -1);
-	
+
+		$oPage->add("<div class=\"page_header\"><h1>".$this->GetIcon()."&nbsp;\n");
+		if (!$oPage->IsPrintableVersion() && ($_SERVER['REQUEST_METHOD'] == 'GET'))
+		{
+			$aIcons[] = '<img src="../images/reload.png" style="cursor:pointer;vertical-align:middle;margin-left:1em;" onclick="window.location.reload();" title="'.htmlentities(Dict::S('UI:Button:Refresh'), ENT_QUOTES, 'UTF-8').'"/>';
+		}
+		if (!$oPage->IsPrintableVersion())
+		{
+			$sPrintableUrl = ApplicationContext::MakeObjectUrl(get_class($this), $this->GetKey()).'&printable=1';
+			//$sPrintModeIcon = '<img src="../images/printableversion.png" style="cursor:pointer;vertical-align:middle;margin-left:1em;" onclick="window.open(\''.$sPrintableUrl.'\', \'printable_version\', \'toolbar=yes,scrollbar=yes\')" title="'.htmlentities(Dict::S('UI:Button:PrintableVersion'), ENT_QUOTES, 'UTF-8').'"/>';
+			$aIcons[] = '<a href="'.$sPrintableUrl.'" target="_blank"><img src="../images/printableversion.png" style="cursor:pointer;vertical-align:middle;margin-left:1em;" title="'.htmlentities(Dict::S('UI:Button:PrintableVersion'), ENT_QUOTES, 'UTF-8').'"/></a>';
+		}
+
 		// Master data sources
-		$sSynchroIcon = '';
 		$bSynchronized = false;
-		$oCreatorTask = null;
-		$bCanBeDeletedByTask = false;
-		$bCanBeDeletedByUser = true;
-		$aMasterSources = array();
-		$aSyncData = $this->GetSynchroData();
-		if (count($aSyncData) > 0)
+		if (!$oPage->IsPrintableVersion())
 		{
-			$bSynchronized = true;
-			foreach ($aSyncData as $iSourceId => $aSourceData)
+			$oCreatorTask = null;
+			$bCanBeDeletedByTask = false;
+			$bCanBeDeletedByUser = true;
+			$aMasterSources = array();
+			$aSyncData = $this->GetSynchroData();
+			if (count($aSyncData) > 0)
 			{
-				$oDataSource = $aSourceData['source'];
-				$oReplica = reset($aSourceData['replica']); // Take the first one!
-
-				$sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica);
-				$sLink = $oDataSource->GetName();
-				if (!empty($sApplicationURL))
-				{
-					$sLink = "<a href=\"$sApplicationURL\" target=\"_blank\">".$oDataSource->GetName()."</a>";
-				}
-				if ($oReplica->Get('status_dest_creator') == 1)
-				{
-					$oCreatorTask = $oDataSource;
-					$bCreatedByTask = true;
-				}
-				else
-				{
-					$bCreatedByTask = false;
-				}
-				if ($bCreatedByTask)
+				$bSynchronized = true;
+				foreach ($aSyncData as $iSourceId => $aSourceData)
 				{
-					$sDeletePolicy = $oDataSource->Get('delete_policy');
-					if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
+					$oDataSource = $aSourceData['source'];
+					$oReplica = reset($aSourceData['replica']); // Take the first one!
+	
+					$sApplicationURL = $oDataSource->GetApplicationUrl($this, $oReplica);
+					$sLink = $oDataSource->GetName();
+					if (!empty($sApplicationURL))
 					{
-						$bCanBeDeletedByTask = true;
+						$sLink = "<a href=\"$sApplicationURL\" target=\"_blank\">".$oDataSource->GetName()."</a>";
 					}
-					$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
-					if ($sUserDeletePolicy == 'nobody')
+					if ($oReplica->Get('status_dest_creator') == 1)
 					{
-						$bCanBeDeletedByUser = false;
+						$oCreatorTask = $oDataSource;
+						$bCreatedByTask = true;
 					}
-					elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator())
+					else
 					{
-						$bCanBeDeletedByUser = false;
+						$bCreatedByTask = false;
 					}
-					else // everybody...
+					if ($bCreatedByTask)
 					{
+						$sDeletePolicy = $oDataSource->Get('delete_policy');
+						if (($sDeletePolicy == 'delete') || ($sDeletePolicy == 'update_then_delete'))
+						{
+							$bCanBeDeletedByTask = true;
+						}
+						$sUserDeletePolicy = $oDataSource->Get('user_delete_policy');
+						if ($sUserDeletePolicy == 'nobody')
+						{
+							$bCanBeDeletedByUser = false;
+						}
+						elseif (($sUserDeletePolicy == 'administrators') && !UserRights::IsAdministrator())
+						{
+							$bCanBeDeletedByUser = false;
+						}
+						else // everybody...
+						{
+						}
 					}
+					$aMasterSources[$iSourceId]['datasource'] = $oDataSource;
+					$aMasterSources[$iSourceId]['url'] = $sLink;
+					$aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen');
 				}
-				$aMasterSources[$iSourceId]['datasource'] = $oDataSource;
-				$aMasterSources[$iSourceId]['url'] = $sLink;
-				$aMasterSources[$iSourceId]['last_synchro'] = $oReplica->Get('status_last_seen');
-			}
-
-			if (is_object($oCreatorTask))
-			{
-				$sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url'];
-				if (!$bCanBeDeletedByUser)
+	
+				if (is_object($oCreatorTask))
 				{
-					$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sTaskUrl)."</p>";
+					$sTaskUrl = $aMasterSources[$oCreatorTask->GetKey()]['url'];
+					if (!$bCanBeDeletedByUser)
+					{
+						$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectCannotBeDeletedByUser_Source', $sTaskUrl)."</p>";
+					}
+					else
+					{
+						$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."</p>";
+					}
+					if ($bCanBeDeletedByTask)
+					{
+						$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."</p>";
+					}
 				}
 				else
 				{
-					$sTip = "<p>".Dict::Format('Core:Synchro:TheObjectWasCreatedBy_Source', $sTaskUrl)."</p>";
+					$sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>";
 				}
-				if ($bCanBeDeletedByTask)
+	
+				$sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>";
+				foreach($aMasterSources as $aStruct)
 				{
-					$sTip .= "<p>".Dict::Format('Core:Synchro:TheObjectCanBeDeletedBy_Source', $sTaskUrl)."</p>";
+					$oDataSource = $aStruct['datasource'];
+					$sLink = $aStruct['url'];
+					$sTip .= "<p style=\"white-space:nowrap\">".$oDataSource->GetIcon(true, 'style="vertical-align:middle"')."&nbsp;$sLink<br/>";
+					$sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$aStruct['last_synchro']."</p>";
 				}
+				$aIcons[] = '&nbsp;<img style="vertical-align:middle;" id="synchro_icon" src="../images/locked.png"/>';
+				$sTip = addslashes($sTip);
+				$oPage->add_ready_script("$('#synchro_icon').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
 			}
-			else
-			{
-				$sTip = "<p>".Dict::S('Core:Synchro:ThisObjectIsSynchronized')."</p>";
-			}
-
-			$sTip .= "<p><b>".Dict::S('Core:Synchro:ListOfDataSources')."</b></p>";
-			foreach($aMasterSources as $aStruct)
-			{
-				$oDataSource = $aStruct['datasource'];
-				$sLink = $aStruct['url'];
-				$sTip .= "<p style=\"white-space:nowrap\">".$oDataSource->GetIcon(true, 'style="vertical-align:middle"')."&nbsp;$sLink<br/>";
-				$sTip .= Dict::S('Core:Synchro:LastSynchro').'<br/>'.$aStruct['last_synchro']."</p>";
-			}
-			$sSynchroIcon = '&nbsp;<img style="vertical-align:middle;" id="synchro_icon" src="../images/locked.png"/>';
-			$sTip = addslashes($sTip);
-			$oPage->add_ready_script("$('#synchro_icon').qtip( { content: '$sTip', show: 'mouseover', hide: { fixed: true }, style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
 		}
 	
-		$oPage->add("<div class=\"page_header\"><h1>".$this->GetIcon()."&nbsp;\n");
-		$sRefreshIcon = '';
-		if ($_SERVER['REQUEST_METHOD'] == 'GET')
-		{
-			$sRefreshIcon = '<img src="../images/reload.png" style="cursor:pointer;vertical-align:middle;margin-left:1em;" onclick="window.location.reload();" title="'.htmlentities(Dict::S('UI:Button:Refresh'), ENT_QUOTES, 'UTF-8').'"/>';
-		}
-		$oPage->add(MetaModel::GetName(get_class($this)).": <span class=\"hilite\">".$this->GetName()."</span>$sRefreshIcon $sSynchroIcon</h1>\n");
+		$sIcons = implode(' ', $aIcons);
+		$oPage->add(MetaModel::GetName(get_class($this)).": <span class=\"hilite\">".$this->GetName()."</span>$sIcons</h1>\n");
 		$oPage->add("</div>\n");
 		
 	}

+ 20 - 13
application/datatable.class.inc.php

@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010-2013 Combodo SARL
+// Copyright (C) 2010-2015 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -18,7 +18,7 @@
 /**
  * Data Table to display a set of objects in a tabular manner in HTML
  *
- * @copyright   Copyright (C) 2010-2012 Combodo SARL
+ * @copyright   Copyright (C) 2010-2015 Combodo SARL
  * @license     http://opensource.org/licenses/AGPL-3.0
  */
 
@@ -290,17 +290,24 @@ EOF;
 	
 	protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
 	{
-		$sMenuTitle = Dict::S('UI:ConfigureThisList');
-		$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
-
-		$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
-		$aActions = array(
-			$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
-		);
-		$this->oSet->Rewind();
-		utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
-		$this->oSet->Rewind();
-		$sHtml .= $oPage->RenderPopupMenuItems($aActions);
+		if (!$oPage->IsPrintableVersion())
+		{
+			$sMenuTitle = Dict::S('UI:ConfigureThisList');
+			$sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png"><ul>';
+	
+			$oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
+			$aActions = array(
+				$oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
+			);
+			$this->oSet->Rewind();
+			utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
+			$this->oSet->Rewind();
+			$sHtml .= $oPage->RenderPopupMenuItems($aActions);
+		}
+		else
+		{
+			$sHtml = '';
+		}
 		return $sHtml;
 	}
 	

+ 34 - 25
application/displayblock.class.inc.php

@@ -850,21 +850,24 @@ class DisplayBlock
 			break;
 			
 			case 'search':
-			$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
-			$sHtml .= "<div id=\"ds_$sId\" class=\"$sStyle\">\n";
-			$oPage->add_ready_script(
+			if (!$oPage->IsPrintableVersion())
+			{
+				$sStyle = (isset($aExtraParams['open']) && ($aExtraParams['open'] == 'true')) ? 'SearchDrawer' : 'SearchDrawer DrawerClosed';
+				$sHtml .= "<div id=\"ds_$sId\" class=\"$sStyle\">\n";
+				$oPage->add_ready_script(
 <<<EOF
-	$("#dh_$sId").click( function() {
-		$("#ds_$sId").slideToggle('normal', function() { $("#ds_$sId").parent().resize(); FixSearchFormsDisposition(); } );
-		$("#dh_$sId").toggleClass('open');
-	});
+		$("#dh_$sId").click( function() {
+			$("#ds_$sId").slideToggle('normal', function() { $("#ds_$sId").parent().resize(); FixSearchFormsDisposition(); } );
+			$("#dh_$sId").toggleClass('open');
+		});
 EOF
-			);
-			$aExtraParams['currentId'] = $sId;
-			$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
-	 		$sHtml .= "</div>\n";
-	 		$sHtml .= "<div class=\"HRDrawer\"></div>\n";
-	 		$sHtml .= "<div id=\"dh_$sId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
+				);
+				$aExtraParams['currentId'] = $sId;
+				$sHtml .= cmdbAbstractObject::GetSearchForm($oPage, $this->m_oSet, $aExtraParams);
+		 		$sHtml .= "</div>\n";
+		 		$sHtml .= "<div class=\"HRDrawer\"></div>\n";
+		 		$sHtml .= "<div id=\"dh_$sId\" class=\"DrawerHandle\">".Dict::S('UI:SearchToggle')."</div>\n";
+		 	}
 			break;
 			
 			case 'open_flash_chart':
@@ -1234,12 +1237,15 @@ class HistoryBlock extends DisplayBlock
 		$sHtml = '';
 		$bTruncated = false;
 		$oSet = new CMDBObjectSet($this->m_oFilter, array('date'=>false));
-		if (($this->iLimitStart > 0) || ($this->iLimitCount > 0))
+		if (!$oPage->IsPrintableVersion())
 		{
-			$oSet->SetLimit($this->iLimitCount, $this->iLimitStart);
-			if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count())
+			if (($this->iLimitStart > 0) || ($this->iLimitCount > 0))
 			{
-				$bTruncated = true;
+				$oSet->SetLimit($this->iLimitCount, $this->iLimitStart);
+				if (($this->iLimitCount - $this->iLimitStart) < $oSet->Count())
+				{
+					$bTruncated = true;
+				}
 			}
 		}
 		$sHtml .= "<!-- filter: ".($this->m_oFilter->ToOQL())."-->\n";
@@ -1643,17 +1649,20 @@ class MenuBlock extends DisplayBlock
 			$aShortcutActions = array();
 		}
 		
-		if (count($aFavoriteActions) > 0)
-		{
-			$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
-		}
-		else
+		if (!$oPage->IsPrintableVersion())
 		{
-			$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
+			if (count($aFavoriteActions) > 0)
+			{
+				$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:OtherActions')."\n<ul>\n";
+			}
+			else
+			{
+				$sHtml .= "<div class=\"itop_popup actions_menu\"><ul>\n<li>".Dict::S('UI:Menu:Actions')."\n<ul>\n";
+			}
+	
+			$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
 		}
 
-		$sHtml .= $oPage->RenderPopupMenuItems($aActions, $aFavoriteActions);
-
 		static $bPopupScript = false;
 		if (!$bPopupScript)
 		{

+ 64 - 8
application/itopwebpage.class.inc.php

@@ -38,9 +38,9 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
 	private $m_sInitScript;
 	protected $m_oTabs;
 
-	public function __construct($sTitle)
+	public function __construct($sTitle, $bPrintable = false)
 	{
-		parent::__construct($sTitle);
+		parent::__construct($sTitle, $bPrintable);
 		$this->m_oTabs = new TabManager();
 
 		ApplicationContext::SetUrlMakerClass('iTopStandardURLMaker');
@@ -82,7 +82,15 @@ class iTopWebPage extends NiceWebPage implements iTabbedPage
 		$this->add_dict_entry('UI:FillAllMandatoryFields');
 		$this->add_dict_entry('UI:Button:Cancel');
 		$this->add_dict_entry('UI:Button:Done');
-		
+
+		if (!$this->IsPrintableVersion())
+		{
+			$this->PrepareLayout();
+		}
+	}
+
+	protected function PrepareLayout()
+	{
 		$bForceMenuPane = utils::ReadParam('force_menu_pane', null);
 		$sInitClosed = '';
 		if (($bForceMenuPane !== null) && ($bForceMenuPane == 0))
@@ -612,6 +620,9 @@ EOF
 		{
 			$sSouthPane .= $oExtensionInstance->GetSouthPaneHtml($this);
 		}
+
+		// Render the tabs in the page (if any)
+		$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content, $this);
 		
 		// Put here the 'ready scripts' that must be executed after all others
 		$aMultiselectOptions = array(
@@ -689,7 +700,35 @@ EOF
 					}
 					$sHtml .= "<script type=\"text/javascript\" src=\"$s_script\"></script>\n";
 			}
-			$this->add_script("var iPaneVisWatchDog  = window.setTimeout('FixPaneVis()',5000);\n\$(document).ready(function() {\n{$this->m_sInitScript};\nwindow.setTimeout('onDelayedReady()',10)\n});");
+			if (!$this->IsPrintableVersion())
+			{
+				$this->add_script("var iPaneVisWatchDog  = window.setTimeout('FixPaneVis()',5000);");
+			}
+			$this->add_script("\$(document).ready(function() {\n{$this->m_sInitScript};\nwindow.setTimeout('onDelayedReady()',10)\n});");
+			if ($this->IsPrintableVersion())
+			{
+				$this->add_ready_script(
+<<<EOF
+var sHiddeableChapters = '<div class="light ui-tabs ui-widget ui-widget-content ui-corner-all">';
+sHiddeableChapters += '<ul role="tablist" class="ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all">';
+for (sId in oHiddeableChapters)
+{
+	sHiddeableChapters += '<li tabindex="-1" role="tab" class="ui-state-default ui-corner-top hideable-chapter" chapter-id="'+sId+'"><span class="tab ui-tabs-anchor">' + oHiddeableChapters[sId] + '</span></li>';
+	//alert(oHiddeableChapters[sId]);
+}
+sHiddeableChapters += '</ul></div>';
+$('#hiddeable_chapters').html(sHiddeableChapters);
+$('.hideable-chapter').click(function(){
+	var sChapterId = $(this).attr('chapter-id');
+	$('#'+sChapterId).toggle();
+	$(this).toggleClass('strikethrough');
+});
+$('legend').css('cursor', 'pointer').click(function(){
+		$(this).parent('fieldset').toggleClass('not-printable strikethrough');
+	});
+EOF
+				);
+			}
 			if (count($this->m_aReadyScripts)>0)
 			{
 				$this->add_script("\nonDelayedReady = function() {\n".implode("\n", $this->m_aReadyScripts)."\n}\n");
@@ -718,7 +757,20 @@ EOF
 		$sHtml .= "<link rel=\"shortcut icon\" href=\"".utils::GetAbsoluteUrlAppRoot()."images/favicon.ico\" />\n";
 	
 		$sHtml .= "</head>\n";
-		$sHtml .= "<body>\n";
+		$sBodyClass = "";
+		if ($this->IsPrintableVersion())
+		{
+			$sBodyClass = 'printable-version';
+		}
+		$sHtml .= "<body class=\"$sBodyClass\">\n";
+		if ($this->IsPrintableVersion())
+		{
+			$sHtml .= "<div class=\"explain-printable not-printable\">";
+			$sHtml .= '<p>'.Dict::S('UI:ExplainPrintable').'</p>';
+			$sHtml .= "<div id=\"hiddeable_chapters\"></div>";
+			$sHtml .= '<button onclick="window.print()">'.htmlentities(Dict::S('UI:Button:GoPrint'), ENT_QUOTES, 'UTF-8').'</button>';
+			$sHtml .= "</div>";
+		}
 
 		// Render the revision number
 		if (ITOP_REVISION == '$WCREV$')
@@ -743,10 +795,14 @@ EOF
 			$sText = Dict::S("UI:YourSearch");
 			$sOnClick = " onclick=\"this.value='';this.onclick=null;\"";
 		}
-		// Render the tabs in the page (if any)
-		$this->s_content = $this->m_oTabs->RenderIntoContent($this->s_content);
 
-		if ($this->GetOutputFormat() == 'html')
+		if ($this->IsPrintableVersion())
+		{
+			$sHtml .= ' <!-- Beginning of page content -->';
+			$sHtml .= self::FilterXSS($this->s_content);
+			$sHtml .= ' <!-- End of page content -->';
+		}
+		elseif ($this->GetOutputFormat() == 'html')
 		{
 			$oAppContext = new ApplicationContext();
 			

+ 4 - 4
application/nicewebpage.class.inc.php

@@ -1,5 +1,5 @@
 <?php
-// Copyright (C) 2010-2012 Combodo SARL
+// Copyright (C) 2010-2015 Combodo SARL
 //
 //   This file is part of iTop.
 //
@@ -20,7 +20,7 @@
 /**
  * Class NiceWebPage
  *
- * @copyright   Copyright (C) 2010-2012 Combodo SARL
+ * @copyright   Copyright (C) 2010-2015 Combodo SARL
  * @license     http://opensource.org/licenses/AGPL-3.0
  */
 
@@ -33,9 +33,9 @@ class NiceWebPage extends WebPage
 	var $m_aReadyScripts;
 	var $m_sRootUrl;
 	
-    public function __construct($s_title)
+    public function __construct($s_title, $bPrintable = false)
     {
-        parent::__construct($s_title);
+        parent::__construct($s_title, $bPrintable);
 		$this->m_aReadyScripts = array();
 		$this->add_linked_script("../js/jquery-1.10.0.min.js");
 		$this->add_linked_script("../js/jquery-migrate-1.2.1.min.js"); // Needed since many other plugins still rely on oldies like $.browser

+ 107 - 50
application/webpage.class.inc.php

@@ -71,8 +71,9 @@ class WebPage implements Page
 	protected $bTrashUnexpectedOutput;
 	protected $s_sOutputFormat;
 	protected $a_OutputOptions;
+	protected $bPrintable;
 	    
-    public function __construct($s_title)
+    public function __construct($s_title, $bPrintable = false)
     {
         $this->s_title = $s_title;
         $this->s_content = "";
@@ -92,6 +93,7 @@ class WebPage implements Page
 		$this->bTrashUnexpectedOutput = false;
 		$this->s_OutputFormat = utils::ReadParam('output_format', 'html');
 		$this->a_OutputOptions = array();
+		$this->bPrintable = $bPrintable;
         ob_start(); // Start capturing the output
     }
 	
@@ -687,7 +689,16 @@ class WebPage implements Page
 		}
 		return $bResult;
 	}
-	
+
+	/**
+	 * Check whether the output must be printable (using print.css, for sure!)
+	 * @return bool ...
+	 */
+	public function IsPrintableVersion()
+	{
+		return $this->bPrintable;
+	}
+
 	/** 
 	 * Retrieves the value of a named output option for the given format
 	 * @param string $sFormat The format: html or pdf
@@ -724,32 +735,34 @@ class WebPage implements Page
 	{
 		$sPrevUrl = '';
 		$sHtml = '';
-		foreach ($aActions as $aAction)
+		if (!$this->IsPrintableVersion())
 		{
-			$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
-			$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
-			$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
-			if (empty($aAction['url']))
+			foreach ($aActions as $aAction)
 			{
-				if ($sPrevUrl != '') // Don't output consecutively two separators...
+				$sClass = isset($aAction['class']) ? " class=\"{$aAction['class']}\"" : "";
+				$sOnClick = isset($aAction['onclick']) ? ' onclick="'.htmlspecialchars($aAction['onclick'], ENT_QUOTES, "UTF-8").'"' : '';
+				$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
+				if (empty($aAction['url']))
+				{
+					if ($sPrevUrl != '') // Don't output consecutively two separators...
+					{
+						$sHtml .= "<li>{$aAction['label']}</li>";			
+					}
+					$sPrevUrl = '';
+				}
+				else
 				{
-					$sHtml .= "<li>{$aAction['label']}</li>";			
+					$sHtml .= "<li><a $sTarget href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
+					$sPrevUrl = $aAction['url'];
 				}
-				$sPrevUrl = '';
 			}
-			else
+			$sHtml .= "</ul></li></ul></div>";
+			foreach(array_reverse($aFavoriteActions) as $aAction)
 			{
-				$sHtml .= "<li><a $sTarget href=\"{$aAction['url']}\"$sClass $sOnClick>{$aAction['label']}</a></li>";
-				$sPrevUrl = $aAction['url'];
+				$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
+				$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";			
 			}
-		}
-		$sHtml .= "</ul></li></ul></div>";
-		foreach(array_reverse($aFavoriteActions) as $aAction)
-		{
-			$sTarget = isset($aAction['target']) ? " target=\"{$aAction['target']}\"" : "";
-			$sHtml .= "<div class=\"actions_button\"><a $sTarget href='{$aAction['url']}'>{$aAction['label']}</a></div>";			
-		}
-		
+		}		
 		return $sHtml;
 	}
 
@@ -1023,7 +1036,7 @@ class TabManager
 		return "window.setTimeout(\"$('$sSelector').tabs('select', $tab_index);\", 100);"; // Let the time to the tabs widget to initialize
 	}
 	
-	public function RenderIntoContent($sContent)
+	public function RenderIntoContent($sContent, WebPage $oPage)
 	{
 		// Render the tabs in the page (if any)
 		foreach($this->m_aTabs as $sTabContainerName => $aTabs)
@@ -1033,42 +1046,86 @@ class TabManager
 			$container_index = 0;
 			if (count($aTabs['tabs']) > 0)
 			{
-				$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
-				$sTabs .= "<ul>\n";
-				// Display the unordered list that will be rendered as the tabs
-				$i = 0;
-				foreach($aTabs['tabs'] as $sTabName => $aTabData)
+				if ($oPage->IsPrintableVersion())
 				{
-					switch($aTabData['type'])
+					$oPage->add_ready_script(
+<<< EOF
+oHiddeableChapters = {};
+EOF
+					);
+					$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
+					$i = 0;
+					foreach($aTabs['tabs'] as $sTabName => $aTabData)
 					{
-						case 'ajax':
-						$sTabs .= "<li data-cache=\"".($aTabData['cache'] ? 'true' : 'false')."\"><a href=\"{$aTabData['url']}\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
-						break;
-						
-						case 'html':
-						default:
-						$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";	
+						$sTabNameEsc = addslashes($sTabName);
+						$sTabId = "tab_{$sPrefix}{$container_index}$i";
+						switch($aTabData['type'])
+						{
+							case 'ajax':
+							$sTabHtml = '';
+							$sUrl = $aTabData['url'];	
+							$oPage->add_ready_script(
+<<< EOF
+$.post('$sUrl', {printable: '1'}, function(data){
+	$('#$sTabId > .printable-tab-content').append(data);
+});
+EOF
+							);
+							break;
+							
+							case 'html':
+							default:
+							$sTabHtml = $aTabData['html'];
+						}
+						$sTabs .= "<div class=\"printable-tab\" id=\"$sTabId\"><h2 class=\"printable-tab-title\">".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</h2><div class=\"printable-tab-content\">".$sTabHtml."</div></div>\n";	
+						$oPage->add_ready_script(
+<<< EOF
+oHiddeableChapters['$sTabId'] = '$sTabNameEsc';
+EOF
+						);
+						$i++;
 					}
-					$i++;
+					$sTabs .= "</div>\n<!-- end of tabs-->\n";
 				}
-				$sTabs .= "</ul>\n";
-				// Now add the content of the tabs themselves
-				$i = 0;
-				foreach($aTabs['tabs'] as $sTabName => $aTabData)
+				else
 				{
-					switch($aTabData['type'])
+					$sTabs = "<!-- tabs -->\n<div id=\"tabbedContent_{$sPrefix}{$container_index}\" class=\"light\">\n";
+					$sTabs .= "<ul>\n";
+					// Display the unordered list that will be rendered as the tabs
+					$i = 0;
+					foreach($aTabs['tabs'] as $sTabName => $aTabData)
 					{
-						case 'ajax':
-						// Nothing to add
-						break;
-						
-						case 'html':
-						default:
-						$sTabs .= "<div id=\"tab_{$sPrefix}{$container_index}$i\">".$aTabData['html']."</div>\n";	
+						switch($aTabData['type'])
+						{
+							case 'ajax':
+							$sTabs .= "<li data-cache=\"".($aTabData['cache'] ? 'true' : 'false')."\"><a href=\"{$aTabData['url']}\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";
+							break;
+							
+							case 'html':
+							default:
+							$sTabs .= "<li><a href=\"#tab_{$sPrefix}{$container_index}$i\" class=\"tab\"><span>".htmlentities($sTabName, ENT_QUOTES, 'UTF-8')."</span></a></li>\n";	
+						}
+						$i++;
+					}
+					$sTabs .= "</ul>\n";
+					// Now add the content of the tabs themselves
+					$i = 0;
+					foreach($aTabs['tabs'] as $sTabName => $aTabData)
+					{
+						switch($aTabData['type'])
+						{
+							case 'ajax':
+							// Nothing to add
+							break;
+							
+							case 'html':
+							default:
+							$sTabs .= "<div id=\"tab_{$sPrefix}{$container_index}$i\">".$aTabData['html']."</div>\n";	
+						}
+						$i++;
 					}
-					$i++;
+					$sTabs .= "</div>\n<!-- end of tabs-->\n";
 				}
-				$sTabs .= "</div>\n<!-- end of tabs-->\n";
 			}
 			$sContent = str_replace("\$Tabs:$sTabContainerName\$", $sTabs, $sContent);
 			$container_index++;

+ 9 - 1
core/displayablegraph.class.inc.php

@@ -1181,6 +1181,7 @@ class DisplayableGraph extends SimpleGraph
 			}
 			$aExcludedByClass[get_class($oObj)][] = $oObj->GetKey();
 		}
+		$oP->add("<div class=\"not-printable\">\n");
 		$oP->add("<div id=\"ds_flash\" class=\"SearchDrawer\" style=\"display:none;\">\n");
 		$oP->add_ready_script(
 <<<EOF
@@ -1213,6 +1214,7 @@ EOF
 		$oP->add("</div>\n");
 		$oP->add("<div class=\"HRDrawer\"></div>\n");
 		$oP->add("<div id=\"dh_flash\" class=\"DrawerHandle\">".Dict::S('UI:ElementsDisplayed')."</div>\n");
+	 	$oP->add("</div>\n"); // class="not-printable"
 
 		$aAdditionalContexts = array();
 		foreach($aContextDefs as $sKey => $aDefinition)
@@ -1248,7 +1250,13 @@ EOF
 			}
 	
 			$sId = 'graph';
-			$oP->add('<div id="'.$sId.'" class="simple-graph"></div>');
+			$sStyle = '';
+			if ($oP->IsPrintableVersion())
+			{
+				// Optimize for printing on A4/Letter vertically
+				$sStyle = 'max-width: 17cm; margin-left:auto; margin-right:auto;';
+			}
+			$oP->add('<div id="'.$sId.'" class="simple-graph" style="'.$sStyle.'"></div>');
 			$aParams = array(
 				'source_url' => $sLoadFromURL,
 				'sources' => ($this->bDirectionDown ? $this->aSourceObjects : $this->aSinkObjects),

+ 4 - 2
core/ormcaselog.class.inc.php

@@ -217,6 +217,8 @@ class ormCaseLog {
 	 */	 	
 	public function GetAsHTML(WebPage $oP = null, $bEditMode = false, $aTransfoHandler = null)
 	{
+		$bPrintableVersion = (utils::ReadParam('printable', '0') == '1');
+
 		$sHtml = '<table style="width:100%;table-layout:fixed"><tr><td>'; // Use table-layout:fixed to force the with to be independent from the actual content
 		$iPos = 0;
 		$aIndex = $this->m_aIndex;
@@ -228,7 +230,7 @@ class ormCaseLog {
 		}
 		for($index=count($aIndex)-1 ; $index >= 0 ; $index--)
 		{
-			if ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS)
+			if (!$bPrintableVersion && ($index < count($aIndex) - CASELOG_VISIBLE_ITEMS))
 			{
 				$sOpen = '';
 				$sDisplay = 'style="display:none;"';
@@ -296,7 +298,7 @@ class ormCaseLog {
 			}
 			else
 			{
-				if (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0)
+				if (!$bPrintableVersion && (count($this->m_aIndex) - CASELOG_VISIBLE_ITEMS > 0))
 				{
 					$sOpen = '';
 					$sDisplay = 'style="display:none;"';

+ 31 - 0
css/light-grey.css

@@ -19,6 +19,12 @@ body {
 }
 
 
+body.printable-version {
+  margin: 1.5em;
+  overflow: auto;
+}
+
+
 /*
  * to prevent flicker, hide the pane's content until it's ready
  */
@@ -1987,3 +1993,28 @@ table.export_parameters td {
 }
 
 
+div.explain-printable {
+  border: 5px solid #1c94c4;
+  background: #d6e8ef;
+  color: black;
+  padding: 10px;
+  margin: 0;
+  font-size: 12px;
+}
+
+
+.hideable-chapter {
+  cursor: pointer;
+}
+
+
+.hideable-chapter.strikethrough span.ui-tabs-anchor {
+  text-decoration: line-through;
+}
+
+
+.strikethrough {
+  text-decoration: line-through;
+}
+
+

+ 26 - 1
css/light-grey.scss

@@ -19,6 +19,11 @@ body {
 	overflow: hidden;	/* Remove scroll bars on browser window */
 }
 
+body.printable-version {
+	margin:1.5em;
+	overflow:auto;
+}
+
 /* to prevent flicker, hide the pane's content until it's ready */
 .ui-layout-center, .ui-layout-north, .ui-layout-south {
     display: none;
@@ -1466,4 +1471,24 @@ table.export_parameters td {
 	display: inline-block;
 	margin-left: 1em;
 	margin-right: 1em;
-} 
+}
+div.explain-printable {
+	border: 5px solid $complement-color;
+	background: $complement-light;
+	color: #000;
+	padding: 10px;
+	margin: 0;
+	font-size: 12px;
+}
+
+.hideable-chapter {
+	cursor: pointer;
+}
+
+.hideable-chapter.strikethrough span.ui-tabs-anchor {
+	text-decoration: line-through;
+}
+
+.strikethrough {
+	text-decoration: line-through;
+}

+ 13 - 3
css/print.css

@@ -1,4 +1,5 @@
 @CHARSET "UTF-8";
+.not-printable { display:none; }
 span.ui-layout-resizer { display: none; }
 #header-logo { display: none; }
 #logo { display: none; }
@@ -8,8 +9,17 @@ div.footer { display:none; }
 #inner_menu { display: none; }
 div.actions_button { display:none; }
 div.itop_popup { display:none; }
-div.HRDrawer { display:none; }
-div.DrawerHandle { display:none; }
 a.tab { display:none; }
 div.itop-tab { border: #ccc 1px solid; margin-top: 1em; padding-bottom:1em; }
-#combodo_logo { display:none; };
+#combodo_logo { display:none; }
+div.graph_config { display:none; }
+h2.printable-tab-title {
+	border-bottom: 2px solid;
+	page-break-after: avoid;
+}
+div#tabbedContent_0 { border:none; }
+p a, td a, p a:hover, td a:hover {
+	padding-left: 0;
+	background: transparent;
+}
+body { margin:none; }

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

@@ -1079,6 +1079,9 @@ When associated with a trigger, each action is given an "order" number, specifyi
 	'UI:OrderByHint_Values' => 'Sort order: %1$s',
 	'UI:Menu:AddToDashboard' => 'Add To Dashboard...',
 	'UI:Button:Refresh' => 'Refresh',
+	'UI:Button:PrintableVersion' => 'Printable version',
+	'UI:Button:GoPrint' => 'Print...',
+	'UI:ExplainPrintable' => 'Click onto the chapters or items that you want to exclude from the print. This header and other tuning controls will not be printed. It is highly recommended to use the "pint preview" feature provided by your browser, before proceeding.',
 
 	'UI:ConfigureThisList' => 'Configure This List...',
 	'UI:ListConfigurationTitle' => 'List Configuration',

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

@@ -923,6 +923,9 @@ Lors de l\'association à un déclencheur, on attribue à chaque action un numé
 	'UI:OrderByHint_Values' => 'Ordre de tri: %1$s',
 	'UI:Menu:AddToDashboard' => 'Ajouter au Tableau de Bord...',
 	'UI:Button:Refresh' => 'Rafraîchir',
+	'UI:Button:PrintableVersion' => 'Version imprimable',
+	'UI:Button:GoPrint' => 'Imprimer...',
+	'UI:ExplainPrintable' => 'Cliquez sur les chapitres ou les éléments que vous voulez exclure de l\'impression. Cet en-tête, ainsi que les éléments de contrôle qui pourraient apparaitre dans cette page, ne seront pas imprimés. Il est recommandé d\'utiliser la fonction "Aperçu avant impression" de votre navigateur pour voir le rendu exact avant de lancer l\'impression.',
 
 	'UI:ConfigureThisList' => 'Configurer Cette Liste...',
 	'UI:ListConfigurationTitle' => 'Configuration de la liste',

BIN
images/printableversion.png


+ 3 - 1
pages/UI.php

@@ -276,6 +276,7 @@ try
 
 	require_once(APPROOT.'/application/startup.inc.php');
 	$operation = utils::ReadParam('operation', '');
+	$bPrintable = (utils::ReadParam('printable', 0) == '1');
 
 	$oKPI = new ExecutionKPI();
 	$oKPI->ComputeAndReport('Data model loaded');
@@ -288,9 +289,10 @@ try
 
 	$oKPI->ComputeAndReport('User login');
 
-	$oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'));
+	$oP = new iTopWebPage(Dict::S('UI:WelcomeToITop'), $bPrintable);
 	$oP->SetMessage($sLoginMessage);
 
+
 	// All the following actions use advanced forms that require more javascript to be loaded
 	switch($operation)
 	{