dashlet.class.inc.php 40 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407
  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. );
  302. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  303. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  304. $oPage->add('</div>');
  305. }
  306. public function GetPropertiesFields(DesignerForm $oForm)
  307. {
  308. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  309. $oForm->AddField($oField);
  310. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  311. $oForm->AddField($oField);
  312. $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
  313. $oForm->AddField($oField);
  314. }
  315. static public function GetInfo()
  316. {
  317. return array(
  318. 'label' => 'Object list',
  319. 'icon' => 'images/dashlet-list.png',
  320. 'description' => 'Object list dashlet',
  321. );
  322. }
  323. static public function CanCreateFromOQL()
  324. {
  325. return true;
  326. }
  327. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL)
  328. {
  329. $oField = new DesignerTextField('title', 'Title', '');
  330. $oForm->AddField($oField);
  331. $oField = new DesignerHiddenField('query', 'Query', $sOQL);
  332. $oForm->AddField($oField);
  333. $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
  334. $oForm->AddField($oField);
  335. }
  336. }
  337. abstract class DashletGroupBy extends Dashlet
  338. {
  339. public function __construct($sId)
  340. {
  341. parent::__construct($sId);
  342. $this->aProperties['title'] = 'Contacts grouped by location';
  343. $this->aProperties['query'] = 'SELECT Contact';
  344. $this->aProperties['group_by'] = 'location_name';
  345. $this->aProperties['style'] = 'table';
  346. }
  347. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  348. {
  349. $sTitle = $this->aProperties['title'];
  350. $sQuery = $this->aProperties['query'];
  351. $sGroupBy = $this->aProperties['group_by'];
  352. $sStyle = $this->aProperties['style'];
  353. if ($sQuery == '')
  354. {
  355. $oPage->add('<p>Please enter a valid OQL query</p>');
  356. }
  357. elseif ($sGroupBy == '')
  358. {
  359. $oPage->add('<p>Please select the field on which the objects will be grouped together</p>');
  360. }
  361. else
  362. {
  363. $oFilter = DBObjectSearch::FromOQL($sQuery);
  364. $sClassAlias = $oFilter->GetClassAlias();
  365. if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
  366. {
  367. $sAttCode = $aMatches[1];
  368. $sFunction = $aMatches[2];
  369. switch($sFunction)
  370. {
  371. case 'hour':
  372. $sGroupByLabel = 'Hour of '.$sAttCode. ' (0-23)';
  373. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 31
  374. break;
  375. case 'month':
  376. $sGroupByLabel = 'Month of '.$sAttCode. ' (1 - 12)';
  377. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%m')"; // 0 -> 31
  378. break;
  379. case 'day_of_week':
  380. $sGroupByLabel = 'Day of week for '.$sAttCode. ' (sunday to saturday)';
  381. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%w')";
  382. break;
  383. case 'day_of_month':
  384. $sGroupByLabel = 'Day of month for'.$sAttCode;
  385. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%e')"; // 0 -> 31
  386. break;
  387. default:
  388. $sGroupByLabel = 'Unknown group by function '.$sFunction;
  389. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  390. }
  391. }
  392. else
  393. {
  394. $sAttCode = $sGroupBy;
  395. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  396. $sGroupByLabel = MetaModel::GetLabel($oFilter->GetClass(), $sAttCode);
  397. }
  398. switch($sStyle)
  399. {
  400. case 'bars':
  401. $sType = 'open_flash_chart';
  402. $aExtraParams = array(
  403. 'chart_type' => 'bars',
  404. 'chart_title' => $sTitle,
  405. 'group_by' => $sGroupByExpr,
  406. 'group_by_label' => $sGroupByLabel,
  407. );
  408. $sHtmlTitle = ''; // done in the itop block
  409. break;
  410. case 'pie':
  411. $sType = 'open_flash_chart';
  412. $aExtraParams = array(
  413. 'chart_type' => 'pie',
  414. 'chart_title' => $sTitle,
  415. 'group_by' => $sGroupByExpr,
  416. 'group_by_label' => $sGroupByLabel,
  417. );
  418. $sHtmlTitle = ''; // done in the itop block
  419. break;
  420. case 'table':
  421. default:
  422. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  423. $sType = 'count';
  424. $aExtraParams = array(
  425. 'group_by' => $sGroupByExpr,
  426. 'group_by_label' => $sGroupByLabel,
  427. );
  428. break;
  429. }
  430. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  431. if ($sHtmlTitle != '')
  432. {
  433. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  434. }
  435. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  436. $oBlock = new DisplayBlock($oFilter, $sType);
  437. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  438. $oPage->add('</div>');
  439. }
  440. }
  441. public function GetPropertiesFields(DesignerForm $oForm)
  442. {
  443. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  444. $oForm->AddField($oField);
  445. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  446. $oForm->AddField($oField);
  447. try
  448. {
  449. // Group by field: build the list of possible values (attribute codes + ...)
  450. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  451. $sClass = $oSearch->GetClass();
  452. $aGroupBy = array();
  453. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  454. {
  455. if (!$oAttDef->IsScalar()) continue; // skip link sets
  456. $sLabel = $oAttDef->GetLabel();
  457. if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE))
  458. {
  459. $sLabel = $oAttDef->GetLabel().' (strict)';
  460. }
  461. $aGroupBy[$sAttCode] = $sLabel;
  462. if ($oAttDef instanceof AttributeDateTime)
  463. {
  464. $aGroupBy[$sAttCode.':hour'] = $oAttDef->GetLabel().' (hour)';
  465. $aGroupBy[$sAttCode.':month'] = $oAttDef->GetLabel().' (month)';
  466. $aGroupBy[$sAttCode.':day_of_week'] = $oAttDef->GetLabel().' (day of week)';
  467. $aGroupBy[$sAttCode.':day_of_month'] = $oAttDef->GetLabel().' (day of month)';
  468. }
  469. }
  470. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  471. $oField->SetAllowedValues($aGroupBy);
  472. }
  473. catch(Exception $e)
  474. {
  475. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  476. }
  477. $oForm->AddField($oField);
  478. $aStyles = array(
  479. 'pie' => 'Pie chart',
  480. 'bars' => 'Bar chart',
  481. 'table' => 'Table',
  482. );
  483. $oField = new DesignerComboField('style', 'Style', $this->aProperties['style']);
  484. $oField->SetAllowedValues($aStyles);
  485. $oForm->AddField($oField);
  486. }
  487. public function Update($aValues, $aUpdatedFields)
  488. {
  489. if (in_array('query', $aUpdatedFields))
  490. {
  491. try
  492. {
  493. $sCurrQuery = $aValues['query'];
  494. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  495. $sCurrClass = $oCurrSearch->GetClass();
  496. $sPrevQuery = $this->aProperties['query'];
  497. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  498. $sPrevClass = $oPrevSearch->GetClass();
  499. if ($sCurrClass != $sPrevClass)
  500. {
  501. $this->bFormRedrawNeeded = true;
  502. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  503. $this->aProperties['group_by'] = '';
  504. }
  505. }
  506. catch(Exception $e)
  507. {
  508. $this->bFormRedrawNeeded = true;
  509. }
  510. }
  511. $oDashlet = parent::Update($aValues, $aUpdatedFields);
  512. if (in_array('style', $aUpdatedFields))
  513. {
  514. switch($aValues['style'])
  515. {
  516. // Style changed, mutate to the specified type of chart
  517. case 'pie':
  518. $oDashlet = new DashletGroupByPie($this->sId);
  519. break;
  520. case 'bars':
  521. $oDashlet = new DashletGroupByBars($this->sId);
  522. break;
  523. case 'table':
  524. $oDashlet = new DashletGroupByTable($this->sId);
  525. break;
  526. }
  527. $oDashlet->FromParams($aValues);
  528. $oDashlet->bRedrawNeeded = true;
  529. $oDashlet->bFormRedrawNeeded = true;
  530. }
  531. return $oDashlet;
  532. }
  533. static public function GetInfo()
  534. {
  535. return array(
  536. 'label' => 'Objects grouped by...',
  537. 'icon' => 'images/dashlet-object-grouped.png',
  538. 'description' => 'Grouped objects dashlet',
  539. );
  540. }
  541. static public function CanCreateFromOQL()
  542. {
  543. return true;
  544. }
  545. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL)
  546. {
  547. $oField = new DesignerTextField('title', 'Title', '');
  548. $oForm->AddField($oField);
  549. $oField = new DesignerHiddenField('query', 'Query', $sOQL);
  550. $oForm->AddField($oField);
  551. try
  552. {
  553. // Group by field: build the list of possible values (attribute codes + ...)
  554. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  555. $sClass = $oSearch->GetClass();
  556. $aGroupBy = array();
  557. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  558. {
  559. if (!$oAttDef->IsScalar()) continue; // skip link sets
  560. if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) continue; // skip external keys
  561. $aGroupBy[$sAttCode] = $oAttDef->GetLabel();
  562. if ($oAttDef instanceof AttributeDateTime)
  563. {
  564. //date_format(start_date, '%d')
  565. $aGroupBy['date_of_'.$sAttCode] = 'Day of '.$oAttDef->GetLabel();
  566. }
  567. }
  568. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  569. $oField->SetAllowedValues($aGroupBy);
  570. }
  571. catch(Exception $e)
  572. {
  573. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  574. }
  575. $oForm->AddField($oField);
  576. $oField = new DesignerHiddenField('style', '', $this->aProperties['style']);
  577. $oForm->AddField($oField);
  578. }
  579. }
  580. class DashletGroupByPie extends DashletGroupBy
  581. {
  582. public function __construct($sId)
  583. {
  584. parent::__construct($sId);
  585. $this->aProperties['style'] = 'pie';
  586. }
  587. static public function GetInfo()
  588. {
  589. return array(
  590. 'label' => 'Pie Chart',
  591. 'icon' => 'images/dashlet-pie-chart.png',
  592. 'description' => 'Pie Chart',
  593. );
  594. }
  595. }
  596. class DashletGroupByPie2 extends DashletGroupByPie
  597. {
  598. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  599. {
  600. $sTitle = addslashes($this->aProperties['title']);
  601. $sQuery = $this->aProperties['query'];
  602. $sGroupBy = $this->aProperties['group_by'];
  603. $oSearch = DBObjectSearch::FromOQL($sQuery);
  604. $sClassAlias = $oSearch->GetClassAlias();
  605. $aQueryParams = array();
  606. $aGroupBy = array();
  607. $oGroupByExp = Expression::FromOQL($sClassAlias.'.'.$sGroupBy);
  608. $aGroupBy['grouped_by_1'] = $oGroupByExp;
  609. $sSql = MetaModel::MakeGroupByQuery($oSearch, $aQueryParams, $aGroupBy);
  610. $aRes = CMDBSource::QueryToArray($sSql);
  611. $aGroupBy = array();
  612. $aLabels = array();
  613. $iTotalCount = 0;
  614. foreach ($aRes as $aRow)
  615. {
  616. $sValue = $aRow['grouped_by_1'];
  617. $aLabels[] = ($sValue == '') ? 'Empty (%%.%%)' : $sValue.' (%%.%%)'; //TODO: localize
  618. $aGroupBy[] = (int) $aRow['_itop_count_'];
  619. $iTotalCount += $aRow['_itop_count_'];
  620. }
  621. $aURLs = array();
  622. $sContext = ''; //TODO get the context ??
  623. foreach($aGroupBy as $sValue => $iValue)
  624. {
  625. // Build the search for this subset
  626. $oSubsetSearch = clone $oSearch;
  627. $oCondition = new BinaryExpression($oGroupByExp, '=', new ScalarExpression($sValue));
  628. $oSubsetSearch->AddConditionExpression($oCondition);
  629. // Todo - à finir
  630. $aURLs[] = 'http://www.combodo.com/itop'; //utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&format=html{$sContext}&filter=".addslashes($oSubsetSearch->serialize());
  631. }
  632. $sJSValues = json_encode($aGroupBy);
  633. $sJSHrefs = json_encode($aURLs);
  634. $sJSLabels = json_encode($aLabels);
  635. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  636. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  637. $oPage->add_ready_script("$('#chart_{$sId}').pie_chart({chart_label: '$sTitle', values: $sJSValues, labels: $sJSLabels, hrefs: $sJSHrefs });");
  638. }
  639. static public function GetInfo()
  640. {
  641. return array(
  642. 'label' => 'Pie (Raphael)',
  643. 'icon' => 'images/dashlet-pie-chart.png',
  644. 'description' => 'Pure JS Pie Chart',
  645. );
  646. }
  647. }
  648. class DashletGroupByBars extends DashletGroupBy
  649. {
  650. public function __construct($sId)
  651. {
  652. parent::__construct($sId);
  653. $this->aProperties['style'] = 'bars';
  654. }
  655. static public function GetInfo()
  656. {
  657. return array(
  658. 'label' => 'Bar Chart',
  659. 'icon' => 'images/dashlet-bar-chart.png',
  660. 'description' => 'Bar Chart',
  661. );
  662. }
  663. }
  664. class DashletGroupByTable extends DashletGroupBy
  665. {
  666. public function __construct($sId)
  667. {
  668. parent::__construct($sId);
  669. $this->aProperties['style'] = 'table';
  670. }
  671. static public function GetInfo()
  672. {
  673. return array(
  674. 'label' => 'Group By (table)',
  675. 'icon' => 'images/dashlet-groupby-table.png',
  676. 'description' => 'List (Grouped by a field)',
  677. );
  678. }
  679. }
  680. class DashletHeaderStatic extends Dashlet
  681. {
  682. public function __construct($sId)
  683. {
  684. parent::__construct($sId);
  685. $this->aProperties['title'] = 'Contacts';
  686. $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png';
  687. }
  688. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  689. {
  690. $sTitle = $this->aProperties['title'];
  691. $sIcon = $this->aProperties['icon'];
  692. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  693. $oPage->add('<div class="dashlet-content">');
  694. $oPage->add('<div class="main_header">');
  695. $oPage->add('<img src="'.$sIconPath.'">');
  696. $oPage->add('<h1>'.Dict::S($sTitle).'</h1>');
  697. $oPage->add('</div>');
  698. $oPage->add('</div>');
  699. }
  700. public function GetPropertiesFields(DesignerForm $oForm)
  701. {
  702. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  703. $oForm->AddField($oField);
  704. $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']);
  705. $aAllIcons = self::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  706. ksort($aAllIcons);
  707. $aValues = array();
  708. foreach($aAllIcons as $sFilePath)
  709. {
  710. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  711. }
  712. $oField->SetAllowedValues($aValues);
  713. $oForm->AddField($oField);
  714. }
  715. static public function GetInfo()
  716. {
  717. return array(
  718. 'label' => 'Header',
  719. 'icon' => 'images/dashlet-header.png',
  720. 'description' => 'Header with stats (grouped by...)',
  721. );
  722. }
  723. static public function FindIcons($sBaseDir, $sDir = '')
  724. {
  725. $aResult = array();
  726. // Populate automatically the list of icon files
  727. if ($hDir = @opendir($sBaseDir.'/'.$sDir))
  728. {
  729. while (($sFile = readdir($hDir)) !== false)
  730. {
  731. $aMatches = array();
  732. if (($sFile != '.') && ($sFile != '..') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
  733. {
  734. $sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
  735. $aResult = array_merge($aResult, self::FindIcons($sBaseDir, $sDirSubPath));
  736. }
  737. if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
  738. {
  739. $aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
  740. }
  741. }
  742. closedir($hDir);
  743. }
  744. return $aResult;
  745. }
  746. }
  747. class DashletHeaderDynamic extends Dashlet
  748. {
  749. public function __construct($sId)
  750. {
  751. parent::__construct($sId);
  752. $this->aProperties['title'] = 'Contacts';
  753. $this->aProperties['icon'] = 'itop-config-mgmt-1.0.0/images/contact.png';
  754. $this->aProperties['subtitle'] = 'Contacts';
  755. $this->aProperties['query'] = 'SELECT Contact';
  756. $this->aProperties['group_by'] = 'status';
  757. $this->aProperties['values'] = array('active', 'inactive');
  758. }
  759. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  760. {
  761. $sTitle = $this->aProperties['title'];
  762. $sIcon = $this->aProperties['icon'];
  763. $sSubtitle = $this->aProperties['subtitle'];
  764. $sQuery = $this->aProperties['query'];
  765. $sGroupBy = $this->aProperties['group_by'];
  766. $aValues = $this->aProperties['values'];
  767. $oFilter = DBObjectSearch::FromOQL($sQuery);
  768. $sClass = $oFilter->GetClass();
  769. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  770. if (MetaModel::IsValidAttCode($sClass, $sGroupBy))
  771. {
  772. if (count($aValues) == 0)
  773. {
  774. $aAllowed = MetaModel::GetAllowedValues_att($sClass, $sGroupBy);
  775. if (is_array($aAllowed))
  776. {
  777. $aValues = array_keys($aAllowed);
  778. }
  779. }
  780. }
  781. if (count($aValues) > 0)
  782. {
  783. // Stats grouped by <group_by>
  784. $sCSV = implode(',', $aValues);
  785. $aExtraParams = array(
  786. 'title[block]' => $sTitle,
  787. 'label[block]' => $sSubtitle,
  788. 'status[block]' => $sGroupBy,
  789. 'status_codes[block]' => $sCSV,
  790. 'context_filter' => 1,
  791. );
  792. }
  793. else
  794. {
  795. // Simple stats
  796. $aExtraParams = array(
  797. 'title[block]' => $sTitle,
  798. 'label[block]' => $sSubtitle,
  799. 'context_filter' => 1,
  800. );
  801. }
  802. $oPage->add('<div class="dashlet-content">');
  803. $oPage->add('<div class="main_header">');
  804. $oPage->add('<img src="'.$sIconPath.'">');
  805. $oBlock = new DisplayBlock($oFilter, 'summary');
  806. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  807. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  808. $oPage->add('</div>');
  809. $oPage->add('</div>');
  810. }
  811. public function GetPropertiesFields(DesignerForm $oForm)
  812. {
  813. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  814. $oForm->AddField($oField);
  815. $oField = new DesignerIconSelectionField('icon', 'Icon', $this->aProperties['icon']);
  816. $aAllIcons = DashletHeaderStatic::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  817. ksort($aAllIcons);
  818. $aValues = array();
  819. foreach($aAllIcons as $sFilePath)
  820. {
  821. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  822. }
  823. $oField->SetAllowedValues($aValues);
  824. $oForm->AddField($oField);
  825. $oField = new DesignerTextField('subtitle', 'Subtitle', $this->aProperties['subtitle']);
  826. $oForm->AddField($oField);
  827. $oField = new DesignerTextField('query', 'Query', $this->aProperties['query']);
  828. $oForm->AddField($oField);
  829. try
  830. {
  831. // Group by field: build the list of possible values (attribute codes + ...)
  832. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  833. $sClass = $oSearch->GetClass();
  834. $aGroupBy = array();
  835. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  836. {
  837. if (!$oAttDef instanceof AttributeEnum && (!$oAttDef instanceof AttributeFinalClass || !MetaModel::HasChildrenClasses($sClass))) continue;
  838. $sLabel = $oAttDef->GetLabel();
  839. $aGroupBy[$sAttCode] = $sLabel;
  840. }
  841. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  842. $oField->SetAllowedValues($aGroupBy);
  843. }
  844. catch(Exception $e)
  845. {
  846. $oField = new DesignerTextField('group_by', 'Group by', $this->aProperties['group_by']);
  847. }
  848. $oForm->AddField($oField);
  849. $oField = new DesignerComboField('values', 'Values (CSV list)', $this->aProperties['values']);
  850. $oField->MultipleSelection(true);
  851. if (MetaModel::IsValidAttCode($sClass, $this->aProperties['group_by']))
  852. {
  853. $aValues = MetaModel::GetAllowedValues_att($sClass, $this->aProperties['group_by']);
  854. $oField->SetAllowedValues($aValues);
  855. }
  856. else
  857. {
  858. $oField->SetReadOnly();
  859. }
  860. $oForm->AddField($oField);
  861. }
  862. public function Update($aValues, $aUpdatedFields)
  863. {
  864. if (in_array('query', $aUpdatedFields))
  865. {
  866. try
  867. {
  868. $sCurrQuery = $aValues['query'];
  869. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  870. $sCurrClass = $oCurrSearch->GetClass();
  871. $sPrevQuery = $this->aProperties['query'];
  872. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  873. $sPrevClass = $oPrevSearch->GetClass();
  874. if ($sCurrClass != $sPrevClass)
  875. {
  876. $this->bFormRedrawNeeded = true;
  877. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  878. $this->aProperties['group_by'] = '';
  879. $this->aProperties['values'] = array();
  880. }
  881. }
  882. catch(Exception $e)
  883. {
  884. $this->bFormRedrawNeeded = true;
  885. }
  886. }
  887. if (in_array('group_by', $aUpdatedFields))
  888. {
  889. $this->bFormRedrawNeeded = true;
  890. $this->aProperties['values'] = array();
  891. }
  892. return parent::Update($aValues, $aUpdatedFields);
  893. }
  894. static public function GetInfo()
  895. {
  896. return array(
  897. 'label' => 'Header with statistics',
  898. 'icon' => 'images/dashlet-header-stats.png',
  899. 'description' => 'Header with stats (grouped by...)',
  900. );
  901. }
  902. }
  903. class DashletBadge extends Dashlet
  904. {
  905. public function __construct($sId)
  906. {
  907. parent::__construct($sId);
  908. $this->aProperties['class'] = 'Contact';
  909. $this->aCSSClasses[] = 'dashlet-inline';
  910. $this->aCSSClasses[] = 'dashlet-badge';
  911. }
  912. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  913. {
  914. $sClass = $this->aProperties['class'];
  915. $oPage->add('<div class="dashlet-content">');
  916. $oFilter = new DBObjectSearch($sClass);
  917. $oBlock = new DisplayBlock($oFilter, 'actions');
  918. $aExtraParams = array(
  919. 'context_filter' => 1,
  920. );
  921. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  922. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  923. $oPage->add('</div>');
  924. if ($bEditMode)
  925. {
  926. // Since the container div is not rendered the same way in edit mode, add the 'inline' style to it
  927. $oPage->add_ready_script("$('#dashlet_".$this->sId."').addClass('dashlet-inline');");
  928. }
  929. }
  930. public function GetPropertiesFields(DesignerForm $oForm)
  931. {
  932. $oClassesSet = new ValueSetEnumClasses('bizmodel', array());
  933. $aClasses = $oClassesSet->GetValues(array());
  934. $aLinkClasses = array();
  935. foreach(MetaModel::GetClasses('bizmodel') as $sClass)
  936. {
  937. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  938. {
  939. if ($oAttDef instanceof AttributeLinkedSetIndirect)
  940. {
  941. $aLinkClasses[$oAttDef->GetLinkedClass()] = true;
  942. }
  943. }
  944. }
  945. $oField = new DesignerIconSelectionField('class', 'Class', $this->aProperties['class']);
  946. ksort($aClasses);
  947. $aValues = array();
  948. foreach($aClasses as $sClass => $sClass)
  949. {
  950. if (!array_key_exists($sClass, $aLinkClasses))
  951. {
  952. $sIconUrl = MetaModel::GetClassIcon($sClass, false);
  953. $sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
  954. if (($sIconUrl == '') || !file_exists($sIconFilePath))
  955. {
  956. // The icon does not exist, leet's use a transparent one of the same size.
  957. $sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/transparent_32_32.png';
  958. }
  959. $aValues[] = array('value' => $sClass, 'label' => $sClass, 'icon' => $sIconUrl);
  960. }
  961. }
  962. $oField->SetAllowedValues($aValues);
  963. $oForm->AddField($oField);
  964. }
  965. static public function GetInfo()
  966. {
  967. return array(
  968. 'label' => 'Badge',
  969. 'icon' => 'images/dashlet-badge.png',
  970. 'description' => 'Object Icon with new/search',
  971. );
  972. }
  973. }
  974. class DashletProto extends Dashlet
  975. {
  976. public function __construct($sId)
  977. {
  978. parent::__construct($sId);
  979. $this->aProperties['class'] = 'Foo';
  980. }
  981. protected $aValues1;
  982. protected $aValues2;
  983. protected $aStats;
  984. protected $sGroupByLabel1;
  985. protected $sGroupByLabel2;
  986. protected $iTotalCount;
  987. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  988. {
  989. $this->Compute();
  990. //$sHtmlTitle = $this->aProperties['title'];
  991. $sHtmlTitle = "Group by made on two dimensions";
  992. // Build the output
  993. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  994. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  995. $oPage->p(Dict::Format('UI:Pagination:HeaderNoSelection', $this->iTotalCount));
  996. $oPage->add('<table>');
  997. // Header
  998. $oPage->add('<tr>');
  999. $oPage->add('<td></td>');
  1000. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1001. {
  1002. $oPage->add('<td><b>'.$sDisplayValue2.'</b></td>');
  1003. }
  1004. $oPage->add('</tr>');
  1005. // Contents
  1006. foreach ($this->aValues1 as $sValue1 => $sDisplayValue1)
  1007. {
  1008. $oPage->add('<tr>');
  1009. $oPage->add('<td><b>'.$sDisplayValue1.'</b></td>');
  1010. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1011. {
  1012. @$aResData = $this->aStats[$sValue1][$sValue2];
  1013. if (is_array($aResData))
  1014. {
  1015. $sRes = '<a href="'.$aResData['href'].'">'.$aResData['count'].'</a>';
  1016. }
  1017. else
  1018. {
  1019. // Missing result => 0
  1020. $sRes = '-';
  1021. }
  1022. $oPage->add('<td>'.$sRes.'</td>');
  1023. }
  1024. $oPage->add('</tr>');
  1025. }
  1026. $oPage->add('</table>');
  1027. $oPage->add('</div>');
  1028. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  1029. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  1030. $aAxisX = $this->aValues1;
  1031. $aAxisY = $this->aValues2;
  1032. $aData = array();
  1033. $aHref = array();
  1034. foreach ($this->aValues1 as $sValue1 => $sDisplayValue1)
  1035. {
  1036. foreach ($this->aValues2 as $sValue2 => $sDisplayValue2)
  1037. {
  1038. @$aResData = $this->aStats[$sValue1][$sValue2];
  1039. if (is_array($aResData))
  1040. {
  1041. $aData[$sValue1][$sValue2] = $aResData['count'];
  1042. $aHref[$sValue1][$sValue2] = $aResData['href'];
  1043. }
  1044. else
  1045. {
  1046. // Missing result => 0
  1047. $aData[$sValue1][$sValue2] = 0;
  1048. $aHref[$sValue1][$sValue2] = '';
  1049. }
  1050. }
  1051. }
  1052. $sJSAxisX = json_encode($aAxisX);
  1053. $sJSAxisY = json_encode($aAxisY);
  1054. $sJSData = json_encode($aData);
  1055. $sJSHref = json_encode($aHref);
  1056. $sTitle = addslashes($sHtmlTitle);
  1057. $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, hrefs: $sJSHref, axis_x: $sJSAxisX, axis_y: $sJSAxisY});");
  1058. }
  1059. protected function Compute()
  1060. {
  1061. // Read and interpret parameters
  1062. //
  1063. //$sFoo = $this->aProperties['foo'];
  1064. if (false)
  1065. {
  1066. $oFilter = DBObjectSearch::FromOQL('SELECT FunctionalCI');
  1067. $sGroupBy1 = 'FunctionalCI.status';
  1068. $this->sGroupByLabel1 = MetaModel::GetLabel('FunctionalCI', 'status');
  1069. $aFill1 = array(
  1070. 'production',
  1071. 'implementation',
  1072. );
  1073. $aFill1 = null;
  1074. //$sGroupBy2 = 'org_id_friendlyname';
  1075. $sGroupBy2 = 'FunctionalCI.org_id';
  1076. $this->sGroupByLabel2 = MetaModel::GetLabel('FunctionalCI', 'org_id');
  1077. $aFill2 = array(
  1078. '2',
  1079. '1',
  1080. );
  1081. //$aFill2 = null;
  1082. }
  1083. else
  1084. {
  1085. $oFilter = DBObjectSearch::FromOQL('SELECT Incident AS i');
  1086. $sGroupBy1 = "DATE_FORMAT(i.start_date, '%H')";
  1087. $this->sGroupByLabel1 = 'Hour of '.MetaModel::GetLabel('Incident', 'start_date');
  1088. $aFill1 = array(9, 10, 11, 12, 13, 14, 15, 16, 17, 18);
  1089. //$aFill1 = null;
  1090. $sGroupBy2 = "DATE_FORMAT(i.start_date, '%w')";
  1091. $this->sGroupByLabel2 = 'Week day of '.MetaModel::GetLabel('Incident', 'start_date');
  1092. $aFill2 = array(1, 2, 3, 4, 5);
  1093. //$aFill2 = null;
  1094. }
  1095. // Do compute the statistics
  1096. //
  1097. $this->aValues1 = array();
  1098. $this->aValues2 = array();
  1099. $this->aStats = array();
  1100. $this->iTotalCount = 0;
  1101. $sAlias = $oFilter->GetClassAlias();
  1102. //$oGroupByExp1 = new FieldExpression($sGroupBy1, $sAlias);
  1103. $oGroupByExp1 = Expression::FromOQL($sGroupBy1);
  1104. //$oGroupByExp2 = new FieldExpression($sGroupBy2, $sAlias);
  1105. $oGroupByExp2 = Expression::FromOQL($sGroupBy2);
  1106. $aGroupBy = array();
  1107. $aGroupBy['grouped_by_1'] = $oGroupByExp1;
  1108. $aGroupBy['grouped_by_2'] = $oGroupByExp2;
  1109. $sSql = MetaModel::MakeGroupByQuery($oFilter, array(), $aGroupBy);
  1110. $aRes = CMDBSource::QueryToArray($sSql);
  1111. // Prepare a blank and ordered grid
  1112. if (is_array($aFill1))
  1113. {
  1114. foreach ($aFill1 as $sValue1)
  1115. {
  1116. $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value
  1117. $this->aValues1[$sValue1] = $sDisplayValue1;
  1118. }
  1119. }
  1120. if (is_array($aFill2))
  1121. {
  1122. foreach ($aFill2 as $sValue2)
  1123. {
  1124. $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value
  1125. $this->aValues2[$sValue2] = $sDisplayValue2;
  1126. }
  1127. }
  1128. $oAppContext = new ApplicationContext();
  1129. $sParams = $oAppContext->GetForLink();
  1130. foreach ($aRes as $aRow)
  1131. {
  1132. $iCount = $aRow['_itop_count_'];
  1133. $this->iTotalCount += $iCount;
  1134. $sValue1 = $aRow['grouped_by_1'];
  1135. $sValue2 = $aRow['grouped_by_2'];
  1136. $bValidResult = true;
  1137. if (is_array($aFill1) && !in_array($sValue1, $aFill1))
  1138. {
  1139. $bValidResult = false;
  1140. }
  1141. if (is_array($aFill2) && !in_array($sValue2, $aFill2))
  1142. {
  1143. $bValidResult = false;
  1144. }
  1145. if ($bValidResult)
  1146. {
  1147. // Build the search for this subset
  1148. $oSubsetSearch = clone $oFilter;
  1149. $oCondition = new BinaryExpression($oGroupByExp1, '=', new ScalarExpression($sValue1));
  1150. $oSubsetSearch->AddConditionExpression($oCondition);
  1151. $oCondition = new BinaryExpression($oGroupByExp2, '=', new ScalarExpression($sValue2));
  1152. $oSubsetSearch->AddConditionExpression($oCondition);
  1153. $sFilter = urlencode($oSubsetSearch->serialize());
  1154. $this->aStats[$sValue1][$sValue2] = array (
  1155. 'href' => utils::GetAbsoluteUrlAppRoot()."pages/UI.php?operation=search&dosearch=1&$sParams&filter=$sFilter",
  1156. 'count' => $iCount,
  1157. );
  1158. if (!array_key_exists($sValue1, $this->aValues1))
  1159. {
  1160. $sDisplayValue1 = $aGroupBy['grouped_by_1']->MakeValueLabel($oFilter, $sValue1, $sValue1); // default to the raw value
  1161. $this->aValues1[$sValue1] = $sDisplayValue1;
  1162. }
  1163. if (!array_key_exists($sValue2, $this->aValues2))
  1164. {
  1165. $sDisplayValue2 = $aGroupBy['grouped_by_2']->MakeValueLabel($oFilter, $sValue2, $sValue2); // default to the raw value
  1166. $this->aValues2[$sValue2] = $sDisplayValue2;
  1167. }
  1168. }
  1169. }
  1170. }
  1171. public function GetPropertiesFields(DesignerForm $oForm)
  1172. {
  1173. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  1174. $oForm->AddField($oField);
  1175. }
  1176. static public function GetInfo()
  1177. {
  1178. return array(
  1179. 'label' => 'Test3D',
  1180. 'icon' => 'images/dashlet-groupby2-table.png',
  1181. 'description' => 'Group by on two dimensions',
  1182. );
  1183. }
  1184. }
  1185. class DashletHeatMap extends Dashlet
  1186. {
  1187. public function __construct($sId)
  1188. {
  1189. parent::__construct($sId);
  1190. $this->aProperties['class'] = 'Contact';
  1191. $this->aProperties['title'] = 'Test';
  1192. }
  1193. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  1194. {
  1195. $sTitle = addslashes($this->aProperties['title']);
  1196. $sId = 'chart_'.($bEditMode? 'edit_' : '').$this->sId;
  1197. $oPage->add('<div id="chart_'.$sId.'" class="dashlet-content"></div>');
  1198. $aAxisX = array(0 => 'Lun', 1 => 'Ma');
  1199. $aAxisY = array(0 => '12h', 1 => '13h');
  1200. $aData = array(
  1201. 0 => array(1, 2),
  1202. 1 => array(3, 4),
  1203. );
  1204. $sJSAxisX = json_encode($aAxisX);
  1205. $sJSAxisY = json_encode($aAxisY);
  1206. $sJSData = json_encode($aData);
  1207. $oPage->add_ready_script("$('#chart_{$sId}').heatmap_chart({chart_label: '$sTitle', values: $sJSData, axis_x: $sJSAxisX, axis_y: $sJSAxisY});");
  1208. }
  1209. public function GetPropertiesFields(DesignerForm $oForm)
  1210. {
  1211. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  1212. $oForm->AddField($oField);
  1213. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  1214. $oForm->AddField($oField);
  1215. }
  1216. static public function GetInfo()
  1217. {
  1218. return array(
  1219. 'label' => 'Heatmap (Raphael)',
  1220. 'icon' => 'images/dashlet-heatmap.png',
  1221. 'description' => 'Pure JS Heat Map Chart',
  1222. );
  1223. }
  1224. }