1 <?php
2
3
4 define('REX_LIST_OPT_SORT', 0);
5 define('REX_LIST_OPT_SORT_DIRECTION', 1);
6
7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
45
46 47 48 49 50
51 class rex_list implements rex_url_provider_interface
52 {
53 use rex_factory_trait;
54
55 private $db;
56 private $query;
57 private $sql;
58 private $debug;
59 private $noRowsMessage;
60
61
62 private $name;
63 private $params;
64 private $rows;
65
66
67 private $formAttributes;
68
69
70 private $customColumns;
71 private $columnNames;
72 private $columnLabels;
73 private $columnFormates;
74 private $columnOptions;
75 private $columnAttributes;
76 private $columnLayouts;
77 private $columnParams;
78 private $columnDisabled;
79
80
81 private $defaultColumnLayout;
82
83
84 private $caption;
85 private $tableAttributes;
86 private $tableColumnGroups;
87
88
89 private $linkAttributes;
90
91
92 private ;
93
94 95 96 97 98 99 100 101
102 protected function __construct($query, $rowsPerPage = 30, $listName = null, $debug = false, $db = 1)
103 {
104
105 if (!$listName) {
106
107 $listName = substr(md5($query), 0, 8);
108 }
109
110
111 $this->db = $db;
112 $this->query = $query;
113 $this->sql = rex_sql::factory($db);
114 $this->debug = $debug;
115 $this->sql->setDebug($this->debug);
116 $this->name = $listName;
117 $this->caption = '';
118 $this->rows = 0;
119 $this->params = [];
120 $this->tableAttributes = [];
121 $this->noRowsMessage = rex_i18n::msg('list_no_rows');
122
123
124 $this->formAttributes = [];
125
126
127 $this->customColumns = [];
128 $this->columnLabels = [];
129 $this->columnFormates = [];
130 $this->columnParams = [];
131 $this->columnOptions = [];
132 $this->columnAttributes = [];
133 $this->columnLayouts = [];
134 $this->columnDisabled = [];
135
136
137 $this->defaultColumnLayout = ['<th>###VALUE###</th>', '<td data-title="###LABEL###">###VALUE###</td>'];
138
139
140 $this->tableAttributes = [];
141 $this->tableColumnGroups = [];
142
143
144 $this->linkAttributes = [];
145
146
147 $cursorName = $listName .'_start';
148 if (null === rex_request($cursorName, 'int', null) && rex_request('start', 'int')) {
149
150 $cursorName = 'start';
151 }
152 $this->pager = new rex_pager($rowsPerPage, $cursorName);
153
154
155 $this->sql->setQuery($this->prepareQuery($query));
156 $sql = rex_sql::factory($db);
157 $sql->setQuery('SELECT FOUND_ROWS() as '. $sql->escapeIdentifier('rows'));
158 $this->rows = $sql->getValue('rows');
159 $this->pager->setRowCount($this->rows);
160
161 foreach ($this->sql->getFieldnames() as $columnName) {
162 $this->columnNames[] = $columnName;
163 }
164
165
166 if (rex::isBackend()) {
167 $this->loadBackendConfig();
168 }
169
170 $this->init();
171 }
172
173 174 175 176 177 178 179 180 181
182 public static function factory($query, $rowsPerPage = 30, $listName = null, $debug = false, $db = 1)
183 {
184 $class = static::getFactoryClass();
185 return new $class($query, $rowsPerPage, $listName, $debug, $db);
186 }
187
188 public function init()
189 {
190
191 }
192
193
194
195 196 197 198 199
200 public function getName()
201 {
202 return $this->name;
203 }
204
205 206 207 208 209
210 public function getMessage()
211 {
212 return rex_escape(rex_request($this->getName() . '_msg', 'string'));
213 }
214
215 216 217 218 219
220 public function getWarning()
221 {
222 return rex_escape(rex_request($this->getName() . '_warning', 'string'));
223 }
224
225 226 227 228 229 230
231 public function setCaption($caption)
232 {
233 $this->caption = $caption;
234 }
235
236 237 238 239 240
241 public function getCaption()
242 {
243 return $this->caption;
244 }
245
246 public function setNoRowsMessage($msg)
247 {
248 $this->noRowsMessage = $msg;
249 }
250
251 public function getNoRowsMessage()
252 {
253 return $this->noRowsMessage;
254 }
255
256 public function addParam($name, $value)
257 {
258 $this->params[$name] = $value;
259 }
260
261 public function getParams()
262 {
263 return $this->params;
264 }
265
266 protected function loadBackendConfig()
267 {
268 $this->addParam('page', rex_be_controller::getCurrentPage());
269 }
270
271 public function addTableAttribute($attrName, $attrValue)
272 {
273 $this->tableAttributes[$attrName] = $attrValue;
274 }
275
276 public function getTableAttributes()
277 {
278 return $this->tableAttributes;
279 }
280
281 public function addFormAttribute($attrName, $attrValue)
282 {
283 $this->formAttributes[$attrName] = $attrValue;
284 }
285
286 public function getFormAttributes()
287 {
288 return $this->formAttributes;
289 }
290
291 public function addLinkAttribute($columnName, $attrName, $attrValue)
292 {
293 $this->linkAttributes[$columnName][$attrName] = $attrValue;
294 }
295
296 public function getLinkAttributes($column, $default = null)
297 {
298 return isset($this->linkAttributes[$column]) ? $this->linkAttributes[$column] : $default;
299 }
300
301
302
303 304 305 306 307 308 309 310
311 public function addColumn($columnHead, $columnBody, $columnIndex = -1, $columnLayout = null)
312 {
313
314 if ($columnIndex < 0) {
315 $columnIndex = count($this->columnNames);
316 }
317
318 array_splice($this->columnNames, $columnIndex, 0, [$columnHead]);
319 $this->customColumns[$columnHead] = $columnBody;
320 $this->setColumnLayout($columnHead, $columnLayout);
321 }
322
323 324 325 326 327
328 public function removeColumn($columnName)
329 {
330 $this->columnDisabled[] = $columnName;
331 }
332
333 334 335 336 337 338
339 public function setColumnLayout($columnHead, $columnLayout)
340 {
341 $this->columnLayouts[$columnHead] = $columnLayout;
342 }
343
344 345 346 347 348 349 350
351 public function getColumnLayout($columnName)
352 {
353 if (isset($this->columnLayouts[$columnName]) && is_array($this->columnLayouts[$columnName])) {
354 return $this->columnLayouts[$columnName];
355 }
356
357 return $this->defaultColumnLayout;
358 }
359
360 361 362
363 public function getColumnLayouts()
364 {
365 return $this->columnLayouts;
366 }
367
368 369 370 371 372 373 374 375
376 public function getColumnName($columnIndex, $default = null)
377 {
378 if (isset($this->columnNames[$columnIndex])) {
379 return $this->columnNames[$columnIndex];
380 }
381
382 return $default;
383 }
384
385 386 387 388 389
390 public function getColumnNames()
391 {
392 return $this->columnNames;
393 }
394
395 396 397 398 399 400
401 public function setColumnLabel($columnName, $label)
402 {
403 $this->columnLabels[$columnName] = $label;
404 }
405
406 407 408 409 410 411 412 413 414 415 416
417 public function getColumnLabel($columnName, $default = null)
418 {
419 if (isset($this->columnLabels[$columnName])) {
420 return $this->columnLabels[$columnName];
421 }
422
423 return $default === null ? $columnName : $default;
424 }
425
426 427 428 429 430 431 432 433
434 public function setColumnFormat($columnName, $format_type, $format = '', array $params = [])
435 {
436 $this->columnFormates[$columnName] = [$format_type, $format, $params];
437 }
438
439 440 441 442 443 444 445 446
447 public function getColumnFormat($columnName, $default = null)
448 {
449 if (isset($this->columnFormates[$columnName])) {
450 return $this->columnFormates[$columnName];
451 }
452
453 return $default;
454 }
455
456 457 458 459 460 461
462 public function setColumnSortable($columnName, $direction = 'asc')
463 {
464 $this->setColumnOption($columnName, REX_LIST_OPT_SORT, true);
465 $this->setColumnOption($columnName, REX_LIST_OPT_SORT_DIRECTION, strtolower($direction));
466 }
467
468 469 470 471 472 473 474 475
476 public function setColumnOption($columnName, $option, $value)
477 {
478 $this->columnOptions[$columnName][$option] = $value;
479 }
480
481 482 483 484 485 486 487 488 489
490 public function getColumnOption($columnName, $option, $default = null)
491 {
492 if ($this->hasColumnOption($columnName, $option)) {
493 return $this->columnOptions[$columnName][$option];
494 }
495 return $default;
496 }
497
498 499 500 501 502 503 504 505
506 public function hasColumnOption($columnName, $option)
507 {
508 return isset($this->columnOptions[$columnName][$option]);
509 }
510
511 512 513 514 515 516
517 public function setColumnParams($columnName, array $params = [])
518 {
519 $this->columnParams[$columnName] = $params;
520 }
521
522 523 524 525 526 527 528
529 public function getColumnParams($columnName)
530 {
531 if (isset($this->columnParams[$columnName]) && is_array($this->columnParams[$columnName])) {
532 return $this->columnParams[$columnName];
533 }
534 return [];
535 }
536
537 538 539 540 541 542 543
544 public function hasColumnParams($columnName)
545 {
546 return isset($this->columnParams[$columnName]) && is_array($this->columnParams[$columnName]) && count($this->columnParams[$columnName]) > 0;
547 }
548
549
550
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572 573 574 575 576
577 public function addTableColumnGroup(array $columns, $columnGroupSpan = null)
578 {
579 $tableColumnGroup = ['columns' => []];
580 if ($columnGroupSpan) {
581 $tableColumnGroup['span'] = $columnGroupSpan;
582 }
583 $this->tableColumnGroups[] = $tableColumnGroup;
584
585 foreach ($columns as $column) {
586 if (is_array($column)) {
587 $this->addTableColumn(isset($column['width']) ? $column['width'] : null, isset($column['span']) ? $column['span'] : null, isset($column['class']) ? $column['class'] : null);
588 } else {
589 $this->addTableColumn($column);
590 }
591 }
592 }
593
594 595 596
597 public function getTableColumnGroups()
598 {
599 return $this->tableColumnGroups;
600 }
601
602 603 604 605 606 607
608 public function addTableColumn($width, $span = null, $class = null)
609 {
610 $tableColumn = [];
611 if (is_numeric($width)) {
612 $width = $width . 'px';
613 }
614 if ($width && '*' != $width) {
615 $tableColumn['style'] = 'width:' . $width;
616 }
617 if ($span) {
618 $tableColumn['span'] = $span;
619 }
620 if ($class) {
621 $tableColumn['class'] = $class;
622 }
623
624 $lastIndex = count($this->tableColumnGroups) - 1;
625
626 if ($lastIndex < 0) {
627
628 $this->addTableColumnGroup([]);
629 ++$lastIndex;
630 }
631
632 $groupColumns = $this->tableColumnGroups[$lastIndex]['columns'];
633 $groupColumns[] = $tableColumn;
634 $this->tableColumnGroups[$lastIndex]['columns'] = $groupColumns;
635 }
636
637
638
639 640 641
642 public function getUrl(array $params = [], $escape = true)
643 {
644 $params = array_merge($this->getParams(), $params);
645
646 $params['list'] = $this->getName();
647
648 if (!isset($params['sort'])) {
649 $sortColumn = $this->getSortColumn();
650 if ($sortColumn != null) {
651 $params['sort'] = $sortColumn;
652 $params['sorttype'] = $this->getSortType();
653 }
654 }
655
656 $_params = [];
657 foreach ($params as $name => $value) {
658 if (is_array($value)) {
659 foreach ($value as $v) {
660 $_params[$name] = $v;
661 }
662 } else {
663 $_params[$name] = $value;
664 }
665 }
666
667 return rex::isBackend() ? rex_url::backendController($_params, $escape) : rex_url::frontendController($_params, $escape);
668 }
669
670 671 672 673 674 675 676 677 678 679 680 681 682
683 public function getParsedUrl($params = [], $escape = true)
684 {
685 $params = array_merge($this->getParams(), $params);
686
687 $params['list'] = $this->getName();
688
689 if (!isset($params['sort'])) {
690 $sortColumn = $this->getSortColumn();
691 if ($sortColumn != null) {
692 $params['sort'] = $sortColumn;
693 $params['sorttype'] = $this->getSortType();
694 }
695 }
696
697 $_params = [];
698 foreach ($params as $name => $value) {
699 if (is_array($value)) {
700 foreach ($value as $v) {
701 $_params[$name] = $this->replaceVariables($v);
702 }
703 } else {
704 $_params[$name] = $this->replaceVariables($value);
705 }
706 }
707 return rex::isBackend() ? rex_url::backendController($_params, $escape) : rex_url::frontendController($_params, $escape);
708 }
709
710
711
712 713 714 715 716 717 718
719 protected function prepareQuery($query)
720 {
721 $rowsPerPage = $this->pager->getRowsPerPage();
722 $startRow = $this->pager->getCursor();
723
724
725 $query = preg_replace('/^\s*SELECT/i', 'SELECT SQL_CALC_FOUND_ROWS', $query, 1);
726
727 $sortColumn = $this->getSortColumn();
728 if ($sortColumn != '') {
729 $sortType = $this->getSortType();
730
731 $sql = rex_sql::factory($this->db);
732 $sortColumn = $sql->escapeIdentifier($sortColumn);
733
734 if (stripos($query, ' ORDER BY ') === false) {
735 $query .= ' ORDER BY ' . $sortColumn . ' ' . $sortType;
736 } else {
737 $query = preg_replace('/ORDER\sBY\s[^ ]*(\sasc|\sdesc)?/i', 'ORDER BY ' . $sortColumn . ' ' . $sortType, $query);
738 }
739 }
740
741 if (stripos($query, ' LIMIT ') === false) {
742 $query .= ' LIMIT ' . $startRow . ',' . $rowsPerPage;
743 }
744
745 return $query;
746 }
747
748 749 750 751 752
753 public function getRows()
754 {
755 return $this->rows;
756 }
757
758 759 760 761 762
763 public function ()
764 {
765 return $this->pager;
766 }
767
768 769 770 771 772 773 774
775 public function getSortColumn($default = null)
776 {
777 if (rex_request('list', 'string') == $this->getName()) {
778 return rex_request('sort', 'string', $default);
779 }
780 return $default;
781 }
782
783 784 785 786 787 788 789
790 public function getSortType($default = null)
791 {
792 if (rex_request('list', 'string') == $this->getName()) {
793 $sortType = strtolower(rex_request('sorttype', 'string'));
794
795 if (in_array($sortType, ['asc', 'desc'])) {
796 return $sortType;
797 }
798 }
799 return $default;
800 }
801
802 803 804 805 806
807 protected function ()
808 {
809 $fragment = new rex_fragment();
810 $fragment->setVar('urlprovider', $this);
811 $fragment->setVar('pager', $this->pager);
812 return $fragment->parse('core/navigations/pagination.php');
813 }
814
815 816 817 818 819
820 public function ()
821 {
822 $s = '';
823 824 825 826 827
828 return $s;
829 }
830
831 832 833 834 835
836 public function ()
837 {
838 $s = '';
839 $s .= $this->getPagination();
840
841 return $s;
842 }
843
844
845
846 public function replaceVariable($string, $varname)
847 {
848 return str_replace('###' . $varname . '###', rex_escape($this->getValue($varname)), $string);
849 }
850
851 852 853 854 855 856 857
858 public function replaceVariables($value)
859 {
860 if (strpos($value, '###') === false) {
861 return $value;
862 }
863
864 $columnNames = $this->getColumnNames();
865
866 if (is_array($columnNames)) {
867 foreach ($columnNames as $columnName) {
868
869 if (isset($this->customColumns[$columnName])) {
870 continue;
871 }
872
873 $value = $this->replaceVariable($value, $columnName);
874 }
875 }
876 return $value;
877 }
878
879 public function isCustomFormat($format)
880 {
881 return is_array($format) && isset($format[0]) && $format[0] == 'custom';
882 }
883
884 885 886 887 888 889 890 891 892 893
894 public function formatValue($value, $format, $escape, $field = null)
895 {
896 if (is_array($format)) {
897
898 if ($this->isCustomFormat($format)) {
899 $format[2] = isset($format[2]) ? $format[2] : [];
900 $format[1] = [$format[1], ['list' => $this, 'field' => $field, 'value' => $value, 'format' => $format[0], 'escape' => $escape, 'params' => $format[2]]];
901 }
902
903 $value = rex_formatter::format($value, $format[0], $format[1]);
904 }
905
906
907 if ($escape &&
908 !$this->isCustomFormat($format) &&
909 $format[0] != 'email' &&
910 $format[0] != 'url'
911 ) {
912 $value = rex_escape($value);
913 }
914
915 return $value;
916 }
917
918 protected function _getAttributeString($array)
919 {
920 $s = '';
921
922 foreach ($array as $name => $value) {
923 $s .= ' ' . rex_escape($name, 'html_attr') . '="' . rex_escape($value) . '"';
924 }
925
926 return $s;
927 }
928
929 public function getColumnLink($columnName, $columnValue, $params = [])
930 {
931 return '<a href="' . $this->getParsedUrl(array_merge($this->getColumnParams($columnName), $params)) . '"' . $this->_getAttributeString($this->getLinkAttributes($columnName, [])) . '>' . $columnValue . '</a>';
932 }
933
934 public function getValue($colname)
935 {
936 return isset($this->customColumns[$colname]) ? $this->customColumns[$colname] : $this->sql->getValue($colname);
937 }
938
939 public function getArrayValue($colname)
940 {
941 return json_decode($this->getValue($colname), true);
942 }
943
944 945 946 947 948
949 public function get()
950 {
951 rex_extension::registerPoint(new rex_extension_point('REX_LIST_GET', $this, [], true));
952
953 $s = "\n";
954
955
956 $this->addFormAttribute('action', $this->getUrl([], false));
957 $this->addFormAttribute('method', 'post');
958
959
960 $caption = $this->getCaption();
961 $tableColumnGroups = $this->getTableColumnGroups();
962 $class = 'table';
963 if (isset($this->tableAttributes['class'])) {
964 $class .= ' ' . $this->tableAttributes['class'];
965 }
966 $this->addTableAttribute('class', $class);
967
968
969 $columnFormates = [];
970 $columnNames = [];
971 foreach ($this->getColumnNames() as $columnName) {
972 if (!in_array($columnName, $this->columnDisabled)) {
973 $columnNames[] = $columnName;
974 }
975 }
976
977
978 $sortColumn = $this->getSortColumn();
979 $sortType = $this->getSortType();
980 $warning = $this->getWarning();
981 $message = $this->getMessage();
982 $nbRows = $this->getRows();
983
984 $header = $this->getHeader();
985 $footer = $this->getFooter();
986
987 if ($warning != '') {
988 $s .= rex_view::warning($warning) . "\n";
989 } elseif ($message != '') {
990 $s .= rex_view::info($message) . "\n";
991 }
992
993 if ($header != '') {
994 $s .= $header . "\n";
995 }
996
997 $s .= '<form' . $this->_getAttributeString($this->getFormAttributes()) . '>' . "\n";
998 $s .= ' <table' . $this->_getAttributeString($this->getTableAttributes()) . '>' . "\n";
999
1000 if ($caption != '') {
1001 $s .= ' <caption>' . rex_escape($caption) . '</caption>' . "\n";
1002 }
1003
1004 if (count($tableColumnGroups) > 0) {
1005 foreach ($tableColumnGroups as $tableColumnGroup) {
1006 $tableColumns = $tableColumnGroup['columns'];
1007 unset($tableColumnGroup['columns']);
1008
1009 $s .= ' <colgroup' . $this->_getAttributeString($tableColumnGroup) . '>' . "\n";
1010
1011 foreach ($tableColumns as $tableColumn) {
1012 $s .= ' <col' . $this->_getAttributeString($tableColumn) . ' />' . "\n";
1013 }
1014
1015 $s .= ' </colgroup>' . "\n";
1016 }
1017 }
1018
1019 $s .= ' <thead>' . "\n";
1020 $s .= ' <tr>' . "\n";
1021 foreach ($columnNames as $columnName) {
1022 $columnHead = $this->getColumnLabel($columnName);
1023 if ($this->hasColumnOption($columnName, REX_LIST_OPT_SORT)) {
1024 if ($columnName == $sortColumn) {
1025 $columnSortType = $sortType == 'desc' ? 'asc' : 'desc';
1026 } else {
1027 $columnSortType = $this->getColumnOption($columnName, REX_LIST_OPT_SORT_DIRECTION, 'asc');
1028 }
1029 $columnHead = '<a href="' . $this->getUrl([$this->pager->getCursorName() => $this->pager->getCursor(), 'sort' => $columnName, 'sorttype' => $columnSortType]) . '">' . $columnHead . '</a>';
1030 }
1031
1032 $layout = $this->getColumnLayout($columnName);
1033 $s .= ' ' . str_replace('###VALUE###', $columnHead, $layout[0]) . "\n";
1034
1035
1036 $columnFormates[$columnName] = $this->getColumnFormat($columnName);
1037 }
1038 $s .= ' </tr>' . "\n";
1039 $s .= ' </thead>' . "\n";
1040
1041 if ($footer != '') {
1042 $s .= ' <tfoot>' . "\n";
1043 $s .= $footer;
1044 $s .= ' </tfoot>' . "\n";
1045 }
1046
1047 if ($nbRows > 0) {
1048 $maxRows = $nbRows - $this->pager->getCursor();
1049
1050 $s .= ' <tbody>' . "\n";
1051 for ($i = 0; $i < $this->pager->getRowsPerPage() && $i < $maxRows; ++$i) {
1052 $s .= ' <tr>' . "\n";
1053 foreach ($columnNames as $columnName) {
1054 $columnValue = $this->formatValue($this->getValue($columnName), $columnFormates[$columnName], !isset($this->customColumns[$columnName]), $columnName);
1055
1056 if (!$this->isCustomFormat($columnFormates[$columnName]) && $this->hasColumnParams($columnName)) {
1057 $columnValue = $this->getColumnLink($columnName, $columnValue);
1058 }
1059
1060 $columnHead = $this->getColumnLabel($columnName);
1061 $layout = $this->getColumnLayout($columnName);
1062 $columnValue = str_replace(['###VALUE###', '###LABEL###'], [$columnValue, $columnHead], $layout[1]);
1063 $columnValue = $this->replaceVariables($columnValue);
1064 $s .= ' ' . $columnValue . "\n";
1065 }
1066 $s .= ' </tr>' . "\n";
1067
1068 $this->sql->next();
1069 }
1070 $s .= ' </tbody>' . "\n";
1071 } else {
1072 $s .= '<tr class="table-no-results"><td colspan="' . count($columnNames) . '">' . $this->getNoRowsMessage() . '</td></tr>';
1073 }
1074
1075 $s .= ' </table>' . "\n";
1076 $s .= '</form>' . "\n";
1077
1078 return $s;
1079 }
1080
1081 public function show()
1082 {
1083 echo $this->get();
1084 }
1085 }
1086