datatable.class.inc.php 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961
  1. <?php
  2. // Copyright (C) 2010-2017 Combodo SARL
  3. //
  4. // This file is part of iTop.
  5. //
  6. // iTop is free software; you can redistribute it and/or modify
  7. // it under the terms of the GNU Affero General Public License as published by
  8. // the Free Software Foundation, either version 3 of the License, or
  9. // (at your option) any later version.
  10. //
  11. // iTop is distributed in the hope that it will be useful,
  12. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  14. // GNU Affero General Public License for more details.
  15. //
  16. // You should have received a copy of the GNU Affero General Public License
  17. // along with iTop. If not, see <http://www.gnu.org/licenses/>
  18. /**
  19. * Data Table to display a set of objects in a tabular manner in HTML
  20. *
  21. * @copyright Copyright (C) 2010-2017 Combodo SARL
  22. * @license http://opensource.org/licenses/AGPL-3.0
  23. */
  24. class DataTable
  25. {
  26. protected $iListId; // Unique ID inside the web page
  27. protected $sTableId; // identifier for saving the settings (combined with the class aliases)
  28. protected $oSet; // The set of objects to display
  29. protected $aClassAliases; // The aliases (alias => class) inside the set
  30. protected $iNbObjects; // Total number of objects inthe set
  31. protected $bUseCustomSettings; // Whether or not the current display uses custom settings
  32. protected $oDefaultSettings; // the default settings for displaying such a list
  33. protected $bShowObsoleteData;
  34. /**
  35. * @param $iListId mixed Unique ID for this div/table in the page
  36. * @param $oSet DBObjectSet The set of data to display
  37. * @param $aClassAliases Hash The list of classes/aliases to be displayed in this set $sAlias => $sClassName
  38. * @param $sTableId mixed A string (or null) identifying this table in order to persist its settings
  39. */
  40. public function __construct($iListId, $oSet, $aClassAliases, $sTableId = null)
  41. {
  42. $this->iListId = utils::GetSafeId($iListId); // Make a "safe" ID for jQuery
  43. $this->oSet = $oSet;
  44. $this->aClassAliases = $aClassAliases;
  45. $this->sTableId = $sTableId;
  46. $this->iNbObjects = $oSet->Count();
  47. $this->bUseCustomSettings = false;
  48. $this->oDefaultSettings = null;
  49. $this->bShowObsoleteData = $oSet->GetShowObsoleteData();
  50. }
  51. public function Display(WebPage $oPage, DataTableSettings $oSettings, $bActionsMenu, $sSelectMode, $bViewLink, $aExtraParams)
  52. {
  53. $this->oDefaultSettings = $oSettings;
  54. // Identified tables can have their own specific settings
  55. $oCustomSettings = DataTableSettings::GetTableSettings($this->aClassAliases, $this->sTableId);
  56. if ($oCustomSettings != null)
  57. {
  58. // Custom settings overload the default ones
  59. $this->bUseCustomSettings = true;
  60. if ($this->oDefaultSettings->iDefaultPageSize == 0)
  61. {
  62. $oCustomSettings->iDefaultPageSize = 0;
  63. }
  64. }
  65. else
  66. {
  67. $oCustomSettings = $oSettings;
  68. }
  69. if ($oCustomSettings->iDefaultPageSize > 0)
  70. {
  71. $this->oSet->SetLimit($oCustomSettings->iDefaultPageSize);
  72. }
  73. $this->oSet->SetOrderBy($oCustomSettings->GetSortOrder());
  74. // Load only the requested columns
  75. $aColumnsToLoad = array();
  76. foreach($oCustomSettings->aColumns as $sAlias => $aColumnsInfo)
  77. {
  78. foreach($aColumnsInfo as $sAttCode => $aData)
  79. {
  80. if ($sAttCode != '_key_')
  81. {
  82. if ($aData['checked'])
  83. {
  84. $aColumnsToLoad[$sAlias][] = $sAttCode;
  85. }
  86. else
  87. {
  88. // See if this column is a must to load
  89. $sClass = $this->aClassAliases[$sAlias];
  90. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  91. if ($oAttDef->alwaysLoadInTables())
  92. {
  93. $aColumnsToLoad[$sAlias][] = $sAttCode;
  94. }
  95. }
  96. }
  97. }
  98. }
  99. $this->oSet->OptimizeColumnLoad($aColumnsToLoad);
  100. $bToolkitMenu = true;
  101. if (isset($aExtraParams['toolkit_menu']))
  102. {
  103. $bToolkitMenu = (bool) $aExtraParams['toolkit_menu'];
  104. }
  105. if (UserRights::IsPortalUser())
  106. {
  107. // Portal users have a limited access to data, for now they can only see what's configured for them
  108. $bToolkitMenu = false;
  109. }
  110. return $this->GetAsHTML($oPage, $oCustomSettings->iDefaultPageSize, $oCustomSettings->iDefaultPageSize, 0, $oCustomSettings->aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams);
  111. }
  112. public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
  113. {
  114. $sObjectsCount = $this->GetObjectCount($oPage, $sSelectMode);
  115. $sPager = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
  116. $sActionsMenu = '';
  117. $sToolkitMenu = '';
  118. if ($bActionsMenu)
  119. {
  120. $sActionsMenu = $this->GetActionsMenu($oPage, $aExtraParams);
  121. }
  122. if ($bToolkitMenu)
  123. {
  124. $sToolkitMenu = $this->GetToolkitMenu($oPage, $aExtraParams);
  125. }
  126. $sDataTable = $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
  127. $sConfigDlg = $this->GetTableConfigDlg($oPage, $aColumns, $bViewLink, $iDefaultPageSize);
  128. $sHtml = "<table id=\"datatable_{$this->iListId}\" class=\"datatable\">";
  129. $sHtml .= "<tr><td>";
  130. $sHtml .= "<table style=\"width:100%;\">";
  131. $sHtml .= "<tr><td class=\"pagination_container\">$sObjectsCount</td><td class=\"menucontainer\">$sToolkitMenu $sActionsMenu</td></tr>";
  132. $sHtml .= "<tr>$sPager</tr>";
  133. $sHtml .= "</table>";
  134. $sHtml .= "</td></tr>";
  135. $sHtml .= "<tr><td class=\"datacontents\">$sDataTable</td></tr>";
  136. $sHtml .= "</table>\n";
  137. $oPage->add_at_the_end($sConfigDlg);
  138. $aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
  139. $aOptions = array(
  140. 'sPersistentId' => '',
  141. 'sFilter' => $this->oSet->GetFilter()->serialize(),
  142. 'oColumns' => $aColumns,
  143. 'sSelectMode' => $sSelectMode,
  144. 'sViewLink' => ($bViewLink ? 'true' : 'false'),
  145. 'iNbObjects' => $this->iNbObjects,
  146. 'iDefaultPageSize' => $iDefaultPageSize,
  147. 'iPageSize' => $iPageSize,
  148. 'iPageIndex' => $iPageIndex,
  149. 'oClassAliases' => $this->aClassAliases,
  150. 'sTableId' => $this->sTableId,
  151. 'oExtraParams' => $aExtraParams,
  152. 'sRenderUrl' => utils::GetAbsoluteUrlAppRoot().'pages/ajax.render.php',
  153. 'oRenderParameters' => array('str' => ''), // Forces JSON to encode this as a object...
  154. 'oDefaultSettings' => array('str' => ''), // Forces JSON to encode this as a object...
  155. 'oLabels' => array('moveup' => Dict::S('UI:Button:MoveUp'), 'movedown' => Dict::S('UI:Button:MoveDown')),
  156. );
  157. if($this->oDefaultSettings != null)
  158. {
  159. $aOptions['oDefaultSettings'] = $this->GetAsHash($this->oDefaultSettings);
  160. }
  161. $sJSOptions = json_encode($aOptions);
  162. $oPage->add_ready_script("$('#datatable_{$this->iListId}').datatable($sJSOptions);");
  163. return $sHtml;
  164. }
  165. /**
  166. * When refreshing the body of a paginated table, get the rows of the table (inside the TBODY)
  167. * return string The HTML rows to insert inside the <tbody> node
  168. */
  169. public function GetAsHTMLTableRows(WebPage $oPage, $iPageSize, $aColumns, $sSelectMode, $bViewLink, $aExtraParams)
  170. {
  171. $aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
  172. $aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
  173. $sHtml = '';
  174. foreach($aValues as $aRow)
  175. {
  176. $sHtml .= $oPage->GetTableRow($aRow, $aAttribs);
  177. }
  178. return $sHtml;
  179. }
  180. protected function GetObjectCount(WebPage $oPage, $sSelectMode)
  181. {
  182. if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
  183. {
  184. $sHtml = '<div class="pagination_objcount">'.Dict::Format('UI:Pagination:HeaderSelection', '<span id="total">'.$this->iNbObjects.'</span>', '<span class="selectedCount">0</span>').'</div>';
  185. }
  186. else
  187. {
  188. $sHtml = '<div class="pagination_objcount">'.Dict::Format('UI:Pagination:HeaderNoSelection', '<span id="total">'.$this->iNbObjects.'</span>').'</div>';
  189. }
  190. return $sHtml;
  191. }
  192. protected function GetPager(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex)
  193. {
  194. $sHtml = '';
  195. if ($iPageSize < 1) // Display all
  196. {
  197. $sPagerStyle = 'style="display:none"'; // no limit: display the full table, so hide the "pager" UI
  198. // WARNING: mPDF does not take the "display" style into account
  199. // when applied to a <td> or a <table> tag, so make sure you apply this to a div
  200. }
  201. else
  202. {
  203. $sPagerStyle = '';
  204. }
  205. $sCombo = '<select class="pagesize">';
  206. for($iPage = 1; $iPage < 5; $iPage++)
  207. {
  208. $iNbItems = $iPage * $iDefaultPageSize;
  209. $sSelected = ($iNbItems == $iPageSize) ? 'selected="selected"' : '';
  210. $sCombo .= "<option $sSelected value=\"$iNbItems\">$iNbItems</option>";
  211. }
  212. $sSelected = ($iPageSize < 1) ? 'selected="selected"' : '';
  213. $sCombo .= "<option $sSelected value=\"-1\">".Dict::S('UI:Pagination:All')."</option>";
  214. $sCombo .= '</select>';
  215. $sPages = Dict::S('UI:Pagination:PagesLabel');
  216. $sPageSizeCombo = Dict::Format('UI:Pagination:PageSize', $sCombo);
  217. $iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
  218. if ($iNbPages == 1)
  219. {
  220. // No need to display the pager
  221. $sPagerStyle = 'style="display:none"';
  222. }
  223. $aPagesToDisplay = array();
  224. for($idx = 0; $idx <= min(4, $iNbPages-1); $idx++)
  225. {
  226. if ($idx == 0)
  227. {
  228. $aPagesToDisplay[$idx] = '<span page="0" class="curr_page">1</span>';
  229. }
  230. else
  231. {
  232. $aPagesToDisplay[$idx] = "<span id=\"gotopage_$idx\" class=\"gotopage\" page=\"$idx\">".(1+$idx)."</span>";
  233. }
  234. }
  235. $iLastPageIdx = $iNbPages - 1;
  236. if (!isset($aPagesToDisplay[$iLastPageIdx]))
  237. {
  238. unset($aPagesToDisplay[$idx - 1]); // remove the last page added to make room for the very last page
  239. $aPagesToDisplay[$iLastPageIdx] = "<span id=\"gotopage_$iLastPageIdx\" class=\"gotopage\" page=\"$iLastPageIdx\">... $iNbPages</span>";
  240. }
  241. $sPagesLinks = implode('', $aPagesToDisplay);
  242. $sPagesList = '['.implode(',', array_keys($aPagesToDisplay)).']';
  243. $sSelectionMode = ($iNbPages == 1) ? '' : 'positive';
  244. $sHtml =
  245. <<<EOF
  246. <td colspan="2">
  247. <div $sPagerStyle>
  248. <table id="pager{$this->iListId}" class="pager"><tr>
  249. <td>$sPages</td>
  250. <td><img src="../images/first.png" class="first"/></td>
  251. <td><img src="../images/prev.png" class="prev"/></td>
  252. <td><span id="index">$sPagesLinks</span></td>
  253. <td><img src="../images/next.png" class="next"/></td>
  254. <td><img src="../images/last.png" class="last"/></td>
  255. <td>$sPageSizeCombo</td>
  256. <td><span id="loading">&nbsp;</span><input type="hidden" name="selectionMode" value="$sSelectionMode"></input>
  257. </td>
  258. </tr>
  259. </table>
  260. </div>
  261. </td>
  262. EOF;
  263. return $sHtml;
  264. }
  265. protected function GetActionsMenu(WebPage $oPage, $aExtraParams)
  266. {
  267. $oMenuBlock = new MenuBlock($this->oSet->GetFilter(), 'list');
  268. $sHtml = $oMenuBlock->GetRenderContent($oPage, $aExtraParams, $this->iListId);
  269. return $sHtml;
  270. }
  271. protected function GetToolkitMenu(WebPage $oPage, $aExtraParams)
  272. {
  273. if (!$oPage->IsPrintableVersion())
  274. {
  275. $sMenuTitle = Dict::S('UI:ConfigureThisList');
  276. $sHtml = '<div class="itop_popup toolkit_menu" id="tk_'.$this->iListId.'"><ul><li><img src="../images/toolkit_menu.png?itopversion='.ITOP_VERSION.'"><ul>';
  277. $oMenuItem1 = new JSPopupMenuItem('iTop::ConfigureList', $sMenuTitle, "$('#datatable_dlg_".$this->iListId."').dialog('open');");
  278. $aActions = array(
  279. $oMenuItem1->GetUID() => $oMenuItem1->GetMenuItem(),
  280. );
  281. $this->oSet->Rewind();
  282. utils::GetPopupMenuItems($oPage, iPopupMenuExtension::MENU_OBJLIST_TOOLKIT, $this->oSet, $aActions, $this->sTableId, $this->iListId);
  283. $this->oSet->Rewind();
  284. $sHtml .= $oPage->RenderPopupMenuItems($aActions);
  285. }
  286. else
  287. {
  288. $sHtml = '';
  289. }
  290. return $sHtml;
  291. }
  292. protected function GetTableConfigDlg(WebPage $oPage, $aColumns, $bViewLink, $iDefaultPageSize)
  293. {
  294. $sHtml = "<div id=\"datatable_dlg_{$this->iListId}\" style=\"display: none;\">";
  295. $sHtml .= "<form onsubmit=\"return false\">";
  296. $sChecked = ($this->bUseCustomSettings) ? '' : 'checked';
  297. $sHtml .= "<p><input id=\"dtbl_dlg_settings_{$this->iListId}\" type=\"radio\" name=\"settings\" $sChecked value=\"defaults\"><label for=\"dtbl_dlg_settings_{$this->iListId}\">&nbsp;".Dict::S('UI:UseDefaultSettings').'</label></p>';
  298. $sHtml .= "<fieldset>";
  299. $sChecked = ($this->bUseCustomSettings) ? 'checked': '';
  300. $sHtml .= "<legend class=\"transparent\"><input id=\"dtbl_dlg_specific_{$this->iListId}\" type=\"radio\" class=\"specific_settings\" name=\"settings\" $sChecked value=\"specific\"><label for=\"dtbl_dlg_specific_{$this->iListId}\">&nbsp;".Dict::S('UI:UseSpecificSettings')."</label></legend>";
  301. $sHtml .= Dict::S('UI:ColumnsAndSortOrder').'<br/><ul class="sortable_field_list" id="sfl_'.$this->iListId.'"></ul>';
  302. $sHtml .= '<p>'.Dict::Format('UI:Display_X_ItemsPerPage', '<input type="text" size="4" name="page_size" value="'.$iDefaultPageSize.'">').'</p>';
  303. $sHtml .= "</fieldset>";
  304. $sHtml .= "<fieldset>";
  305. $sSaveChecked = ($this->sTableId != null) ? 'checked' : '';
  306. $sCustomDisabled = ($this->sTableId == null) ? 'disabled="disabled" stay-disabled="true" ' : '';
  307. $sCustomChecked = ($this->sTableId != null) ? 'checked' : '';
  308. $sGenericChecked = ($this->sTableId == null) ? 'checked' : '';
  309. $sHtml .= "<legend class=\"transparent\"><input id=\"dtbl_dlg_save_{$this->iListId}\" type=\"checkbox\" $sSaveChecked name=\"save_settings\"><label for=\"dtbl_dlg_save_{$this->iListId}\">&nbsp;".Dict::S('UI:UseSavetheSettings')."</label></legend>";
  310. $sHtml .= "<p><input id=\"dtbl_dlg_this_list_{$this->iListId}\" type=\"radio\" name=\"scope\" $sCustomChecked $sCustomDisabled value=\"this_list\"><label for=\"dtbl_dlg_this_list_{$this->iListId}\">&nbsp;".Dict::S('UI:OnlyForThisList').'</label>&nbsp;&nbsp;&nbsp;&nbsp;';
  311. $sHtml .= "<input id=\"dtbl_dlg_all_{$this->iListId}\" type=\"radio\" name=\"scope\" $sGenericChecked value=\"defaults\"><label for=\"dtbl_dlg_all_{$this->iListId}\">&nbsp;".Dict::S('UI:ForAllLists').'</label></p>';
  312. $sHtml .= "</fieldset>";
  313. $sHtml .= '<table style="width:100%"><tr><td style="text-align:center;">';
  314. $sHtml .= '<button type="button" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgCancel\'); $(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\')">'.Dict::S('UI:Button:Cancel').'</button>';
  315. $sHtml .= '</td><td style="text-align:center;">';
  316. $sHtml .= '<button type="submit" onclick="$(\'#datatable_'.$this->iListId.'\').datatable(\'onDlgOk\');$(\'#datatable_dlg_'.$this->iListId.'\').dialog(\'close\');">'.Dict::S('UI:Button:Ok').'</button>';
  317. $sHtml .= '</td></tr></table>';
  318. $sHtml .= "</form>";
  319. $sHtml .= "</div>";
  320. $sDlgTitle = addslashes(Dict::S('UI:ListConfigurationTitle'));
  321. $oPage->add_ready_script("$('#datatable_dlg_{$this->iListId}').dialog({autoOpen: false, title: '$sDlgTitle', width: 500, close: function() { $('#datatable_{$this->iListId}').datatable('onDlgCancel'); } });");
  322. return $sHtml;
  323. }
  324. public function GetAsHash($oSetting)
  325. {
  326. $aSettings = array('iDefaultPageSize' => $oSetting->iDefaultPageSize, 'oColumns' => $oSetting->aColumns);
  327. return $aSettings;
  328. }
  329. protected function GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink)
  330. {
  331. $aAttribs = array();
  332. if ($sSelectMode == 'multiple')
  333. {
  334. $aAttribs['form::select'] = array('label' => "<input type=\"checkbox\" onClick=\"CheckAll('.selectList{$this->iListId}:not(:disabled)', this.checked);\" class=\"checkAll\"></input>", 'description' => Dict::S('UI:SelectAllToggle+'));
  335. }
  336. else if ($sSelectMode == 'single')
  337. {
  338. $aAttribs['form::select'] = array('label' => "", 'description' => '');
  339. }
  340. foreach($this->aClassAliases as $sAlias => $sClassName)
  341. {
  342. foreach($aColumns[$sAlias] as $sAttCode => $aData)
  343. {
  344. if ($aData['checked'])
  345. {
  346. if ($sAttCode == '_key_')
  347. {
  348. $aAttribs['key_'.$sAlias] = array('label' => MetaModel::GetName($sClassName), 'description' => '');
  349. }
  350. else
  351. {
  352. $oAttDef = MetaModel::GetAttributeDef($sClassName, $sAttCode);
  353. $aAttribs[$sAttCode.'_'.$sAlias] = array('label' => MetaModel::GetLabel($sClassName, $sAttCode), 'description' => $oAttDef->GetOrderByHint());
  354. }
  355. }
  356. }
  357. }
  358. return $aAttribs;
  359. }
  360. protected function GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
  361. {
  362. $bLocalize = true;
  363. if (isset($aExtraParams['localize_values']))
  364. {
  365. $bLocalize = (bool) $aExtraParams['localize_values'];
  366. }
  367. $aValues = array();
  368. $this->oSet->Seek(0);
  369. $iMaxObjects = $iPageSize;
  370. while (($aObjects = $this->oSet->FetchAssoc()) && ($iMaxObjects != 0))
  371. {
  372. $bFirstObject = true;
  373. $aRow = array();
  374. foreach($this->aClassAliases as $sAlias => $sClassName)
  375. {
  376. if (is_object($aObjects[$sAlias]))
  377. {
  378. $sHilightClass = $aObjects[$sAlias]->GetHilightClass();
  379. if ($sHilightClass != '')
  380. {
  381. $aRow['@class'] = $sHilightClass;
  382. }
  383. if ((($sSelectMode == 'single') || ($sSelectMode == 'multiple')) && $bFirstObject)
  384. {
  385. if (array_key_exists('selection_enabled', $aExtraParams) && isset($aExtraParams['selection_enabled'][$aObjects[$sAlias]->GetKey()]))
  386. {
  387. $sDisabled = ($aExtraParams['selection_enabled'][$aObjects[$sAlias]->GetKey()]) ? '' : ' disabled="disabled"';
  388. }
  389. else
  390. {
  391. $sDisabled = '';
  392. }
  393. if ($sSelectMode == 'single')
  394. {
  395. $aRow['form::select'] = "<input type=\"radio\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
  396. }
  397. else
  398. {
  399. $aRow['form::select'] = "<input type=\"checkBox\" $sDisabled class=\"selectList{$this->iListId}\" name=\"selectObject[]\" value=\"".$aObjects[$sAlias]->GetKey()."\"></input>";
  400. }
  401. }
  402. foreach($aColumns[$sAlias] as $sAttCode => $aData)
  403. {
  404. if ($aData['checked'])
  405. {
  406. if ($sAttCode == '_key_')
  407. {
  408. $aRow['key_'.$sAlias] = $aObjects[$sAlias]->GetHyperLink();
  409. }
  410. else
  411. {
  412. $aRow[$sAttCode.'_'.$sAlias] = $aObjects[$sAlias]->GetAsHTML($sAttCode, $bLocalize);
  413. }
  414. }
  415. }
  416. }
  417. else
  418. {
  419. foreach($aColumns[$sAlias] as $sAttCode => $aData)
  420. {
  421. if ($aData['checked'])
  422. {
  423. if ($sAttCode == '_key_')
  424. {
  425. $aRow['key_'.$sAlias] = '';
  426. }
  427. else
  428. {
  429. $aRow[$sAttCode.'_'.$sAlias] = '';
  430. }
  431. }
  432. }
  433. }
  434. $bFirstObject = false;
  435. }
  436. $aValues[] = $aRow;
  437. $iMaxObjects--;
  438. }
  439. return $aValues;
  440. }
  441. public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
  442. {
  443. $iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
  444. if ($iPageSize < 1)
  445. {
  446. $iPageSize = -1; // convention: no pagination
  447. }
  448. $aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
  449. $aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
  450. $sHtml = '<table class="listContainer">';
  451. foreach($this->oSet->GetFilter()->GetInternalParams() as $sName => $sValue)
  452. {
  453. $aExtraParams['query_params'][$sName] = $sValue;
  454. }
  455. $aExtraParams['show_obsolete_data'] = $this->bShowObsoleteData;
  456. $sHtml .= "<tr><td>";
  457. $sHtml .= $oPage->GetTable($aAttribs, $aValues);
  458. $sHtml .= '</td></tr>';
  459. $sHtml .= '</table>';
  460. $iCount = $this->iNbObjects;
  461. $aArgs = $this->oSet->GetArgs();
  462. $sExtraParams = addslashes(str_replace('"', "'", json_encode(array_merge($aExtraParams, $aArgs)))); // JSON encode, change the style of the quotes and escape them
  463. $sSelectModeJS = '';
  464. $sHeaders = '';
  465. if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
  466. {
  467. $sSelectModeJS = $sSelectMode;
  468. $sHeaders = 'headers: { 0: {sorter: false}},';
  469. }
  470. $sDisplayKey = ($bViewLink) ? 'true' : 'false';
  471. // Protect against duplicate elements in the Zlist
  472. $aUniqueOrderedList = array();
  473. foreach($this->aClassAliases as $sAlias => $sClassName)
  474. {
  475. foreach($aColumns[$sAlias] as $sAttCode => $aData)
  476. {
  477. if ($aData['checked'])
  478. {
  479. $aUniqueOrderedList[$sAttCode] = true;
  480. }
  481. }
  482. }
  483. $aUniqueOrderedList = array_keys($aUniqueOrderedList);
  484. $sJSColumns = json_encode($aColumns);
  485. $sJSClassAliases = json_encode($this->aClassAliases);
  486. $sCssCount = isset($aExtraParams['cssCount']) ? ", cssCount: '{$aExtraParams['cssCount']}'" : '';
  487. $this->oSet->ApplyParameters();
  488. // Display the actual sort order of the table
  489. $aRealSortOrder = $this->oSet->GetRealSortOrder();
  490. $aDefaultSort = array();
  491. $iColOffset = 0;
  492. if (($sSelectMode == 'single') || ($sSelectMode == 'multiple'))
  493. {
  494. $iColOffset += 1;
  495. }
  496. if ($bViewLink)
  497. {
  498. // $iColOffset += 1;
  499. }
  500. foreach($aRealSortOrder as $sColCode => $bAscending)
  501. {
  502. $iPos = array_search($sColCode, $aUniqueOrderedList);
  503. if ($iPos !== false)
  504. {
  505. $aDefaultSort[] = "[".($iColOffset+$iPos).",".($bAscending ? '0' : '1')."]";
  506. }
  507. else if (($iPos = array_search(preg_replace('/_friendlyname$/', '', $sColCode), $aUniqueOrderedList)) !== false)
  508. {
  509. // if sorted on the friendly name of an external key, then consider it sorted on the column that shows the links
  510. $aDefaultSort[] = "[".($iColOffset+$iPos).",".($bAscending ? '0' : '1')."]";
  511. }
  512. else if($sColCode == 'friendlyname' && $bViewLink)
  513. {
  514. $aDefaultSort[] = "[".($iColOffset).",".($bAscending ? '0' : '1')."]";
  515. }
  516. }
  517. $sFakeSortList = '';
  518. if (count($aDefaultSort) > 0)
  519. {
  520. $sFakeSortList = '['.implode(',', $aDefaultSort).']';
  521. }
  522. $sOQL = addslashes($this->oSet->GetFilter()->serialize());
  523. $oPage->add_ready_script(
  524. <<<EOF
  525. var oTable = $('#{$this->iListId} table.listResults');
  526. oTable.tableHover();
  527. oTable.tablesorter( { $sHeaders widgets: ['myZebra', 'truncatedList']} ).tablesorterPager({container: $('#pager{$this->iListId}'), totalRows:$iCount, size: $iPageSize, filter: '$sOQL', extra_params: '$sExtraParams', select_mode: '$sSelectModeJS', displayKey: $sDisplayKey, columns: $sJSColumns, class_aliases: $sJSClassAliases $sCssCount});
  528. EOF
  529. );
  530. if ($sFakeSortList != '')
  531. {
  532. $oPage->add_ready_script("oTable.trigger(\"fakesorton\", [$sFakeSortList]);");
  533. }
  534. //if ($iNbPages == 1)
  535. if (false)
  536. {
  537. if (isset($aExtraParams['cssCount']))
  538. {
  539. $sCssCount = $aExtraParams['cssCount'];
  540. if ($sSelectMode == 'single')
  541. {
  542. $sSelectSelector = ":radio[name^=selectObj]";
  543. }
  544. else if ($sSelectMode == 'multiple')
  545. {
  546. $sSelectSelector = ":checkbox[name^=selectObj]";
  547. }
  548. $oPage->add_ready_script(
  549. <<<EOF
  550. $('#{$this->iListId} table.listResults $sSelectSelector').change(function() {
  551. var c = $('{$sCssCount}');
  552. var v = $('#{$this->iListId} table.listResults $sSelectSelector:checked').length;
  553. c.val(v);
  554. $('#{$this->iListId} .selectedCount').text(v);
  555. c.trigger('change');
  556. });
  557. EOF
  558. );
  559. }
  560. }
  561. return $sHtml;
  562. }
  563. public function UpdatePager(WebPage $oPage, $iDefaultPageSize, $iStart)
  564. {
  565. $iPageSize = ($iDefaultPageSize < 1) ? 1 : $iDefaultPageSize;
  566. $iPageIndex = 1 + floor($iStart / $iPageSize);
  567. $sHtml = $this->GetPager($oPage, $iPageSize, $iDefaultPageSize, $iPageIndex);
  568. $oPage->add_ready_script("$('#pager{$this->iListId}').html('".str_replace("\n", ' ', addslashes($sHtml))."');");
  569. if ($iDefaultPageSize < 1)
  570. {
  571. $oPage->add_ready_script("$('#pager{$this->iListId}').parent().hide()");
  572. }
  573. else
  574. {
  575. $oPage->add_ready_script("$('#pager{$this->iListId}').parent().show()");
  576. }
  577. }
  578. }
  579. /**
  580. * Simplified version of the data table with less "decoration" (and no paging)
  581. * which is optimized for printing
  582. */
  583. class PrintableDataTable extends DataTable
  584. {
  585. public function GetAsHTML(WebPage $oPage, $iPageSize, $iDefaultPageSize, $iPageIndex, $aColumns, $bActionsMenu, $bToolkitMenu, $sSelectMode, $bViewLink, $aExtraParams)
  586. {
  587. return $this->GetHTMLTable($oPage, $aColumns, $sSelectMode, -1, $bViewLink, $aExtraParams);
  588. }
  589. public function GetHTMLTable(WebPage $oPage, $aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams)
  590. {
  591. $iNbPages = ($iPageSize < 1) ? 1 : ceil($this->iNbObjects / $iPageSize);
  592. if ($iPageSize < 1)
  593. {
  594. $iPageSize = -1; // convention: no pagination
  595. }
  596. $aAttribs = $this->GetHTMLTableConfig($aColumns, $sSelectMode, $bViewLink);
  597. $aValues = $this->GetHTMLTableValues($aColumns, $sSelectMode, $iPageSize, $bViewLink, $aExtraParams);
  598. $sHtml = $oPage->GetTable($aAttribs, $aValues);
  599. return $sHtml;
  600. }
  601. }
  602. class DataTableSettings implements Serializable
  603. {
  604. public $aClassAliases;
  605. public $sTableId;
  606. public $iDefaultPageSize;
  607. public $aColumns;
  608. public function __construct($aClassAliases, $sTableId = null)
  609. {
  610. $this->aClassAliases = $aClassAliases;
  611. $this->sTableId = $sTableId;
  612. $this->iDefaultPageSize = 10;
  613. $this->aColumns = array();
  614. }
  615. protected function Init($iDefaultPageSize, $aSortOrder, $aColumns)
  616. {
  617. $this->iDefaultPageSize = $iDefaultPageSize;
  618. $this->aColumns = $aColumns;
  619. $this->FixVisibleColumns();
  620. }
  621. public function serialize()
  622. {
  623. // Save only the 'visible' columns
  624. $aColumns = array();
  625. foreach($this->aClassAliases as $sAlias => $sClass)
  626. {
  627. $aColumns[$sAlias] = array();
  628. foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
  629. {
  630. unset($aData['label']); // Don't save the display name
  631. unset($aData['alias']); // Don't save the alias (redundant)
  632. unset($aData['code']); // Don't save the code (redundant)
  633. if ($aData['checked'])
  634. {
  635. $aColumns[$sAlias][$sAttCode] = $aData;
  636. }
  637. }
  638. }
  639. return serialize(
  640. array(
  641. 'iDefaultPageSize' => $this->iDefaultPageSize,
  642. 'aColumns' => $aColumns,
  643. )
  644. );
  645. }
  646. public function unserialize($sData)
  647. {
  648. $aData = unserialize($sData);
  649. $this->iDefaultPageSize = $aData['iDefaultPageSize'];
  650. $this->aColumns = $aData['aColumns'];
  651. foreach($this->aClassAliases as $sAlias => $sClass)
  652. {
  653. foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
  654. {
  655. $aFieldData = false;
  656. if ($sAttCode == '_key_')
  657. {
  658. $aFieldData = $this->GetFieldData($sAlias, $sAttCode, null, true /* bChecked */, $aData['sort']);
  659. }
  660. else if (MetaModel::isValidAttCode($sClass, $sAttCode))
  661. {
  662. $oAttDef = MetaModel::GetAttributeDef($sClass, $sAttCode);
  663. $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $aData['sort']);
  664. }
  665. if ($aFieldData)
  666. {
  667. $this->aColumns[$sAlias][$sAttCode] = $aFieldData;
  668. }
  669. else
  670. {
  671. unset($this->aColumns[$sAlias][$sAttCode]);
  672. }
  673. }
  674. }
  675. $this->FixVisibleColumns();
  676. }
  677. static public function GetDataModelSettings($aClassAliases, $bViewLink, $aDefaultLists)
  678. {
  679. $oSettings = new DataTableSettings($aClassAliases);
  680. // Retrieve the class specific settings for each class/alias based on the 'list' ZList
  681. //TODO let the caller pass some other default settings (another Zlist, extre fields...)
  682. $aColumns = array();
  683. foreach($aClassAliases as $sAlias => $sClass)
  684. {
  685. if ($aDefaultLists == null)
  686. {
  687. $aList = cmdbAbstract::FlattenZList(MetaModel::GetZListItems($sClass, 'list'));
  688. }
  689. else
  690. {
  691. $aList = $aDefaultLists[$sAlias];
  692. }
  693. $aSortOrder = MetaModel::GetOrderByDefault($sClass);
  694. if ($bViewLink)
  695. {
  696. $sSort = 'none';
  697. if(array_key_exists('friendlyname', $aSortOrder))
  698. {
  699. $sSort = $aSortOrder['friendlyname'] ? 'asc' : 'desc';
  700. }
  701. $sNormalizedFName = MetaModel::NormalizeFieldSpec($sClass, 'friendlyname');
  702. if(array_key_exists($sNormalizedFName, $aSortOrder))
  703. {
  704. $sSort = $aSortOrder[$sNormalizedFName] ? 'asc' : 'desc';
  705. }
  706. $aColumns[$sAlias]['_key_'] = $oSettings->GetFieldData($sAlias, '_key_', null, true /* bChecked */, $sSort);
  707. }
  708. foreach($aList as $sAttCode)
  709. {
  710. $sSort = 'none';
  711. if(array_key_exists($sAttCode, $aSortOrder))
  712. {
  713. $sSort = $aSortOrder[$sAttCode] ? 'asc' : 'desc';
  714. }
  715. $oAttDef = Metamodel::GetAttributeDef($sClass, $sAttCode);
  716. $aFieldData = $oSettings->GetFieldData($sAlias, $sAttCode, $oAttDef, true /* bChecked */, $sSort);
  717. if ($aFieldData) $aColumns[$sAlias][$sAttCode] = $aFieldData;
  718. }
  719. }
  720. $iDefaultPageSize = appUserPreferences::GetPref('default_page_size', MetaModel::GetConfig()->GetMinDisplayLimit());
  721. $oSettings->Init($iDefaultPageSize, $aSortOrder, $aColumns);
  722. return $oSettings;
  723. }
  724. protected function FixVisibleColumns()
  725. {
  726. foreach($this->aClassAliases as $sAlias => $sClass)
  727. {
  728. foreach($this->aColumns[$sAlias] as $sAttCode => $aData)
  729. {
  730. // Remove non-existent columns
  731. // TODO: check if the existing ones are still valid (in case their type changed)
  732. if (($sAttCode != '_key_') && (!MetaModel::IsValidAttCode($sClass, $sAttCode)))
  733. {
  734. unset($this->aColumns[$sAlias][$sAttCode]);
  735. }
  736. }
  737. $aList = MetaModel::ListAttributeDefs($sClass);
  738. // Add the other (non visible ones), sorted in alphabetical order
  739. $aTempData = array();
  740. foreach($aList as $sAttCode => $oAttDef)
  741. {
  742. if ( (!array_key_exists($sAttCode, $this->aColumns[$sAlias])) && (!$oAttDef instanceof AttributeLinkSet))
  743. {
  744. $aFieldData = $this->GetFieldData($sAlias, $sAttCode, $oAttDef, false /* bChecked */, 'none');
  745. if ($aFieldData) $aTempData[$aFieldData['label']] = $aFieldData;
  746. }
  747. }
  748. ksort($aTempData);
  749. foreach($aTempData as $sLabel => $aFieldData)
  750. {
  751. $this->aColumns[$sAlias][$aFieldData['code']] = $aFieldData;
  752. }
  753. }
  754. }
  755. static public function GetTableSettings($aClassAliases, $sTableId = null, $bOnlyOnTable = false)
  756. {
  757. $pref = null;
  758. $oSettings = new DataTableSettings($aClassAliases, $sTableId);
  759. if ($sTableId != null)
  760. {
  761. // An identified table, let's fetch its own settings (if any)
  762. $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey($sTableId), null);
  763. }
  764. if ($pref == null)
  765. {
  766. if (!$bOnlyOnTable)
  767. {
  768. // Try the global preferred values for this class / set of classes
  769. $pref = appUserPreferences::GetPref($oSettings->GetPrefsKey(null), null);
  770. }
  771. if ($pref == null)
  772. {
  773. // no such settings, use the default values provided by the data model
  774. return null;
  775. }
  776. }
  777. $oSettings->unserialize($pref);
  778. return $oSettings;
  779. }
  780. public function GetSortOrder()
  781. {
  782. $aSortOrder = array();
  783. foreach($this->aColumns as $sAlias => $aColumns)
  784. {
  785. foreach($aColumns as $aColumn)
  786. {
  787. if ($aColumn['sort'] != 'none')
  788. {
  789. $sCode = ($aColumn['code'] == '_key_') ? 'friendlyname' : $aColumn['code'];
  790. $aSortOrder[$sCode] = ($aColumn['sort']=='asc'); // true for ascending, false for descending
  791. }
  792. }
  793. break; // TODO: For now the Set object supports only sorting on the first class of the set
  794. }
  795. return $aSortOrder;
  796. }
  797. public function Save($sTargetTableId = null)
  798. {
  799. $sSaveId = is_null($sTargetTableId) ? $this->sTableId : $sTargetTableId;
  800. if ($sSaveId == null) return false; // Cannot save, the table is not identified, use SaveAsDefault instead
  801. $sSettings = $this->serialize();
  802. appUserPreferences::SetPref($this->GetPrefsKey($sSaveId), $sSettings);
  803. return true;
  804. }
  805. public function SaveAsDefault()
  806. {
  807. $sSettings = $this->serialize();
  808. appUserPreferences::SetPref($this->GetPrefsKey(null), $sSettings);
  809. return true;
  810. }
  811. /**
  812. * Clear the preferences for this particular table
  813. * @param $bResetAll boolean If true,the settings for all tables of the same class(es)/alias(es) are reset
  814. */
  815. public function ResetToDefault($bResetAll)
  816. {
  817. if (($this->sTableId == null) && (!$bResetAll)) return false; // Cannot reset, the table is not identified, use force $bResetAll instead
  818. if ($bResetAll)
  819. {
  820. // Turn the key into a suitable PCRE pattern
  821. $sKey = $this->GetPrefsKey(null);
  822. $sPattern = str_replace(array('|'), array('\\|'), $sKey); // escape the | character
  823. $sPattern = '#^'.str_replace(array('*'), array('.*'), $sPattern).'$#'; // Don't use slash as the delimiter since it's used in our key to delimit aliases
  824. appUserPreferences::UnsetPref($sPattern, true);
  825. }
  826. else
  827. {
  828. appUserPreferences::UnsetPref($this->GetPrefsKey($this->sTableId), false);
  829. }
  830. return true;
  831. }
  832. protected function GetPrefsKey($sTableId = null)
  833. {
  834. if ($sTableId == null) $sTableId = '*';
  835. $aKeys = array();
  836. foreach($this->aClassAliases as $sAlias => $sClass)
  837. {
  838. $aKeys[] = $sAlias.'-'.$sClass;
  839. }
  840. return implode('/', $aKeys).'|'.$sTableId;
  841. }
  842. protected function GetFieldData($sAlias, $sAttCode, $oAttDef, $bChecked, $sSort)
  843. {
  844. $ret = false;
  845. if ($sAttCode == '_key_')
  846. {
  847. $sLabel = Dict::Format('UI:ExtKey_AsLink', MetaModel::GetName($this->aClassAliases[$sAlias]));
  848. $ret = array(
  849. 'label' => $sLabel,
  850. 'checked' => true,
  851. 'disabled' => true,
  852. 'alias' => $sAlias,
  853. 'code' => $sAttCode,
  854. 'sort' => $sSort,
  855. );
  856. }
  857. else if (!$oAttDef->IsLinkSet())
  858. {
  859. $sLabel = $oAttDef->GetLabel();
  860. if ($oAttDef->IsExternalKey())
  861. {
  862. $sLabel = Dict::Format('UI:ExtKey_AsLink', $oAttDef->GetLabel());
  863. }
  864. else if ($oAttDef->IsExternalField())
  865. {
  866. if ($oAttDef->IsFriendlyName())
  867. {
  868. $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
  869. }
  870. else
  871. {
  872. $oExtAttDef = $oAttDef->GetExtAttDef();
  873. $sLabel = Dict::Format('UI:ExtField_AsRemoteField', $oAttDef->GetLabel(), $oExtAttDef->GetLabel());
  874. }
  875. }
  876. elseif ($oAttDef instanceof AttributeFriendlyName)
  877. {
  878. $sLabel = Dict::Format('UI:ExtKey_AsFriendlyName', $oAttDef->GetLabel());
  879. }
  880. $ret = array(
  881. 'label' => $sLabel,
  882. 'checked' => $bChecked,
  883. 'disabled' => false,
  884. 'alias' => $sAlias,
  885. 'code' => $sAttCode,
  886. 'sort' => $sSort,
  887. );
  888. }
  889. return $ret;
  890. }
  891. }