dashlet.class.inc.php 30 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088
  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>'.$e->GetUserFriendlyDescription().'</h2>');
  138. $oPage->add('</div>');
  139. }
  140. }
  141. catch(OqlException $e)
  142. {
  143. $oPage->add('<div class="dashlet-content">');
  144. $oPage->p($e->GetUserFriendlyDescription());
  145. $oPage->add('</div>');
  146. }
  147. catch(Exception $e)
  148. {
  149. $oPage->add('<div class="dashlet-content">');
  150. $oPage->p($e->getMessage());
  151. $oPage->add('</div>');
  152. }
  153. if ($bEnclosingDiv)
  154. {
  155. $oPage->add('</div>');
  156. }
  157. if ($bEditMode)
  158. {
  159. $sClass = get_class($this);
  160. $oPage->add_ready_script(
  161. <<<EOF
  162. $('#dashlet_$sId').dashlet({dashlet_id: '$sId', dashlet_class: '$sClass'});
  163. EOF
  164. );
  165. }
  166. }
  167. public function SetID($sId)
  168. {
  169. $this->sId = $sId;
  170. }
  171. public function GetID()
  172. {
  173. return $this->sId;
  174. }
  175. abstract public function Render($oPage, $bEditMode = false, $aExtraParams = array());
  176. abstract public function GetPropertiesFields(DesignerForm $oForm);
  177. public function ToXml(DOMNode $oContainerNode)
  178. {
  179. }
  180. public function Update($aValues, $aUpdatedFields)
  181. {
  182. foreach($aUpdatedFields as $sProp)
  183. {
  184. if (array_key_exists($sProp, $this->aProperties))
  185. {
  186. $this->aProperties[$sProp] = $aValues[$sProp];
  187. }
  188. }
  189. return $this;
  190. }
  191. public function IsRedrawNeeded()
  192. {
  193. return $this->bRedrawNeeded;
  194. }
  195. public function IsFormRedrawNeeded()
  196. {
  197. return $this->bFormRedrawNeeded;
  198. }
  199. static public function GetInfo()
  200. {
  201. return array(
  202. 'label' => '',
  203. 'icon' => '',
  204. 'description' => '',
  205. );
  206. }
  207. public function GetForm()
  208. {
  209. $oForm = new DesignerForm();
  210. $oForm->SetPrefix("dashlet_". $this->GetID());
  211. $oForm->SetParamsContainer('params');
  212. $this->GetPropertiesFields($oForm);
  213. $oDashletClassField = new DesignerHiddenField('dashlet_class', '', get_class($this));
  214. $oForm->AddField($oDashletClassField);
  215. $oDashletIdField = new DesignerHiddenField('dashlet_id', '', $this->GetID());
  216. $oForm->AddField($oDashletIdField);
  217. return $oForm;
  218. }
  219. static public function IsVisible()
  220. {
  221. return true;
  222. }
  223. static public function CanCreateFromOQL()
  224. {
  225. return false;
  226. }
  227. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null)
  228. {
  229. // Default: do nothing since it's not supported
  230. }
  231. }
  232. class DashletEmptyCell extends Dashlet
  233. {
  234. public function __construct($sId)
  235. {
  236. parent::__construct($sId);
  237. }
  238. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  239. {
  240. $oPage->add('&nbsp;');
  241. }
  242. public function GetPropertiesFields(DesignerForm $oForm)
  243. {
  244. }
  245. static public function GetInfo()
  246. {
  247. return array(
  248. 'label' => 'Empty Cell',
  249. 'icon' => 'images/dashlet-text.png',
  250. 'description' => 'Empty Cell Dashlet Placeholder',
  251. );
  252. }
  253. static public function IsVisible()
  254. {
  255. return false;
  256. }
  257. }
  258. class DashletPlainText extends Dashlet
  259. {
  260. public function __construct($sId)
  261. {
  262. parent::__construct($sId);
  263. $this->aProperties['text'] = Dict::S('UI:DashletPlainText:Prop-Text:Default');
  264. }
  265. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  266. {
  267. $sText = htmlentities($this->aProperties['text'], ENT_QUOTES, 'UTF-8');
  268. $sId = 'plaintext_'.($bEditMode? 'edit_' : '').$this->sId;
  269. $oPage->add('<div id='.$sId.'" class="dashlet-content">'.$sText.'</div>');
  270. }
  271. public function GetPropertiesFields(DesignerForm $oForm)
  272. {
  273. $oField = new DesignerLongTextField('text', Dict::S('UI:DashletPlainText:Prop-Text'), $this->aProperties['text']);
  274. $oField->SetMandatory();
  275. $oForm->AddField($oField);
  276. }
  277. static public function GetInfo()
  278. {
  279. return array(
  280. 'label' => Dict::S('UI:DashletPlainText:Label'),
  281. 'icon' => 'images/dashlet-text.png',
  282. 'description' => Dict::S('UI:DashletPlainText:Description'),
  283. );
  284. }
  285. }
  286. class DashletObjectList extends Dashlet
  287. {
  288. public function __construct($sId)
  289. {
  290. parent::__construct($sId);
  291. $this->aProperties['title'] = '';
  292. $this->aProperties['query'] = 'SELECT Contact';
  293. $this->aProperties['menu'] = false;
  294. }
  295. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  296. {
  297. $sTitle = $this->aProperties['title'];
  298. $sQuery = $this->aProperties['query'];
  299. $sShowMenu = $this->aProperties['menu'] ? '1' : '0';
  300. $oPage->add('<div class="dashlet-content">');
  301. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  302. if ($sHtmlTitle != '')
  303. {
  304. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  305. }
  306. $oFilter = DBObjectSearch::FromOQL($sQuery);
  307. $oBlock = new DisplayBlock($oFilter, 'list');
  308. $aExtraParams = array(
  309. 'menu' => $sShowMenu,
  310. 'table_id' => 'Dashlet'.$this->sId,
  311. );
  312. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  313. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  314. $oPage->add('</div>');
  315. }
  316. public function GetPropertiesFields(DesignerForm $oForm)
  317. {
  318. $oField = new DesignerTextField('title', Dict::S('UI:DashletObjectList:Prop-Title'), $this->aProperties['title']);
  319. $oForm->AddField($oField);
  320. $oField = new DesignerLongTextField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $this->aProperties['query']);
  321. $oField->SetMandatory();
  322. $oForm->AddField($oField);
  323. $oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
  324. $oForm->AddField($oField);
  325. }
  326. static public function GetInfo()
  327. {
  328. return array(
  329. 'label' => Dict::S('UI:DashletObjectList:Label'),
  330. 'icon' => 'images/dashlet-list.png',
  331. 'description' => Dict::S('UI:DashletObjectList:Description'),
  332. );
  333. }
  334. static public function CanCreateFromOQL()
  335. {
  336. return true;
  337. }
  338. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null)
  339. {
  340. $oField = new DesignerTextField('title', Dict::S('UI:DashletObjectList:Prop-Title'), '');
  341. $oForm->AddField($oField);
  342. $oField = new DesignerHiddenField('query', Dict::S('UI:DashletObjectList:Prop-Query'), $sOQL);
  343. $oField->SetMandatory();
  344. $oForm->AddField($oField);
  345. $oField = new DesignerBooleanField('menu', Dict::S('UI:DashletObjectList:Prop-Menu'), $this->aProperties['menu']);
  346. $oForm->AddField($oField);
  347. }
  348. }
  349. abstract class DashletGroupBy extends Dashlet
  350. {
  351. public function __construct($sId)
  352. {
  353. parent::__construct($sId);
  354. $this->aProperties['title'] = '';
  355. $this->aProperties['query'] = 'SELECT Contact';
  356. $this->aProperties['group_by'] = 'status';
  357. $this->aProperties['style'] = 'table';
  358. }
  359. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  360. {
  361. $sTitle = $this->aProperties['title'];
  362. $sQuery = $this->aProperties['query'];
  363. $sGroupBy = $this->aProperties['group_by'];
  364. $sStyle = $this->aProperties['style'];
  365. // First perform the query - if the OQL is not ok, it will generate an exception : no need to go further
  366. $oFilter = DBObjectSearch::FromOQL($sQuery);
  367. $sClass = $oFilter->GetClass();
  368. $sClassAlias = $oFilter->GetClassAlias();
  369. // Check groupby... it can be wrong at this stage
  370. if (preg_match('/^(.*):(.*)$/', $sGroupBy, $aMatches))
  371. {
  372. $sAttCode = $aMatches[1];
  373. $sFunction = $aMatches[2];
  374. }
  375. else
  376. {
  377. $sAttCode = $sGroupBy;
  378. $sFunction = null;
  379. }
  380. if (!MetaModel::IsValidAttCode($sClass, $sAttCode))
  381. {
  382. $oPage->add('<p>'.Dict::S('UI:DashletGroupBy:MissingGroupBy').'</p>');
  383. }
  384. else
  385. {
  386. $sAttLabel = MetaModel::GetLabel($sClass, $sAttCode);
  387. if (!is_null($sFunction))
  388. {
  389. $sFunction = $aMatches[2];
  390. switch($sFunction)
  391. {
  392. case 'hour':
  393. $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Hour', $sAttLabel);
  394. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%H')"; // 0 -> 23
  395. break;
  396. case 'month':
  397. $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Month', $sAttLabel);
  398. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%m')"; // 0 -> 31
  399. break;
  400. case 'day_of_week':
  401. $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:DayOfWeek', $sAttLabel);
  402. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%w')";
  403. break;
  404. case 'day_of_month':
  405. $sGroupByLabel = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:DayOfMonth', $sAttLabel);
  406. $sGroupByExpr = "DATE_FORMAT($sClassAlias.$sAttCode, '%e')"; // 0 -> 31
  407. break;
  408. default:
  409. $sGroupByLabel = 'Unknown group by function '.$sFunction;
  410. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  411. }
  412. }
  413. else
  414. {
  415. $sGroupByExpr = $sClassAlias.'.'.$sAttCode;
  416. $sGroupByLabel = $sAttLabel;
  417. }
  418. switch($sStyle)
  419. {
  420. case 'bars':
  421. $sType = 'open_flash_chart';
  422. $aExtraParams = array(
  423. 'chart_type' => 'bars',
  424. 'chart_title' => $sTitle,
  425. 'group_by' => $sGroupByExpr,
  426. 'group_by_label' => $sGroupByLabel,
  427. );
  428. $sHtmlTitle = ''; // done in the itop block
  429. break;
  430. case 'pie':
  431. $sType = 'open_flash_chart';
  432. $aExtraParams = array(
  433. 'chart_type' => 'pie',
  434. 'chart_title' => $sTitle,
  435. 'group_by' => $sGroupByExpr,
  436. 'group_by_label' => $sGroupByLabel,
  437. );
  438. $sHtmlTitle = ''; // done in the itop block
  439. break;
  440. case 'table':
  441. default:
  442. $sHtmlTitle = htmlentities(Dict::S($sTitle), ENT_QUOTES, 'UTF-8'); // done in the itop block
  443. $sType = 'count';
  444. $aExtraParams = array(
  445. 'group_by' => $sGroupByExpr,
  446. 'group_by_label' => $sGroupByLabel,
  447. );
  448. break;
  449. }
  450. $oPage->add('<div style="text-align:center" class="dashlet-content">');
  451. if ($sHtmlTitle != '')
  452. {
  453. $oPage->add('<h1>'.$sHtmlTitle.'</h1>');
  454. }
  455. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  456. $oBlock = new DisplayBlock($oFilter, $sType);
  457. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  458. $oPage->add('</div>');
  459. }
  460. }
  461. protected function GetGroupByOptions($sOql)
  462. {
  463. $oSearch = DBObjectSearch::FromOQL($sOql);
  464. $sClass = $oSearch->GetClass();
  465. $aGroupBy = array();
  466. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  467. {
  468. if (!$oAttDef->IsScalar()) continue; // skip link sets
  469. if ($oAttDef instanceof AttributeFriendlyName) continue;
  470. if ($oAttDef instanceof AttributeExternalField) continue;
  471. $sLabel = $oAttDef->GetLabel();
  472. $aGroupBy[$sAttCode] = $sLabel;
  473. if ($oAttDef instanceof AttributeDateTime)
  474. {
  475. $aGroupBy[$sAttCode.':hour'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Hour', $sLabel);
  476. $aGroupBy[$sAttCode.':month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-Month', $sLabel);
  477. $aGroupBy[$sAttCode.':day_of_week'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfWeek', $sLabel);
  478. $aGroupBy[$sAttCode.':day_of_month'] = Dict::Format('UI:DashletGroupBy:Prop-GroupBy:Select-DayOfMonth', $sLabel);
  479. }
  480. }
  481. asort($aGroupBy);
  482. return $aGroupBy;
  483. }
  484. public function GetPropertiesFields(DesignerForm $oForm)
  485. {
  486. $oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), $this->aProperties['title']);
  487. $oForm->AddField($oField);
  488. $oField = new DesignerLongTextField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $this->aProperties['query']);
  489. $oField->SetMandatory();
  490. $oForm->AddField($oField);
  491. try
  492. {
  493. // Group by field: build the list of possible values (attribute codes + ...)
  494. $aGroupBy = $this->GetGroupByOptions($this->aProperties['query']);
  495. $oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']);
  496. $oField->SetMandatory();
  497. $oField->SetAllowedValues($aGroupBy);
  498. }
  499. catch(Exception $e)
  500. {
  501. $oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), $this->aProperties['group_by']);
  502. $oField->SetReadOnly();
  503. }
  504. $oForm->AddField($oField);
  505. $aStyles = array(
  506. 'pie' => Dict::S('UI:DashletGroupByPie:Label'),
  507. 'bars' => Dict::S('UI:DashletGroupByBars:Label'),
  508. 'table' => Dict::S('UI:DashletGroupByTable:Label'),
  509. );
  510. $oField = new DesignerComboField('style', Dict::S('UI:DashletGroupBy:Prop-Style'), $this->aProperties['style']);
  511. $oField->SetMandatory();
  512. $oField->SetAllowedValues($aStyles);
  513. $oForm->AddField($oField);
  514. }
  515. public function Update($aValues, $aUpdatedFields)
  516. {
  517. if (in_array('query', $aUpdatedFields))
  518. {
  519. try
  520. {
  521. $sCurrQuery = $aValues['query'];
  522. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  523. $sCurrClass = $oCurrSearch->GetClass();
  524. $sPrevQuery = $this->aProperties['query'];
  525. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  526. $sPrevClass = $oPrevSearch->GetClass();
  527. if ($sCurrClass != $sPrevClass)
  528. {
  529. $this->bFormRedrawNeeded = true;
  530. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  531. $this->aProperties['group_by'] = '';
  532. }
  533. }
  534. catch(Exception $e)
  535. {
  536. $this->bFormRedrawNeeded = true;
  537. }
  538. }
  539. $oDashlet = parent::Update($aValues, $aUpdatedFields);
  540. if (in_array('style', $aUpdatedFields))
  541. {
  542. switch($aValues['style'])
  543. {
  544. // Style changed, mutate to the specified type of chart
  545. case 'pie':
  546. $oDashlet = new DashletGroupByPie($this->sId);
  547. break;
  548. case 'bars':
  549. $oDashlet = new DashletGroupByBars($this->sId);
  550. break;
  551. case 'table':
  552. $oDashlet = new DashletGroupByTable($this->sId);
  553. break;
  554. }
  555. $oDashlet->FromParams($aValues);
  556. $oDashlet->bRedrawNeeded = true;
  557. $oDashlet->bFormRedrawNeeded = true;
  558. }
  559. return $oDashlet;
  560. }
  561. static public function GetInfo()
  562. {
  563. // Note: no need to translate, should never be visible to the end-user!
  564. return array(
  565. 'label' => 'Objects grouped by...',
  566. 'icon' => 'images/dashlet-object-grouped.png',
  567. 'description' => 'Grouped objects dashlet (abstract)',
  568. );
  569. }
  570. static public function CanCreateFromOQL()
  571. {
  572. return true;
  573. }
  574. public function GetPropertiesFieldsFromOQL(DesignerForm $oForm, $sOQL = null)
  575. {
  576. $oField = new DesignerTextField('title', Dict::S('UI:DashletGroupBy:Prop-Title'), '');
  577. $oForm->AddField($oField);
  578. $oField = new DesignerHiddenField('query', Dict::S('UI:DashletGroupBy:Prop-Query'), $sOQL);
  579. $oField->SetMandatory();
  580. $oForm->AddField($oField);
  581. if (!is_null($sOQL))
  582. {
  583. $oField = new DesignerComboField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
  584. $aGroupBy = $this->GetGroupByOptions($sOQL);
  585. $oField->SetAllowedValues($aGroupBy);
  586. }
  587. else
  588. {
  589. // Creating a form for reading parameters!
  590. $oField = new DesignerTextField('group_by', Dict::S('UI:DashletGroupBy:Prop-GroupBy'), null);
  591. }
  592. $oField->SetMandatory();
  593. $oForm->AddField($oField);
  594. $oField = new DesignerHiddenField('style', '', $this->aProperties['style']);
  595. $oField->SetMandatory();
  596. $oForm->AddField($oField);
  597. }
  598. }
  599. class DashletGroupByPie extends DashletGroupBy
  600. {
  601. public function __construct($sId)
  602. {
  603. parent::__construct($sId);
  604. $this->aProperties['style'] = 'pie';
  605. }
  606. static public function GetInfo()
  607. {
  608. return array(
  609. 'label' => Dict::S('UI:DashletGroupByPie:Label'),
  610. 'icon' => 'images/dashlet-pie-chart.png',
  611. 'description' => Dict::S('UI:DashletGroupByPie:Description'),
  612. );
  613. }
  614. }
  615. class DashletGroupByBars extends DashletGroupBy
  616. {
  617. public function __construct($sId)
  618. {
  619. parent::__construct($sId);
  620. $this->aProperties['style'] = 'bars';
  621. }
  622. static public function GetInfo()
  623. {
  624. return array(
  625. 'label' => Dict::S('UI:DashletGroupByBars:Label'),
  626. 'icon' => 'images/dashlet-bar-chart.png',
  627. 'description' => Dict::S('UI:DashletGroupByBars:Description'),
  628. );
  629. }
  630. }
  631. class DashletGroupByTable extends DashletGroupBy
  632. {
  633. public function __construct($sId)
  634. {
  635. parent::__construct($sId);
  636. $this->aProperties['style'] = 'table';
  637. }
  638. static public function GetInfo()
  639. {
  640. return array(
  641. 'label' => Dict::S('UI:DashletGroupByTable:Label'),
  642. 'description' => Dict::S('UI:DashletGroupByTable:Description'),
  643. 'icon' => 'images/dashlet-groupby-table.png',
  644. );
  645. }
  646. }
  647. class DashletHeaderStatic extends Dashlet
  648. {
  649. public function __construct($sId)
  650. {
  651. parent::__construct($sId);
  652. $this->aProperties['title'] = Dict::S('UI:DashletHeaderStatic:Prop-Title:Default');
  653. $sIcon = MetaModel::GetClassIcon('Contact', false);
  654. $sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon);
  655. $this->aProperties['icon'] = $sIcon;
  656. }
  657. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  658. {
  659. $sTitle = $this->aProperties['title'];
  660. $sIcon = $this->aProperties['icon'];
  661. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  662. $oPage->add('<div class="dashlet-content">');
  663. $oPage->add('<div class="main_header">');
  664. $oPage->add('<img src="'.$sIconPath.'">');
  665. $oPage->add('<h1>'.Dict::S($sTitle).'</h1>');
  666. $oPage->add('</div>');
  667. $oPage->add('</div>');
  668. }
  669. public function GetPropertiesFields(DesignerForm $oForm)
  670. {
  671. $oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderStatic:Prop-Title'), $this->aProperties['title']);
  672. $oForm->AddField($oField);
  673. $oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderStatic:Prop-Icon'), $this->aProperties['icon']);
  674. $aAllIcons = self::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  675. ksort($aAllIcons);
  676. $aValues = array();
  677. foreach($aAllIcons as $sFilePath)
  678. {
  679. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  680. }
  681. $oField->SetAllowedValues($aValues);
  682. $oForm->AddField($oField);
  683. }
  684. static public function GetInfo()
  685. {
  686. return array(
  687. 'label' => Dict::S('UI:DashletHeaderStatic:Label'),
  688. 'icon' => 'images/dashlet-header.png',
  689. 'description' => Dict::S('UI:DashletHeaderStatic:Description'),
  690. );
  691. }
  692. static public function FindIcons($sBaseDir, $sDir = '')
  693. {
  694. $aResult = array();
  695. // Populate automatically the list of icon files
  696. if ($hDir = @opendir($sBaseDir.'/'.$sDir))
  697. {
  698. while (($sFile = readdir($hDir)) !== false)
  699. {
  700. $aMatches = array();
  701. if (($sFile != '.') && ($sFile != '..') && is_dir($sBaseDir.'/'.$sDir.'/'.$sFile))
  702. {
  703. $sDirSubPath = ($sDir == '') ? $sFile : $sDir.'/'.$sFile;
  704. $aResult = array_merge($aResult, self::FindIcons($sBaseDir, $sDirSubPath));
  705. }
  706. if (preg_match("/\.(png|jpg|jpeg|gif)$/i", $sFile, $aMatches)) // png, jp(e)g and gif are considered valid
  707. {
  708. $aResult[$sFile.'_'.$sDir] = $sDir.'/'.$sFile;
  709. }
  710. }
  711. closedir($hDir);
  712. }
  713. return $aResult;
  714. }
  715. }
  716. class DashletHeaderDynamic extends Dashlet
  717. {
  718. public function __construct($sId)
  719. {
  720. parent::__construct($sId);
  721. $this->aProperties['title'] = Dict::S('UI:DashletHeaderDynamic:Prop-Title:Default');
  722. $sIcon = MetaModel::GetClassIcon('Contact', false);
  723. $sIcon = str_replace(utils::GetAbsoluteUrlModulesRoot(), '', $sIcon);
  724. $this->aProperties['icon'] = $sIcon;
  725. $this->aProperties['subtitle'] = Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle:Default');
  726. $this->aProperties['query'] = 'SELECT Contact';
  727. $this->aProperties['group_by'] = 'status';
  728. $this->aProperties['values'] = array('active', 'inactive');
  729. }
  730. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  731. {
  732. $sTitle = $this->aProperties['title'];
  733. $sIcon = $this->aProperties['icon'];
  734. $sSubtitle = $this->aProperties['subtitle'];
  735. $sQuery = $this->aProperties['query'];
  736. $sGroupBy = $this->aProperties['group_by'];
  737. $aValues = $this->aProperties['values'];
  738. $oFilter = DBObjectSearch::FromOQL($sQuery);
  739. $sClass = $oFilter->GetClass();
  740. $sIconPath = utils::GetAbsoluteUrlModulesRoot().$sIcon;
  741. if (MetaModel::IsValidAttCode($sClass, $sGroupBy))
  742. {
  743. if (count($aValues) == 0)
  744. {
  745. $aAllowed = MetaModel::GetAllowedValues_att($sClass, $sGroupBy);
  746. if (is_array($aAllowed))
  747. {
  748. $aValues = array_keys($aAllowed);
  749. }
  750. }
  751. }
  752. if (count($aValues) > 0)
  753. {
  754. // Stats grouped by <group_by>
  755. $sCSV = implode(',', $aValues);
  756. $aExtraParams = array(
  757. 'title[block]' => $sTitle,
  758. 'label[block]' => $sSubtitle,
  759. 'status[block]' => $sGroupBy,
  760. 'status_codes[block]' => $sCSV,
  761. 'context_filter' => 1,
  762. );
  763. }
  764. else
  765. {
  766. // Simple stats
  767. $aExtraParams = array(
  768. 'title[block]' => $sTitle,
  769. 'label[block]' => $sSubtitle,
  770. 'context_filter' => 1,
  771. );
  772. }
  773. $oPage->add('<div class="dashlet-content">');
  774. $oPage->add('<div class="main_header">');
  775. $oPage->add('<img src="'.$sIconPath.'">');
  776. $oBlock = new DisplayBlock($oFilter, 'summary');
  777. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  778. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  779. $oPage->add('</div>');
  780. $oPage->add('</div>');
  781. }
  782. public function GetPropertiesFields(DesignerForm $oForm)
  783. {
  784. $oField = new DesignerTextField('title', Dict::S('UI:DashletHeaderDynamic:Prop-Title'), $this->aProperties['title']);
  785. $oForm->AddField($oField);
  786. $oField = new DesignerIconSelectionField('icon', Dict::S('UI:DashletHeaderDynamic:Prop-Icon'), $this->aProperties['icon']);
  787. $aAllIcons = DashletHeaderStatic::FindIcons(APPROOT.'env-'.utils::GetCurrentEnvironment());
  788. ksort($aAllIcons);
  789. $aValues = array();
  790. foreach($aAllIcons as $sFilePath)
  791. {
  792. $aValues[] = array('value' => $sFilePath, 'label' => basename($sFilePath), 'icon' => utils::GetAbsoluteUrlModulesRoot().$sFilePath);
  793. }
  794. $oField->SetAllowedValues($aValues);
  795. $oForm->AddField($oField);
  796. $oField = new DesignerTextField('subtitle', Dict::S('UI:DashletHeaderDynamic:Prop-Subtitle'), $this->aProperties['subtitle']);
  797. $oForm->AddField($oField);
  798. $oField = new DesignerTextField('query', Dict::S('UI:DashletHeaderDynamic:Prop-Query'), $this->aProperties['query']);
  799. $oField->SetMandatory();
  800. $oForm->AddField($oField);
  801. try
  802. {
  803. // Group by field: build the list of possible values (attribute codes + ...)
  804. $oSearch = DBObjectSearch::FromOQL($this->aProperties['query']);
  805. $sClass = $oSearch->GetClass();
  806. $aGroupBy = array();
  807. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  808. {
  809. if (!$oAttDef instanceof AttributeEnum && (!$oAttDef instanceof AttributeFinalClass || !MetaModel::HasChildrenClasses($sClass))) continue;
  810. $sLabel = $oAttDef->GetLabel();
  811. $aGroupBy[$sAttCode] = $sLabel;
  812. }
  813. $oField = new DesignerComboField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']);
  814. $oField->SetMandatory();
  815. $oField->SetAllowedValues($aGroupBy);
  816. }
  817. catch(Exception $e)
  818. {
  819. $oField = new DesignerTextField('group_by', Dict::S('UI:DashletHeaderDynamic:Prop-GroupBy'), $this->aProperties['group_by']);
  820. $oField->SetReadOnly();
  821. }
  822. $oForm->AddField($oField);
  823. $oField = new DesignerComboField('values', Dict::S('UI:DashletHeaderDynamic:Prop-Values'), $this->aProperties['values']);
  824. $oField->MultipleSelection(true);
  825. if (isset($sClass) && MetaModel::IsValidAttCode($sClass, $this->aProperties['group_by']))
  826. {
  827. $aValues = MetaModel::GetAllowedValues_att($sClass, $this->aProperties['group_by']);
  828. $oField->SetAllowedValues($aValues);
  829. }
  830. else
  831. {
  832. $oField->SetReadOnly();
  833. }
  834. $oForm->AddField($oField);
  835. }
  836. public function Update($aValues, $aUpdatedFields)
  837. {
  838. if (in_array('query', $aUpdatedFields))
  839. {
  840. try
  841. {
  842. $sCurrQuery = $aValues['query'];
  843. $oCurrSearch = DBObjectSearch::FromOQL($sCurrQuery);
  844. $sCurrClass = $oCurrSearch->GetClass();
  845. $sPrevQuery = $this->aProperties['query'];
  846. $oPrevSearch = DBObjectSearch::FromOQL($sPrevQuery);
  847. $sPrevClass = $oPrevSearch->GetClass();
  848. if ($sCurrClass != $sPrevClass)
  849. {
  850. $this->bFormRedrawNeeded = true;
  851. // wrong but not necessary - unset($aUpdatedFields['group_by']);
  852. $this->aProperties['group_by'] = '';
  853. $this->aProperties['values'] = array();
  854. }
  855. }
  856. catch(Exception $e)
  857. {
  858. $this->bFormRedrawNeeded = true;
  859. }
  860. }
  861. if (in_array('group_by', $aUpdatedFields))
  862. {
  863. $this->bFormRedrawNeeded = true;
  864. $this->aProperties['values'] = array();
  865. }
  866. return parent::Update($aValues, $aUpdatedFields);
  867. }
  868. static public function GetInfo()
  869. {
  870. return array(
  871. 'label' => Dict::S('UI:DashletHeaderDynamic:Label'),
  872. 'icon' => 'images/dashlet-header-stats.png',
  873. 'description' => Dict::S('UI:DashletHeaderDynamic:Description'),
  874. );
  875. }
  876. }
  877. class DashletBadge extends Dashlet
  878. {
  879. public function __construct($sId)
  880. {
  881. parent::__construct($sId);
  882. $this->aProperties['class'] = 'Contact';
  883. $this->aCSSClasses[] = 'dashlet-inline';
  884. $this->aCSSClasses[] = 'dashlet-badge';
  885. }
  886. public function Render($oPage, $bEditMode = false, $aExtraParams = array())
  887. {
  888. $sClass = $this->aProperties['class'];
  889. $oPage->add('<div class="dashlet-content">');
  890. $oFilter = new DBObjectSearch($sClass);
  891. $oBlock = new DisplayBlock($oFilter, 'actions');
  892. $aExtraParams = array(
  893. 'context_filter' => 1,
  894. );
  895. $sBlockId = 'block_'.$this->sId.($bEditMode ? '_edit' : ''); // make a unique id (edition occuring in the same DOM)
  896. $oBlock->Display($oPage, $sBlockId, $aExtraParams);
  897. $oPage->add('</div>');
  898. if ($bEditMode)
  899. {
  900. // Since the container div is not rendered the same way in edit mode, add the 'inline' style to it
  901. $oPage->add_ready_script("$('#dashlet_".$this->sId."').addClass('dashlet-inline');");
  902. }
  903. }
  904. public function GetPropertiesFields(DesignerForm $oForm)
  905. {
  906. $oClassesSet = new ValueSetEnumClasses('bizmodel', array());
  907. $aClasses = $oClassesSet->GetValues(array());
  908. $aLinkClasses = array();
  909. foreach(MetaModel::GetClasses('bizmodel') as $sClass)
  910. {
  911. foreach(MetaModel::ListAttributeDefs($sClass) as $sAttCode => $oAttDef)
  912. {
  913. if ($oAttDef instanceof AttributeLinkedSetIndirect)
  914. {
  915. $aLinkClasses[$oAttDef->GetLinkedClass()] = true;
  916. }
  917. }
  918. }
  919. $oField = new DesignerIconSelectionField('class', Dict::S('UI:DashletBadge:Prop-Class'), $this->aProperties['class']);
  920. ksort($aClasses);
  921. $aValues = array();
  922. foreach($aClasses as $sClass => $sClass)
  923. {
  924. if (!array_key_exists($sClass, $aLinkClasses))
  925. {
  926. $sIconUrl = MetaModel::GetClassIcon($sClass, false);
  927. $sIconFilePath = str_replace(utils::GetAbsoluteUrlAppRoot(), APPROOT, $sIconUrl);
  928. if (($sIconUrl == '') || !file_exists($sIconFilePath))
  929. {
  930. // The icon does not exist, leet's use a transparent one of the same size.
  931. $sIconUrl = utils::GetAbsoluteUrlAppRoot().'images/transparent_32_32.png';
  932. }
  933. $aValues[] = array('value' => $sClass, 'label' => MetaModel::GetName($sClass), 'icon' => $sIconUrl);
  934. }
  935. }
  936. $oField->SetAllowedValues($aValues);
  937. $oForm->AddField($oField);
  938. }
  939. static public function GetInfo()
  940. {
  941. return array(
  942. 'label' => Dict::S('UI:DashletBadge:Label'),
  943. 'icon' => 'images/dashlet-badge.png',
  944. 'description' => Dict::S('UI:DashletBadge:Description'),
  945. );
  946. }
  947. }
  948. ?>