PageRenderTime 52ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/QuickApps/View/Helper/TableHelper.php

http://github.com/QuickAppsCMS/QuickApps-CMS
PHP | 383 lines | 205 code | 47 blank | 131 comment | 44 complexity | 8e0881a77e2e4b9e3055158d6c8d8f56 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, GPL-3.0
  1. <?php
  2. /**
  3. * Table Helper
  4. *
  5. * PHP version 5
  6. *
  7. * @package QuickApps.View.Helper
  8. * @version 1.0
  9. * @author Christopher Castro <chris@quickapps.es>
  10. * @link http://www.quickappscms.org
  11. */
  12. /**
  13. * ### Expected data's structure
  14. *
  15. * `$data` MUST be a numeric array. For example, any list result of `Model::find()` or paginated result:
  16. *
  17. * $data = array(
  18. * 0 => array(
  19. * 'Model' => array('field1' => 'data', ...),
  20. * 'Model2' => ...
  21. * ),
  22. * ....
  23. * );
  24. *
  25. * ### Options
  26. *
  27. * `columns` (array): Information about each of the columns of your table:
  28. *
  29. * array(
  30. * 'columns' => array(
  31. * 'Column Title' => array(
  32. * 'value' => , (string) Values to display when filling this column.
  33. * You can specify array paths to find in the $data array. e.g.: `{Model.field}`
  34. * See TableHelper::_renderCell() for more tags.
  35. * 'thOptions' => , (array) <th> tag options for this column. This will affect table header only.
  36. * 'tdOptions' => , (array) <td> tag options for this column. This will affect table body (result rows) only.
  37. * 'sort' => Optional (string) `Model.field`
  38. * ),
  39. * 'Other Column' => array( ... ),
  40. * ...
  41. * );
  42. *
  43. */
  44. class TableHelper extends AppHelper {
  45. /**
  46. * Helpers used by TableHelper.
  47. *
  48. * @var array
  49. */
  50. public $helpers = array('Html', 'Paginator');
  51. /**
  52. * Default table rendering options.
  53. * `columns` (array): Settings for each table's column. (see TableHelper::$__columnDefaults).
  54. * `headerPosition` (mixed): Column titles position. 'top', 'top&bottom', bottom'. Or (boolean) FALSE for no titles.
  55. * `noItemsMessage` (string): Message to show if there are no rows to display.
  56. * `tableOptions` (array): <table> tag attributes.
  57. * `trOptions` (array): <tr> tag attributes for every row of content (between <tbody></tbody>).
  58. * `paginate.position` (string): Pagination row position, 'top' or 'top&bottom' or 'bottom'.
  59. * `paginate.trOptions` (array): <tr> tags attributes.
  60. * `paginate.tdOptions` (array): <td> tags attributes.
  61. *
  62. * @var array
  63. */
  64. private $__defaults = array(
  65. 'columns' => array(),
  66. 'headerPosition' => 'top',
  67. 'noItemsMessage' => 'There are no items to display',
  68. 'tableOptions' => array('cellpadding' => 0, 'cellspacing' => 0, 'border' => 0),
  69. 'trOptions' => array(),
  70. 'paginate' => array(
  71. 'options' => array(),
  72. 'prev' => array(
  73. 'title' => 'Ť Previous ',
  74. 'options' => array(),
  75. 'disabledTitle' => null,
  76. 'disabledOptions' => array('class' => 'disabled')
  77. ),
  78. 'numbers' => array(
  79. 'options' => array(
  80. 'before' => ' &nbsp; ',
  81. 'after' => ' &nbsp; ',
  82. 'modulus' => 10,
  83. 'separator' => ' &nbsp; ',
  84. 'tag' => 'span',
  85. 'first' => 'First ',
  86. 'last' => ' Last',
  87. 'ellipsis' => '...'
  88. )
  89. ),
  90. 'next' => array(
  91. 'title' => ' Next ť',
  92. 'options' => array(),
  93. 'disabledTitle' => null,
  94. 'disabledOptions' => array('class' => 'disabled')
  95. ),
  96. 'position' => 'bottom',
  97. 'trOptions' => array('class' => 'paginator'),
  98. 'tdOptions' => array('align' => 'center')
  99. )
  100. );
  101. /**
  102. * Column default options.
  103. * `value` (string): Cell's content. You can use special tags, see TableHelper::_renderCell().
  104. * `thOptions` (array): <th> tag attributes for header cells (between <thead></thead>).
  105. * `tdOptions` (array): <td> tag attributes for body cells (for each row of content between <tbody></tbody>).
  106. * `sort` (mixed): Set to a string indicating the column name (`Model.column`). Set to (boolean) FALSE for do not sort this column.
  107. *
  108. * @var array
  109. */
  110. private $__columnDefaults = array(
  111. 'value' => '',
  112. 'thOptions' => array('align' => 'left'),
  113. 'tdOptions' => array('align' => 'left'),
  114. 'sort' => false
  115. );
  116. /**
  117. * Holds the number of columns of the table being rendered.
  118. *
  119. * @var integer
  120. */
  121. private $__colsCount = 0;
  122. /**
  123. * Renders out HTML table.
  124. *
  125. * @param array $data Data to fill table rows
  126. * @param array $options Table options.
  127. * @return string HTML table element
  128. * @see TableHelper::$__defaults
  129. */
  130. public function create($data, $options) {
  131. $this->__defaults['paginate']['prev']['title'] = __t('Ť Previous ');
  132. $this->__defaults['paginate']['next']['title'] = __t(' Next ť');
  133. if (isset($options['paginate']) && $options['paginate'] === true) {
  134. unset($options['paginate']);
  135. } else {
  136. $this->__defaults['paginate'] = !isset($options['paginate']) ? false : $this->__defaults['paginate'];
  137. }
  138. $options = Hash::merge($this->__defaults, $options);
  139. $this->__colsCount = count($options['columns']);
  140. $out = sprintf('<table%s>', $this->Html->_parseAttributes($options['tableOptions'])) . "\n";
  141. if (count($data) > 0) {
  142. $print_header_top = ($options['headerPosition'] !== false && in_array($options['headerPosition'], array('top', 'top&bottom')));
  143. $print_paginator_top = ($options['paginate'] !== false && in_array($options['paginate']['position'], array('top', 'top&bottom')));
  144. if ($print_header_top || $print_paginator_top) {
  145. $out .= "\t<thead>\n";
  146. $out .= $print_header_top ? $this->_renderHeader($options) : '';
  147. $out .= $print_paginator_top ? $this->_renderPaginator($options) : '';
  148. $out .= "\n\t</thead>\n";
  149. }
  150. $out .= "\t<tbody>\n";
  151. $count = 1;
  152. foreach ($data as $i => $r_data) {
  153. $td = '';
  154. foreach ($options['columns'] as $name => $c_data) {
  155. $c_data = array_merge($this->__columnDefaults, $c_data);
  156. $td .= "\n\t";
  157. $td .= $this->Html->useTag('tablecell',
  158. $this->Html->_parseAttributes($c_data['tdOptions']),
  159. $this->_renderCell($c_data['value'], $data[$i])
  160. );
  161. $td .= "\t";
  162. }
  163. $tr_options = array(
  164. 'class' => ($count%2 ? 'even' : 'odd')
  165. );
  166. if (!empty($options['trOptions']) && is_array($options['trOptions'])) {
  167. foreach ($options['trOptions'] as $key => $val) {
  168. $val = $this->_renderCell($val, $data[$i]);
  169. if ($key == 'class') {
  170. $tr_options['class'] = $tr_options['class'] . " {$val}";
  171. } else {
  172. $tr_options[$key] = $val;
  173. }
  174. }
  175. }
  176. $out .= $this->Html->useTag('tablerow', $this->Html->_parseAttributes($tr_options), $td);
  177. $count++;
  178. }
  179. $out .= "\t</tbody>\n";
  180. $print_header_bottom = ($options['headerPosition'] !== false && in_array($options['headerPosition'], array('bottom', 'top&bottom')));
  181. $print_paginator_bottom = ($options['paginate'] != false && in_array($options['paginate']['position'], array('bottom', 'top&bottom')));
  182. if ($print_header_bottom || $print_paginator_bottom) {
  183. $out .= "\t<tfoot>\n";
  184. $out .= $print_header_bottom ? $this->_renderHeader($options) : '';
  185. $out .= $print_paginator_bottom ? $this->_renderPaginator($options) : '';
  186. $out .= "\n\t</tfoot>\n";
  187. }
  188. } else {
  189. $options['noItemsMessage'] = $options['noItemsMessage'] != 'There are no items to display' ? $options['noItemsMessage'] : __t($options['noItemsMessage']);
  190. $td = $this->Html->useTag('tablecell', $this->Html->_parseAttributes(array('colspan' => $this->__colsCount)), $options['noItemsMessage']);
  191. $out .= $this->Html->useTag('tablerow', $this->Html->_parseAttributes(array('class' => 'even')), $td);
  192. }
  193. $out .= "</table>\n";
  194. return $out;
  195. }
  196. /**
  197. * Render the given cell.
  198. * Looks for special tags to be replaced, valid tags are:
  199. * - URL. e.g.: {url}/my/url.html{url}
  200. * - Array path. e.g.: {Node.slug}
  201. * - Image. e.g.: {img class='width' border=0}/url/to/image.jpg{/img}
  202. * - Link. e.g.: {link class='css-class' title='Link title'}Link label|/link/url.html{/link}
  203. * - Translation __t(). e.g.: {t}Translate this{/t}
  204. * - Translation __d(). e.g.: {d|System}System module will translate this{/d}
  205. * - PHP code. e.g.: {php} return 'Testing'; {/php}
  206. *
  207. * @param string $value Cell content
  208. * @param array $row_data Array of data of the row that cell belongs to
  209. * @return string HTML table cell content
  210. */
  211. protected function _renderCell($value, $row_data) {
  212. // look for urls. e.g.: {url}/my/url.html{url}
  213. preg_match_all('/\{url\}(.+)\{\/url\}/iUs', $value, $url);
  214. if (isset($url[1]) && !empty($url[1])) {
  215. foreach ($url[0] as $i => $m) {
  216. $value = str_replace($m, $this->Html->url(trim($url[1][$i]), true), $value);
  217. }
  218. }
  219. // look for array paths. e.g.: {Node.slug}
  220. preg_match_all('/\{([\{\}0-9a-zA-Z_\.]+)\}/iUs', $value, $path);
  221. if (isset($path[1]) && !empty($path[1])) {
  222. $exclude = array('{d}', '{/d}', '{t}', '{/t}', '{php}', '{/php}', '{img}', '{/img}', '{link}', '{/link}');
  223. foreach ($path[0] as $i => $m) {
  224. if (in_array($m, $exclude)) {
  225. continue;
  226. }
  227. $value = str_replace($m, addslashes(array_pop(Hash::extract($row_data, trim($path[1][$i])))), $value);
  228. }
  229. }
  230. // look for images. {img class='width' border=0}/url/to/image.jpg{/img}
  231. preg_match_all('/\{img(.*?)\}(.+)\{\/img\}/i', $value, $img);
  232. if (isset($img[1]) && !empty($img[1])) {
  233. foreach ($img[0] as $i => $m) {
  234. $opts = isset($img[1][$i]) ? QuickApps::parseHooktagAttributes(trim($img[1][$i])) : array();
  235. $opts = empty($opts) ? array(): $opts;
  236. $value = str_replace($m, $this->Html->image($img[2][$i], $opts), $value);
  237. }
  238. }
  239. // look for links. e.g..: {link class='css-class' title='Link title'}Link label|/link/url.html{/link}
  240. preg_match_all('/\{link(.*?)\}(.*)\|(.*)\{\/link\}/i', $value, $link);
  241. if (isset($link[1]) && !empty($link[1])) {
  242. foreach ($link[0] as $i => $m) {
  243. $opts = isset($link[1][$i]) ? QuickApps::parseHooktagAttributes(trim($link[1][$i])) : array();
  244. $opts = empty($opts) ? array(): $opts;
  245. if (isset($opts['escape'])) {
  246. $opts['escape'] = $opts['escape'] == "true";
  247. }
  248. $value = str_replace($m, $this->Html->link(trim($link[2][$i]), $link[3][$i], $opts), $value);
  249. }
  250. }
  251. // look for __t(). e.g.: {t}Translate this{/t}
  252. preg_match_all('/\{t\}(.+)\{\/t\}/i', $value, $t);
  253. if (isset($t[1]) && !empty($t[1])) {
  254. foreach ($t[0] as $i => $m) {
  255. $value = str_replace($m, __t($t[1][$i]), $value);
  256. }
  257. }
  258. // look for __d(). e.g.: {d|System}System module will translate this{/d}
  259. preg_match_all('/\{d\|(.+)\}(.+)\{\/d\}/i', $value, $d);
  260. if (isset($d[1]) && !empty($d[1])) {
  261. foreach ($d[0] as $i => $m) {
  262. $value = str_replace($m, __d($d[1][$i], $d[2][$i]), $value);
  263. }
  264. }
  265. // look for php code. e.g.: {php} return 'Testing'; {/php}
  266. preg_match_all('/\{php\}(.+)\{\/php\}/iUs', $value, $php);
  267. if (isset($php[1]) && !empty($php[1])) {
  268. foreach ($php[0] as $i => $m) {
  269. $value = str_replace($m, $this->__php_eval("<?php {$php[1][$i]}", $row_data), $value);
  270. }
  271. }
  272. return $value;
  273. }
  274. /**
  275. * Evaluate a string of PHP code.
  276. *
  277. * This is a wrapper around PHP's eval(). It uses output buffering to capture both
  278. * returned and printed text. Unlike eval(), we require code to be surrounded by
  279. * <?php ?> tags; in other words, we evaluate the code as if it were a stand-alone
  280. * PHP file.
  281. *
  282. * Using this wrapper also ensures that the PHP code which is evaluated can not
  283. * overwrite any variables in the calling code, unlike a regular eval() call.
  284. *
  285. * @param string $code The code to evaluate.
  286. * @return
  287. * A string containing the printed output of the code, followed by the returned
  288. * output of the code.
  289. *
  290. */
  291. private function __php_eval($code, $row_data = array()) {
  292. ob_start();
  293. print eval('?>' . $code);
  294. $output = ob_get_contents();
  295. ob_end_clean();
  296. return $output;
  297. }
  298. /**
  299. * Renders table's header.
  300. *
  301. * @return string HTML
  302. */
  303. protected function _renderHeader($options, $footer = false) {
  304. $th = $out ='';
  305. if ($footer && $options['paginate'] !== false && in_array($options['paginate']['position'], array('top', 'top&bottom'))) {
  306. @$out .= $this->_renderPaginator($options);
  307. }
  308. foreach ($options['columns'] as $name => $data) {
  309. $data = array_merge($this->__columnDefaults, $data);
  310. if ($options['paginate'] !== false && is_string($data['sort'])) {
  311. @$name = $this->Paginator->sort($data['sort'], $name);
  312. }
  313. $th .= "\t\t". $this->Html->useTag('tableheader', $this->Html->_parseAttributes($data['thOptions']), $name) . "\n";
  314. }
  315. $out .= $this->Html->useTag('tablerow', null, $th);
  316. return $out;
  317. }
  318. /**
  319. * Renders table's pagination-row.
  320. *
  321. * @return string HTML
  322. */
  323. protected function _renderPaginator($array) {
  324. $out = $paginator = '';
  325. $array = $array['paginate'];
  326. $array['numbers']['options']['first'] = $array['numbers']['options']['first'] != 'First ' ? __t($array['numbers']['options']['first']) : $array['numbers']['options']['first'];
  327. $array['numbers']['options']['last'] = $array['numbers']['options']['last'] != ' Last' ? __t($array['numbers']['options']['last']) : $array['numbers']['options']['last'];
  328. $paginator .= $this->Paginator->options($array['options']);
  329. $paginator .= $this->Paginator->prev($array['prev']['title'], $array['prev']['options'], $array['prev']['disabledTitle'], $array['prev']['disabledOptions']);
  330. $paginator .= $this->Paginator->numbers($array['numbers']['options']);
  331. $paginator .= $this->Paginator->next($array['next']['title'], $array['next']['options'], $array['next']['disabledTitle'], $array['next']['disabledOptions']);
  332. $td = $this->Html->useTag('tablecell', $this->Html->_parseAttributes(array_merge(array('colspan' => $this->__colsCount), $array['tdOptions'])), $paginator);
  333. $out .= $this->Html->useTag('tablerow', $this->Html->_parseAttributes($array['trOptions']), $td);
  334. return $out;
  335. }
  336. }