template.class.inc.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402
  1. <?php
  2. // Copyright (C) 2010 Combodo SARL
  3. //
  4. // This program is free software; you can redistribute it and/or modify
  5. // it under the terms of the GNU General Public License as published by
  6. // the Free Software Foundation; version 3 of the License.
  7. //
  8. // This program is distributed in the hope that it will be useful,
  9. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. // GNU General Public License for more details.
  12. //
  13. // You should have received a copy of the GNU General Public License
  14. // along with this program; if not, write to the Free Software
  15. // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
  16. /**
  17. * Class DisplayTemplate
  18. *
  19. * @author Erwan Taloc <erwan.taloc@combodo.com>
  20. * @author Romain Quetiez <romain.quetiez@combodo.com>
  21. * @author Denis Flaven <denis.flaven@combodo.com>
  22. * @license http://www.opensource.org/licenses/gpl-3.0.html LGPL
  23. */
  24. require_once(APPROOT.'/application/displayblock.class.inc.php');
  25. /**
  26. * This class manages the special template format used internally to build the iTop web pages
  27. */
  28. class DisplayTemplate
  29. {
  30. protected $m_sTemplate;
  31. protected $m_aTags;
  32. static protected $iBlockCount = 0;
  33. public function __construct($sTemplate)
  34. {
  35. $this->m_aTags = array (
  36. 'itopblock',
  37. 'itopcheck',
  38. 'itoptabs',
  39. 'itoptab',
  40. 'itoptoggle',
  41. 'itopstring',
  42. 'sqlblock'
  43. );
  44. $this->m_sTemplate = $sTemplate;
  45. }
  46. public function Render(WebPage $oPage, $aParams = array())
  47. {
  48. $this->m_sTemplate = MetaModel::ApplyParams($this->m_sTemplate, $aParams);
  49. $iStart = 0;
  50. $iEnd = strlen($this->m_sTemplate);
  51. $iCount = 0;
  52. $iBeforeTagPos = $iStart;
  53. $iAfterTagPos = $iStart;
  54. while($sTag = $this->GetNextTag($iStart, $iEnd))
  55. {
  56. $sContent = $this->GetTagContent($sTag, $iStart, $iEnd);
  57. $iAfterTagPos = $iEnd + strlen('</'.$sTag.'>');
  58. $sOuterTag = substr($this->m_sTemplate, $iStart, $iAfterTagPos - $iStart);
  59. $oPage->add(substr($this->m_sTemplate, $iBeforeTagPos, $iStart - $iBeforeTagPos));
  60. if ($sTag == DisplayBlock::TAG_BLOCK)
  61. {
  62. try
  63. {
  64. $oBlock = DisplayBlock::FromTemplate($sOuterTag);
  65. if (is_object($oBlock))
  66. {
  67. $oBlock->Display($oPage, 'block_'.self::$iBlockCount, $aParams);
  68. }
  69. }
  70. catch(OQLException $e)
  71. {
  72. $oPage->p('Error in template (please contact your administrator) - Invalid query<!--'.$sOuterTag.'-->');
  73. }
  74. catch(Exception $e)
  75. {
  76. $oPage->p('Error in template (please contact your administrator)<!--'.$e->getMessage().'--><!--'.$sOuterTag.'-->');
  77. }
  78. self::$iBlockCount++;
  79. }
  80. else
  81. {
  82. $aAttributes = $this->GetTagAttributes($sTag, $iStart, $iEnd);
  83. //$oPage->p("Tag: $sTag - ($iStart, $iEnd)");
  84. $this->RenderTag($oPage, $sTag, $aAttributes, $sContent);
  85. }
  86. $iAfterTagPos = $iEnd + strlen('</'.$sTag.'>');
  87. $iBeforeTagPos = $iAfterTagPos;
  88. $iStart = $iEnd;
  89. $iEnd = strlen($this->m_sTemplate);
  90. $iCount++;
  91. }
  92. $oPage->add(substr($this->m_sTemplate, $iAfterTagPos));
  93. }
  94. public function GetNextTag(&$iStartPos, &$iEndPos)
  95. {
  96. $iChunkStartPos = $iStartPos;
  97. $sNextTag = null;
  98. $iStartPos = $iEndPos;
  99. foreach($this->m_aTags as $sTag)
  100. {
  101. // Search for the opening tag
  102. $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.' ', $iChunkStartPos);
  103. if ($iOpeningPos === false)
  104. {
  105. $iOpeningPos = stripos($this->m_sTemplate, '<'.$sTag.'>', $iChunkStartPos);
  106. }
  107. if ($iOpeningPos !== false)
  108. {
  109. $iClosingPos = stripos($this->m_sTemplate, '</'.$sTag.'>', $iOpeningPos);
  110. }
  111. if ( ($iOpeningPos !== false) && ($iClosingPos !== false))
  112. {
  113. if ($iOpeningPos < $iStartPos)
  114. {
  115. // This is the next tag
  116. $iStartPos = $iOpeningPos;
  117. $iEndPos = $iClosingPos;
  118. $sNextTag = $sTag;
  119. }
  120. }
  121. }
  122. return $sNextTag;
  123. }
  124. public function GetTagContent($sTag, $iStartPos, $iEndPos)
  125. {
  126. $sContent = "";
  127. $iContentStart = strpos($this->m_sTemplate, '>', $iStartPos); // Content of tag start immediatly after the first closing bracket
  128. if ($iContentStart !== false)
  129. {
  130. $sContent = substr($this->m_sTemplate, 1+$iContentStart, $iEndPos - $iContentStart - 1);
  131. }
  132. return $sContent;
  133. }
  134. public function GetTagAttributes($sTag, $iStartPos, $iEndPos)
  135. {
  136. $aAttr = array();
  137. $iAttrStart = strpos($this->m_sTemplate, ' ', $iStartPos); // Attributes start just after the first space
  138. $iAttrEnd = strpos($this->m_sTemplate, '>', $iStartPos); // Attributes end just before the first closing bracket
  139. if ( ($iAttrStart !== false) && ($iAttrEnd !== false) && ($iAttrEnd > $iAttrStart))
  140. {
  141. $sAttributes = substr($this->m_sTemplate, 1+$iAttrStart, $iAttrEnd - $iAttrStart - 1);
  142. $aAttributes = explode(' ', $sAttributes);
  143. foreach($aAttributes as $sAttr)
  144. {
  145. if ( preg_match('/(.+) *= *"(.+)"$/', $sAttr, $aMatches) )
  146. {
  147. $aAttr[strtolower($aMatches[1])] = $aMatches[2];
  148. }
  149. }
  150. }
  151. return $aAttr;
  152. }
  153. protected function RenderTag($oPage, $sTag, $aAttributes, $sContent)
  154. {
  155. static $iTabContainerCount = 0;
  156. switch($sTag)
  157. {
  158. case 'itoptabs':
  159. $oPage->AddTabContainer('Tabs_'.$iTabContainerCount);
  160. $oPage->SetCurrentTabContainer('Tabs_'.$iTabContainerCount);
  161. $iTabContainerCount++;
  162. //$oPage->p('Content:<pre>'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'</pre>');
  163. $oTemplate = new DisplayTemplate($sContent);
  164. $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
  165. $oPage->SetCurrentTabContainer('');
  166. break;
  167. case 'itopcheck':
  168. $sClassName = $aAttributes['class'];
  169. if (MetaModel::IsValidClass($sClassName) && UserRights::IsActionAllowed($sClassName, UR_ACTION_READ))
  170. {
  171. $oTemplate = new DisplayTemplate($sContent);
  172. $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
  173. }
  174. else
  175. {
  176. // Leave a trace for those who'd like to understand why nothing is displayed
  177. $oPage->add("<!-- class $sClassName does not exist, skipping some part of the template -->\n");
  178. }
  179. break;
  180. case 'itoptab':
  181. $oPage->SetCurrentTab(Dict::S(str_replace('_', ' ', $aAttributes['name'])));
  182. $oTemplate = new DisplayTemplate($sContent);
  183. $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
  184. //$oPage->p('iTop Tab Content:<pre>'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'</pre>');
  185. $oPage->SetCurrentTab('');
  186. break;
  187. case 'itoptoggle':
  188. $sName = isset($aAttributes['name']) ? $aAttributes['name'] : 'Tagada';
  189. $bOpen = isset($aAttributes['open']) ? $aAttributes['open'] : true;
  190. $oPage->StartCollapsibleSection(Dict::S($sName), $bOpen);
  191. $oTemplate = new DisplayTemplate($sContent);
  192. $oTemplate->Render($oPage, array()); // no params to apply, they have already been applied
  193. //$oPage->p('iTop Tab Content:<pre>'.htmlentities($sContent, ENT_QUOTES, 'UTF-8').'</pre>');
  194. $oPage->EndCollapsibleSection();
  195. break;
  196. case 'itopstring':
  197. $oPage->add(Dict::S($sContent));
  198. break;
  199. case 'sqlblock':
  200. $oBlock = SqlBlock::FromTemplate($sContent);
  201. $oBlock->RenderContent($oPage);
  202. break;
  203. case 'itopblock': // No longer used, handled by DisplayBlock::FromTemplate see above
  204. $oPage->add("<!-- Application Error: should be handled by DisplayBlock::FromTemplate -->");
  205. break;
  206. default:
  207. // Unknown tag, just ignore it or now -- output an HTML comment
  208. $oPage->add("<!-- unsupported tag: $sTag -->");
  209. }
  210. }
  211. /**
  212. * Unit test
  213. */
  214. static public function UnitTest()
  215. {
  216. require_once(APPROOT.'/application/startup.inc.php');
  217. require_once(APPROOT."/application/itopwebpage.class.inc.php");
  218. $sTemplate = '<div class="page_header">
  219. <div class="actions_details"><a href="#"><span>Actions</span></a></div>
  220. <h1>$class$: <span class="hilite">$name$</span></h1>
  221. <itopblock blockclass="HistoryBlock" type="toggle" encoding="text/oql">SELECT CMDBChangeOp WHERE objkey = $id$ AND objclass = \'$class$\'</itopblock>
  222. </div>
  223. <img src="../../images/connect_to_network.png" style="margin-top:-10px; margin-right:10px; float:right">
  224. <itoptabs>
  225. <itoptab name="Interfaces">
  226. <itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT Interface AS i WHERE i.device_id = $id$</itopblock>
  227. </itoptab>
  228. <itoptab name="Contacts">
  229. <itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT Contact AS c JOIN lnkContactToCI AS l ON l.contact_id = c.id WHERE l.ci_id = $id$</itopblock>
  230. </itoptab>
  231. <itoptab name="Documents">
  232. <itopblock blockclass="DisplayBlock" type="list" encoding="text/oql">SELECT Document AS d JOIN lnkDocumentToCI as l ON l.document_id = d.id WHERE l.ci_id = $id$)</itopblock>
  233. </itoptab>
  234. </itoptabs>';
  235. $oPage = new iTopWebPage('Unit Test');
  236. //$oPage->add("Template content: <pre>".htmlentities($sTemplate, ENT_QUOTES, 'UTF-8')."</pre>\n");
  237. $oTemplate = new DisplayTemplate($sTemplate);
  238. $oTemplate->Render($oPage, array('class'=>'Network device','pkey'=> 271, 'name' => 'deliversw01.mecanorama.fr', 'org_id' => 3));
  239. $oPage->output();
  240. }
  241. }
  242. /**
  243. * Special type of template for displaying the details of an object
  244. * On top of the defaut 'blocks' managed by the parent class, the following placeholders
  245. * are available in such a template:
  246. * $attribute_code$ An attribute of the object (in edit mode this is the input for the attribute)
  247. * $attribute_code->label()$ The label of an attribute
  248. * $PlugIn:plugInClass->properties()$ The ouput of OnDisplayProperties of the specified plugInClass
  249. */
  250. class ObjectDetailsTemplate extends DisplayTemplate
  251. {
  252. public function __construct($sTemplate, $oObj, $sFormPrefix = '')
  253. {
  254. parent::__construct($sTemplate);
  255. $this->m_oObj = $oObj;
  256. $this->m_sPrefix = $sFormPrefix;
  257. }
  258. public function Render(WebPage $oPage, $aParams = array(), $bEditMode = false)
  259. {
  260. $sStateAttCode = MetaModel :: GetStateAttributeCode(get_class($this->m_oObj));
  261. $aTemplateFields = array();
  262. preg_match_all('/\\$this->([a-z0-9_]+)\\$/', $this->m_sTemplate, $aMatches);
  263. $aTemplateFields = $aMatches[1];
  264. preg_match_all('/\\$this->field\\(([a-z0-9_]+)\\)\\$/', $this->m_sTemplate, $aMatches);
  265. $aTemplateFields = array_merge($aTemplateFields, $aMatches[1]);
  266. $aFieldsComments = (isset($aParams['fieldsComments'])) ? $aParams['fieldsComments'] : array();
  267. $aFieldsMap = array();
  268. $sClass = get_class($this->m_oObj);
  269. // Renders the fields used in the template
  270. foreach(MetaModel::ListAttributeDefs(get_class($this->m_oObj)) as $sAttCode => $oAttDef)
  271. {
  272. $aParams['this->label('.$sAttCode.')'] = $oAttDef->GetLabel();
  273. $aParams['this->comments('.$sAttCode.')'] = isset($aFieldsComments[$sAttCode]) ? $aFieldsComments[$sAttCode] : '';
  274. $iInputId = '2_'.$sAttCode; // TODO: generate a real/unique prefix...
  275. if (in_array($sAttCode, $aTemplateFields))
  276. {
  277. if ($this->m_oObj->IsNew())
  278. {
  279. $iFlags = $this->m_oObj->GetInitialStateAttributeFlags($sAttCode);
  280. }
  281. else
  282. {
  283. $iFlags = $this->m_oObj->GetAttributeFlags($sAttCode);
  284. }
  285. if (($iFlags & OPT_ATT_MANDATORY) && $this->m_oObj->IsNew())
  286. {
  287. $iFlags = $iFlags & ~OPT_ATT_READONLY; // Mandatory fields cannot be read-only when creating an object
  288. }
  289. if ((!$oAttDef->IsWritable()) || ($sStateAttCode == $sAttCode))
  290. {
  291. $iFlags = $iFlags | OPT_ATT_READONLY;
  292. }
  293. if ($iFlags & OPT_ATT_HIDDEN)
  294. {
  295. $aParams['this->label('.$sAttCode.')'] = '';
  296. $aParams['this->field('.$sAttCode.')'] = '';
  297. $aParams['this->comments('.$sAttCode.')'] = '';
  298. $aParams['this->'.$sAttCode] = '';
  299. }
  300. else
  301. {
  302. if ($bEditMode && ($iFlags & (OPT_ATT_READONLY|OPT_ATT_SLAVE)))
  303. {
  304. // Check if the attribute is not read-only because of a synchro...
  305. $aReasons = array();
  306. $sSynchroIcon = '';
  307. if ($iFlags & OPT_ATT_SLAVE)
  308. {
  309. $iSynchroFlags = $this->m_oObj->GetSynchroReplicaFlags($sAttCode, $aReasons);
  310. $sSynchroIcon = "&nbsp;<img id=\"synchro_$sInputId\" src=\"../images/transp-lock.png\" style=\"vertical-align:middle\"/>";
  311. $sTip = '';
  312. foreach($aReasons as $aRow)
  313. {
  314. $sTip .= "<p>Synchronized with {$aRow['name']} - {$aRow['description']}</p>";
  315. }
  316. $oPage->add_ready_script("$('#synchro_$iInputId').qtip( { content: '$sTip', show: 'mouseover', hide: 'mouseout', style: { name: 'dark', tip: 'leftTop' }, position: { corner: { target: 'rightMiddle', tooltip: 'leftTop' }} } );");
  317. }
  318. // Attribute is read-only
  319. $sHTMLValue = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetAsHTML($sAttCode);
  320. $sHTMLValue .= '<input type="hidden" id="'.$iInputId.'" name="attr_'.$sAttCode.'" value="'.htmlentities($this->m_oObj->Get($sAttCode), ENT_QUOTES, 'UTF-8').'"/></span>';
  321. $aFieldsMap[$sAttCode] = $iInputId;
  322. $aParams['this->comments('.$sAttCode.')'] = $sSynchroIcon;
  323. }
  324. if ($bEditMode && !($iFlags & OPT_ATT_READONLY)) //TODO: check the data synchro status...
  325. {
  326. $aParams['this->field('.$sAttCode.')'] = "<span id=\"field_{$iInputId}\">".$this->m_oObj->GetFormElementForField($oPage, $sClass, $sAttCode, $oAttDef,
  327. $this->m_oObj->Get($sAttCode),
  328. $this->m_oObj->GetEditValue($sAttCode),
  329. $iInputId, // InputID
  330. '',
  331. $iFlags,
  332. array('this' => $this->m_oObj) // aArgs
  333. ).'</span>';
  334. $aFieldsMap[$sAttCode] = $iInputId;
  335. }
  336. else
  337. {
  338. $aParams['this->field('.$sAttCode.')'] = $this->m_oObj->GetAsHTML($sAttCode);
  339. }
  340. $aParams['this->'.$sAttCode] = "<table class=\"field\"><tr><td class=\"label\">".$aParams['this->label('.$sAttCode.')'].":</td><td>".$aParams['this->field('.$sAttCode.')']."</td><td>".$aParams['this->comments('.$sAttCode.')']."</td></tr></table>";
  341. }
  342. }
  343. }
  344. // Renders the PlugIns used in the template
  345. preg_match_all('/\\$PlugIn:([A-Za-z0-9_]+)->properties\\(\\)\\$/', $this->m_sTemplate, $aMatches);
  346. $aPlugInProperties = $aMatches[1];
  347. foreach($aPlugInProperties as $sPlugInClass)
  348. {
  349. $oInstance = MetaModel::GetPlugins('iApplicationUIExtension', $sPlugInClass);
  350. if ($oInstance != null) // Safety check...
  351. {
  352. $offset = $oPage->start_capture();
  353. $oInstance->OnDisplayProperties($this->m_oObj, $oPage, $bEditMode);
  354. $sContent = $oPage->end_capture($offset);
  355. $aParams["PlugIn:{$sPlugInClass}->properties()"]= $sContent;
  356. }
  357. else
  358. {
  359. $aParams["PlugIn:{$sPlugInClass}->properties()"]= "Missing PlugIn: $sPlugInClass";
  360. }
  361. }
  362. $offset = $oPage->start_capture();
  363. parent::Render($oPage, $aParams);
  364. $sContent = $oPage->end_capture($offset);
  365. // Remove empty table rows in case some attributes are hidden...
  366. $sContent = preg_replace('/<tr[^>]*>\s*(<td[^>]*>\s*<\\/td>)+\s*<\\/tr>/im', '', $sContent);
  367. $oPage->add($sContent);
  368. return $aFieldsMap;
  369. }
  370. }
  371. //DisplayTemplate::UnitTest();
  372. ?>