dashlet.class.inc.php 40 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408
  1. <?php
  2. // Copyright (C) 2012 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. require_once(APPROOT.'application/forms.class.inc.php');
  17. /**
  18. * Base class for all 'dashlets' (i.e. widgets to be inserted into a dashboard)
  19. *
  20. */
  21. abstract class Dashlet
  22. {
  23. protected $sId;
  24. protected $bRedrawNeeded;
  25. protected $bFormRedrawNeeded;
  26. protected $aProperties; // array of {property => value}
  27. protected $aCSSClasses;
  28. public function __construct($sId)
  29. {
  30. $this->sId = $sId;
  31. $this->bRedrawNeeded = true; // By default: redraw each time a property changes
  32. $this->bFormRedrawNeeded = false; // By default: no need to redraw the form (independent fields)
  33. $this->aProperties = array(); // By default: there is no property
  34. $this->aCSSClasses = array('dashlet');
  35. }
  36. // Assuming that a property has the type of its default value, set in the constructor
  37. //
  38. public function Str2Prop($sProperty, $sValue)
  39. {
  40. $refValue = $this->aProperties[$sProperty];
  41. $sRefType = gettype($refValue);
  42. if ($sRefType == 'boolean')
  43. {
  44. $ret = ($sValue == 'true');
  45. }
  46. elseif ($sRefType == 'array')
  47. {
  48. $ret = explode(',', $sValue);
  49. }
  50. else
  51. {
  52. $ret = $sValue;
  53. settype($ret, $sRefType);
  54. }
  55. return $ret;
  56. }
  57. public function Prop2Str($value)
  58. {
  59. $sType = gettype($value);
  60. if ($sType == 'boolean')
  61. {
  62. $sRet = $value ? 'true' : 'false';
  63. }
  64. elseif ($sType == 'array')
  65. {
  66. $sRet = implode(',', $value);
  67. }
  68. else
  69. {
  70. $sRet = (string) $value;
  71. }
  72. return $sRet;
  73. }
  74. public function FromDOMNode($oDOMNode)
  75. {
  76. foreach ($this->aProperties as $sProperty => $value)
  77. {
  78. $this->oDOMNode = $oDOMNode->getElementsByTagName($sProperty)->item(0);
  79. if ($this->oDOMNode != null)
  80. {
  81. $newvalue = $this->Str2Prop($sProperty, $this->oDOMNode->textContent);
  82. $this->aProperties[$sProperty] = $newvalue;
  83. }
  84. }
  85. }
  86. public function ToDOMNode($oDOMNode)
  87. {
  88. foreach ($this->aProperties as $sProperty => $value)
  89. {
  90. $sXmlValue = $this->Prop2Str($value);
  91. $oPropNode = $oDOMNode->ownerDocument->createElement($sProperty, $sXmlValue);
  92. $oDOMNode->appendChild($oPropNode);
  93. }
  94. }
  95. public function FromXml($sXml)
  96. {
  97. $oDomDoc = new DOMDocument('1.0', 'UTF-8');
  98. $oDomDoc->loadXml($sXml);
  99. $this->FromDOMNode($oDomDoc->firstChild);
  100. }
  101. public function FromParams($aParams)
  102. {
  103. foreach ($this->aProperties as $sProperty => $value)
  104. {
  105. if (array_key_exists($sProperty, $aParams))
  106. {
  107. $this->aProperties[$sProperty] = $aParams[$sProperty];
  108. }
  109. }
  110. }
  111. public function DoRender($oPage, $bEditMode = false, $bEnclosingDiv = true, $aExtraParams = array())
  112. {
  113. $sCSSClasses = implode(' ', $this->aCSSClasses);
  114. $sId = $this->GetID();
  115. if ($bEnclosingDiv)
  116. {
  117. if ($bEditMode)
  118. {
  119. $oPage->add('<div class="'.$sCSSClasses.'" id="dashlet_'.$sId.'">');
  120. }
  121. else
  122. {
  123. $oPage->add('<div class="'.$sCSSClasses.'">');
  124. }
  125. }
  126. try
  127. {
  128. $this->Render($oPage, $bEditMode, $aExtraParams);
  129. }
  130. catch(UnknownClassOqlException $e)
  131. {
  132. // Maybe the class is part of a non-installed module, fail silently
  133. // Except in Edit mode
  134. if ($bEditMode)
  135. {
  136. $oPage->add('<div class="dashlet-content">');
  137. $oPage->add('<h2>Unknown Class: '.$e->GetWrongWord().', did you mean '.OqlException::FindClosestString($e->GetWrongWord(), $e->GetSuggestions()).'?</h2>'); //TODO localize and
  138. $oPage->add('</div>');
  139. }
  140. }
  141. catch(Exception $e)
  142. {
  143. $oPage->p($e->getMessage());
  144. }
  145. if ($bEnclosingDiv)
  146. {
  147. $oPage->add('</div>');
  148. }
  149. if ($bEditMode)
  150. {
  151. $sClass = get_class($this);
  152. $oPage->add_ready_script(
  153. <<<EOF
  154. $('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass'});
  155. EOF
  156. );
  157. }
  158. }
  159. public function SetID($sId)
  160. {
  161. $this->sId = $sId;
  162. }
  163. public function GetID()
  164. {
  165. return $this->sId;
  166. }
  167. abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
  168. abstract public function GetPropertiesFields(DesignerForm $oForm);
  169. public function ToXml(DOMNode $oContainerNode)
  170. {
  171. }
  172. public function Update($aValues, $aUpdatedFields)
  173. {
  174. foreach($aUpdatedFields as $sProp)
  175. {
  176. if (array_key_exists($sProp, $this->aProperties))
  177. {
  178. $this->aProperties[$sProp] = $aValues[$sProp];
  179. }
  180. }
  181. return $this;
  182. }
  183. public function IsRedrawNeeded()
  184. {
  185. return $this->bRedrawNeeded;
  186. }
  187. public function IsFormRedrawNeeded()
  188. {
  189. return $this->bFormRedrawNeeded;
  190. }
  191. static public function GetInfo()
  192. {
  193. return array(
  194. 'label' => '',
  195. 'icon' => '',
  196. 'description' => '',
  197. );
  198. }
  199. public function GetForm()
  200. {
  201. $oForm = new DesignerForm();
  202. $oForm->SetPrefix("dashlet_". $this->GetID());
  203. $oForm->SetParamsContainer('params');
  204. $this->GetPropertiesFields($oForm);
  205. $oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
  206. $oForm->AddField($oDashletClassField);
  207. $oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
  208. $oForm->AddField($oDashletIdField);
  209. return $oForm;
  210. }
  211. static public function IsVisible()
  212. {
  213. return true;
  214. }
  215. static public function CanCreateFromOQL()
  216. {
  217. return false;
  218. }
  219. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL)
  220. {
  221. // Default: do nothing since it's not supported
  222. }
  223. }
  224. class DashletEmptyCell extends Dashlet
  225. {
  226. public function __construct($sId)
  227. {
  228. parent::__construct($sId);
  229. }
  230. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  231. {
  232. $oPage->add('&nbsp;');
  233. }
  234. public function GetPropertiesFields(DesignerForm $oForm)
  235. {
  236. }
  237. static public function GetInfo()
  238. {
  239. return array(
  240. 'label' => 'Empty Cell',
  241. 'icon' => 'images/dashlet-text.png',
  242. 'description' => 'Empty Cell Dashlet Placeholder',
  243. );
  244. }
  245. static public function IsVisible()
  246. {
  247. return false;
  248. }
  249. }
  250. class DashletRichText extends Dashlet
  251. {
  252. public function __construct($sId)
  253. {
  254. parent::__construct($sId);
  255. $this->aProperties['text'] = "<h1>Lorem ipsum</h1>\n<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed non risus. Suspendisse lectus tortor, dignissim sit amet, adipiscing nec, ultricies sed, dolor. Cras elementum ultrices diam. Maecenas ligula massa, varius a, semper congue, euismod non, mi. Proin porttitor, orci nec nonummy molestie, enim est eleifend mi, non fermentum diam nisl sit amet erat. Duis semper.</p><p>Duis arcu massa, scelerisque vitae, consequat in, pretium a, enim. Pellentesque congue. Ut in risus volutpat libero pharetra tempor. Cras vestibulum bibendum augue. Praesent egestas leo in pede. Praesent blandit odio eu enim. Pellentesque sed dui ut augue blandit sodales. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Aliquam nibh. Mauris ac mauris sed pede pellentesque fermentum. Maecenas adipiscing ante non diam sodales hendrerit. Ut velit mauris, egestas sed, gravida nec, ornare ut, mi. Aenean ut orci vel massa suscipit pulvinar. Nulla sollicitudin. Fusce varius, ligula non tempus aliquam, nunc turpis ullamcorper nibh, in tempus sapien eros vitae ligula. Pellentesque rhoncus nunc et augue.</p><p>Integer id felis. Curabitur aliquet pellentesque diam. Integer quis metus vitae elit lobortis egestas. Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Morbi vel erat non mauris convallis vehicula. Nulla et sapien. Integer tortor tellus, aliquam faucibus, convallis id, congue eu, quam. Mauris ullamcorper felis vitae erat. Proin feugiat, augue non elementum posuere, metus purus iaculis lectus, et tristique ligula justo vitae magna. Aliquam convallis sollicitudin purus. Praesent aliquam, enim at fermentum mollis, ligula massa adipiscing nisl, ac euismod nibh nisl eu lectus. Fusce vulputate sem at sapien. Vivamus leo. Aliquam euismod libero eu enim. Nulla nec felis sed leo placerat imperdiet.</p><p>Aenean suscipit nulla in justo. Suspendisse cursus rutrum augue. Nulla tincidunt tincidunt mi. Curabitur iaculis, lorem vel rhoncus faucibus, felis magna fermentum augue, et ultricies lacus lorem varius purus. Curabitur eu amet.</p>";
  256. }
  257. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  258. {
  259. $sText = $this->aProperties['text'];
  260. $sId = 'richtext_'.($bEditMode? 'edit_' : '').$this->sId;
  261. $oPage->add('<div id='.$sId.'" class="dashlet-content">'.$sText.'</div>');
  262. }
  263. public function GetPropertiesFields(DesignerForm $oForm)
  264. {
  265. $oField = new DesignerLongTextField('text', 'Text', $this->aProperties['text']);
  266. $oForm->AddField($oField);
  267. }
  268. static public function GetInfo()
  269. {
  270. return array(
  271. 'label' => 'Rich text',
  272. 'icon' => 'images/dashlet-text.png',
  273. 'description' => 'Text formatted with HTML tags',
  274. );
  275. }
  276. }
  277. class DashletObjectList extends Dashlet
  278. {
  279. public function __construct($sId)
  280. {
  281. parent::__construct($sId);
  282. $this->aProperties['title'] = 'Hardcoded list of "my requests"';
  283. $this->aProperties['query'] = 'SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved")';
  284. $this->aProperties['menu'] = false;
  285. }
  286. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  287. {
  288. $sTitle = $this->aProperties['title'];
  289. $sQuery = $this->aProperties['query'];
  290. $sShowMenu = $this->aProperties['menu'] ? '1' : '0';
  291. $oPage->add('<div class="dashlet-content">');
  292. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  293. if ($sHtmlTitle != '')
  294. {
  295. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  296. }
  297. $oFilter = DBObjectSearch::FromOQL($sQuery);
  298. $oBlock = new DisplayBlock($oFilter, 'list');
  299. $aExtraParams = array(
  300. 'menu' => $sShowMenu,
  301. 'table_id' => 'Dashlet'.$this->sId,
  302. );
  303. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  304. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  305. $oPage->add('</div>');
  306. }
  307. public function GetPropertiesFields(DesignerForm $oForm)
  308. {
  309. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  310. $oForm->AddField($oField);
  311. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  312. $oForm->AddField($oField);
  313. $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
  314. $oForm->AddField($oField);
  315. }
  316. static public function GetInfo()
  317. {
  318. return array(
  319. 'label' => 'Object list',
  320. 'icon' => 'images/dashlet-list.png',
  321. 'description' => 'Object list dashlet',
  322. );
  323. }
  324. static public function CanCreateFromOQL()
  325. {
  326. return true;
  327. }
  328. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL)
  329. {
  330. $oField = new DesignerTextField('title', 'Title', '');
  331. $oForm->AddField($oField);
  332. $oField = new DesignerHiddenField('query', 'Query', $sOQL);
  333. $oForm->AddField($oField);
  334. $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
  335. $oForm->AddField($oField);
  336. }
  337. }
  338. abstract class DashletGroupBy extends Dashlet
  339. {
  340. public function __construct($sId)
  341. {
  342. parent::__construct($sId);
  343. $this->aProperties['title'] = 'Contacts grouped by location';
  344. $this->aProperties['query'] = 'SELECT Contact';
  345. $this->aProperties['group_by'] = 'location_name';
  346. $this->aProperties['style'] = 'table';
  347. }
  348. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  349. {
  350. $sTitle = $this->aProperties['title'];
  351. $sQuery = $this->aProperties['query'];
  352. $sGroupBy = $this->aProperties['group_by'];
  353. $sStyle = $this->aProperties['style'];
  354. if ($sQuery == '')
  355. {
  356. $oPage->add('<p>Please enter a valid OQL query</p>');
  357. }
  358. elseif ($sGroupBy == '')
  359. {
  360. $oPage->add('<p>Please select the field on which the objects will be grouped together</p>');
  361. }
  362. else
  363. {
  364. $oFilter = DBObjectSearch::FromOQL($sQuery);
  365. $sClassAlias = $oFilter->GetClassAlias();
  366. if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
  367. {
  368. $sAttCode = $aMatches[1];
  369. $sFunction = $aMatches[2];
  370. switch($sFunction)
  371. {
  372. case 'hour':
  373. $sGroupByLabel = 'Hour of '.$sAttCode. ' (0-23)';
  374. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 31
  375. break;
  376. case 'month':
  377. $sGroupByLabel = 'Month of '.$sAttCode. ' (1 - 12)';
  378. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%m')"; // 0 -> 31
  379. break;
  380. case 'day_of_week':
  381. $sGroupByLabel = 'Day of week for '.$sAttCode. ' (sunday to saturday)';
  382. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%w')";
  383. break;
  384. case 'day_of_month':
  385. $sGroupByLabel = 'Day of month for'.$sAttCode;
  386. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%e')"; // 0 -> 31
  387. break;
  388. default:
  389. $sGroupByLabel = 'Unknown group by function '.$sFunction;
  390. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  391. }
  392. }
  393. else
  394. {
  395. $sAttCode = $sGroupBy;
  396. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  397. $sGroupByLabel = MetaModel::GetLabel($oFilter->GetClass(), $sAttCode);
  398. }
  399. switch($sStyle)
  400. {
  401. case 'bars':
  402. $sType = 'open_flash_chart';
  403. $aExtraParams = array(
  404. 'chart_type' => 'bars',
  405. 'chart_title' => $sTitle,
  406. 'group_by' => $sGroupByExpr,
  407. 'group_by_label' => $sGroupByLabel,
  408. );
  409. $sHtmlTitle = ''; // done in the itop block
  410. break;
  411. case 'pie':
  412. $sType = 'open_flash_chart';
  413. $aExtraParams = array(
  414. 'chart_type' => 'pie',
  415. 'chart_title' => $sTitle,
  416. 'group_by' => $sGroupByExpr,
  417. 'group_by_label' => $sGroupByLabel,
  418. );
  419. $sHtmlTitle = ''; // done in the itop block
  420. break;
  421. case 'table':
  422. default:
  423. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  424. $sType = 'count';
  425. $aExtraParams = array(
  426. 'group_by' => $sGroupByExpr,
  427. 'group_by_label' => $sGroupByLabel,
  428. );
  429. break;
  430. }
  431. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  432. if ($sHtmlTitle != '')
  433. {
  434. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  435. }
  436. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  437. $oBlock = new DisplayBlock($oFilter, $sType);
  438. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  439. $oPage->add('</div>');
  440. }
  441. }
  442. public function GetPropertiesFields(DesignerForm $oForm)
  443. {
  444. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  445. $oForm->AddField($oField);
  446. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  447. $oForm->AddField($oField);
  448. try
  449. {
  450. // Group by field: build the list of possible values (attribute codes + ...)
  451. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  452. $sClass = $oSearch->GetClass();
  453. $aGroupBy = array();
  454. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  455. {
  456. if (!$oAttDef->IsScalar()) continue; // skip link sets
  457. $sLabel = $oAttDef->GetLabel();
  458. if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
  459. {
  460. $sLabel = $oAttDef->GetLabel().' (strict)';
  461. }
  462. $aGroupBy[$sAttCode] = $sLabel;
  463. if ($oAttDef instanceof AttributeDateTime)
  464. {
  465. $aGroupBy[$sAttCode.':hour'] = $oAttDef->GetLabel().' (hour)';
  466. $aGroupBy[$sAttCode.':month'] = $oAttDef->GetLabel().' (month)';
  467. $aGroupBy[$sAttCode.':day_of_week'] = $oAttDef->GetLabel().' (day of week)';
  468. $aGroupBy[$sAttCode.':day_of_month'] = $oAttDef->GetLabel().' (day of month)';
  469. }
  470. }
  471. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  472. $oField->SetAllowedValues($aGroupBy);
  473. }
  474. catch(Exception $e)
  475. {
  476. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  477. }
  478. $oForm->AddField($oField);
  479. $aStyles = array(
  480. 'pie' => 'Pie chart',
  481. 'bars' => 'Bar chart',
  482. 'table' => 'Table',
  483. );
  484. $oField = new DesignerComboField('style', 'Style', $this->aProperties['style']);
  485. $oField->SetAllowedValues($aStyles);
  486. $oForm->AddField($oField);
  487. }
  488. public function Update($aValues, $aUpdatedFields)
  489. {
  490. if (in_array('query', $aUpdatedFields))
  491. {
  492. try
  493. {
  494. $sCurrQuery = $aValues['query'];
  495. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  496. $sCurrClass = $oCurrSearch->GetClass();
  497. $sPrevQuery = $this->aProperties['query'];
  498. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  499. $sPrevClass = $oPrevSearch->GetClass();
  500. if ($sCurrClass != $sPrevClass)
  501. {
  502. $this->bFormRedrawNeeded = true;
  503. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  504. $this->aProperties['group_by'] = '';
  505. }
  506. }
  507. catch(Exception $e)
  508. {
  509. $this->bFormRedrawNeeded = true;
  510. }
  511. }
  512. $oDashlet = parent::Update($aValues, $aUpdatedFields);
  513. if (in_array('style', $aUpdatedFields))
  514. {
  515. switch($aValues['style'])
  516. {
  517. // Style changed, mutate to the specified type of chart
  518. case 'pie':
  519. $oDashlet = new DashletGroupByPie($this->sId);
  520. break;
  521. case 'bars':
  522. $oDashlet = new DashletGroupByBars($this->sId);
  523. break;
  524. case 'table':
  525. $oDashlet = new DashletGroupByTable($this->sId);
  526. break;
  527. }
  528. $oDashlet->FromParams($aValues);
  529. $oDashlet->bRedrawNeeded = true;
  530. $oDashlet->bFormRedrawNeeded = true;
  531. }
  532. return $oDashlet;
  533. }
  534. static public function GetInfo()
  535. {
  536. return array(
  537. 'label' => 'Objects grouped by...',
  538. 'icon' => 'images/dashlet-object-grouped.png',
  539. 'description' => 'Grouped objects dashlet',
  540. );
  541. }
  542. static public function CanCreateFromOQL()
  543. {
  544. return true;
  545. }
  546. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL)
  547. {
  548. $oField = new DesignerTextField('title', 'Title', '');
  549. $oForm->AddField($oField);
  550. $oField = new DesignerHiddenField('query', 'Query', $sOQL);
  551. $oForm->AddField($oField);
  552. try
  553. {
  554. // Group by field: build the list of possible values (attribute codes + ...)
  555. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  556. $sClass = $oSearch->GetClass();
  557. $aGroupBy = array();
  558. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  559. {
  560. if (!$oAttDef->IsScalar()) continue; // skip link sets
  561. if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) continue; // skip external keys
  562. $aGroupBy[$sAttCode] = $oAttDef->GetLabel();
  563. if ($oAttDef instanceof AttributeDateTime)
  564. {
  565. //date_format(start_date, '%d')
  566. $aGroupBy['date_of_'.$sAttCode] = 'Day of '.$oAttDef->GetLabel();
  567. }
  568. }
  569. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  570. $oField->SetAllowedValues($aGroupBy);
  571. }
  572. catch(Exception $e)
  573. {
  574. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  575. }
  576. $oForm->AddField($oField);
  577. $oField = new DesignerHiddenField('style', '', $this->aProperties['style']);
  578. $oForm->AddField($oField);
  579. }
  580. }
  581. class DashletGroupByPie extends DashletGroupBy
  582. {
  583. public function __construct($sId)
  584. {
  585. parent::__construct($sId);
  586. $this->aProperties['style'] = 'pie';
  587. }
  588. static public function GetInfo()
  589. {
  590. return array(
  591. 'label' => 'Pie Chart',
  592. 'icon' => 'images/dashlet-pie-chart.png',
  593. 'description' => 'Pie Chart',
  594. );
  595. }
  596. }
  597. class DashletGroupByPie2 extends DashletGroupByPie
  598. {
  599. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  600. {
  601. $sTitle = addslashes($this->aProperties['title']);
  602. $sQuery = $this->aProperties['query'];
  603. $sGroupBy = $this->aProperties['group_by'];
  604. $oSearch = DBObjectSearch::FromOQL($sQuery);
  605. $sClassAlias = $oSearch->GetClassAlias();
  606. $aQueryParams = array();
  607. $aGroupBy = array();
  608. $oGroupByExp = Expression::FromOQL($sClassAlias.'.'.$sGroupBy);
  609. $aGroupBy['grouped_by_1'] = $oGroupByExp;
  610. $sSql = MetaModel::MakeGroupByQuery($oSearch, $aQueryParams, $aGroupBy);
  611. $aRes = CMDBSource::QueryToArray($sSql);
  612. $aGroupBy = array();
  613. $aLabels = array();
  614. $iTotalCount = 0;
  615. foreach ($aRes as $aRow)
  616. {
  617. $sValue = $aRow['grouped_by_1'];
  618. $aLabels[] = ($sValue == '') ? 'Empty (%%.%%)' : $sValue.' (%%.%%)'; //TODO: localize
  619. $aGroupBy[] = (int) $aRow['_itop_count_'];
  620. $iTotalCount += $aRow['_itop_count_'];
  621. }
  622. $aURLs = array();
  623. $sContext = ''; //TODO get the context ??
  624. foreach($aGroupBy as $sValue => $iValue)
  625. {
  626. // Build the search for this subset
  627. $oSubsetSearch = clone $oSearch;
  628. $oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
  629. $oSubsetSearch->AddConditionExpression($oCondition);
  630. // Todo - à finir
  631. $aURLs[] = 'http://www.combodo.com/itop'; //utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".urlencode($oSubsetSearch->serialize());
  632. }
  633. $sJSValues = json_encode($aGroupBy);
  634. $sJSHrefs = json_encode($aURLs);
  635. $sJSLabels = json_encode($aLabels);
  636. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  637. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  638. $oPage->add_ready_script("$('#chart_{$sId}').pie_chart({chart_label: '$sTitle', values: $sJSValues, labels: $sJSLabels, hrefs: $sJSHrefs });");
  639. }
  640. static public function GetInfo()
  641. {
  642. return array(
  643. 'label' => 'Pie (Raphael)',
  644. 'icon' => 'images/dashlet-pie-chart.png',
  645. 'description' => 'Pure JS Pie Chart',
  646. );
  647. }
  648. }
  649. class DashletGroupByBars extends DashletGroupBy
  650. {
  651. public function __construct($sId)
  652. {
  653. parent::__construct($sId);
  654. $this->aProperties['style'] = 'bars';
  655. }
  656. static public function GetInfo()
  657. {
  658. return array(
  659. 'label' => 'Bar Chart',
  660. 'icon' => 'images/dashlet-bar-chart.png',
  661. 'description' => 'Bar Chart',
  662. );
  663. }
  664. }
  665. class DashletGroupByTable extends DashletGroupBy
  666. {
  667. public function __construct($sId)
  668. {
  669. parent::__construct($sId);
  670. $this->aProperties['style'] = 'table';
  671. }
  672. static public function GetInfo()
  673. {
  674. return array(
  675. 'label' => 'Group By (table)',
  676. 'icon' => 'images/dashlet-groupby-table.png',
  677. 'description' => 'List (Grouped by a field)',
  678. );
  679. }
  680. }
  681. class DashletHeaderStatic extends Dashlet
  682. {
  683. public function __construct($sId)
  684. {
  685. parent::__construct($sId);
  686. $this->aProperties['title'] = 'Contacts';
  687. $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png';
  688. }
  689. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  690. {
  691. $sTitle = $this->aProperties['title'];
  692. $sIcon = $this->aProperties['icon'];
  693. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  694. $oPage->add('<div class="dashlet-content">');
  695. $oPage->add('<div class="main_header">');
  696. $oPage->add('<img src="'.$sIconPath.'">');
  697. $oPage->add('<h1>'.Dict::S($sTitle).'</h1>');
  698. $oPage->add('</div>');
  699. $oPage->add('</div>');
  700. }
  701. public function GetPropertiesFields(DesignerForm $oForm)
  702. {
  703. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  704. $oForm->AddField($oField);
  705. $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']);
  706. $aAllIcons = self::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  707. ksort($aAllIcons);
  708. $aValues = array();
  709. foreach($aAllIcons as $sFilePath)
  710. {
  711. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  712. }
  713. $oField->SetAllowedValues($aValues);
  714. $oForm->AddField($oField);
  715. }
  716. static public function GetInfo()
  717. {
  718. return array(
  719. 'label' => 'Header',
  720. 'icon' => 'images/dashlet-header.png',
  721. 'description' => 'Header with stats (grouped by...)',
  722. );
  723. }
  724. static public function FindIcons($sBaseDir, $sDir = '')
  725. {
  726. $aResult = array();
  727. // Populate automatically the list of icon files
  728. if ($hDir = @opendir($sBaseDir.'/'.$sDir))
  729. {
  730. while (($sFile = readdir($hDir)) !== false)
  731. {
  732. $aMatches = array();
  733. if (($sFile != '.') && ($sFile != '..') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
  734. {
  735. $sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
  736. $aResult = array_merge($aResult, self::FindIcons($sBaseDir, $sDirSubPath));
  737. }
  738. if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
  739. {
  740. $aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
  741. }
  742. }
  743. closedir($hDir);
  744. }
  745. return $aResult;
  746. }
  747. }
  748. class DashletHeaderDynamic extends Dashlet
  749. {
  750. public function __construct($sId)
  751. {
  752. parent::__construct($sId);
  753. $this->aProperties['title'] = 'Contacts';
  754. $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png';
  755. $this->aProperties['subtitle'] = 'Contacts';
  756. $this->aProperties['query'] = 'SELECT Contact';
  757. $this->aProperties['group_by'] = 'status';
  758. $this->aProperties['values'] = array('active', 'inactive');
  759. }
  760. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  761. {
  762. $sTitle = $this->aProperties['title'];
  763. $sIcon = $this->aProperties['icon'];
  764. $sSubtitle = $this->aProperties['subtitle'];
  765. $sQuery = $this->aProperties['query'];
  766. $sGroupBy = $this->aProperties['group_by'];
  767. $aValues = $this->aProperties['values'];
  768. $oFilter = DBObjectSearch::FromOQL($sQuery);
  769. $sClass = $oFilter->GetClass();
  770. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  771. if (MetaModel::IsValidAttCode($sClass, $sGroupBy))
  772. {
  773. if (count($aValues) == 0)
  774. {
  775. $aAllowed = MetaModel::GetAllowedValues_att($sClass, $sGroupBy);
  776. if (is_array($aAllowed))
  777. {
  778. $aValues = array_keys($aAllowed);
  779. }
  780. }
  781. }
  782. if (count($aValues) > 0)
  783. {
  784. // Stats grouped by <group_by>
  785. $sCSV = implode(',', $aValues);
  786. $aExtraParams = array(
  787. 'title[block]' => $sTitle,
  788. 'label[block]' => $sSubtitle,
  789. 'status[block]' => $sGroupBy,
  790. 'status_codes[block]' => $sCSV,
  791. 'context_filter' => 1,
  792. );
  793. }
  794. else
  795. {
  796. // Simple stats
  797. $aExtraParams = array(
  798. 'title[block]' => $sTitle,
  799. 'label[block]' => $sSubtitle,
  800. 'context_filter' => 1,
  801. );
  802. }
  803. $oPage->add('<div class="dashlet-content">');
  804. $oPage->add('<div class="main_header">');
  805. $oPage->add('<img src="'.$sIconPath.'">');
  806. $oBlock = new DisplayBlock($oFilter, 'summary');
  807. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  808. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  809. $oPage->add('</div>');
  810. $oPage->add('</div>');
  811. }
  812. public function GetPropertiesFields(DesignerForm $oForm)
  813. {
  814. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  815. $oForm->AddField($oField);
  816. $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']);
  817. $aAllIcons = DashletHeaderStatic::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  818. ksort($aAllIcons);
  819. $aValues = array();
  820. foreach($aAllIcons as $sFilePath)
  821. {
  822. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  823. }
  824. $oField->SetAllowedValues($aValues);
  825. $oForm->AddField($oField);
  826. $oField = new DesignerTextField('subtitle', 'Subtitle', $this->aProperties['subtitle']);
  827. $oForm->AddField($oField);
  828. $oField = new DesignerTextField('query', 'Query', $this->aProperties['query']);
  829. $oForm->AddField($oField);
  830. try
  831. {
  832. // Group by field: build the list of possible values (attribute codes + ...)
  833. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  834. $sClass = $oSearch->GetClass();
  835. $aGroupBy = array();
  836. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  837. {
  838. if (!$oAttDef instanceof AttributeEnum && (!$oAttDef instanceof AttributeFinalClass || !MetaModel::HasChildrenClasses($sClass))) continue;
  839. $sLabel = $oAttDef->GetLabel();
  840. $aGroupBy[$sAttCode] = $sLabel;
  841. }
  842. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  843. $oField->SetAllowedValues($aGroupBy);
  844. }
  845. catch(Exception $e)
  846. {
  847. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  848. }
  849. $oForm->AddField($oField);
  850. $oField = new DesignerComboField('values', 'Values (CSV list)', $this->aProperties['values']);
  851. $oField->MultipleSelection(true);
  852. if (MetaModel::IsValidAttCode($sClass, $this->aProperties['group_by']))
  853. {
  854. $aValues = MetaModel::GetAllowedValues_att($sClass, $this->aProperties['group_by']);
  855. $oField->SetAllowedValues($aValues);
  856. }
  857. else
  858. {
  859. $oField->SetReadOnly();
  860. }
  861. $oForm->AddField($oField);
  862. }
  863. public function Update($aValues, $aUpdatedFields)
  864. {
  865. if (in_array('query', $aUpdatedFields))
  866. {
  867. try
  868. {
  869. $sCurrQuery = $aValues['query'];
  870. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  871. $sCurrClass = $oCurrSearch->GetClass();
  872. $sPrevQuery = $this->aProperties['query'];
  873. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  874. $sPrevClass = $oPrevSearch->GetClass();
  875. if ($sCurrClass != $sPrevClass)
  876. {
  877. $this->bFormRedrawNeeded = true;
  878. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  879. $this->aProperties['group_by'] = '';
  880. $this->aProperties['values'] = array();
  881. }
  882. }
  883. catch(Exception $e)
  884. {
  885. $this->bFormRedrawNeeded = true;
  886. }
  887. }
  888. if (in_array('group_by', $aUpdatedFields))
  889. {
  890. $this->bFormRedrawNeeded = true;
  891. $this->aProperties['values'] = array();
  892. }
  893. return parent::Update($aValues, $aUpdatedFields);
  894. }
  895. static public function GetInfo()
  896. {
  897. return array(
  898. 'label' => 'Header with statistics',
  899. 'icon' => 'images/dashlet-header-stats.png',
  900. 'description' => 'Header with stats (grouped by...)',
  901. );
  902. }
  903. }
  904. class DashletBadge extends Dashlet
  905. {
  906. public function __construct($sId)
  907. {
  908. parent::__construct($sId);
  909. $this->aProperties['class'] = 'Contact';
  910. $this->aCSSClasses[] = 'dashlet-inline';
  911. $this->aCSSClasses[] = 'dashlet-badge';
  912. }
  913. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  914. {
  915. $sClass = $this->aProperties['class'];
  916. $oPage->add('<div class="dashlet-content">');
  917. $oFilter = new DBObjectSearch($sClass);
  918. $oBlock = new DisplayBlock($oFilter, 'actions');
  919. $aExtraParams = array(
  920. 'context_filter' => 1,
  921. );
  922. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  923. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  924. $oPage->add('</div>');
  925. if ($bEditMode)
  926. {
  927. // Since the container div is not rendered the same way in edit mode, add the 'inline' style to it
  928. $oPage->add_ready_script("$('#dashlet_".$this->sId."').addClass('dashlet-inline');");
  929. }
  930. }
  931. public function GetPropertiesFields(DesignerForm $oForm)
  932. {
  933. $oClassesSet = new ValueSetEnumClasses('bizmodel', array());
  934. $aClasses = $oClassesSet->GetValues(array());
  935. $aLinkClasses = array();
  936. foreach(MetaModel::GetClasses('bizmodel') as $sClass)
  937. {
  938. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  939. {
  940. if ($oAttDef instanceof AttributeLinkedSetIndirect)
  941. {
  942. $aLinkClasses[$oAttDef->GetLinkedClass()] = true;
  943. }
  944. }
  945. }
  946. $oField = new DesignerIconSelectionField('class', 'Class', $this->aProperties['class']);
  947. ksort($aClasses);
  948. $aValues = array();
  949. foreach($aClasses as $sClass => $sClass)
  950. {
  951. if (!array_key_exists($sClass, $aLinkClasses))
  952. {
  953. $sIconUrl = MetaModel::GetClassIcon($sClass, false);
  954. $sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
  955. if (($sIconUrl == '') || !file_exists($sIconFilePath))
  956. {
  957. // The icon does not exist, leet's use a transparent one of the same size.
  958. $sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/transparent_32_32.png';
  959. }
  960. $aValues[] = array('value' => $sClass, 'label' => $sClass, 'icon' => $sIconUrl);
  961. }
  962. }
  963. $oField->SetAllowedValues($aValues);
  964. $oForm->AddField($oField);
  965. }
  966. static public function GetInfo()
  967. {
  968. return array(
  969. 'label' => 'Badge',
  970. 'icon' => 'images/dashlet-badge.png',
  971. 'description' => 'Object Icon with new/search',
  972. );
  973. }
  974. }
  975. class DashletProto extends Dashlet
  976. {
  977. public function __construct($sId)
  978. {
  979. parent::__construct($sId);
  980. $this->aProperties['class'] = 'Foo';
  981. }
  982. protected $aValues1;
  983. protected $aValues2;
  984. protected $aStats;
  985. protected $sGroupByLabel1;
  986. protected $sGroupByLabel2;
  987. protected $iTotalCount;
  988. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  989. {
  990. $this->Compute();
  991. //$sHtmlTitle = $this->aProperties['title'];
  992. $sHtmlTitle = "Group by made on two dimensions";
  993. // Build the output
  994. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  995. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  996. $oPage->p(Dict::Format('UI:Pagination:HeaderNoSelection', $this->iTotalCount));
  997. $oPage->add('<table>');
  998. // Header
  999. $oPage->add('<tr>');
  1000. $oPage->add('<td></td>');
  1001. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1002. {
  1003. $oPage->add('<td><b>'.$sDisplayValue2.'</b></td>');
  1004. }
  1005. $oPage->add('</tr>');
  1006. // Contents
  1007. foreach ($this->aValues1 as $sValue1 => $sDisplayValue1)
  1008. {
  1009. $oPage->add('<tr>');
  1010. $oPage->add('<td><b>'.$sDisplayValue1.'</b></td>');
  1011. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1012. {
  1013. @$aResData = $this->aStats[$sValue1][$sValue2];
  1014. if (is_array($aResData))
  1015. {
  1016. $sRes = '<a href="'.$aResData['href'].'">'.$aResData['count'].'</a>';
  1017. }
  1018. else
  1019. {
  1020. // Missing result => 0
  1021. $sRes = '-';
  1022. }
  1023. $oPage->add('<td>'.$sRes.'</td>');
  1024. }
  1025. $oPage->add('</tr>');
  1026. }
  1027. $oPage->add('</table>');
  1028. $oPage->add('</div>');
  1029. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  1030. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  1031. $aAxisX = $this->aValues1;
  1032. $aAxisY = $this->aValues2;
  1033. $aData = array();
  1034. $aHref = array();
  1035. foreach ($this->aValues1 as $sValue1 => $sDisplayValue1)
  1036. {
  1037. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1038. {
  1039. @$aResData = $this->aStats[$sValue1][$sValue2];
  1040. if (is_array($aResData))
  1041. {
  1042. $aData[$sValue1][$sValue2] = $aResData['count'];
  1043. $aHref[$sValue1][$sValue2] = $aResData['href'];
  1044. }
  1045. else
  1046. {
  1047. // Missing result => 0
  1048. $aData[$sValue1][$sValue2] = 0;
  1049. $aHref[$sValue1][$sValue2] = '';
  1050. }
  1051. }
  1052. }
  1053. $sJSAxisX = json_encode($aAxisX);
  1054. $sJSAxisY = json_encode($aAxisY);
  1055. $sJSData = json_encode($aData);
  1056. $sJSHref = json_encode($aHref);
  1057. $sTitle = addslashes($sHtmlTitle);
  1058. $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, hrefs: $sJSHref, axis_x: $sJSAxisX, axis_y: $sJSAxisY});");
  1059. }
  1060. protected function Compute()
  1061. {
  1062. // Read and interpret parameters
  1063. //
  1064. //$sFoo = $this->aProperties['foo'];
  1065. if (false)
  1066. {
  1067. $oFilter = DBObjectSearch::FromOQL('SELECT FunctionalCI');
  1068. $sGroupBy1 = 'FunctionalCI.status';
  1069. $this->sGroupByLabel1 = MetaModel::GetLabel('FunctionalCI', 'status');
  1070. $aFill1 = array(
  1071. 'production',
  1072. 'implementation',
  1073. );
  1074. $aFill1 = null;
  1075. //$sGroupBy2 = 'org_id_friendlyname';
  1076. $sGroupBy2 = 'FunctionalCI.org_id';
  1077. $this->sGroupByLabel2 = MetaModel::GetLabel('FunctionalCI', 'org_id');
  1078. $aFill2 = array(
  1079. '2',
  1080. '1',
  1081. );
  1082. //$aFill2 = null;
  1083. }
  1084. else
  1085. {
  1086. $oFilter = DBObjectSearch::FromOQL('SELECT Incident AS i');
  1087. $sGroupBy1 = "DATE_FORMAT(i.start_date, '%H')";
  1088. $this->sGroupByLabel1 = 'Hour of '.MetaModel::GetLabel('Incident', 'start_date');
  1089. $aFill1 = array(9, 10, 11, 12, 13, 14, 15, 16, 17, 18);
  1090. //$aFill1 = null;
  1091. $sGroupBy2 = "DATE_FORMAT(i.start_date, '%w')";
  1092. $this->sGroupByLabel2 = 'Week day of '.MetaModel::GetLabel('Incident', 'start_date');
  1093. $aFill2 = array(1, 2, 3, 4, 5);
  1094. //$aFill2 = null;
  1095. }
  1096. // Do compute the statistics
  1097. //
  1098. $this->aValues1 = array();
  1099. $this->aValues2 = array();
  1100. $this->aStats = array();
  1101. $this->iTotalCount = 0;
  1102. $sAlias = $oFilter->GetClassAlias();
  1103. //$oGroupByExp1 = new FieldExpression($sGroupBy1, $sAlias);
  1104. $oGroupByExp1 = Expression::FromOQL($sGroupBy1);
  1105. //$oGroupByExp2 = new FieldExpression($sGroupBy2, $sAlias);
  1106. $oGroupByExp2 = Expression::FromOQL($sGroupBy2);
  1107. $aGroupBy = array();
  1108. $aGroupBy['grouped_by_1'] = $oGroupByExp1;
  1109. $aGroupBy['grouped_by_2'] = $oGroupByExp2;
  1110. $sSql = MetaModel::MakeGroupByQuery($oFilter, array(), $aGroupBy);
  1111. $aRes = CMDBSource::QueryToArray($sSql);
  1112. // Prepare a blank and ordered grid
  1113. if (is_array($aFill1))
  1114. {
  1115. foreach ($aFill1 as $sValue1)
  1116. {
  1117. $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value
  1118. $this->aValues1[$sValue1] = $sDisplayValue1;
  1119. }
  1120. }
  1121. if (is_array($aFill2))
  1122. {
  1123. foreach ($aFill2 as $sValue2)
  1124. {
  1125. $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value
  1126. $this->aValues2[$sValue2] = $sDisplayValue2;
  1127. }
  1128. }
  1129. $oAppContext = new ApplicationContext();
  1130. $sParams = $oAppContext->GetForLink();
  1131. foreach ($aRes as $aRow)
  1132. {
  1133. $iCount = $aRow['_itop_count_'];
  1134. $this->iTotalCount += $iCount;
  1135. $sValue1 = $aRow['grouped_by_1'];
  1136. $sValue2 = $aRow['grouped_by_2'];
  1137. $bValidResult = true;
  1138. if (is_array($aFill1) && !in_array($sValue1, $aFill1))
  1139. {
  1140. $bValidResult = false;
  1141. }
  1142. if (is_array($aFill2) && !in_array($sValue2, $aFill2))
  1143. {
  1144. $bValidResult = false;
  1145. }
  1146. if ($bValidResult)
  1147. {
  1148. // Build the search for this subset
  1149. $oSubsetSearch = clone $oFilter;
  1150. $oCondition = new BinaryExpression($oGroupByExp1, '=', new ScalarExpression($sValue1));
  1151. $oSubsetSearch->AddConditionExpression($oCondition);
  1152. $oCondition = new BinaryExpression($oGroupByExp2, '=', new ScalarExpression($sValue2));
  1153. $oSubsetSearch->AddConditionExpression($oCondition);
  1154. $sFilter = urlencode($oSubsetSearch->serialize());
  1155. $this->aStats[$sValue1][$sValue2] = array (
  1156. 'href' => utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=".urlencode($sFilter),
  1157. 'count' => $iCount,
  1158. );
  1159. if (!array_key_exists($sValue1, $this->aValues1))
  1160. {
  1161. $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value
  1162. $this->aValues1[$sValue1] = $sDisplayValue1;
  1163. }
  1164. if (!array_key_exists($sValue2, $this->aValues2))
  1165. {
  1166. $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value
  1167. $this->aValues2[$sValue2] = $sDisplayValue2;
  1168. }
  1169. }
  1170. }
  1171. }
  1172. public function GetPropertiesFields(DesignerForm $oForm)
  1173. {
  1174. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  1175. $oForm->AddField($oField);
  1176. }
  1177. static public function GetInfo()
  1178. {
  1179. return array(
  1180. 'label' => 'Test3D',
  1181. 'icon' => 'images/dashlet-groupby2-table.png',
  1182. 'description' => 'Group by on two dimensions',
  1183. );
  1184. }
  1185. }
  1186. class DashletHeatMap extends Dashlet
  1187. {
  1188. public function __construct($sId)
  1189. {
  1190. parent::__construct($sId);
  1191. $this->aProperties['class'] = 'Contact';
  1192. $this->aProperties['title'] = 'Test';
  1193. }
  1194. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  1195. {
  1196. $sTitle = addslashes($this->aProperties['title']);
  1197. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  1198. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  1199. $aAxisX = array(0 => 'Lun', 1 => 'Ma');
  1200. $aAxisY = array(0 => '12h', 1 => '13h');
  1201. $aData = array(
  1202. 0 => array(1, 2),
  1203. 1 => array(3, 4),
  1204. );
  1205. $sJSAxisX = json_encode($aAxisX);
  1206. $sJSAxisY = json_encode($aAxisY);
  1207. $sJSData = json_encode($aData);
  1208. $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, axis_x: $sJSAxisX, axis_y: $sJSAxisY});");
  1209. }
  1210. public function GetPropertiesFields(DesignerForm $oForm)
  1211. {
  1212. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  1213. $oForm->AddField($oField);
  1214. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  1215. $oForm->AddField($oField);
  1216. }
  1217. static public function GetInfo()
  1218. {
  1219. return array(
  1220. 'label' => 'Heatmap (Raphael)',
  1221. 'icon' => 'images/dashlet-heatmap.png',
  1222. 'description' => 'Pure JS Heat Map Chart',
  1223. );
  1224. }
  1225. }