1 <?php
2
3 4 5 6 7 8 9 10
11
12 namespace Icybee;
13
14 use ICanBoogie\ActiveRecord\Model;
15 use ICanBoogie\ActiveRecord\Query;
16 use ICanBoogie\I18n;
17 use ICanBoogie\Operation;
18
19 use Brickrouge\Alert;
20 use Brickrouge\Button;
21 use Brickrouge\Element;
22 use Brickrouge\Form;
23 use Brickrouge\Ranger;
24 use Brickrouge\Text;
25
26 use Icybee\Element\ActionbarSearch;
27 use Icybee\ManageBlock\Column;
28 use Icybee\ManageBlock\Options;
29 use Icybee\ManageBlock\Translator;
30 use Icybee\Element\ActionbarContextual;
31
32
33
34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53
54 class ManageBlock extends Element
55 {
56 const DISCREET_PLACEHOLDER = '<span class="lighter">―</span>';
57
58 const T_BLOCK = '#manager-block';
59 const T_COLUMNS_ORDER = '#manager-columns-order';
60 const T_ORDER_BY = '#manager-order-by';
61
62
63
64
65
66 const ORDER_ASC = 'asc';
67 const ORDER_DESC = 'desc';
68
69 static protected function add_assets(\Brickrouge\Document $document)
70 {
71 parent::add_assets($document);
72
73 $document->js->add('manage.js', -170);
74 $document->css->add(\Icybee\ASSETS . 'css/manage.css', -170);
75
76 $document->js->add('manage/operations.js', -170);
77 }
78
79 80 81 82 83
84 public $module;
85
86 87 88 89 90
91 protected $model;
92
93 94 95 96 97
98 protected function get_model()
99 {
100 return $this->model;
101 }
102
103 104 105 106 107
108 protected $columns;
109
110 111 112 113 114
115 protected $records;
116
117 118 119 120 121
122 protected $count;
123
124 125 126 127 128
129 protected function get_primary_key()
130 {
131 return $this->model->primary;
132 }
133
134 135 136 137 138
139 protected $jobs = array();
140
141 protected $browse;
142
143 144 145 146 147
148 protected $t;
149
150 151 152 153 154
155 protected function get_t()
156 {
157 return $this->t;
158 }
159
160 161 162 163 164
165 protected $options;
166
167 168 169 170 171
172 protected function get_options()
173 {
174 return $this->options;
175 }
176
177 public function __construct(Module $module, array $attributes)
178 {
179
180
181 if (method_exists($this, 'get_query_conditions'))
182 {
183 throw new \Exception("The <q>get_query_conditions()</q> method is deprecated. Use <q>alter_query()</q> instead.");
184 }
185
186 if (method_exists($this, 'extend_column'))
187 {
188 throw new \Exception("The <q>extend_column()</q> method is deprecated. Define columns with classes.");
189 }
190
191 if (method_exists($this, 'extend_columns'))
192 {
193 throw new \Exception("The <q>extend_columns()</q> method is deprecated. Define columns with classes.");
194 }
195
196 if (method_exists($this, 'retrieve_options'))
197 {
198 throw new \Exception("The <q>retrieve_options()</q> method is deprecated. Use <q>resolve_options()</q>.");
199 }
200
201 if (method_exists($this, 'store_options'))
202 {
203 throw new \Exception("The <q>store_options()</q> method is deprecated. Use the Options instance.");
204 }
205
206 if (method_exists($this, 'alter_range_query'))
207 {
208 throw new \Exception("The <q>alter_range_query()</q> method is deprecated. Use columns and <q>alter_query_with_limit()</q>.");
209 }
210
211 if (method_exists($this, 'load_range'))
212 {
213 throw new \Exception("The <q>load_range()</q> method is deprecated. Use <q>fetch_records()</q>.");
214 }
215
216 if (method_exists($this, 'parseColumns'))
217 {
218 throw new \Exception("The <q>parseColumns()</q> method is deprecated. Use <q>resolve_columns()</q>.");
219 }
220
221 if (method_exists($this, 'columns'))
222 {
223 throw new \Exception("The <q>columns()</q> method is deprecated. Use <q>get_available_columns()</q>.");
224 }
225
226 if (method_exists($this, 'jobs'))
227 {
228 throw new \Exception("The <q>jobs()</q> method is deprecated. Use <q>get_available_jobs()</q>.");
229 }
230
231 if (method_exists($this, 'addJob'))
232 {
233 throw new \Exception("The <q>addJob()</q> method is deprecated. Use <q>resolve_jobs()</q>.");
234 }
235
236 if (method_exists($this, 'getJobs'))
237 {
238 throw new \Exception("The <q>getJobs()</q> method is deprecated. Use <q>render_jobs()</q>.");
239 }
240
241 if (method_exists($this, 'render_limiter'))
242 {
243 throw new \Exception("The <q>render_limiter()</q> method is deprecated. Use <q>render_controls()</q>.");
244 }
245
246 $class_reflection = new \ReflectionClass($this);
247
248 foreach ($class_reflection->getMethods() as $method_reflection)
249 {
250 if (strpos($method_reflection->name, 'extend_column_') === 0)
251 {
252 throw new \Exception("The <q>{$method_reflection->name}</q> method is deprecated. Use a column class.");
253 }
254
255 if (strpos($method_reflection->name, 'render_column_') === 0)
256 {
257 throw new \Exception("The <q>{$method_reflection->name}</q> method is deprecated. Use a column class.");
258 }
259
260 if (strpos($method_reflection->name, 'render_cell_') === 0)
261 {
262 throw new \Exception("The <q>{$method_reflection->name}</q> method is deprecated. Use a column class.");
263 }
264 }
265
266
267
268 parent::__construct('div', $attributes + array('class' => 'listview listview-interactive'));
269
270 $this->module = $module;
271 $this->model = $module->model;
272 $this->t = new Translator($module);
273 $this->columns = $this->get_columns();
274 $this->jobs = $this->get_jobs();
275 }
276
277 278 279 280 281
282 protected function get_available_columns()
283 {
284 $primary_key = $this->model->primary;
285
286 if ($primary_key)
287 {
288 return array($primary_key => 'Icybee\ManageBlock\KeyColumn');
289 }
290
291 return array();
292 }
293
294 protected function get_columns()
295 {
296 $columns = $this->get_available_columns();
297
298 new \Icybee\ManageBlock\RegisterColumnsEvent($this, $columns);
299
300 $columns = $this->resolve_columns($columns);
301
302 new \Icybee\ManageBlock\AlterColumnsEvent($this, $columns);
303
304 foreach ($columns as $column_id => $column)
305 {
306 if ($column instanceof Column)
307 {
308 continue;
309 }
310
311 throw new \UnexpectedValueException(\ICanBoogie\format
312 (
313 'Column %id must be an instance of Column. Given: %type. :data', array
314 (
315 '%id' => $column_id,
316 '%type' => gettype($column),
317 ':data' => $column
318 )
319 ));
320 }
321
322 return $columns;
323 }
324
325 protected function resolve_columns(array $columns)
326 {
327 $columns_order = $this[self::T_COLUMNS_ORDER];
328
329 if ($columns_order)
330 {
331 $primary = $this->model->primary;
332
333 if ($primary)
334 {
335 array_unshift($columns_order, $primary);
336 }
337
338 $columns_order = array_combine($columns_order, array_fill(0, count($columns_order), null));
339 $columns = array_intersect_key($columns, $columns_order);
340 $columns = array_merge($columns_order, $columns);
341 }
342
343 $resolved_columns = array();
344
345 foreach ($columns as $id => $options)
346 {
347 if ($options === null)
348 {
349 throw new \Exception(\ICanBoogie\format("Column %id is not defined.", array('id' => $id)));
350 }
351
352 $construct = __CLASS__ . '\Column';
353
354 if (is_string($options))
355 {
356 $construct = $options;
357 $options = array();
358 }
359
360 $resolved_columns[$id] = new $construct($this, $id, $options);
361 }
362
363 return $resolved_columns;
364 }
365
366 367 368 369 370
371 protected function get_available_jobs()
372 {
373 return array();
374 }
375
376 377 378 379 380
381 protected function get_jobs()
382 {
383 $jobs = $this->get_available_jobs();
384 $jobs = $this->resolve_jobs($jobs);
385
386 return $jobs;
387 }
388
389 390 391 392 393 394 395
396 protected function resolve_jobs(array $jobs)
397 {
398 if ($this->primary_key)
399 {
400 $jobs = array_merge(array(Module::OPERATION_DELETE => $this->t('delete.operation.short_title')), $jobs);
401 }
402
403 return $jobs;
404 }
405
406 407 408 409 410 411 412 413 414 415 416
417 protected function update_filters(array $filters, array $modifiers)
418 {
419 static $as_strings = array('char', 'varchar', 'date', 'datetime', 'timestamp');
420
421 $fields = $this->model->extended_schema['fields'];
422
423 foreach ($modifiers as $identifier => $value)
424 {
425 if (empty($fields[$identifier]))
426 {
427 continue;
428 }
429
430 $type = $fields[$identifier]['type'];
431
432 if ($type == 'boolean')
433 {
434 $value = $value === '' ? null : filter_var($value, FILTER_VALIDATE_BOOLEAN, FILTER_NULL_ON_FAILURE);
435 }
436 else if ($type == 'integer')
437 {
438 $value = filter_var($value, FILTER_VALIDATE_INT, FILTER_NULL_ON_FAILURE);
439 }
440 else if (in_array($type, $as_strings))
441 {
442 if ($value === '')
443 {
444 $value = null;
445 }
446 }
447 else continue;
448
449 if ($value === null)
450 {
451 unset($filters[$identifier]);
452
453 continue;
454 }
455
456 $filters[$identifier] = $value;
457 }
458
459 foreach ($this->columns as $id => $column)
460 {
461 $filters = $column->alter_filters($filters, $modifiers);
462 }
463
464 return $filters;
465 }
466
467 468 469 470 471 472 473 474 475 476 477 478
479 protected function update_options(Options $options, array $modifiers)
480 {
481 $modifiers['filters'] = $this->update_filters($options->filters, $modifiers);
482
483 return $options->update($modifiers);
484 }
485
486 487 488 489 490 491 492 493 494 495 496 497 498 499 500 501
502 protected function resolve_order($order_by, $order_direction)
503 {
504 $columns = $this->columns;
505 $default_order = $this[self::T_ORDER_BY];
506
507 if (!$order_by && $default_order)
508 {
509 list($order_by, $order_direction) = (array) $default_order + array(1 => 'desc');
510
511 $order_direction = ($order_direction == 'desc') ? -1 : 1;
512 }
513
514 if ($order_by && empty($columns[$order_by]))
515 {
516 \ICanBoogie\log_error("Undefined column for order: !order.", array('order' => $order_by));
517
518 $order_by = null;
519 $order_direction = null;
520 }
521
522 if (!$order_direction && isset($columns[$order_by]))
523 {
524 $order_direction = $columns[$order_by]->default_order;
525 }
526
527 return array($order_by, $order_direction);
528 }
529
530 531 532 533 534 535 536 537 538 539
540 protected function resolve_options($name, array $modifiers)
541 {
542 $options = new Options($name);
543 $options->retrieve();
544 $options = $this->update_options($options, $modifiers);
545
546 list($order_by, $order_direction) = $this->resolve_order($options->order_by, $options->order_direction);
547
548 $options->order_by = $order_by;
549 $options->order_direction = $order_direction;
550 $options->store();
551
552 return $options;
553 }
554
555 556 557 558 559 560
561 public function render()
562 {
563 global $core;
564
565 $options = $this->options = $this->resolve_options($this->module->flat_id, $core->request->params);
566 $order_by = $options->order_by;
567
568 if ($order_by)
569 {
570 $order_column = $this->columns[$order_by];
571 $order_column->order = $options->order_direction;
572 }
573
574 try
575 {
576 $query = $this->resolve_query($options);
577 $records = $this->fetch_records($query);
578
579 if ($records)
580 {
581 $records = $this->alter_records($records);
582 $this->records = array_values($records);
583 }
584 }
585 catch (\Exception $e)
586 {
587 $options->order_by = null;
588 $options->order_direction = null;
589 $options->filters = array();
590 $options->store();
591
592 $rendered_exception = \Brickrouge\render_exception($e);
593
594 return <<<EOT
595 <div class="alert alert-error alert-block undissmisable">
596 <p>There was an error in the SQL statement, orders and filters have been reseted,
597 please reload the page.</p>
598
599 $rendered_exception
600 </div>
601 EOT;
602 }
603
604 $html = parent::render();
605 $document = \Brickrouge\get_document();
606
607 foreach ($this->columns as $column)
608 {
609 $column->add_assets($document);
610 }
611
612 return $html;
613 }
614
615 616 617
618 protected function render_inner_html()
619 {
620 global $core;
621
622 $records = $this->records;
623 $options = $this->options;
624
625 if ($records || $options->filters)
626 {
627 if ($records)
628 {
629 $body = '<tbody>' . $this->render_body() . '</tbody>';
630 }
631 else
632 {
633 $body = '<tbody class="empty"><tr><td colspan="' . count($this->columns) . '">' . $this->render_empty_body() . '</td></tr></tbody>';
634 }
635
636 $head = $this->render_head();
637 $foot = $this->render_foot();
638
639 $content = <<<EOT
640 <table>
641 $head
642 $foot
643 $body
644 </table>
645 EOT;
646 }
647 else
648 {
649 $body = $this->render_empty_body();
650 $foot = $this->render_foot();
651 $columns_n = count($this->columns);
652
653 $content = <<<EOT
654 <table>
655 <tbody class="empty" td colspan="$columns_n">$body</tbody>
656 $foot
657 </table>
658 EOT;
659 }
660
661
662
663 $search = $this->render_search();
664
665 $core->events->attach(function(ActionbarSearch\AlterInnerHTMLEvent $event, ActionbarSearch $sender) use($search)
666 {
667 $event->html .= $search;
668 });
669
670
671
672 $rendered_jobs = $this->render_jobs($this->jobs);
673
674 $core->events->attach(function(ActionbarContextual\CollectItemsEvent $event, ActionbarContextual $target) use($rendered_jobs) {
675
676 $event->items[] = $rendered_jobs;
677
678 });
679
680
681
682 return $content;
683 }
684
685 686 687
688 protected function render_outer_html()
689 {
690 $html = parent::render_outer_html();
691
692 $operation_name = Operation::DESTINATION;
693 $operation_value = $this->module->id;
694
695 $block_name = self::T_BLOCK;
696 $block_value = $this[self::T_BLOCK] ?: 'manage';
697
698 return <<<EOT
699 <form id="manager" method="GET" action="">
700 <input type="hidden" name="{$operation_name}" value="{$operation_value}" />
701 <input type="hidden" name="{$block_name}" value="{$block_value}" />
702 $html
703 </form>
704 EOT;
705 }
706
707 708 709 710 711 712 713 714 715 716
717 protected function resolve_query(Options $options)
718 {
719 $query = new Query($this->model);
720 $query = $this->alter_query($query, $options->filters);
721
722
723
724 new ManageBlock\AlterQueryEvent($this, $query, $options);
725
726
727
728 $search = $options->search;
729
730 if ($search)
731 {
732 $query = $this->alter_query_with_search($query, $search);
733 }
734
735
736
737
738
739 $start = $options->start;
740 $count = $this->count = $query->count;
741
742 if ($start > $count)
743 {
744 $options->start = 1;
745 $options->store();
746 }
747 else if ($start < -$count)
748 {
749 $options->start = 1;
750 $options->store();
751 }
752 else if ($start < 0)
753 {
754 $start = -(-($start - 1) % $count) + $count;
755 $start = ceil($start / $options->limit) * $options->limit + 1;
756
757 $options->start = $start;
758 $options->store();
759 }
760
761 $order_by = $options->order_by;
762
763 if ($order_by)
764 {
765 $query = $this->columns[$order_by]->alter_query_with_order($query, $options->order_direction);
766 }
767
768 return $this->alter_query_with_range($query, $options->start - 1, $options->limit);
769 }
770
771 772 773 774 775 776 777 778 779 780
781 protected function alter_query(Query $query, array $filters)
782 {
783 foreach ($this->columns as $id => $column)
784 {
785 if (!isset($filters[$id]))
786 {
787 continue;
788 }
789
790 $query = $column->alter_query_with_filter($query, $filters[$id]);
791 }
792
793 return $query;
794 }
795
796 797 798 799 800 801 802 803
804 protected function alter_query_with_search(Query $query, $search)
805 {
806 static $supported_types = array('char', 'varchar', 'text');
807
808 $words = explode(' ', $search);
809 $words = array_map('trim', $words);
810
811 $queries = array();
812 $fields = $this->model->extended_schema['fields'];
813
814 foreach ($words as $word)
815 {
816 $concats = '';
817
818 foreach ($fields as $identifier => $definition)
819 {
820 $type = $definition['type'];
821
822 if (!in_array($type, $supported_types))
823 {
824 continue;
825 }
826
827 if (!empty($definition['null']))
828 {
829 $identifier = "IFNULL(`$identifier`, \"\")";
830 }
831
832 $concats .= ', `' . $identifier . '`';
833 }
834
835 if (!$concats)
836 {
837 continue;
838 }
839
840 $query->where('CONCAT_WS(" ", ' . substr($concats, 2) . ') LIKE ?', "%{$word}%");
841 }
842
843 return $query;
844 }
845
846 847 848 849 850 851 852 853 854
855 protected function alter_query_with_range(Query $query, $offset, $limit)
856 {
857 return $query->limit($offset, $limit);
858 }
859
860 861 862 863 864
865 protected function fetch_records(Query $query)
866 {
867 return $query->all;
868 }
869
870 871 872 873 874 875 876 877 878 879
880 protected function alter_records(array $records)
881 {
882 foreach ($this->columns as $column)
883 {
884 $records = $column->alter_records($records);
885 }
886
887 return $records;
888 }
889
890 891 892 893 894
895 protected function render_head()
896 {
897 $cells = '';
898
899 foreach ($this->columns as $id => $column)
900 {
901 $cells .= $this->render_column($column, $id);
902 }
903
904 return <<<EOT
905 <thead>
906 <tr>$cells</tr>
907 </thead>
908 EOT;
909 }
910
911 912 913 914 915 916 917 918
919 protected function render_column(Column $column, $id)
920 {
921 $class = 'header--' . \Brickrouge\normalize($id) . ' ' . $column->class;
922
923 if ($this->count > 1 || $this->options->filters || $this->options->search)
924 {
925 $orderable = $column->orderable;
926
927 if ($orderable)
928 {
929 $class .= ' orderable';
930 }
931
932 $filtering = $column->is_filtering;
933
934 if ($filtering)
935 {
936 $class .= ' filtering';
937 }
938
939 $filters = $column->filters;
940
941 if ($filters)
942 {
943 $class .= ' filters';
944 }
945 }
946 else
947 {
948 $orderable = false;
949 $filtering = false;
950 $filters = array();
951 }
952
953 $header_options = $column->render_options();
954
955 if ($header_options)
956 {
957 $class .= ' has-options';
958 }
959
960 $header = $column->render_header();
961
962 if (!$header)
963 {
964 $class .= ' has-no-label';
965 }
966
967 $class = trim($class);
968
969 return <<<EOT
970 <th class="$class"><div>{$header}{$header_options}</div></th>
971 EOT;
972 }
973
974 975 976 977 978 979 980 981 982 983 984
985 protected function render_columns_cells(array $columns)
986 {
987 $rendered_columns_cells = array();
988
989 foreach ($columns as $id => $column)
990 {
991 foreach ($this->records as $record)
992 {
993 try
994 {
995 $content = (string) $column->render_cell($record);
996 }
997 catch (\Exception $e)
998 {
999 $content = \Brickrouge\render_exception($e);
1000 }
1001
1002 $rendered_columns_cells[$id][] = $content;
1003 }
1004 }
1005
1006 return $rendered_columns_cells;
1007 }
1008
1009 1010 1011 1012 1013 1014 1015
1016 protected function apply_discreet_filter(array $rendered_columns_cells)
1017 {
1018 $discreet_column_cells = $rendered_columns_cells;
1019 $columns = $this->columns;
1020
1021 foreach ($discreet_column_cells as $id => &$cells)
1022 {
1023 $column = $columns[$id];
1024
1025 if (!$column->discreet)
1026 {
1027 continue;
1028 }
1029
1030 $last_content = null;
1031
1032 foreach ($cells as &$content)
1033 {
1034 if ($last_content !== $content || !$content)
1035 {
1036 $last_content = $content;
1037
1038 continue;
1039 }
1040
1041 $content = self::DISCREET_PLACEHOLDER;
1042 }
1043 }
1044
1045 return $discreet_column_cells;
1046 }
1047
1048 1049 1050 1051 1052 1053 1054
1055 protected function columns_to_rows(array $rendered_columns_cells)
1056 {
1057 $rows = array();
1058
1059 foreach ($rendered_columns_cells as $column_id => $cells)
1060 {
1061 foreach ($cells as $i => $cell)
1062 {
1063 $rows[$i][$column_id] = $cell;
1064 }
1065 }
1066
1067 return $rows;
1068 }
1069
1070 1071 1072 1073 1074 1075 1076 1077 1078
1079 protected function render_rows(array $rows)
1080 {
1081 global $core;
1082
1083 $rendered_rows = array();
1084 $columns = $this->columns;
1085 $records = $this->records;
1086 $key = $this->primary_key;
1087 $module = $this->module;
1088 $user = $core->user;
1089
1090 foreach ($rows as $i => $cells)
1091 {
1092 $html = '';
1093
1094 foreach ($cells as $column_id => $cell)
1095 {
1096 $html .= '<td class="'
1097 . trim('cell--' . \Brickrouge\normalize($column_id) . ' ' . $columns[$column_id]->class)
1098 . '">' . ($cell ?: ' ') . '</td>';
1099 }
1100
1101 $tr = new Element('tr', array(Element::INNER_HTML => $html));
1102
1103 if ($key && !$user->has_ownership($module, $records[$i]))
1104 {
1105 $tr->add_class('no-ownership');
1106 }
1107
1108 $rendered_rows[] = $tr;
1109 }
1110
1111 return $rendered_rows;
1112 }
1113
1114 1115 1116 1117 1118
1119 protected function render_body()
1120 {
1121 $rendered_cells = $this->render_columns_cells($this->columns);
1122
1123 new ManageBlock\AlterRenderedCellsEvent($this, $rendered_cells, $this->records);
1124
1125 $rendered_cells = $this->apply_discreet_filter($rendered_cells);
1126 $rows = $this->columns_to_rows($rendered_cells);
1127 $rendered_rows = $this->render_rows($rows);
1128
1129 return implode(PHP_EOL, $rendered_rows);
1130 }
1131
1132 1133 1134 1135 1136
1137 protected function render_empty_body()
1138 {
1139 $search = $this->options->search;
1140 $filters = $this->options->filters;
1141 $context = null;
1142
1143 if ($search)
1144 {
1145 $message = $this->t('Your search <q><strong>!search</strong></q> did not match any record.<br /><br /><a href="?q=" rel="manager/search" data-action="reset" class="btn btn-warning">Reset search filter</a>', array('!search' => $search));
1146 }
1147 else if ($filters)
1148 {
1149
1150
1151 $filters = implode(', ', $filters);
1152 $message = $this->t('Your selection <q><strong>!selection</strong></q> dit not match any record.', array('!selection' => $filters));
1153 }
1154 else
1155 {
1156 $message = $this->t('create_first', array('!url' => \ICanBoogie\Routing\contextualize("/admin/{$this->module->id}/new")));
1157 $context = 'info';
1158 }
1159
1160 return new Alert($message, array(Alert::CONTEXT => $context, 'class' => 'alert listview-alert'));
1161 }
1162
1163 1164 1165 1166 1167
1168 protected function render_search()
1169 {
1170 $search = $this->options->search;
1171
1172 return new Element
1173 (
1174 'div', array
1175 (
1176 Element::CHILDREN => array
1177 (
1178 'q' => new Text
1179 (
1180 array
1181 (
1182 'title' => $this->t('Search in the records'),
1183 'value' => $search,
1184 'size' => '16',
1185 'class' => 'search',
1186 'tabindex' => 0,
1187
1188 'data-placeholder' => $this->t('Search')
1189 )
1190 ),
1191
1192 new Button
1193 (
1194 '', array
1195 (
1196 'type' => 'button',
1197 'class' => 'icon-remove'
1198 )
1199 )
1200 ),
1201
1202 'class' => 'listview-search'
1203 )
1204 );
1205 }
1206
1207 1208 1209 1210 1211
1212 protected function render_controls()
1213 {
1214 $count = $this->count;
1215 $start = $this->options->start;
1216 $limit = $this->options->limit;
1217
1218 if ($count <= 10)
1219 {
1220 $content = $this->t($this->is_filtering || $this->options->search ? "records_count_with_filters" : "records_count", array(':count' => $count));
1221
1222 return <<<EOT
1223 <div class="listview-controls">
1224 $content
1225 </div>
1226 EOT;
1227 }
1228
1229 $ranger = new Ranger
1230 (
1231 'div', array
1232 (
1233 Ranger::T_START => $start,
1234 Ranger::T_LIMIT => $limit,
1235 Ranger::T_COUNT => $count,
1236 Ranger::T_EDITABLE => true,
1237 Ranger::T_NO_ARROWS => true,
1238
1239 'class' => 'listview-start'
1240 )
1241 );
1242
1243 $page_limit_selector = null;
1244
1245 if ($limit >= 20 || $count >= $limit)
1246 {
1247 $page_limit_selector = new Element
1248 (
1249 'select', array
1250 (
1251 Element::OPTIONS => array(10 => 10, 20 => 20, 50 => 50, 100 => 100),
1252
1253 'title' => $this->t('Number of item to display by page'),
1254 'name' => 'limit',
1255 'onchange' => 'this.form.submit()',
1256 'value' => $limit
1257 )
1258 );
1259
1260 $page_limit_selector = '<div class="listview-limit">' . $this->t(':page_limit_selector by page', array(':page_limit_selector' => (string) $page_limit_selector)) . '</div>';
1261 }
1262
1263 $browse = null;
1264
1265 if ($count > $limit)
1266 {
1267 $browse = <<<EOT
1268 <div class="listview-browse">
1269 <a href="?start=previous" class="browse previous" rel="manager"><i class="icon-arrow-left"></i></a>
1270 <a href="?start=next" class="browse next" rel="manager"><i class="icon-arrow-right"></i></a>
1271 </div>
1272 EOT;
1273 }
1274
1275 $this->browse = $browse;
1276
1277
1278
1279 return <<<EOT
1280 <div class="listview-controls">
1281 {$ranger}{$page_limit_selector}{$browse}
1282 </div>
1283 EOT;
1284 }
1285
1286 1287 1288 1289 1290 1291 1292
1293 protected function render_jobs(array $jobs)
1294 {
1295 if (!$jobs)
1296 {
1297 return;
1298 }
1299
1300 $children = array();
1301
1302 foreach ($jobs as $operation => $label)
1303 {
1304 $children[] = new Button($label, array('data-operation' => $operation, 'data-target' => 'manager'));
1305 }
1306
1307 return new Element
1308 (
1309 'div', array
1310 (
1311 Element::CHILDREN => array
1312 (
1313 '<i class="icon-warning-sign context-icon"></i>',
1314
1315 1316 1317 1318 1319 1320 1321 1322 1323 1324 1325
1326
1327 new Element
1328 (
1329 'div', array
1330 (
1331 Element::CHILDREN => $children,
1332
1333 'class' => 'btn-group'
1334 )
1335 )
1336 ),
1337
1338 'data-actionbar-context' => 'operations',
1339 'class' => 'listview-operations inline'
1340 )
1341 );
1342 }
1343
1344 1345 1346 1347 1348
1349 protected function ()
1350 {
1351 $ncolumns = count($this->columns);
1352 $key_column = $this->primary_key ? '<td class="key"> </td>' : '';
1353 $rendered_jobs = null;
1354 $rendered_controls = $this->render_controls();
1355
1356 return <<<EOT
1357 <tfoot>
1358 <tr>
1359 $key_column
1360 <td colspan="{$ncolumns}">{$rendered_jobs}{$rendered_controls}</td>
1361 </tr>
1362 </tfoot>
1363 EOT;
1364 }
1365
1366 1367 1368 1369 1370 1371 1372 1373
1374 public function is_filtering($column_id=null)
1375 {
1376 return $this->options->is_filtering($column_id);
1377 }
1378
1379 protected function get_is_filtering()
1380 {
1381 return $this->is_filtering();
1382 }
1383 }
1384
1385 1386 1387
1388
1389 namespace Icybee\ManageBlock;
1390
1391 use ICanBoogie\ActiveRecord\Query;
1392 use ICanBoogie\Event;
1393
1394 use Icybee\ManageBlock;
1395 use Icybee\ManageBlock\Options;
1396
1397 1398 1399
1400 class RegisterColumnsEvent extends Event
1401 {
1402 1403 1404 1405 1406
1407 public $columns;
1408
1409 1410 1411 1412 1413 1414
1415 public function __construct(ManageBlock $target, array &$columns)
1416 {
1417 $this->columns = &$columns;
1418
1419 parent::__construct($target, 'register_columns');
1420 }
1421
1422 public function add(Column $column, $weight=null)
1423 {
1424 if ($weight)
1425 {
1426 list($position, $relative) = explode(':', $weight) + array('before');
1427
1428 $this->columns = \ICanBoogie\array_insert($this->columns, $relative, $column, $column->id, $position == 'after');
1429 }
1430 else
1431 {
1432 $this->columns[$column->id] = $column;
1433 }
1434 }
1435 }
1436
1437 1438 1439
1440 class AlterColumnsEvent extends Event
1441 {
1442 1443 1444 1445 1446
1447 public $columns;
1448
1449 1450 1451 1452 1453 1454
1455 public function __construct(ManageBlock $target, array &$columns)
1456 {
1457 $this->columns = &$columns;
1458
1459 parent::__construct($target, 'alter_columns');
1460 }
1461
1462 public function add(Column $column, $weight=null)
1463 {
1464 if ($weight)
1465 {
1466 list($position, $relative) = explode(':', $weight) + array('before');
1467
1468 $this->columns = \ICanBoogie\array_insert($this->columns, $relative, $column, $column->id, $position == 'after');
1469 }
1470 else
1471 {
1472 $this->columns[$column->id] = $column;
1473 }
1474 }
1475 }
1476
1477 class AlterRenderedCellsEvent extends Event
1478 {
1479 1480 1481 1482 1483
1484 public $rendered_cells;
1485
1486 1487 1488 1489 1490
1491 public $records;
1492
1493 public function __construct(ManageBlock $target, array &$rendered_cells, array $records)
1494 {
1495 $this->rendered_cells = &$rendered_cells;
1496 $this->records = $records;
1497
1498 parent::__construct($target, 'alter_rendered_cells');
1499 }
1500 }
1501
1502 class AlterQueryEvent extends Event
1503 {
1504 public $query;
1505
1506 public $options;
1507
1508 public function __construct(ManageBlock $target, Query $query, Options $options)
1509 {
1510 $this->query = $query;
1511 $this->options = $options;
1512
1513 parent::__construct($target, 'alter_query');
1514 }
1515 }