BxBaseGrid.php 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039
  1. <?php defined('BX_DOL') or die('hack attempt');
  2. /**
  3. * Copyright (c) UNA, Inc - https://una.io
  4. * MIT License - https://opensource.org/licenses/MIT
  5. *
  6. * @defgroup UnaBaseView UNA Base Representation Classes
  7. * @{
  8. */
  9. /**
  10. * Grid representation.
  11. * @see BxDolGrid
  12. */
  13. class BxBaseGrid extends BxDolGrid
  14. {
  15. protected $_oTemplate;
  16. protected $_oFunctions;
  17. protected $_aPopupOptions = false;
  18. protected $_aQueryAppend = [];
  19. protected $_aQueryAppendExclude = false; // an array of keys which shouldn't be pathed in http requests, but can be stored (used) in 'Query Append' array.
  20. protected $_aQueryAppendExcludeApi = false;
  21. protected $_aQueryReset = false;
  22. protected $_aConfirmMessages = false;
  23. protected $_bSelectAll = false;
  24. protected $_isDisplayPopupOnTextOverflow = true;
  25. public function __construct ($aOptions, $oTemplate)
  26. {
  27. parent::__construct ($aOptions);
  28. if ($oTemplate)
  29. $this->_oTemplate = $oTemplate;
  30. else
  31. $this->_oTemplate = BxDolTemplate::getInstance();
  32. $this->_oFunctions = BxTemplFunctions::getInstanceWithTemplate($this->_oTemplate);
  33. $this->_aPopupOptions = [];
  34. $this->_aQueryAppend = [
  35. $this->_oTemplate->getCodeKey() => $this->_oTemplate->getCode()
  36. ];
  37. $this->_aQueryAppendExclude = [];
  38. $this->_aQueryAppendExcludeApi = [$this->_oTemplate->getCodeKey()];
  39. $this->_aQueryReset = [
  40. $this->_aOptions['filter_get'],
  41. $this->_aOptions['order_get_field'],
  42. $this->_aOptions['order_get_dir'],
  43. $this->_aOptions['paginate_get_start'],
  44. $this->_aOptions['paginate_get_per_page']
  45. ];
  46. $this->_aConfirmMessages = [];
  47. }
  48. public function performActionDisplay()
  49. {
  50. if($this->_bIsApi)
  51. return $this->getCodeAPI();
  52. require_once(BX_DIRECTORY_PATH_INC . "design.inc.php");
  53. echoJson(array(
  54. 'grid' => $this->getCode(false),
  55. 'total_count' => $this->_iTotalCount,
  56. 'total_count_f' => $this->_getCounter()
  57. ));
  58. }
  59. public function performActionReorder()
  60. {
  61. $this->_replaceMarkers ();
  62. $aOrder = bx_get($this->_sObject . '_row');
  63. $iOrder = 0;
  64. foreach ($aOrder as $mixedId)
  65. $this->_updateOrder($mixedId, ++$iOrder);
  66. return $this->_getActionResult([]);
  67. }
  68. public function performActionDelete()
  69. {
  70. $this->_replaceMarkers ();
  71. $aResult = [];
  72. $iAffected = 0;
  73. $aIds = bx_get('ids');
  74. if (!$aIds || !is_array($aIds))
  75. return $this->_getActionResult($aResult);
  76. foreach ($aIds as $mixedId)
  77. $iAffected += $this->_delete($mixedId) ? 1 : 0;
  78. if($iAffected)
  79. $aResult = !$this->_bIsApi ? ['grid' => $this->getCode(false)] : [];
  80. else
  81. $aResult = ['msg' => _t("_sys_grid_delete_failed")];
  82. return $this->_getActionResult($aResult);
  83. }
  84. public function performActionEnable($mixedChecked = null)
  85. {
  86. $this->_replaceMarkers ();
  87. $aIds = bx_get('ids');
  88. if (!$aIds || !is_array($aIds)) {
  89. echoJson(array());
  90. exit;
  91. }
  92. $iChecked = (int)($mixedChecked !== null ? $mixedChecked : bx_get('checked'));
  93. $aAffectedIds = array ();
  94. foreach ($aIds as $mixedId)
  95. if ($this->_enable($mixedId, $iChecked))
  96. $aAffectedIds[] = preg_match("/^[\d\w]+$/", $mixedId) ? $mixedId : (int)$mixedId;
  97. $sAction = $iChecked ? 'enable' : 'disable';
  98. if($this->_bIsApi)
  99. return $aAffectedIds;
  100. echo echoJson(array($sAction => $aAffectedIds));
  101. }
  102. public function getCode ($isDisplayHeader = true)
  103. {
  104. $this->_replaceMarkers ();
  105. if ($isDisplayHeader && empty($this->_aOptions['paginate_url'])) {
  106. // reset page query params if grid is just initialized and it uses AJAX paginate
  107. $this->resetQueryParams();
  108. }
  109. $sPaginate = '';
  110. $aData = array();
  111. $sIdWrapper = 'bx-grid-wrap-' . $this->_sObject;
  112. $sIdContainer = 'bx-grid-cont-' . $this->_sObject;
  113. $sIdTable = 'bx-grid-table-' . $this->_sObject;
  114. $sFilter = $this->_getFilterValue();
  115. $sOrderField = $this->_getOrderValue();
  116. $sOrderDir = 0 === strcasecmp('desc', bx_get($this->_aOptions['order_get_dir'])) ? 'DESC' : 'ASC';
  117. $iStart = 0;
  118. if($this->_aOptions['paginate_get_start'] && ($iStartGet = (int)bx_get($this->_aOptions['paginate_get_start'])) >= 0)
  119. $iStart = $iStartGet;
  120. $iPerPage = 10;
  121. if($this->_aOptions['paginate_get_per_page'] && ($iPerPageGet = (int)bx_get($this->_aOptions['paginate_get_per_page'])) > 0)
  122. $iPerPage = $iPerPageGet;
  123. else if($this->_aOptions['paginate_per_page'])
  124. $iPerPage = (int)$this->_aOptions['paginate_per_page'];
  125. if ($this->_aOptions['paginate_get_start']) {
  126. $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage + 1);
  127. $sPageUrl = false;
  128. if (!empty($this->_aOptions['paginate_url'])) {
  129. $sPageUrl = $this->_aOptions['paginate_url'];
  130. $aParamsAppend = array();
  131. if ($sFilter) {
  132. $aParamsAppend['filter'] = bx_process_input(bx_get($this->_aOptions['filter_get']));
  133. }
  134. if ($sOrderField) {
  135. $aParamsAppend['order_field'] = bx_process_input(bx_get($this->_aOptions['order_get_field']));
  136. $aParamsAppend['order_dir'] = bx_process_input(bx_get($this->_aOptions['order_get_dir']));
  137. }
  138. if ($aParamsAppend)
  139. $sPageUrl = bx_append_url_params($sPageUrl, $aParamsAppend);
  140. }
  141. $aPaginateParams = array(
  142. 'start' => $iStart,
  143. 'per_page' => $iPerPage,
  144. 'page_url' => $sPageUrl ? $sPageUrl : "javascript:glGrids." . $this->_sObject . ".reload('{start}'); void(0);",
  145. );
  146. $oPaginate = new BxTemplPaginate($aPaginateParams, $this->_oTemplate);
  147. $oPaginate->setNumFromDataArray($aData);
  148. if (isset($this->_aOptions['paginate_simple']) && false !== $this->_aOptions['paginate_simple'])
  149. $sPaginate = $oPaginate->getSimplePaginate($this->_aOptions['paginate_simple']);
  150. else
  151. $sPaginate = $oPaginate->getPaginate();
  152. } else {
  153. $aData = $this->_getData ($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
  154. }
  155. if((empty($aData) || !is_array($aData)) && isset($this->_aBrowseParams['empty_message']) && !(bool)$this->_aBrowseParams['empty_message'])
  156. return '';
  157. $sPopupOptions = '{}';
  158. if (!empty($this->_aPopupOptions) && is_array($this->_aPopupOptions))
  159. $sPopupOptions = json_encode($this->_aPopupOptions);
  160. $aQueryAppend = array_merge(is_array($this->_aQueryAppend) ? $this->_aQueryAppend : array(), is_array($this->_aMarkers) ? $this->_aMarkers : array());
  161. if(!empty($this->_aQueryAppendExclude) && is_array($this->_aQueryAppendExclude))
  162. $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExclude));
  163. $sQueryAppend = '{}';
  164. if (!empty($aQueryAppend) && is_array($aQueryAppend))
  165. $sQueryAppend = json_encode($aQueryAppend);
  166. $sConfirmMessages = '{}';
  167. if (!empty($this->_aConfirmMessages) && is_array($this->_aConfirmMessages))
  168. $sConfirmMessages = json_encode($this->_aConfirmMessages);
  169. $iColumns = count($this->_aOptions['fields']);
  170. $aVarsHead = $this->_getRowHead();
  171. $sClassHeader = '';
  172. if($this->_aOptions['show_total_count'] == 0)
  173. $sClassHeader .= ' bx-gh-no-counter';
  174. $aVars = array (
  175. 'object' => $this->_sObject,
  176. 'csrf_token' => BxDolForm::getCsrfToken(),
  177. 'id_table' => $sIdTable,
  178. 'id_cont' => $sIdContainer,
  179. 'id_wrap' => $sIdWrapper,
  180. 'class_table_wrapper' => $this->_aOptions['responsive'] ? 'bx-grid-table-wrapper-responsive' : '',
  181. 'sortable' => empty($this->_aOptions['field_order']) ? 0 : 1,
  182. 'sorting' => empty($this->_getOrderFields()) ? 0 : 1,
  183. 'sorting_field' => $sOrderField,
  184. 'sorting_dir' => $sOrderDir,
  185. 'bx_if:display_head' => array(
  186. 'condition' => !empty($aVarsHead),
  187. 'content' => array(
  188. 'bx_repeat:row_header' => $aVarsHead,
  189. 'columns' => $iColumns,
  190. )
  191. ),
  192. 'bx_repeat:rows_data' => $this->_getRowsDataDesign ($aData),
  193. 'paginate_get_start' => $this->_aOptions['paginate_get_start'],
  194. 'paginate_get_per_page' => $this->_aOptions['paginate_get_per_page'],
  195. 'start' => $iStart,
  196. 'per_page' => $iPerPage,
  197. 'filter' => bx_js_string($sFilter, BX_ESCAPE_STR_APOS),
  198. 'filter_get' => bx_js_string($this->_aOptions['filter_get']),
  199. 'order_field' => bx_js_string($sOrderField, BX_ESCAPE_STR_APOS),
  200. 'order_dir' => bx_js_string($sOrderDir, BX_ESCAPE_STR_APOS),
  201. 'order_get_field' => bx_js_string($this->_aOptions['order_get_field']),
  202. 'order_get_dir' => bx_js_string($this->_aOptions['order_get_dir']),
  203. 'popup_options' => $sPopupOptions,
  204. 'query_append' => $sQueryAppend,
  205. 'confirm_messages' => $sConfirmMessages,
  206. 'columns' => $iColumns,
  207. 'bx_if:display_footer' => array(
  208. 'condition' => !empty($this->_aOptions['actions_bulk']) || !empty($sPaginate),
  209. 'content' => array(
  210. 'bx_if:actions_bulk' => array (
  211. 'condition' => !empty($this->_aOptions['actions_bulk']),
  212. 'content' => array(
  213. 'actions_bulk' => $this->_getActions ('bulk'),
  214. ),
  215. ),
  216. 'paginate' => $sPaginate,
  217. )
  218. ),
  219. 'bx_if:display_header' => array (
  220. 'condition' => $isDisplayHeader,
  221. 'content' => array (
  222. 'class' => $sClassHeader,
  223. 'bx_if:actions_independent' => array (
  224. 'condition' => !empty($this->_aOptions['actions_independent']),
  225. 'content' => array(
  226. 'actions_independent' => $this->_getActions ('independent'),
  227. ),
  228. ),
  229. 'bx_if:filter' => array (
  230. 'condition' => !empty($this->_aOptions['filter_fields']) || !empty($this->_aOptions['filter_fields_translatable']),
  231. 'content' => array(
  232. 'controls' => $this->_getFilterControls(),
  233. ),
  234. ),
  235. 'bx_if:counter' => array (
  236. 'condition' => $this->_aOptions['show_total_count'] == 1,
  237. 'content' => array(
  238. 'counter' => $this->_getCounter(),
  239. ),
  240. ),
  241. ),
  242. ),
  243. );
  244. $this->_addJsCss();
  245. return $this->_oTemplate->parseHtmlByName('grid.html', $aVars);
  246. }
  247. /**
  248. * Get grid code API.
  249. * @return array
  250. */
  251. public function getCodeAPI($bForceReturn = false)
  252. {
  253. $this->_replaceMarkers();
  254. $sFilter = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['filter_get'])));
  255. $sOrderField = bx_unicode_urldecode(bx_process_input(bx_get($this->_aOptions['order_get_field'])));
  256. $sOrderDir = 0 === strcasecmp('desc', bx_get($this->_aOptions['order_get_dir'])) ? 'DESC' : 'ASC';
  257. $iStart = 0;
  258. if($this->_aOptions['paginate_get_start'] && ($iStartGet = (int)bx_get($this->_aOptions['paginate_get_start'])) >= 0)
  259. $iStart = $iStartGet;
  260. $iPerPage = 10;
  261. if($this->_aOptions['paginate_get_per_page'] && ($iPerPageGet = (int)bx_get($this->_aOptions['paginate_get_per_page'])) > 0)
  262. $iPerPage = $iPerPageGet;
  263. else if($this->_aOptions['paginate_per_page'])
  264. $iPerPage = (int)$this->_aOptions['paginate_per_page'];
  265. $aData = $this->_getData($sFilter, $sOrderField, $sOrderDir, $iStart, $iPerPage);
  266. if(!empty($aData) && is_array($aData))
  267. $aData = $this->decodeDataAPI($aData);
  268. $aQueryAppend = [];
  269. if(!empty($this->_aQueryAppend) && is_array($this->_aQueryAppend))
  270. $aQueryAppend = array_merge($aQueryAppend, $this->_aQueryAppend);
  271. if(!empty($this->_aMarkers) && is_array($this->_aMarkers))
  272. $aQueryAppend = array_merge($aQueryAppend, $this->_aMarkers);
  273. if(!empty($this->_aQueryAppendExclude) && is_array($this->_aQueryAppendExclude))
  274. $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExclude));
  275. if(!empty($this->_aQueryAppendExcludeApi) && is_array($this->_aQueryAppendExcludeApi))
  276. $aQueryAppend = array_diff_key($aQueryAppend, array_flip($this->_aQueryAppendExcludeApi));
  277. return [
  278. 'settings' => [
  279. 'object' => $this->_aOptions['object'],
  280. 'field_id' => $this->_aOptions['field_id'],
  281. 'start' => $iStart,
  282. 'per_page' => $iPerPage,
  283. 'filters' => $this->_getFilterControlsAPI(),
  284. 'query_append' => $aQueryAppend
  285. ],
  286. 'header' => $this->_getRowHeadAPI(),
  287. 'data' => defined('BX_API_PAGE') && !$bForceReturn ? [] : $aData,
  288. 'actions' => [
  289. 'independent' => $this->_getActionsAPI('independent'),
  290. 'bulk' => $this->_getActionsAPI('bulk')
  291. ]
  292. ];
  293. }
  294. public function decodeDataAPI($aData)
  295. {
  296. $sKeyId = $this->_aOptions['field_id'];
  297. $sMethodDefault = '_getCellDefault';
  298. $aDataRv = [];
  299. foreach($aData as $iKey => $aRow) {
  300. $aDataRv[$iKey] = [$sKeyId => $aData[$iKey][$sKeyId]];
  301. foreach($this->_aOptions['fields'] as $sKey => $aField) {
  302. $sMethod = '_getCell' . $this->_genMethodName($sKey);
  303. if(!method_exists($this, $sMethod))
  304. $sMethod = $sMethodDefault;
  305. if(($aCell = $this->$sMethod(isset($aData[$iKey][$sKey]) ? $aData[$iKey][$sKey] : _t('_undefined'), $sKey, $aField, $aRow)))
  306. $aDataRv[$iKey][$sKey] = $aCell;
  307. }
  308. }
  309. return $aDataRv;
  310. }
  311. public function getFormBlockAPI($oForm, $sAction, $iId = 0)
  312. {
  313. return [
  314. bx_api_get_block('form', $oForm->getCodeAPI(), [
  315. 'ext' => [
  316. 'name' => $this->_oModule->getName(),
  317. 'title' => $this->getFormBlockTitleAPI($sAction, $iId),
  318. 'request' => ['url' => $this->getFormCallBackUrlAPI($sAction, $iId), 'immutable' => true]]
  319. ]
  320. )
  321. ];
  322. }
  323. public function getFormBlockTitleAPI($sAction, $iId)
  324. {
  325. return '';
  326. }
  327. public function getFormCallBackUrlAPI($sAction, $iId)
  328. {
  329. return '';
  330. }
  331. /**
  332. * Reset query params, like filter and page number
  333. */
  334. public function resetQueryParams()
  335. {
  336. foreach ($this->_aQueryReset as $sKey) {
  337. unset($_GET[$sKey]);
  338. unset($_POST[$sKey]);
  339. }
  340. }
  341. protected function _getRowHead()
  342. {
  343. $aRet = array();
  344. foreach ($this->_aOptions['fields'] as $sKey => $a) {
  345. $sMethod = '_getCellHeaderDefault';
  346. $sCustomMethod = '_getCellHeader' . $this->_genMethodName($sKey);
  347. if (method_exists($this, $sCustomMethod))
  348. $sMethod = $sCustomMethod;
  349. $aRet[] = array('header_cell' => $this->$sMethod($sKey, $a));
  350. }
  351. return $aRet;
  352. }
  353. protected function _getRowHeadAPI()
  354. {
  355. $bResize = false;
  356. $aHeader = [];
  357. foreach($this->_aOptions['fields'] as $sKey => $aField) {
  358. if($sKey == 'checkbox' && (int)$aField['width'] < 8)
  359. $bResize = true;
  360. $sMethod = '_getCellHeader' . $this->_genMethodName($sKey);
  361. if(($aHeaderItem = $this->{method_exists($this, $sMethod) ? $sMethod : '_getCellHeaderDefault'}($sKey, $aField)))
  362. $aHeader[] = $aHeaderItem;
  363. }
  364. if(($sKey = 'checkbox') && $bResize)
  365. $this->_resizeCellAPI($sKey, $this->_aOptions['fields'][$sKey], $aHeader);
  366. return $aHeader;
  367. }
  368. protected function _resizeCellAPI($sKey, $aField, &$aHeader)
  369. {
  370. $iMin = 8;
  371. $iAdd = $iMin - (int)$aField['width'];
  372. $iThreshold = $iMin + $iAdd;
  373. $iReduce = 0;
  374. array_walk($aHeader, function($aItem) use ($iThreshold, &$iReduce) {
  375. if((int)$aItem['width'] >= $iThreshold)
  376. $iReduce += 1;
  377. });
  378. $fSub = round($iAdd / $iReduce, 2);
  379. array_walk($aHeader, function(&$aItem) use ($sKey, $iThreshold, $iAdd, $fSub) {
  380. if($aItem['name'] == $sKey)
  381. $aItem['width'] = (float)$aItem['width'] + $iAdd;
  382. else if((int)$aItem['width'] >= $iThreshold)
  383. $aItem['width'] = (float)$aItem['width'] - $fSub;
  384. else
  385. return;
  386. $aItem['width'] .= '%';
  387. });
  388. }
  389. protected function _getCellHeaderDefault ($sKey, $aField)
  390. {
  391. if($this->_bIsApi)
  392. return [
  393. 'name' => bx_process_output($aField['name']),
  394. 'title' => bx_process_output($aField['title']),
  395. 'width' => $aField['width']
  396. ];
  397. $sHeader = bx_process_output($aField['title']);
  398. if (($aSortingFields = $this->_getOrderFields()) && in_array($sKey, $aSortingFields)) {
  399. $sHeader = '<a href="javascript:void(0);" class="bx-grid-sort-handle">' . $sHeader . '</a><span class="bx-grid-sort-indi"></span>';
  400. $aField['attr_head']['bx_grid_sort_head'] = $sKey;
  401. }
  402. $sAttr = $this->_convertAttrs(
  403. $aField, 'attr_head',
  404. 'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
  405. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  406. );
  407. return $this->_getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr);
  408. }
  409. protected function _getCellHeaderCheckbox ($sKey, $aField)
  410. {
  411. if($this->_bIsApi)
  412. return $this->_getCellHeaderDefault($sKey, $aField);
  413. $aAttr = array(
  414. 'type' => 'checkbox',
  415. 'id' => $this->_sObject . '_check_all',
  416. 'name' => $this->_sObject . '_check_all',
  417. 'onclick' => "$('input[name=" . $this->_sObject . "_check]:not([disabled])').attr('checked', this.checked)"
  418. );
  419. if($this->_bSelectAll)
  420. $aAttr['checked'] = 'checked';
  421. $aField['attr'] = isset($aField['attr']) && is_array($aField['attr']) ? array_merge($aAttr, $aField['attr']) : $aAttr;
  422. $sAttrHead = $this->_convertAttrs(
  423. $aField, 'attr_head',
  424. 'bx-def-padding-sec-bottom bx-def-padding-sec-top bx-grid-header-' . $sKey, // add default classes
  425. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  426. );
  427. return $this->_getCellHeaderWrapper ($sKey, $aField, ' <input ' . $this->_convertAttrs($aField, 'attr') . ' /> ', $sAttrHead);
  428. }
  429. protected function _getCellHeaderWrapper ($sKey, $aField, $sHeader, $sAttr)
  430. {
  431. return '<th ' . $sAttr . '>' . $sHeader . '</th>';
  432. }
  433. /**
  434. * Check if the whole row is disabled.
  435. * When row is disabled - checkbox is not selectable, actions aren't clickable and text is grayed out.
  436. * By default all rows aren't disabled.
  437. * @param $aRow row array
  438. * @return boolean
  439. */
  440. protected function _isRowDisabled($aRow)
  441. {
  442. if (isset($aRow[$this->_aOptions['field_active']]) && !$this->_switcherState2Checked($aRow[$this->_aOptions['field_active']]))
  443. return true;
  444. return false;
  445. }
  446. /**
  447. * Determine how actions are disabled when whole row is disabled.
  448. * @param $aRow row array
  449. * @return null - disable/enable actions when row is disabled/enabled, true - actions are always disabled, false - actions are always enabled
  450. */
  451. protected function _getActionsDisabledBehavior($aRow)
  452. {
  453. return null;
  454. }
  455. /**
  456. * Check if the checkbox is disabled.
  457. * @param $aRow row array
  458. * @return boolean
  459. */
  460. protected function _isCheckboxDisabled($aRow)
  461. {
  462. return $this->_isRowDisabled($aRow);
  463. }
  464. /**
  465. * Is checkbox checked by default ?
  466. * By default no one checkbox is selected.
  467. * @return boolean
  468. */
  469. protected function _isCheckboxSelected($mixedValue, $sKey, $aField, $aRow)
  470. {
  471. return $this->_bSelectAll;
  472. }
  473. /**
  474. * Is switcher on by default ?
  475. * By default no one switcher is on.
  476. * @return boolean
  477. */
  478. protected function _isSwitcherOn($mixedValue, $sKey, $aField, $aRow)
  479. {
  480. return $this->_switcherState2Checked($mixedValue);
  481. }
  482. /**
  483. * Convert switcher checked status to the actual value ?
  484. * @return boolean
  485. */
  486. protected function _switcherChecked2State($isChecked)
  487. {
  488. return $isChecked ? 1 : 0;
  489. }
  490. /**
  491. * Convert switcher value to checked(boolean) value ?
  492. * @return boolean
  493. */
  494. protected function _switcherState2Checked($mixedState)
  495. {
  496. return $mixedState ? true : false;
  497. }
  498. protected function _getRowsDataDesign (&$aData)
  499. {
  500. $aGrid = array();
  501. if(empty($aData))
  502. $aGrid[] = array(
  503. 'id_row' => 0,
  504. 'row_class' => 'bx-grid-table-row-empty',
  505. 'row' => '<td class="bx-def-padding-sec-topbottom" colspan="' . count($this->_aOptions['fields']) . '">' . MsgBox(_t('_Empty')) . '</td>'
  506. );
  507. else
  508. foreach ($aData as $aRow)
  509. $aGrid[] = array(
  510. 'id_row' => $this->_getRowId($aRow),
  511. 'row_class' => $this->_isRowDisabled($aRow) ? 'bx-grid-table-row-disabled bx-def-font-grayed' : '',
  512. 'row' => $this->_getRowDesign($aRow)
  513. );
  514. return $aGrid;
  515. }
  516. protected function _getRowId($mixedRow)
  517. {
  518. if(is_string($mixedRow))
  519. $sId = rand(1, getrandmax());
  520. else
  521. $sId = $mixedRow[$this->_aOptions['field_id']];
  522. return $this->_sObject . '_row_' . $sId;
  523. }
  524. protected function _getRowDesign($mixedRow)
  525. {
  526. $sRow = '';
  527. if(is_string($mixedRow))
  528. $sRow = '<td class="bx-grid-cell bx-gc-string bx-def-padding-sec-topbottom bx-def-font-bold" colspan="' . count($this->_aOptions['fields']) . '">' . $mixedRow . '</td>';
  529. else
  530. foreach($this->_aOptions['fields'] as $sKey => $aField)
  531. $sRow .= $this->_getCellDesign($sKey, $aField, $mixedRow);
  532. return $sRow;
  533. }
  534. protected function _getCellDesign($sKey, $aField, $aRow)
  535. {
  536. $sMethod = '_getCellDefault';
  537. if ($this->_aOptions['field_order'] == $sKey)
  538. $sMethod = '_getCellOrder';
  539. $sCustomMethod = '_getCell' . $this->_genMethodName($sKey);
  540. if (method_exists($this, $sCustomMethod))
  541. $sMethod = $sCustomMethod;
  542. $mixedValue = $this->_getCellData($sKey, $aField, $aRow);
  543. if ($aField['translatable'])
  544. $mixedValue = _t($mixedValue);
  545. $mixedValue = $this->_limitMaxLength($mixedValue, $sKey, $aField, $aRow, $this->_isDisplayPopupOnTextOverflow);
  546. return $this->$sMethod($mixedValue, $sKey, $aField, $aRow);
  547. }
  548. protected function _getCellCheckbox ($mixedValue, $sKey, $aField, $aRow)
  549. {
  550. if($this->_bIsApi)
  551. return ['type' => 'checkbox', 'data' => $aRow[$this->_aOptions['field_id']]];
  552. $sAttr = $this->_convertAttrs(
  553. $aField, 'attr_cell',
  554. 'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
  555. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  556. );
  557. $sDisabled = ($this->_isCheckboxDisabled($aRow) ? 'disabled="disabled"' : '');
  558. $sSelected = ($this->_isCheckboxSelected($mixedValue, $sKey, $aField, $aRow) ? 'checked="checked"' : '');
  559. $sVal = $aRow[$this->_aOptions['field_id']];
  560. return '<td ' . $sAttr . '><input type="checkbox" name="'. $this->_sObject . '_check" value="' . $sVal . '" ' . $sDisabled . ' ' . $sSelected . '/></td>';
  561. }
  562. protected function _getCellSwitcher ($mixedValue, $sKey, $aField, $aRow)
  563. {
  564. if($this->_bIsApi)
  565. return ['type' => 'switcher', 'data' => $aRow[$this->_aOptions['field_active']], 'fld' => $this->_aOptions['field_active']];
  566. $sAttr = $this->_convertAttrs(
  567. $aField, 'attr_cell',
  568. 'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
  569. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  570. );
  571. $oForm = new BxTemplFormView(array(), $this->_oTemplate);
  572. $oForm->addCssJs();
  573. $aInput = array(
  574. 'type' => 'switcher',
  575. 'name' => $this->_sObject . '_switch_' . $aRow[$this->_aOptions['field_id']],
  576. 'caption' => '',
  577. 'attrs' => array (
  578. 'bx_grid_action_single' => 'enable',
  579. 'bx_grid_action_confirm' => '',
  580. 'bx_grid_action_data' => $aRow[$this->_aOptions['field_id']],
  581. ),
  582. 'value' => $aRow[$this->_aOptions['field_id']],
  583. 'checked' => $this->_isSwitcherOn(isset($aRow[$this->_aOptions['field_active']]) ? $aRow[$this->_aOptions['field_active']] : false, $sKey, $aField, $aRow),
  584. );
  585. $sSwitcher = $oForm->genInput($aInput);
  586. return '<td ' . $sAttr . '>' . $sSwitcher . '</td>';
  587. }
  588. protected function _getCellOrder ($mixedValue, $sKey, $aField, $aRow)
  589. {
  590. if($this->_bIsApi)
  591. return ['type' => 'order', 'data'=> $mixedValue];
  592. $sAttr = $this->_convertAttrs(
  593. $aField, 'attr_cell',
  594. 'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
  595. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  596. );
  597. return '<td ' . $sAttr . '><div id="' . $this->_sObject . '_cell_' . $aRow[$this->_aOptions['field_id']] . '" class="bx-grid-drag-handle"><i class="sys-icon align-justify"></i></div></td>';
  598. }
  599. protected function _getCellActions ($mixedValue, $sKey, $aField, $aRow)
  600. {
  601. $sAttr = $this->_convertAttrs(
  602. $aField, 'attr_cell',
  603. 'bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
  604. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  605. );
  606. $mixedDisabledBehavior = $this->_getActionsDisabledBehavior($aRow);
  607. $mixedActions = $this->_getActions('single', $aRow[$this->_aOptions['field_id']], false, null === $mixedDisabledBehavior ? $this->_isRowDisabled($aRow) : $mixedDisabledBehavior, null !== $mixedDisabledBehavior, $aRow);
  608. if($this->_bIsApi)
  609. return ['type' => 'actions', 'data' => $mixedActions];
  610. return '<td ' . $sAttr . '><div class="bx-grid-cell-single-actions-wrapper bx-def-margin-thd-neg">' . $mixedActions . '</div></td>';
  611. }
  612. protected function _getCellDefault ($mixedValue, $sKey, $aField, $aRow)
  613. {
  614. if($this->_bIsApi)
  615. return ['type' => 'text', 'value'=> (int)$aField['translatable'] ? _t($mixedValue) : $mixedValue];
  616. $sAttr = $this->_convertAttrs(
  617. $aField, 'attr_cell',
  618. 'bx-grid-cell' . (!empty($aField['name']) ? ' bx-gc-' . $aField['name'] : '') . ' bx-def-padding-sec-bottom bx-def-padding-sec-top', // add default classes
  619. isset($aField['width']) ? 'width:' . $aField['width'] : false // add default styles
  620. );
  621. return '<td ' . $sAttr . '>' . $mixedValue . '</td>';
  622. }
  623. protected function _getActions ($sType, $sActionData = false, $isSmall = false, $isDisabled = false, $isPermanentState = false, $aRow = array())
  624. {
  625. $sActionsType = 'actions_' . $sType;
  626. if (empty($this->_aOptions[$sActionsType]) || !is_array($this->_aOptions[$sActionsType]))
  627. return '';
  628. $mixedResult = $this->_bIsApi ? [] : '';
  629. foreach ($this->_aOptions[$sActionsType] as $sKey => $a) {
  630. if(!$a['active'] || (!$a['title'] && !$a['icon']))
  631. continue;
  632. $sFunc = '_getAction' . $this->_genMethodName($sKey);
  633. if (!method_exists($this, $sFunc))
  634. $sFunc = empty($a) ? '_getActionDivider' : '_getActionDefault';
  635. if (!isset($a['attr']['bx_grid_action'])) {
  636. $a['attr']['bx_grid_action_' . $sType] = $sKey;
  637. if (!empty($sActionData))
  638. $a['attr']['bx_grid_action_data'] = $sActionData;
  639. if (isset($a['confirm']))
  640. $a['attr']['bx_grid_action_confirm'] = $a['confirm'] ? 1 : 0;
  641. $a['attr']['bx_grid_action_reset_paginate'] = false === strpos($sKey, 'delete') ? 0 : 1; // reset paginate after deleting row
  642. }
  643. if ($isPermanentState && !isset($a['attr']['bx_grid_permanent_state']))
  644. $a['attr']['bx_grid_permanent_state'] = 1;
  645. if ($this->_bIsApi)
  646. $mixedResult[] = $this->$sFunc($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
  647. else
  648. $mixedResult .= $this->$sFunc($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
  649. }
  650. return $mixedResult;
  651. }
  652. protected function _getActionsAPI ($sType)
  653. {
  654. $sActionsType = 'actions_' . $sType;
  655. if(empty($this->_aOptions[$sActionsType]) || !is_array($this->_aOptions[$sActionsType]))
  656. return [];
  657. foreach ($this->_aOptions[$sActionsType] as $sKey => $aAction) {
  658. $sMethod = '_getAction' . $this->_genMethodName($sKey);
  659. if(!method_exists($this, $sMethod))
  660. $sMethod = '_getActionDefault';
  661. if(!isset($aAction['attr']))
  662. $aAction['attr'] = [];
  663. if(($_aAction = $this->$sMethod($sType, $sKey, $aAction)))
  664. $this->_aOptions[$sActionsType][$sKey] = $_aAction;
  665. }
  666. return $this->_aOptions[$sActionsType];
  667. }
  668. protected function _getActionDelete($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
  669. {
  670. if($this->_bIsApi)
  671. return array_merge($a, ['name' => $sKey, 'type' => 'callback', 'on_callback' => 'hide_row']);
  672. return $this->_getActionDefault($sType, $sKey, $a, $isSmall, $isDisabled, $aRow);
  673. }
  674. protected function _getActionDefault ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
  675. {
  676. if ($a['icon_only'] && empty($a['attr']['title']) && !empty($a['title']))
  677. $a['attr']['title'] = $a['title'];
  678. if ($this->_bIsApi) {
  679. $sParams = '';
  680. if($sType == 'single' && ($sFieldId = $this->_aOptions['field_id']) && isset($aRow[$sFieldId]))
  681. $sParams = '&id=' . $aRow[$sFieldId];
  682. return array_merge($a, ['name' => $sKey, 'type' => 'modal', 'action' => $sKey, 'params' => $sParams]);
  683. }
  684. $sAttr = $this->_convertAttrs(
  685. $a, 'attr',
  686. 'bx-btn bx-def-margin-thd' . ($isSmall ? ' bx-btn-small' : '') . ($isDisabled ? ' bx-btn-disabled' : '') // add default classes
  687. );
  688. $sIcon = '';
  689. $sImage = '';
  690. if (!empty($a['icon'])) {
  691. list($sIconFont, $sIconUrl, $sIconA, $sIconHtml) = $this->_oFunctions->getIcon($a['icon']);
  692. if ($sIconFont)
  693. $sIcon = '<i class="sys-icon ' . $sIconFont . '"></i>';
  694. else if($sIconUrl)
  695. $sImage = '<img style="background-image:url(' . $sIconUrl . ');" src="' . $this->_oTemplate->getIconUrl('spacer.gif') .'" />';
  696. else if($sIconHtml)
  697. $sImage = $sIconHtml;
  698. }
  699. return '<button ' . $sAttr . '>' . $sIcon . $sImage . ($a['icon_only'] || empty($a['title']) ? '' : '<u>' . $a['title'] . '</u>') . '</button>';
  700. }
  701. protected function _getActionDivider ($sType, $sKey, $a, $isSmall = false, $isDisabled = false, $aRow = array())
  702. {
  703. return '<div class="bx-grid-actions-divider bx-def-margin-sec-left"> | </div>';
  704. }
  705. protected function _getActionResult($aResult)
  706. {
  707. return $this->_bIsApi ? $aResult : echoJson($aResult);
  708. }
  709. protected function _getFilterControls ()
  710. {
  711. $oForm = new BxTemplFormView([]);
  712. $aInput = array(
  713. 'type' => 'text',
  714. 'name' => 'keyword',
  715. 'attrs' => array(
  716. 'id' => 'bx-grid-search-' . $this->_sObject
  717. )
  718. );
  719. $this->_oTemplate->addCss('forms.css');
  720. return $oForm->genRow($aInput);
  721. }
  722. protected function _getFilterControlsAPI($aFilters = [])
  723. {
  724. if(!empty($this->_aOptions['filter_fields']) || !empty($this->_aOptions['filter_fields_translatable']))
  725. $aFilters['search'] = [];
  726. return $aFilters;
  727. }
  728. protected function _getFilterOnChange()
  729. {
  730. return '';
  731. }
  732. protected function _getFilterSelectOne($sFilterName, $sFilterValue, $aFilterValues, $mixedAddSelectOne = true, $bAsArray = false)
  733. {
  734. if(empty($sFilterName))
  735. return '';
  736. $aInputValues = [];
  737. if(($mixedAddSelectOne === true && ($sKey = '_Select_one')) || (is_string($mixedAddSelectOne) && ($sKey = $mixedAddSelectOne)))
  738. $aInputValues[''] = _t($sKey);
  739. foreach($aFilterValues as $mixedKey => $mixedValue)
  740. if(is_array($mixedValue) && isset($mixedValue['key'], $mixedValue['value']))
  741. $aInputValues[$mixedValue['key']] = _t($mixedValue['value']);
  742. else
  743. $aInputValues[$mixedKey] = _t($mixedValue);
  744. $aInputSelect = [
  745. 'type' => 'select',
  746. 'name' => $sFilterName,
  747. 'attrs' => [
  748. 'id' => 'bx-grid-' . $sFilterName . '-' . $this->_sObject,
  749. 'onChange' => ($sOnChange = $this->_getFilterOnChange()) ? 'javascript:' . $sOnChange : '',
  750. ],
  751. 'value' => $sFilterValue,
  752. 'values' => $aInputValues
  753. ];
  754. if($bAsArray)
  755. return $aInputSelect;
  756. $oForm = new BxTemplFormView([]);
  757. return $oForm->genRow($aInputSelect);
  758. }
  759. protected function _getFilterButton($bAsArray = false)
  760. {
  761. $sOnChange = $this->_getFilterOnChange();
  762. $aInputButton = [
  763. 'type' => 'button',
  764. 'name' => 'button',
  765. 'attrs' => [
  766. 'id' => 'bx-grid-button-' . $this->_sObject,
  767. 'onClick' => 'javascript:' . $sOnChange,
  768. ],
  769. 'value' => _t('_Search'),
  770. ];
  771. if($bAsArray)
  772. return $aInputButton;
  773. $oForm = new BxTemplFormView([]);
  774. return $oForm->genRow($aInputButton);
  775. }
  776. protected function _getSearchInput($bAsArray = false)
  777. {
  778. $sOnChange = $this->_getFilterOnChange();
  779. $aInputSearch = [
  780. 'type' => 'text',
  781. 'name' => 'search',
  782. 'attrs' => [
  783. 'id' => 'bx-grid-search-' . $this->_sObject,
  784. 'onKeyup' => 'javascript:$(this).off(\'keyup focusout\'); ' . $sOnChange,
  785. 'onBlur' => 'javascript:' . $sOnChange,
  786. ]
  787. ];
  788. if($bAsArray)
  789. return $aInputSearch;
  790. $oForm = new BxTemplFormView([]);
  791. return $oForm->genRow($aInputSearch);
  792. }
  793. protected function _getCounter()
  794. {
  795. return _t('_sys_grid_total_count', $this->_iTotalCount);
  796. }
  797. protected function _limitMaxLength ($mixedValue, $sKey, $aField, $aRow, $isDisplayPopupOnTextOverflow, $bReturnString = true)
  798. {
  799. if((int)$aField['chars_limit'] <= 0)
  800. return $mixedValue;
  801. return $this->_oFunctions->getStringWithLimitedLength([strip_tags($mixedValue), $mixedValue], $aField['chars_limit'], $isDisplayPopupOnTextOverflow, $bReturnString);
  802. }
  803. protected function _convertAttrs ($aField, $sAttrName, $sClasses = false, $sStyles = false)
  804. {
  805. if (!empty($aField['hidden_on'])) {
  806. $aHiddenOn = array(
  807. pow(2, BX_DB_HIDDEN_PHONE - 1) => 'bx-def-media-phone-hide',
  808. pow(2, BX_DB_HIDDEN_TABLET - 1) => 'bx-def-media-tablet-hide',
  809. pow(2, BX_DB_HIDDEN_DESKTOP - 1) => 'bx-def-media-desktop-hide',
  810. pow(2, BX_DB_HIDDEN_MOBILE - 1) => 'bx-def-mobile-app-hide'
  811. );
  812. foreach ($aHiddenOn as $iHiddenOn => $sClass)
  813. if ((int)$aField['hidden_on'] & $iHiddenOn)
  814. $sClasses .= ' ' . $sClass;
  815. }
  816. return bx_convert_array2attrs(
  817. isset($aField[$sAttrName]) && is_array($aField[$sAttrName]) ? $aField[$sAttrName] : array(),
  818. $sClasses,
  819. $sStyles
  820. );
  821. }
  822. protected function _updateOrder($mixedId, $iOrder)
  823. {
  824. $oDb = BxDolDb::getInstance();
  825. $sTable = $this->_aOptions['table'];
  826. $sFieldId = $this->_aOptions['field_id'];
  827. $sFieldOrder = $this->_aOptions['field_order'];
  828. $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `{$sFieldOrder}` = ? WHERE `{$sFieldId}` = ?", $iOrder, $mixedId);
  829. return $oDb->query($sQuery);
  830. }
  831. protected function _delete ($mixedId)
  832. {
  833. $oDb = BxDolDb::getInstance();
  834. $sTable = $this->_aOptions['table'];
  835. $sFieldId = $this->_aOptions['field_id'];
  836. $sQuery = $oDb->prepare("DELETE FROM `{$sTable}` WHERE `{$sFieldId}` = ?", $mixedId);
  837. return $oDb->query($sQuery);
  838. }
  839. protected function _enable ($mixedId, $isChecked)
  840. {
  841. $oDb = BxDolDb::getInstance();
  842. $sTable = $this->_aOptions['table'];
  843. $sFieldId = $this->_aOptions['field_id'];
  844. $sFieldActive = $this->_aOptions['field_active'];
  845. $sQuery = $oDb->prepare("UPDATE `{$sTable}` SET `$sFieldActive` = ? WHERE `{$sFieldId}` = ?", $this->_switcherChecked2State($isChecked), $mixedId);
  846. return $oDb->query($sQuery);
  847. }
  848. protected function _addJsCss()
  849. {
  850. if ($this->_aOptions['field_order']) {
  851. $this->_oTemplate->addJs(array(
  852. 'jquery-ui/jquery-ui.custom.min.js',
  853. 'URI.min.js',
  854. ));
  855. }
  856. $this->_oTemplate->addJs('BxDolGrid.js');
  857. $this->_oTemplate->addCss('grid.css');
  858. $this->_oTemplate->addJsTranslation('_sys_grid_confirmation');
  859. }
  860. }
  861. /** @} */