瀏覽代碼

Cleanup of the way objects are displayed/edited: DisplayBareProperties is now used for displaying (i.e. read-only) or modifying/creating an object. The means that:
1) the display/edition can be customized by overloading this method
2) the plug-ins's method OnDisplayProperties is now called in edition as well

A new class of template is introduced for building custom object displays: ObjectDetailsTemplate.

git-svn-id: http://svn.code.sf.net/p/itop/code/trunk@1429 a333f486-631f-4898-b8df-5754b55c2be0

dflaven 14 年之前
父節點
當前提交
b5a859e900

+ 138 - 216
application/cmdbabstract.class.inc.php

@@ -204,9 +204,9 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		$oBlock->Display($oPage, -1);
 	}
 
-	function DisplayBareProperties(WebPage $oPage, $bEditMode = false)
+	function DisplayBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix = '')
 	{
-		$oPage->add($this->GetBareProperties($oPage, $bEditMode));		
+		$aFieldsMap = $this->GetBareProperties($oPage, $bEditMode, $sPrefix);		
 
 		// Special case to display the case log, if any...
 		// WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayModifyForm
@@ -214,7 +214,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		{
 			if ($oAttDef instanceof AttributeCaseLog)
 			{
-				$this->DisplayCaseLog($oPage, $sAttCode, '', '', false);
+				$this->DisplayCaseLog($oPage, $sAttCode, '', '', $bEditMode);
 			}
 		}
 
@@ -222,6 +222,8 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		{
 			$oExtensionInstance->OnDisplayProperties($this, $oPage, $bEditMode);
 		}
+		
+		return $aFieldsMap;
 	}
 
 	function DisplayBareRelations(WebPage $oPage, $bEditMode = false)
@@ -382,7 +384,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		}
 	}
 
-	function GetBareProperties(WebPage $oPage, $bEditMode = false)
+	function GetBareProperties(WebPage $oPage, $bEditMode = false, $sPrefix)
 	{
 		$sHtml = '';
 		$oAppContext = new ApplicationContext();	
@@ -396,6 +398,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 		$sHtml = '';
 		$aDetails = array();
 		$iInputId = 0;
+		$aFieldsMap = array();
 		foreach($aDetailsStruct as $sTab => $aCols )
 		{
 			$aDetails[$sTab] = array();
@@ -436,28 +439,94 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 					}
 					foreach($aFields as $sAttCode)
 					{
-						$val = $this->GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode);
-						if ($val != null)
+						if ($bEditMode)
+						{
+
+
+						$sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ' ';
+						$sInfos = ' ';
+						$iFlags = $this->GetAttributeFlags($sAttCode);
+						$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
+						if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
 						{
-							/*
-							 * Removed for now...
-							// Check if the attribute is not mastered by a synchro...
-							$aReasons = array();
-							$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
-							$sSynchroIcon = '';
-							if ($iSynchroFlags & OPT_ATT_SLAVE)
+							if ($oAttDef->IsWritable())
 							{
-								$sSynchroIcon = "&nbsp;<img id=\"synchro_$iInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
-								$sTip = '';
-								foreach($aReasons as $aRow)
+								if ($sStateAttCode == $sAttCode)
 								{
-									$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
+									// State attribute is always read-only from the UI
+									$sHTMLValue = $this->GetStateLabel();
+									$val = array('label' => '<span>'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
+								}
+								else
+								{				
+									$sInputId = $this->m_iFormId.'_'.$sAttCode;
+									if ($iFlags & OPT_ATT_HIDDEN)
+									{
+										// Attribute is hidden, add a hidden input
+										$oPage->add('<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>');
+										$aFieldsMap[$sAttCode] = $sInputId;
+									}
+									else
+									{
+										if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
+										{
+
+											// Check if the attribute is not read-only because of a synchro...
+											$aReasons = array();
+											$sSynchroIcon = '';
+											if ($iFlags & OPT_ATT_SLAVE)
+											{
+												$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
+												$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
+												$sTip = '';
+												foreach($aReasons as $aRow)
+												{
+													$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
+												}
+												$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
+											}
+
+											// Attribute is read-only
+											$sHTMLValue = $this->GetAsHTML($sAttCode);
+											$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>';
+											$aFieldsMap[$sAttCode] = $sInputId;
+											$sComments = $sSynchroIcon;
+										}
+										else
+										{
+											$sValue = $this->Get($sAttCode);
+											$sDisplayValue = $this->GetEditValue($sAttCode);
+											$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
+											$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
+											$aFieldsMap[$sAttCode] = $sInputId;
+											
+										}
+										$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
+									}
 								}
-								$oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
 							}
+							else
+							{
+								$val = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $this->GetAsHTML($sAttCode), 'comments' => $sComments, 'infos' => $sInfos);			
+							}
+						}
+						else
+						{
+							$val = null; // Skip this field
+						}
+
 
-							$val['comments'] = $sSynchroIcon;
-							*/
+
+							
+						}
+						else
+						{
+							// !bEditMode
+							$val = $this->GetFieldAsHtml($sClass, $sAttCode, $sStateAttCode);
+						}
+
+						if ($val != null)
+						{
 							// The field is visible, add it to the current column
 							$aDetails[$sTab][$sColIndex][] = $val;
 							$iInputId++;
@@ -478,7 +547,7 @@ abstract class cmdbAbstractObject extends CMDBObject implements iDisplay
 			}
 			$oPage->add('</tr></table>');
 		}
-		return $sHtml;
+		return $aFieldsMap;
 	}
 
 	
@@ -1606,188 +1675,19 @@ EOF
 		{
 			$sFormAction = $aExtraParams['action'];
 		}
-		$iTransactionId = utils::GetNewTransactionId();
-		$oPage->SetTransactionId($iTransactionId);
-		$oPage->add("<form action=\"$sFormAction\" id=\"form_{$this->m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return OnSubmit('form_{$this->m_iFormId}');\">\n");
-		$oPage->add_ready_script("$(window).unload(function() { OnUnload('$iTransactionId') } );\n");
-
-		$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix);
-		$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
-		$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
-//		$aDetailsList = $this->FLattenZList(MetaModel::GetZListItems($sClass, 'details'));
-		//$aFullList = MetaModel::ListAttributeDefs($sClass);
-		$aList = array();
-		// Compute the list of properties to display, first the attributes in the 'details' list, then 
-		// all the remaining attributes that are not external fields
-//		foreach($aDetailsList as $sAttCode)
-//		{
-//			$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
-//			if (!$oAttDef->IsExternalField())
-//			{
-//				$aList[] = $sAttCode;
-//			}
-//		}
-
-		$aDetailsList = MetaModel::GetZListItems($sClass, 'details');
-		$aDetailsStruct = self::ProcessZlist($aDetailsList, array('UI:PropertiesTab' => array()), 'UI:PropertiesTab', 'col1', '');
-		$sHtml = '';
-		$aDetails = array();
-		foreach($aDetailsStruct as $sTab => $aCols )
-		{
-			$aDetails[$sTab] = array();
-			ksort($aCols);
-			$oPage->SetCurrentTab(Dict::S($sTab));
-			$oPage->add('<table style="vertical-align:top"><tr>');
-			foreach($aCols as $sColIndex => $aFieldsets)
-			{
-				$sLabel = '';
-				$sPreviousLabel = '';
-				$aDetails[$sTab][$sColIndex] = array();
-				$oPage->add('<td style="vertical-align:top">');
-				//$aDetails[$sTab][$sColIndex] = array();
-				foreach($aFieldsets as $sFieldsetName => $aFields)
-				{
-					if (!empty($sFieldsetName) && ($sFieldsetName[0]!='_'))
-					{
-						$sLabel = $sFieldsetName;
-					}
-					else
-					{
-						$sLabel = '';
-					}
-					if ($sLabel != $sPreviousLabel)
-					{
-						if (!empty($sPreviousLabel))
-						{
-							$oPage->add('<fieldset>');
-							$oPage->add('<legend>'.Dict::S($sPreviousLabel).'</legend>');
-						}
-						$oPage->Details($aDetails[$sTab][$sColIndex]);
-						if (!empty($sPreviousLabel))
-						{
-							$oPage->add('</fieldset>');
-						}
-						$aDetails[$sTab][$sColIndex] = array();
-						$sPreviousLabel = $sLabel;
-					}
-					foreach($aFields as $sAttCode)
-					{
-						$aVal = null;
-						$sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '&nbsp;';
-						$sInfos = '&nbsp;';
-						$iFlags = $this->GetAttributeFlags($sAttCode);
-						$oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
-						if ( (!$oAttDef->IsLinkSet()) && (($iFlags & OPT_ATT_HIDDEN) == 0))
-						{
-							if ($oAttDef->IsWritable())
-							{
-								if ($sStateAttCode == $sAttCode)
-								{
-									// State attribute is always read-only from the UI
-									$sHTMLValue = $this->GetStateLabel();
-									$aVal = array('label' => '<span>'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
-								}
-								else
-								{				
-									$sInputId = $this->m_iFormId.'_'.$sAttCode;
-									if ($iFlags & OPT_ATT_HIDDEN)
-									{
-										// Attribute is hidden, add a hidden input
-										$oPage->add('<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>');
-										$aFieldsMap[$sAttCode] = $sInputId;
-									}
-									else
-									{
-										if ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE))
-										{
-
-											// Check if the attribute is not read-only because of a synchro...
-											$aReasons = array();
-											$sSynchroIcon = '';
-											if ($iFlags & OPT_ATT_SLAVE)
-											{
-												$iSynchroFlags = $this->GetSynchroReplicaFlags($sAttCode, $aReasons);
-												$sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
-												$sTip = '';
-												foreach($aReasons as $aRow)
-												{
-													$sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
-												}
-												$oPage->add_ready_script("$('#synchro_$sInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
-											}
-
-											// Attribute is read-only
-											$sHTMLValue = $this->GetAsHTML($sAttCode);
-											$sHTMLValue .= '<input type="hidden" id="'.$sInputId.'" name="attr_'.$sPrefix.$sAttCode.'" value="'.htmlentities($this->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/>';
-											$aFieldsMap[$sAttCode] = $sInputId;
-											$sComments = $sSynchroIcon;
-										}
-										else
-										{
-											$sValue = $this->Get($sAttCode);
-											$sDisplayValue = $this->GetEditValue($sAttCode);
-											$aArgs = array('this' => $this, 'formPrefix' => $sPrefix);
-											$sHTMLValue = "<span id=\"field_{$sInputId}\">".self::GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef, $sValue, $sDisplayValue, $sInputId, '', $iFlags, $aArgs).'</span>';
-											$aFieldsMap[$sAttCode] = $sInputId;
-											
-										}
-										$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $sHTMLValue, 'comments' => $sComments, 'infos' => $sInfos);
-									}
-								}
-							}
-							else
-							{
-								$aVal = array('label' => '<span title="'.$oAttDef->GetDescription().'">'.$oAttDef->GetLabel().'</span>', 'value' => $this->GetAsHTML($sAttCode), 'comments' => $sComments, 'infos' => $sInfos);			
-							}
-						}
-						if ($aVal != null)
-						{
-							// The field is visible, add it to the current column
-							$aDetails[$sTab][$sColIndex][] = $aVal;
-						}				
-					}
-				}
-				if (!empty($sPreviousLabel))
-				{
-					$oPage->add('<fieldset>');
-					$oPage->add('<legend>'.Dict::S($sPreviousLabel).'</legend>');
-				}
-				$oPage->Details($aDetails[$sTab][$sColIndex]);
-				if (!empty($sPreviousLabel))
-				{
-					$oPage->add('</fieldset>');
-				}
-				$oPage->add('</td>');
-			}
-			$oPage->add('</tr></table>');
-		}
-
-		// Special case to display the case log, if any...
-		// WARNING: if you modify the loop below, also check the corresponding code in UpdateObject and DisplayDetails
-		foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
+		// Custom label for the apply button ?
+		if (isset($aExtraParams['custom_button']))
 		{
-			if ($oAttDef instanceof AttributeCaseLog)
-			{
-				$sComments = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '&nbsp;';
-				$this->DisplayCaseLog($oPage, $sAttCode, $sComments, $sPrefix, true);
-				$sInputId = $this->m_iFormId.'_'.$sAttCode;
-				$aFieldsMap[$sAttCode] = $sInputId;
-			}
+			$sApplyButton = $aExtraParams['custom_button'];			
 		}
-		// Now display the relations, one tab per relation
-		if (!isset($aExtraParams['noRelations']))
+		else if ($iKey > 0)
 		{
-			$this->DisplayBareRelations($oPage, true); // Edit mode
+			$sApplyButton = Dict::S('UI:Button:Apply');
 		}
-
-		$oPage->SetCurrentTab('');
-		$oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n");
-		$oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"$iTransactionId\">\n");
-		foreach($aExtraParams as $sName => $value)
+		else
 		{
-			$oPage->add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">\n");
+			$sApplyButton = Dict::S('UI:Button:Create');
 		}
-		$oPage->add($oAppContext->GetForForm());
 		// Custom operation for the form ?
 		if (isset($aExtraParams['custom_operation']))
 		{
@@ -1801,36 +1701,58 @@ EOF
 		{
 			$sOperation = 'apply_new';
 		}
-
-		// Custom label for the apply button ?
-		if (isset($aExtraParams['custom_button']))
+		if ($iKey > 0)
 		{
-			$sApplyButton = $aExtraParams['custom_button'];			
+			// The object already exists in the database, it's a modification
+			$sButtons = "<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n";
+			$sButtons .= "<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n";			
+			$sButtons .= "<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n";
+			$sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n";
 		}
-		else if ($iKey > 0)
+		else
 		{
-			$sApplyButton = Dict::S('UI:Button:Apply');
+			// The object does not exist in the database it's a creation
+			$sButtons = "<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n";			
+			$sButtons .= "<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;&nbsp;&nbsp;\n";
+			$sButtons .= "<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n";
 		}
-		else
+
+
+		$bDisplayActionsAtTop = MetaModel::GetConfig()->Get('display_actions_at_top');
+		$iTransactionId = utils::GetNewTransactionId();
+		$oPage->SetTransactionId($iTransactionId);
+		$oPage->add("<form action=\"$sFormAction\" id=\"form_{$this->m_iFormId}\" enctype=\"multipart/form-data\" method=\"post\" onSubmit=\"return OnSubmit('form_{$this->m_iFormId}');\">\n");
+		$oPage->add_ready_script("$(window).unload(function() { OnUnload('$iTransactionId') } );\n");
+
+		if ($bDisplayActionsAtTop)
 		{
-			$sApplyButton = Dict::S('UI:Button:Create');
+			$oPage->add($sButtons);
 		}
 
-		if ($iKey > 0)
+		$oPage->AddTabContainer(OBJECT_PROPERTIES_TAB, $sPrefix);
+		$oPage->SetCurrentTabContainer(OBJECT_PROPERTIES_TAB);
+		$oPage->SetCurrentTab(Dict::S('UI:PropertiesTab'));
+
+		$aFieldsMap = $this->DisplayBareProperties($oPage, true, $sPrefix);
+		// Now display the relations, one tab per relation
+		if (!isset($aExtraParams['noRelations']))
 		{
-			// The object already exists in the database, it's a modification
-			$oPage->add("<input type=\"hidden\" name=\"id\" value=\"$iKey\">\n");
-			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"{$sOperation}\">\n");			
-			$oPage->add("<button type=\"button\" class=\"action cancel\"><span>".Dict::S('UI:Button:Cancel')."</span></button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
-			$oPage->add("<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n");
+			$this->DisplayBareRelations($oPage, true); // Edit mode
 		}
-		else
+
+		$oPage->SetCurrentTab('');
+		$oPage->add("<input type=\"hidden\" name=\"class\" value=\"$sClass\">\n");
+		$oPage->add("<input type=\"hidden\" name=\"transaction_id\" value=\"$iTransactionId\">\n");
+		foreach($aExtraParams as $sName => $value)
 		{
-			// The object does not exist in the database it's a creation
-			$oPage->add("<input type=\"hidden\" name=\"operation\" value=\"$sOperation\">\n");			
-			$oPage->add("<button type=\"button\" class=\"action cancel\">".Dict::S('UI:Button:Cancel')."</button>&nbsp;&nbsp;&nbsp;&nbsp;\n");
-			$oPage->add("<button type=\"submit\" class=\"action\"><span>{$sApplyButton}</span></button>\n");
+			$oPage->add("<input type=\"hidden\" name=\"$sName\" value=\"$value\">\n");
 		}
+		$oPage->add($oAppContext->GetForForm());
+		if ($bDisplayActionsAtTop)
+		{
+			$oPage->add($sButtons);
+		}
+
 		// Hook the cancel button via jQuery so that it can be unhooked easily as well if needed
 		$sDefaultUrl = utils::GetAbsoluteUrlAppRoot().'pages/UI.php?operation=cancel&'.$oAppContext->GetForLink();
 		$oPage->add_ready_script("$('#form_{$this->m_iFormId} button.cancel').click( function() { BackToDetails('$sClass', $iKey, '$sDefaultUrl')} );");

+ 0 - 7
application/displayblock.class.inc.php

@@ -675,13 +675,6 @@ class DisplayBlock
 			$sHtml .= '<a class="summary" href="'.$sHyperlink.'">'.Dict::Format(str_replace('_', ':', $sLabel), $iCount).'</a>';
 			break;
 			
-			case 'bare_details':
-			while($oObj = $this->m_oSet->Fetch())
-			{
-				$sHtml .= $oObj->GetBareProperties($oPage);
-			}
-			break;
-			
 			case 'csv':
 			$sHtml .= "<textarea style=\"width:95%;height:98%\">\n";
 			$sHtml .= cmdbAbstractObject::GetSetAsCSV($this->m_oSet);

+ 37 - 0
application/itopwebpage.class.inc.php

@@ -888,6 +888,43 @@ EOF
         }
     }
     
+	/**
+	 * Records the current state of the 'html' part of the page output
+	 * @return mixed The current state of the 'html' output
+	 */    
+    public function start_capture()
+    {
+        if (!empty($this->m_sCurrentTabContainer) && !empty($this->m_sCurrentTab))
+        {
+        	$iOffset = isset($this->m_aTabs[$this->m_sCurrentTabContainer][$this->m_sCurrentTab]) ? strlen($this->m_aTabs[$this->m_sCurrentTabContainer][$this->m_sCurrentTab]): 0;
+            return array('tc' => $this->m_sCurrentTabContainer, 'tab' => $this->m_sCurrentTab, 'offset' => $iOffset);
+        }
+        else
+        {
+            parent::start_capture();
+        }
+    }
+
+    /**
+     * Returns the part of the html output that occurred since the call to start_capture
+     * and removes this part from the current html output
+     * @param $offset mixed The value returned by start_capture
+     * @return string The part of the html output that was added since the call to start_capture
+     */    
+    public function end_capture($offset)
+    {
+    	if (is_array($offset))
+    	{
+	    	$sCaptured = substr($this->m_aTabs[$offset['tc']][$offset['tab']], $offset['offset']);
+	    	$this->m_aTabs[$offset['tc']][$offset['tab']] = substr($this->m_aTabs[$offset['tc']][$offset['tab']], 0, $offset['offset']);    		
+    	}
+    	else
+    	{
+    		$sCaptured = parent::end_capture($offset);
+    	}
+    	return $sCaptured;
+    }
+        
     /**
      * Set the message to be displayed in the 'admin-banner' section at the top of the page
      */

+ 86 - 1
application/template.class.inc.php

@@ -233,7 +233,6 @@ class DisplayTemplate
 		<itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = \'$class$\'</itopblock>
 		</div>
 		<img src="../../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
-		<itopblock blockclass="DisplayBlock" asynchronous="false" type="bare_details" encoding="text/oql">SELECT NetworkDevice AS d WHERE d.id = $id$</itopblock>
 		<itoptabs>
 			<itoptab name="Interfaces">
 				<itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT Interface AS i WHERE i.device_id = $id$</itopblock>
@@ -254,6 +253,92 @@ class DisplayTemplate
 	}
 }
 
+/**
+ * Special type of template for displaying the details of an object
+ * On top of the defaut 'blocks' managed by the parent class, the following placeholders
+ * are available in such a template:
+ * $attribute_code$ An attribute of the object (in edit mode this is the input for the attribute)
+ * $attribute_code->label()$ The label of an attribute
+ * $PlugIn:plugInClass->properties()$ The ouput of OnDisplayProperties of the specified plugInClass
+ */
+class ObjectDetailsTemplate extends DisplayTemplate
+{
+	public function __construct($sTemplate, $oObj, $sFormPrefix = '')
+	{
+		parent::__construct($sTemplate);
+		$this->m_oObj = $oObj;
+		$this->m_sPrefix = $sFormPrefix;
+	}
+	
+	public function Render(WebPage $oPage, $aParams = array(), $bEditMode = false)
+	{
+		$aTemplateFields = array();
+		preg_match_all('/\\$([a-z0-9_]+)\\$/', $this->m_sTemplate, $aMatches);
+		$aTemplateFields = $aMatches[1];
+		$aFieldsMap = array();
+
+		$sClass = get_class($this->m_oObj);
+		// Renders the fields used in the template
+		foreach(MetaModel::ListAttributeDefs(get_class($this->m_oObj)) as $sAttCode => $oAttDef)
+		{
+			$aParams[$sAttCode.'->label()'] = $oAttDef->GetLabel();
+			$iInputId = 'Z_'.$sAttCode; // TODO: generate a real/unique prefix...
+			if (in_array($sAttCode, $aTemplateFields))
+			{
+				$iFlags = $this->m_oObj->GetAttributeFlags($sAttCode);
+				if ($iFlags & OPT_ATT_HIDDEN)
+				{
+					$aParams[$sAttCode.'->label()'] = '';
+					$aParams[$sAttCode] = '';
+				}
+				elseif ($bEditMode && !($iFlags && OPT_ATT_READONLY)) //TODO: check the data synchro status...
+				{
+					$aParams[$sAttCode] = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
+						$this->m_oObj->Get($sAttCode),
+						$this->m_oObj->GetEditValue($sAttCode),
+						$iInputId, // InputID
+						'',
+						$iFlags,
+						array('this' => $this->m_oObj) // aArgs
+					).'</span>';
+					$aFieldsMap[$sAttCode] = $iInputId;
+				}
+				else 
+				{
+					$aParams[$sAttCode] = $this->m_oObj->GetAsHTML($sAttCode);
+				}
+			}
+		}
+		
+		// Renders the PlugIns used in the template
+		preg_match_all('/\\$PlugIn:([A-Za-z0-9_]+)->properties\\(\\)\\$/', $this->m_sTemplate, $aMatches);
+		$aPlugInProperties = $aMatches[1];
+		foreach($aPlugInProperties as $sPlugInClass)
+		{
+			$oInstance = MetaModel::GetPlugins('iApplicationUIExtension', $sPlugInClass);
+			if ($oInstance != null) // Safety check...
+			{
+				$offset = $oPage->start_capture();
+				$oInstance->OnDisplayProperties($this->m_oObj, $oPage, $bEditMode);
+				$sContent = $oPage->end_capture($offset);
+				$aParams["PlugIn:{$sPlugInClass}->properties()"]= $sContent;			
+			}
+			else
+			{
+				$aParams["PlugIn:{$sPlugInClass}->properties()"]= "Missing PlugIn: $sPlugInClass";				
+			}			
+		}
+
+		$offset = $oPage->start_capture();
+		parent::Render($oPage, $aParams);
+		$sContent = $oPage->end_capture($offset);
+		// Remove empty table rows in case some attributes are hidden...
+		$sContent = preg_replace('/<tr[^>]*>\s*(<td[^>]*>\s*<\\/td>)+\s*<\\/tr>/im', '', $sContent);
+		$oPage->add($sContent);
+		return $aFieldsMap;
+	}
+}
+
 //DisplayTemplate::UnitTest();
 
 ?>

+ 24 - 2
application/webpage.class.inc.php

@@ -246,6 +246,28 @@ class WebPage
 
 		$this->add($this->GetDetails($aFields));
     }
+
+	/**
+	 * Records the current state of the 'html' part of the page output
+	 * @return mixed The current state of the 'html' output
+	 */    
+    public function start_capture()
+    {
+    	return strlen($this->s_content);
+    }
+    
+    /**
+     * Returns the part of the html output that occurred since the call to start_capture
+     * and removes this part from the current html output
+     * @param $offset mixed The value returned by start_capture
+     * @return string The part of the html output that was added since the call to start_capture
+     */
+    public function end_capture($offset)
+    {
+    	$sCaptured = substr($this->s_content, $offset);
+    	$this->s_content = substr($this->s_content, 0, $offset);
+    	return $sCaptured;
+    }
 	
 	/**
 	 * Build a special kind of TABLE useful for displaying the details of an object from a hash array of data
@@ -302,7 +324,7 @@ class WebPage
 			{
 				$sSelected = ($value == $key) ? ' checked' : '';
 			}
-			$sHTMLValue .= "<input type=\"radio\" id=\"{$iId}_{$key}\" name=\"attr_radio_$sFieldName\" onChange=\"$('#{$iId}').val(this.value).trigger('change');\" value=\"$key\"$sSelected><label class=\"radio\" for=\"{$iId}_{$key}\">&nbsp;$display_value</label>&nbsp;";
+			$sHTMLValue .= "<input type=\"radio\" id=\"{$iId}_{$key}\" name=\"radio_$sFieldName\" onChange=\"$('#{$iId}').val(this.value).trigger('change');\" value=\"$key\"$sSelected><label class=\"radio\" for=\"{$iId}_{$key}\">&nbsp;$display_value</label>&nbsp;";
 			if ($bVertical)
 			{
 				if ($idx == 0)
@@ -314,7 +336,7 @@ class WebPage
 			}
 			$idx++;
 		}
-		$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"attr_$sFieldName\" value=\"$value\"/>";
+		$sHTMLValue .= "<input type=\"hidden\" id=\"$iId\" name=\"$sFieldName\" value=\"$value\"/>";
 		if (!$bVertical)					
 		{
 			// Validation icon at the end of the line

+ 17 - 1
core/metamodel.class.php

@@ -1332,7 +1332,7 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 					{
 						$oExtensionInstance = new $sPHPClass;
 					}
-					self::$m_aExtensionClasses[$sInterface][] = $oExtensionInstance;
+					self::$m_aExtensionClasses[$sInterface][$sPHPClass] = $oExtensionInstance;
 				}
 			}
 		}
@@ -4457,6 +4457,22 @@ if (!array_key_exists($sAttCode, self::$m_aAttribDefs[$sClass]))
 		}
 	}
 
+	/**
+	 * Returns the instance of the specified plug-ins for the given interface
+	 */
+	public static function GetPlugins($sInterface, $sClassName)
+	{
+		$oInstance = null;
+		if (array_key_exists($sInterface, self::$m_aExtensionClasses))
+		{
+			if (array_key_exists($sClassName, self::$m_aExtensionClasses[$sInterface]))
+			{
+				return self::$m_aExtensionClasses[$sInterface][$sClassName];
+			}
+		}
+		return $oInstance;
+	}
+
 	public static function GetCacheEntries(Config $oConfig = null)
 	{
 		if (!function_exists('apc_cache_info')) return array();