/** * Class DisplayTemplate * * @copyright Copyright (C) 2010-2017 Combodo SARL * @license http://opensource.org/licenses/AGPL-3.0 */ require_once(APPROOT.'/application/displayblock.class.inc.php'); /** * This class manages the special template format used internally to build the iTop web pages */ class DisplayTemplate { protected $m_sTemplate; protected $m_aTags; static protected $iBlockCount = 0; public function __construct($sTemplate) { $this->m_aTags = array ( 'itopblock', 'itopcheck', 'itoptabs', 'itoptab', 'itoptoggle', 'itopstring', 'sqlblock' ); $this->m_sTemplate = $sTemplate; } public function Render(WebPage $oPage, $aParams = array()) { $this->m_sTemplate = MetaModel::ApplyParams($this->m_sTemplate, $aParams); $iStart = 0; $iEnd = strlen($this->m_sTemplate); $iCount = 0; $iBeforeTagPos = $iStart; $iAfterTagPos = $iStart; while($sTag = $this->GetNextTag($iStart, $iEnd)) { $sContent = $this->GetTagContent($sTag, $iStart, $iEnd); $iAfterTagPos = $iEnd + strlen(''); $sOuterTag = substr($this->m_sTemplate, $iStart, $iAfterTagPos - $iStart); $oPage->add(substr($this->m_sTemplate, $iBeforeTagPos, $iStart - $iBeforeTagPos)); if ($sTag == DisplayBlock::TAG_BLOCK) { try { $oBlock = DisplayBlock::FromTemplate($sOuterTag); if (is_object($oBlock)) { $oBlock->Display($oPage, 'block_'.self::$iBlockCount, $aParams); } } catch(OQLException $e) { $oPage->p('Error in template (please contact your administrator) - Invalid query'); } catch(Exception $e) { $oPage->p('Error in template (please contact your administrator)'); } self::$iBlockCount++; } else { $aAttributes = $this->GetTagAttributes($sTag, $iStart, $iEnd); //$oPage->p("Tag: $sTag - ($iStart, $iEnd)"); $this->RenderTag($oPage, $sTag, $aAttributes, $sContent); } $iAfterTagPos = $iEnd + strlen(''); $iBeforeTagPos = $iAfterTagPos; $iStart = $iEnd; $iEnd = strlen($this->m_sTemplate); $iCount++; } $oPage->add(substr($this->m_sTemplate, $iAfterTagPos)); } public function GetNextTag(&$iStartPos, &$iEndPos) { $iChunkStartPos = $iStartPos; $sNextTag = null; $iStartPos = $iEndPos; foreach($this->m_aTags as $sTag) { // Search for the opening tag $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.' ', $iChunkStartPos); if ($iOpeningPos === false) { $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.'>', $iChunkStartPos); } if ($iOpeningPos !== false) { $iClosingPos = stripos($this->m_sTemplate, '', $iOpeningPos); } if ( ($iOpeningPos !== false) && ($iClosingPos !== false)) { if ($iOpeningPos < $iStartPos) { // This is the next tag $iStartPos = $iOpeningPos; $iEndPos = $iClosingPos; $sNextTag = $sTag; } } } return $sNextTag; } public function GetTagContent($sTag, $iStartPos, $iEndPos) { $sContent = ""; $iContentStart = strpos($this->m_sTemplate, '>', $iStartPos); // Content of tag start immediatly after the first closing bracket if ($iContentStart !== false) { $sContent = substr($this->m_sTemplate, 1+$iContentStart, $iEndPos - $iContentStart - 1); } return $sContent; } public function GetTagAttributes($sTag, $iStartPos, $iEndPos) { $aAttr = array(); $iAttrStart = strpos($this->m_sTemplate, ' ', $iStartPos); // Attributes start just after the first space $iAttrEnd = strpos($this->m_sTemplate, '>', $iStartPos); // Attributes end just before the first closing bracket if ( ($iAttrStart !== false) && ($iAttrEnd !== false) && ($iAttrEnd > $iAttrStart)) { $sAttributes = substr($this->m_sTemplate, 1+$iAttrStart, $iAttrEnd - $iAttrStart - 1); $aAttributes = explode(' ', $sAttributes); foreach($aAttributes as $sAttr) { if ( preg_match('/(.+) *= *"(.+)"$/', $sAttr, $aMatches) ) { $aAttr[strtolower($aMatches[1])] = $aMatches[2]; } } } return $aAttr; } protected function RenderTag($oPage, $sTag, $aAttributes, $sContent) { static $iTabContainerCount = 0; switch($sTag) { case 'itoptabs': $oPage->AddTabContainer('Tabs_'.$iTabContainerCount); $oPage->SetCurrentTabContainer('Tabs_'.$iTabContainerCount); $iTabContainerCount++; //$oPage->p('Content:
'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'
'); $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied $oPage->SetCurrentTabContainer(''); break; case 'itopcheck': $sClassName = $aAttributes['class']; if (MetaModel::IsValidClass($sClassName) && UserRights::IsActionAllowed($sClassName, UR_ACTION_READ)) { $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied } else { // Leave a trace for those who'd like to understand why nothing is displayed $oPage->add("\n"); } break; case 'itoptab': $oPage->SetCurrentTab(Dict::S(str_replace('_', ' ', $aAttributes['name']))); $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied //$oPage->p('iTop Tab Content:
'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'
'); $oPage->SetCurrentTab(''); break; case 'itoptoggle': $sName = isset($aAttributes['name']) ? $aAttributes['name'] : 'Tagada'; $bOpen = isset($aAttributes['open']) ? $aAttributes['open'] : true; $oPage->StartCollapsibleSection(Dict::S($sName), $bOpen); $oTemplate = new DisplayTemplate($sContent); $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied //$oPage->p('iTop Tab Content:
'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'
'); $oPage->EndCollapsibleSection(); break; case 'itopstring': $oPage->add(Dict::S($sContent)); break; case 'sqlblock': $oBlock = SqlBlock::FromTemplate($sContent); $oBlock->RenderContent($oPage); break; case 'itopblock': // No longer used, handled by DisplayBlock::FromTemplate see above $oPage->add(""); break; default: // Unknown tag, just ignore it or now -- output an HTML comment $oPage->add(""); } } /** * Unit test */ static public function UnitTest() { require_once(APPROOT.'/application/startup.inc.php'); require_once(APPROOT."/application/itopwebpage.class.inc.php"); $sTemplate = ' SELECT Interface AS i WHERE i.device_id = $id$ SELECT Contact AS c JOIN lnkContactToCI AS l ON l.contact_id = c.id WHERE l.ci_id = $id$ SELECT Document AS d JOIN lnkDocumentToCI as l ON l.document_id = d.id WHERE l.ci_id = $id$) '; $oPage = new iTopWebPage('Unit Test'); //$oPage->add("Template content:
".htmlentities($sTemplate, ENT_QUOTES, 'UTF-8')."
\n"); $oTemplate = new DisplayTemplate($sTemplate); $oTemplate->Render($oPage, array('class'=>'Network device','pkey'=> 271, 'name' => 'deliversw01.mecanorama.fr', 'org_id' => 3)); $oPage->output(); } } /** * 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) { $sStateAttCode = MetaModel :: GetStateAttributeCode(get_class($this->m_oObj)); $aTemplateFields = array(); preg_match_all('/\\$this->([a-z0-9_]+)\\$/', $this->m_sTemplate, $aMatches); foreach ($aMatches[1] as $sAttCode) { if (MetaModel::IsValidAttCode(get_class($this->m_oObj), $sAttCode)) { $aTemplateFields[] = $sAttCode; } else { $aParams['this->'.$sAttCode] = ""; } } preg_match_all('/\\$this->field\\(([a-z0-9_]+)\\)\\$/', $this->m_sTemplate, $aMatches); foreach ($aMatches[1] as $sAttCode) { if (MetaModel::IsValidAttCode(get_class($this->m_oObj), $sAttCode)) { $aTemplateFields[] = $sAttCode; } else { $aParams['this->field('.$sAttCode.')'] = ""; } } $aFieldsComments = (isset($aParams['fieldsComments'])) ? $aParams['fieldsComments'] : array(); $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['this->label('.$sAttCode.')'] = $oAttDef->GetLabel(); $aParams['this->comments('.$sAttCode.')'] = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : ''; $iInputId = '2_'.$sAttCode; // TODO: generate a real/unique prefix... if (in_array($sAttCode, $aTemplateFields)) { if ($this->m_oObj->IsNew()) { $iFlags = $this->m_oObj->GetInitialStateAttributeFlags($sAttCode); } else { $iFlags = $this->m_oObj->GetAttributeFlags($sAttCode); } if (($iFlags & OPT_ATT_MANDATORY) && $this->m_oObj->IsNew()) { $iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object } if ((!$oAttDef->IsWritable()) || ($sStateAttCode == $sAttCode)) { $iFlags = $iFlags | OPT_ATT_READONLY; } if ($iFlags & OPT_ATT_HIDDEN) { $aParams['this->label('.$sAttCode.')'] = ''; $aParams['this->field('.$sAttCode.')'] = ''; $aParams['this->comments('.$sAttCode.')'] = ''; $aParams['this->'.$sAttCode] = ''; } else { if ($bEditMode && ($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->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons); $sSynchroIcon = " "; $sTip = ''; foreach($aReasons as $aRow) { $sDescription = htmlentities($aRow['description'], ENT_QUOTES, 'UTF-8'); $sDescription = str_replace(array("\r\n", "\n"), "
", $sDescription); $sTip .= "
"; $sTip .= "
Synchronized with {$aRow['name']}
"; $sTip .= "
$sDescription
"; } $oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );"); } // Attribute is read-only $sHTMLValue = "".$this->m_oObj->GetAsHTML($sAttCode); $sHTMLValue .= ''; $aFieldsMap[$sAttCode] = $iInputId; $aParams['this->comments('.$sAttCode.')'] = $sSynchroIcon; } if ($bEditMode && !($iFlags & OPT_ATT_READONLY)) //TODO: check the data synchro status... { $aParams['this->field('.$sAttCode.')'] = "".$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 ).''; $aFieldsMap[$sAttCode] = $iInputId; } else { $aParams['this->field('.$sAttCode.')'] = $this->m_oObj->GetAsHTML($sAttCode); } $aParams['this->'.$sAttCode] = "
".$aParams['this->label('.$sAttCode.')'].":".$aParams['this->field('.$sAttCode.')']."".$aParams['this->comments('.$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('/]*>\s*(]*>\s*<\\/td>)+\s*<\\/tr>/im', '', $sContent); $oPage->add($sContent); return $aFieldsMap; } } //DisplayTemplate::UnitTest(); ?>