dashlet.class.inc.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650
  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. else
  47. {
  48. $ret = $sValue;
  49. settype($ret, $sRefType);
  50. }
  51. return $ret;
  52. }
  53. public function Prop2Str($value)
  54. {
  55. if (gettype($value) == 'boolean')
  56. {
  57. $sRet = $value ? 'true' : 'false';
  58. }
  59. else
  60. {
  61. $sRet = (string) $value;
  62. }
  63. return $sRet;
  64. }
  65. public function FromDOMNode($oDOMNode)
  66. {
  67. foreach ($this->aProperties as $sProperty => $value)
  68. {
  69. $this->oDOMNode = $oDOMNode->getElementsByTagName($sProperty)->item(0);
  70. if ($this->oDOMNode != null)
  71. {
  72. $newvalue = $this->Str2Prop($sProperty, $this->oDOMNode->textContent);
  73. $this->aProperties[$sProperty] = $newvalue;
  74. }
  75. }
  76. }
  77. public function ToDOMNode($oDOMNode)
  78. {
  79. foreach ($this->aProperties as $sProperty => $value)
  80. {
  81. $sXmlValue = $this->Prop2Str($value);
  82. $oPropNode = $oDOMNode->ownerDocument->createElement($sProperty, $sXmlValue);
  83. $oDOMNode->appendChild($oPropNode);
  84. }
  85. }
  86. public function FromXml($sXml)
  87. {
  88. $oDomDoc = new DOMDocument('1.0', 'UTF-8');
  89. $oDomDoc->loadXml($sXml);
  90. $this->FromDOMNode($oDomDoc->firstChild);
  91. }
  92. public function FromParams($aParams)
  93. {
  94. foreach ($this->aProperties as $sProperty => $value)
  95. {
  96. if (array_key_exists($sProperty, $aParams))
  97. {
  98. $this->aProperties[$sProperty] = $aParams[$sProperty];
  99. }
  100. }
  101. }
  102. public function DoRender($oPage, $bEditMode = false, $aExtraParams = array())
  103. {
  104. $sCSSClasses = implode(' ', $this->aCSSClasses);
  105. if ($bEditMode)
  106. {
  107. $sId = $this->GetID();
  108. $oPage->add('<div class="'.$sCSSClasses.'" id="dashlet_'.$sId.'">');
  109. }
  110. else
  111. {
  112. $oPage->add('<div class="'.$sCSSClasses.'">');
  113. }
  114. $this->Render($oPage, $bEditMode, $aExtraParams);
  115. $oPage->add('</div>');
  116. if ($bEditMode)
  117. {
  118. $sClass = get_class($this);
  119. $oPage->add_ready_script(
  120. <<<EOF
  121. $('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass'});
  122. EOF
  123. );
  124. }
  125. }
  126. public function GetID()
  127. {
  128. return $this->sId;
  129. }
  130. abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
  131. abstract public function GetPropertiesFields(DesignerForm $oForm);
  132. public function ToXml(DOMNode $oContainerNode)
  133. {
  134. }
  135. public function Update($aValues, $aUpdatedFields)
  136. {
  137. foreach($aUpdatedFields as $sProp)
  138. {
  139. if (array_key_exists($sProp, $this->aProperties))
  140. {
  141. $this->aProperties[$sProp] = $aValues[$sProp];
  142. }
  143. }
  144. return $this;
  145. }
  146. public function IsRedrawNeeded()
  147. {
  148. return $this->bRedrawNeeded;
  149. }
  150. public function IsFormRedrawNeeded()
  151. {
  152. return $this->bFormRedrawNeeded;
  153. }
  154. static public function GetInfo()
  155. {
  156. return array(
  157. 'label' => '',
  158. 'icon' => '',
  159. 'description' => '',
  160. );
  161. }
  162. public function GetForm()
  163. {
  164. $oForm = new DesignerForm();
  165. $oForm->SetPrefix("dashlet_". $this->GetID());
  166. $oForm->SetParamsContainer('params');
  167. $this->GetPropertiesFields($oForm);
  168. $oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
  169. $oForm->AddField($oDashletClassField);
  170. $oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
  171. $oForm->AddField($oDashletIdField);
  172. return $oForm;
  173. }
  174. static public function IsVisible()
  175. {
  176. return true;
  177. }
  178. }
  179. class DashletEmptyCell extends Dashlet
  180. {
  181. public function __construct($sId)
  182. {
  183. parent::__construct($sId);
  184. }
  185. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  186. {
  187. $oPage->add('&nbsp;');
  188. }
  189. public function GetPropertiesFields(DesignerForm $oForm)
  190. {
  191. }
  192. static public function GetInfo()
  193. {
  194. return array(
  195. 'label' => 'Empty Cell',
  196. 'icon' => 'images/dashlet-text.png',
  197. 'description' => 'Empty Cell Dashlet Placeholder',
  198. );
  199. }
  200. static public function IsVisible()
  201. {
  202. return false;
  203. }
  204. }
  205. class DashletHelloWorld extends Dashlet
  206. {
  207. public function __construct($sId)
  208. {
  209. parent::__construct($sId);
  210. $this->aProperties['text'] = 'Hello World';
  211. }
  212. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  213. {
  214. $oPage->add('<div style="text-align:center; line-height:5em" class="dashlet-content"><span>'.$this->aProperties['text'].'</span></div>');
  215. }
  216. public function GetPropertiesFields(DesignerForm $oForm)
  217. {
  218. $oField = new DesignerTextField('text', 'Text', $this->aProperties['text']);
  219. $oForm->AddField($oField);
  220. }
  221. static public function GetInfo()
  222. {
  223. return array(
  224. 'label' => 'Hello World',
  225. 'icon' => 'images/dashlet-text.png',
  226. 'description' => 'Hello World test Dashlet',
  227. );
  228. }
  229. }
  230. class DashletObjectList extends Dashlet
  231. {
  232. public function __construct($sId)
  233. {
  234. parent::__construct($sId);
  235. $this->aProperties['title'] = 'Hardcoded list of "my requests"';
  236. $this->aProperties['query'] = 'SELECT UserRequest AS i WHERE i.caller_id = :current_contact_id AND status NOT IN ("closed", "resolved")';
  237. $this->aProperties['menu'] = false;
  238. }
  239. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  240. {
  241. $sTitle = $this->aProperties['title'];
  242. $sQuery = $this->aProperties['query'];
  243. $sShowMenu = $this->aProperties['menu'] ? '1' : '0';
  244. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  245. // C'est quoi ce paramètre "menu" ?
  246. $sXML = '<itopblock BlockClass="DisplayBlock" type="list" asynchronous="false" encoding="text/oql" parameters="menu:'.$sShowMenu.'">'.$sQuery.'</itopblock>';
  247. $aParams = array();
  248. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  249. $oBlock = DisplayBlock::FromTemplate($sXML);
  250. $oBlock->Display($oPage, $sBlockId, $aParams);
  251. $oPage->add('</div>');
  252. }
  253. public function GetPropertiesFields(DesignerForm $oForm)
  254. {
  255. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  256. $oForm->AddField($oField);
  257. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  258. $oForm->AddField($oField);
  259. $oField = new DesignerBooleanField('menu', 'Menu', $this->aProperties['menu']);
  260. $oForm->AddField($oField);
  261. }
  262. static public function GetInfo()
  263. {
  264. return array(
  265. 'label' => 'Object list',
  266. 'icon' => 'images/dashlet-object-list.png',
  267. 'description' => 'Object list dashlet',
  268. );
  269. }
  270. }
  271. abstract class DashletGroupBy extends Dashlet
  272. {
  273. public function __construct($sId)
  274. {
  275. parent::__construct($sId);
  276. $this->aProperties['title'] = 'Hardcoded list of Contacts grouped by location';
  277. $this->aProperties['query'] = 'SELECT Contact';
  278. $this->aProperties['group_by'] = 'location_name';
  279. $this->aProperties['style'] = 'table';
  280. }
  281. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  282. {
  283. $sTitle = $this->aProperties['title'];
  284. $sQuery = $this->aProperties['query'];
  285. $sGroupBy = $this->aProperties['group_by'];
  286. $sStyle = $this->aProperties['style'];
  287. if ($sQuery == '')
  288. {
  289. $oPage->add('<p>Please enter a valid OQL query</p>');
  290. }
  291. elseif ($sGroupBy == '')
  292. {
  293. $oPage->add('<p>Please select the field on which the objects will be grouped together</p>');
  294. }
  295. else
  296. {
  297. switch($sStyle)
  298. {
  299. case 'bars':
  300. $sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:bars;chart_title:'.$sTitle.';group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
  301. $sHtmlTitle = ''; // done in the itop block
  302. break;
  303. case 'pie':
  304. $sXML = '<itopblock BlockClass="DisplayBlock" type="open_flash_chart" parameters="chart_type:pie;chart_title:'.$sTitle.';group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
  305. $sHtmlTitle = ''; // done in the itop block
  306. break;
  307. case 'table':
  308. default:
  309. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  310. $sXML = '<itopblock BlockClass="DisplayBlock" type="count" parameters="group_by:'.$sGroupBy.'" asynchronous="false" encoding="text/oql">'.$sQuery.'</itopblock>';
  311. break;
  312. }
  313. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  314. if ($sHtmlTitle != '')
  315. {
  316. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  317. }
  318. $aParams = array();
  319. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  320. $oBlock = DisplayBlock::FromTemplate($sXML);
  321. $oBlock->Display($oPage, $sBlockId, $aParams);
  322. $oPage->add('</div>');
  323. // TEST Group By as SQL!
  324. //$oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  325. //$sSql = MetaModel::MakeSelectQuery($oSearch);
  326. //$sHtmlSql = htmlentities($sSql, ENT_QUOTES, 'UTF-8');
  327. //$oPage->p($sHtmlSql);
  328. }
  329. }
  330. public function GetPropertiesFields(DesignerForm $oForm)
  331. {
  332. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  333. $oForm->AddField($oField);
  334. $oField = new DesignerLongTextField('query', 'Query', $this->aProperties['query']);
  335. $oForm->AddField($oField);
  336. // Group by field: build the list of possible values (attribute codes + ...)
  337. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  338. $sClass = $oSearch->GetClass();
  339. $aGroupBy = array();
  340. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  341. {
  342. if (!$oAttDef->IsScalar()) continue; // skip link sets
  343. if ($oAttDef->IsExternalKey(EXTKEY_ABSOLUTE)) continue; // skip external keys
  344. $aGroupBy[$sAttCode] = $oAttDef->GetLabel();
  345. if ($oAttDef instanceof AttributeDateTime)
  346. {
  347. //date_format(start_date, '%d')
  348. $aGroupBy['date_of_'.$sAttCode] = 'Day of '.$oAttDef->GetLabel();
  349. }
  350. }
  351. $oField = new DesignerComboField('group_by', 'Group by', $this->aProperties['group_by']);
  352. $oField->SetAllowedValues($aGroupBy);
  353. $oForm->AddField($oField);
  354. $aStyles = array(
  355. 'pie' => 'Pie chart',
  356. 'bars' => 'Bar chart',
  357. 'table' => 'Table',
  358. );
  359. $oField = new DesignerComboField('style', 'Style', $this->aProperties['style']);
  360. $oField->SetAllowedValues($aStyles);
  361. $oForm->AddField($oField);
  362. }
  363. public function Update($aValues, $aUpdatedFields)
  364. {
  365. if (in_array('query', $aUpdatedFields))
  366. {
  367. $sCurrQuery = $aValues['query'];
  368. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  369. $sCurrClass = $oCurrSearch->GetClass();
  370. $sPrevQuery = $this->aProperties['query'];
  371. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  372. $sPrevClass = $oPrevSearch->GetClass();
  373. if ($sCurrClass != $sPrevClass)
  374. {
  375. $this->bFormRedrawNeeded = true;
  376. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  377. $this->aProperties['group_by'] = '';
  378. }
  379. }
  380. $oDashlet = parent::Update($aValues, $aUpdatedFields);
  381. if (in_array('style', $aUpdatedFields))
  382. {
  383. switch($aValues['style'])
  384. {
  385. // Style changed, mutate to the specified type of chart
  386. case 'pie':
  387. $oDashlet = new DashletGroupByPie($this->sId);
  388. break;
  389. case 'bars':
  390. $oDashlet = new DashletGroupByBars($this->sId);
  391. break;
  392. case 'table':
  393. $oDashlet = new DashletGroupByTable($this->sId);
  394. break;
  395. }
  396. $oDashlet->FromParams($aValues);
  397. $oDashlet->bRedrawNeeded = true;
  398. $oDashlet->bFormRedrawNeeded = true;
  399. }
  400. return $oDashlet;
  401. }
  402. static public function GetInfo()
  403. {
  404. return array(
  405. 'label' => 'Objects grouped by...',
  406. 'icon' => 'images/dashlet-object-grouped.png',
  407. 'description' => 'Grouped objects dashlet',
  408. );
  409. }
  410. }
  411. class DashletGroupByPie extends DashletGroupBy
  412. {
  413. public function __construct($sId)
  414. {
  415. parent::__construct($sId);
  416. $this->aProperties['style'] = 'pie';
  417. }
  418. static public function GetInfo()
  419. {
  420. return array(
  421. 'label' => 'Pie Chart',
  422. 'icon' => 'images/dashlet-pie-chart.png',
  423. 'description' => 'Pie Chart',
  424. );
  425. }
  426. }
  427. class DashletGroupByBars extends DashletGroupBy
  428. {
  429. public function __construct($sId)
  430. {
  431. parent::__construct($sId);
  432. $this->aProperties['style'] = 'bars';
  433. }
  434. static public function GetInfo()
  435. {
  436. return array(
  437. 'label' => 'Bar Chart',
  438. 'icon' => 'images/dashlet-bar-chart.png',
  439. 'description' => 'Bar Chart',
  440. );
  441. }
  442. }
  443. class DashletGroupByTable extends DashletGroupBy
  444. {
  445. public function __construct($sId)
  446. {
  447. parent::__construct($sId);
  448. $this->aProperties['style'] = 'table';
  449. }
  450. static public function GetInfo()
  451. {
  452. return array(
  453. 'label' => 'Group By (table)',
  454. 'icon' => 'images/dashlet-group-by-table.png',
  455. 'description' => 'List (Grouped by a field)',
  456. );
  457. }
  458. }
  459. class DashletHeader extends Dashlet
  460. {
  461. public function __construct($sId)
  462. {
  463. parent::__construct($sId);
  464. $this->aProperties['title'] = 'Hardcoded header of contacts';
  465. $this->aProperties['subtitle'] = 'Contacts';
  466. $this->aProperties['class'] = 'Contact';
  467. }
  468. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  469. {
  470. $sTitle = $this->aProperties['title'];
  471. $sSubtitle = $this->aProperties['subtitle'];
  472. $sClass = $this->aProperties['class'];
  473. $sTitleReady = str_replace(':', '_', $sTitle);
  474. $sSubtitleReady = str_replace(':', '_', $sSubtitle);
  475. $sStatusAttCode = MetaModel::GetStateAttributeCode($sClass);
  476. if (($sStatusAttCode == '') && MetaModel::IsValidAttCode($sClass, 'status'))
  477. {
  478. // Based on an enum
  479. $sStatusAttCode = 'status';
  480. $aStates = array_keys(MetaModel::GetAllowedValues_att($sClass, $sStatusAttCode));
  481. }
  482. else
  483. {
  484. // Based on a state variable
  485. $aStates = array_keys(MetaModel::EnumStates($sClass));
  486. }
  487. if ($sStatusAttCode == '')
  488. {
  489. // Simple stats
  490. $sXML = '<itopblock BlockClass="DisplayBlock" type="summary" asynchronous="false" encoding="text/oql" parameters="title[block]:'.$sTitleReady.';context_filter:1;label[block]:'.$sSubtitleReady.'">SELECT '.$sClass.'</itopblock>';
  491. }
  492. else
  493. {
  494. // Stats grouped by "status"
  495. $sStatusList = implode(',', $aStates);
  496. //$oPage->p('State: '.$sStatusAttCode.' states='.$sStatusList);
  497. $sXML = '<itopblock BlockClass="DisplayBlock" type="summary" asynchronous="false" encoding="text/oql" parameters="title[block]:'.$sTitleReady.';context_filter:1;label[block]:'.$sSubtitleReady.';status[block]:status;status_codes[block]:'.$sStatusList.'">SELECT '.$sClass.'</itopblock>';
  498. }
  499. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  500. $oPage->add('<div class="main_header">');
  501. $aParams = array();
  502. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  503. $oBlock = DisplayBlock::FromTemplate($sXML);
  504. $oBlock->Display($oPage, $sBlockId, $aParams);
  505. $oPage->add('</div>');
  506. $oPage->add('</div>');
  507. }
  508. public function GetPropertiesFields(DesignerForm $oForm)
  509. {
  510. $oField = new DesignerTextField('title', 'Title', $this->aProperties['title']);
  511. $oForm->AddField($oField);
  512. $oField = new DesignerTextField('subtitle', 'Subtitle', $this->aProperties['subtitle']);
  513. $oForm->AddField($oField);
  514. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  515. $oForm->AddField($oField);
  516. }
  517. static public function GetInfo()
  518. {
  519. return array(
  520. 'label' => 'Header with stats',
  521. 'icon' => 'images/dashlet-header-stats.png',
  522. 'description' => 'Header with stats (grouped by...)',
  523. );
  524. }
  525. }
  526. class DashletBadge extends Dashlet
  527. {
  528. public function __construct($sId)
  529. {
  530. parent::__construct($sId);
  531. $this->aProperties['class'] = 'Contact';
  532. $this->aCSSClasses[] = 'dashlet-inline';
  533. $this->aCSSClasses[] = 'dashlet-badge';
  534. }
  535. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  536. {
  537. $sClass = $this->aProperties['class'];
  538. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  539. $sXml = "<itopblock BlockClass=\"DisplayBlock\" type=\"actions\" asynchronous=\"false\" encoding=\"text/oql\" parameters=\"context_filter:1\">SELECT $sClass</itopblock>";
  540. $oBlock = DisplayBlock::FromTemplate($sXml);
  541. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  542. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  543. $oPage->add('</div>');
  544. }
  545. public function GetPropertiesFields(DesignerForm $oForm)
  546. {
  547. $oField = new DesignerTextField('class', 'Class', $this->aProperties['class']);
  548. $oForm->AddField($oField);
  549. }
  550. static public function GetInfo()
  551. {
  552. return array(
  553. 'label' => 'Badge',
  554. 'icon' => 'images/dashlet-badge.png',
  555. 'description' => 'Object Icon with new/search',
  556. );
  557. }
  558. }