SortableTable::__construct()   F
last analyzed

Complexity

Conditions 21
Paths > 20000

Size

Total Lines 116
Code Lines 74

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 21
eloc 74
c 0
b 0
f 0
nc 25600
nop 8
dl 0
loc 116
rs 0

How to fix   Long Method    Complexity    Many Parameters   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
/* For licensing terms, see /license.txt */
4
5
use ChamiloSession as Session;
6
7
/**
8
 * This class allows you to display a sortable data-table. It is possible to
9
 * split the data in several pages.
10
 * Using this class you can:
11
 * - automatically create checkboxes of the first table column
12
 *     - a "select all" and "deselect all" link is added
13
 *     - only if you provide a list of actions for the selected items
14
 * - click on the table header to sort the data
15
 * - choose how many items you see per page
16
 * - navigate through all data-pages.
17
 */
18
class SortableTable extends HTML_Table
19
{
20
    /**
21
     * A name for this table.
22
     */
23
    public $table_name;
24
    /**
25
     * The page to display.
26
     */
27
    public $page_nr;
28
    /**
29
     * The column to sort the data.
30
     */
31
    public $column;
32
    /**
33
     * The sorting direction (ASC or DESC).
34
     */
35
    public $direction;
36
    /**
37
     * Number of items to display per page.
38
     */
39
    public $per_page;
40
    /**
41
     * The default number of items to display per page.
42
     */
43
    public $default_items_per_page;
44
    /**
45
     * A prefix for the URL-parameters, can be used on pages with multiple
46
     * SortableTables.
47
     */
48
    public $param_prefix;
49
    /**
50
     * The pager object to split the data in several pages.
51
     */
52
    public $pager;
53
    /**
54
     * The total number of items in the table.
55
     */
56
    public $total_number_of_items;
57
    /**
58
     * The function to get the total number of items.
59
     */
60
    public $get_total_number_function;
61
    /**
62
     * The function to the the data to display.
63
     */
64
    public $get_data_function;
65
    /**
66
     * An array with defined column-filters.
67
     */
68
    public $column_filters;
69
    /**
70
     * A list of actions which will be available through a select list.
71
     */
72
    public $form_actions;
73
    /**
74
     * Additional parameters to pass in the URL.
75
     */
76
    public $additional_parameters;
77
    /**
78
     * Additional attributes for the th-tags.
79
     */
80
    public $th_attributes;
81
    /**
82
     * Additional attributes for the td-tags.
83
     */
84
    public $td_attributes;
85
    /**
86
     * Array with names of the other tables defined on the same page of this
87
     * table.
88
     */
89
    public $other_tables;
90
    /**
91
     * Activates the odd even rows.
92
     * */
93
    public $odd_even_rows_enabled = true;
94
    public $use_jqgrid = false;
95
    public $table_id = null;
96
    public $headers = [];
97
    public $actionButtons = [];
98
    /**
99
     * The array containing all data for this table.
100
     */
101
    public $table_data;
102
    public $hideItemSelector;
103
    // Hide table navigation, better to be use when exporting table to PDF.
104
    public $hideNavigation = false;
105
106
    /**
107
     * @var array Columns to hide
108
     */
109
    private $columnsToHide = [];
110
    private $dataFunctionParams;
111
    private $defaultColumn;
112
    private $defaultItemsPerPage;
113
    private ?string $checkbox_name;
114
115
    /**
116
     * Create a new SortableTable.
117
     *
118
     * @param string $table_name                A name for the table (default = 'table')
119
     * @param string $get_total_number_function A user defined function to get
120
     *                                          the total number of items in the table
121
     * @param string $get_data_function         A function to get the data to display on
122
     *                                          the current page
123
     * @param int    $default_column            The default column on which the data should be
124
     *                                          sorted
125
     * @param int    $default_items_per_page    The default number of items to show
126
     *                                          on one page
127
     * @param string $default_order_direction   The default order direction;
128
     *                                          either the constant 'ASC' or 'DESC'
129
     * @param string $table_id
130
     * @param array  $attributes                They are custom attributes of the table
131
     */
132
    public function __construct(
133
        $table_name = 'table',
134
        $get_total_number_function = null,
135
        $get_data_function = null,
136
        $default_column = 1,
137
        $default_items_per_page = 20,
138
        $default_order_direction = 'ASC',
139
        $table_id = null,
140
        $attributes = []
141
    ) {
142
        if (empty($table_id)) {
143
            $table_id = $table_name.uniqid('table', true);
144
        }
145
146
        if (empty($attributes)) {
147
            $attributes = [];
148
            $attributes['class'] = 'table table-hover table-striped table-bordered data_table';
149
            //$attributes['class'] = 'q-table';
150
            $attributes['id'] = $table_id;
151
        }
152
153
        $this->table_id = $table_id;
154
        parent::__construct($attributes);
155
        $this->table_name = $table_name;
156
        $this->additional_parameters = [];
157
        $this->param_prefix = $table_name.'_';
158
        $this->defaultColumn = (int) $default_column;
159
        $this->defaultItemsPerPage = $default_items_per_page;
160
        $this->hideItemSelector = false;
161
162
        $defaultRow = (int) api_get_setting('display.table_default_row');
163
        if ($defaultRow > 0) {
164
            $this->defaultItemsPerPage = $default_items_per_page = $defaultRow;
165
        }
166
167
        $cleanSessionData = Session::read('clean_sortable_table');
168
        if (true === $cleanSessionData) {
169
            $this->cleanUrlSessionParams();
170
        }
171
        // Allow to change paginate in multiples tabs
172
        $this->per_page = Session::read($this->param_prefix.'per_page', $default_items_per_page);
173
174
        // If per page changed, then reset the page to 1
175
        if (!empty($this->per_page) && isset($_GET[$this->param_prefix.'per_page']) &&
176
            $this->per_page != $_GET[$this->param_prefix.'per_page']
177
        ) {
178
            Session::erase($this->param_prefix.'page_nr');
179
            $_GET[$this->param_prefix.'page_nr'] = 1;
180
        }
181
182
        $this->per_page = isset($_GET[$this->param_prefix.'per_page'])
183
            ? (int) $_GET[$this->param_prefix.'per_page']
184
            : $this->per_page;
185
186
        if (isset($_GET[$this->param_prefix.'per_page'])) {
187
            Session::erase($this->param_prefix.'page_nr');
188
        }
189
190
        $this->page_nr = Session::read($this->param_prefix.'page_nr', 1);
191
        $this->page_nr = isset($_GET[$this->param_prefix.'page_nr'])
192
            ? (int) $_GET[$this->param_prefix.'page_nr']
193
            : $this->page_nr;
194
195
        $this->column = Session::read($this->param_prefix.'column', $default_column);
196
        $this->column = isset($_GET[$this->param_prefix.'column'])
197
            ? (int) $_GET[$this->param_prefix.'column']
198
            : $this->column;
199
200
        // Default direction.
201
        if (in_array(strtoupper($default_order_direction), ['ASC', 'DESC'])) {
202
            $this->direction = $default_order_direction;
203
        }
204
205
        $my_session_direction = Session::read($this->param_prefix.'direction');
206
        if (!empty($my_session_direction)) {
207
            if (!in_array($my_session_direction, ['ASC', 'DESC'])) {
208
                $this->direction = 'ASC';
209
            } else {
210
                if ('ASC' === $my_session_direction) {
211
                    $this->direction = 'ASC';
212
                } elseif ('DESC' === $my_session_direction) {
213
                    $this->direction = 'DESC';
214
                }
215
            }
216
        }
217
218
        if (isset($_GET[$this->param_prefix.'direction'])) {
219
            $my_get_direction = $_GET[$this->param_prefix.'direction'];
220
            if (!in_array($my_get_direction, ['ASC', 'DESC'])) {
221
                $this->direction = 'ASC';
222
            } else {
223
                if ('ASC' === $my_get_direction) {
224
                    $this->direction = 'ASC';
225
                } elseif ('DESC' === $my_get_direction) {
226
                    $this->direction = 'DESC';
227
                }
228
            }
229
        }
230
231
        Session::write($this->param_prefix.'per_page', $this->per_page);
232
        Session::write($this->param_prefix.'direction', $this->direction);
233
        Session::write($this->param_prefix.'page_nr', $this->page_nr);
234
        Session::write($this->param_prefix.'column', $this->column);
235
236
        $this->pager = null;
237
        $this->default_items_per_page = $default_items_per_page;
238
        $this->total_number_of_items = -1;
239
        $this->get_total_number_function = $get_total_number_function;
240
        $this->get_data_function = $get_data_function;
241
        $this->column_filters = [];
242
        $this->form_actions = [];
243
        $this->checkbox_name = null;
244
        $this->td_attributes = [];
245
        $this->th_attributes = [];
246
        $this->other_tables = [];
247
        $this->dataFunctionParams = [];
248
    }
249
250
    /**
251
     * Clean URL params when changing student view.
252
     */
253
    public function cleanUrlSessionParams()
254
    {
255
        Session::erase('clean_sortable_table');
256
257
        $prefix = $this->param_prefix;
258
259
        Session::erase($prefix.'page_nr');
260
        Session::erase($prefix.'column');
261
        Session::erase($prefix.'direction');
262
        Session::erase($prefix.'per_page');
263
264
        $_GET[$this->param_prefix.'per_page'] = $this->default_items_per_page;
265
        $_GET[$this->param_prefix.'page_nr'] = 1;
266
        $_GET[$this->param_prefix.'column'] = $this->defaultColumn;
267
        $_GET[$this->param_prefix.'direction'] = $this->direction;
268
    }
269
270
    /**
271
     * @return array
272
     */
273
    public function getDataFunctionParams()
274
    {
275
        return $this->dataFunctionParams;
276
    }
277
278
    /**
279
     * @param array $dataFunctionParams
280
     *
281
     * @return $this
282
     */
283
    public function setDataFunctionParams($dataFunctionParams)
284
    {
285
        $this->dataFunctionParams = $dataFunctionParams;
286
287
        return $this;
288
    }
289
290
    /**
291
     * Get the Pager object to split the showed data in several pages.
292
     *
293
     * @return Pager_Sliding
294
     */
295
    public function get_pager()
296
    {
297
        if (null === $this->pager) {
298
            $params['mode'] = 'Sliding';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$params was never initialized. Although not strictly required by PHP, it is generally a good practice to add $params = array(); before regardless.
Loading history...
299
            $params['perPage'] = $this->per_page;
300
            $params['totalItems'] = $this->get_total_number_of_items();
301
            $params['urlVar'] = $this->param_prefix.'page_nr';
302
            $params['currentPage'] = $this->page_nr;
303
            $icon_attributes = ['style' => 'vertical-align: middle;'];
304
            $params['prevImg'] = Display::getMdiIcon('step-backward');
305
            $params['nextImg'] = Display::getMdiIcon('step-forward');
306
            $params['firstPageText'] = Display::getMdiIcon('step-backward-2');
307
            $params['lastPageText'] = Display::getMdiIcon('step-forward-2');
308
            $params['firstPagePre'] = '';
309
            $params['lastPagePre'] = '';
310
            $params['firstPagePost'] = '';
311
            $params['lastPagePost'] = '';
312
            $params['spacesBeforeSeparator'] = '';
313
            $params['spacesAfterSeparator'] = '';
314
            $params['curPageLinkClassName'] = 'ch-pager';
315
            $query_vars = array_keys($_GET);
316
            $query_vars_needed = [
317
                $this->param_prefix.'column',
318
                $this->param_prefix.'direction',
319
                $this->param_prefix.'per_page',
320
            ];
321
            if (!empty($this->additional_parameters) && count($this->additional_parameters) > 0) {
322
                $query_vars_needed = array_merge(
323
                    $query_vars_needed,
324
                    array_keys($this->additional_parameters)
325
                );
326
            }
327
            $query_vars_exclude = array_diff($query_vars, $query_vars_needed);
328
            $params['excludeVars'] = $query_vars_exclude;
329
            $this->pager = &Pager::factory($params);
330
        }
331
332
        return $this->pager;
333
    }
334
335
    /**
336
     * Display the table.
337
     */
338
    public function display()
339
    {
340
        echo $this->return_table();
341
    }
342
343
    public function toArray()
344
    {
345
        $headers = array_column($this->getHeaders(), 'label');
346
347
        return array_merge([$headers], $this->table_data);
348
    }
349
350
    /**
351
     * Displays the table, complete with navigation buttons to browse through
352
     * the data-pages.
353
     */
354
    public function return_table()
355
    {
356
        $empty_table = false;
357
        $content = $this->get_table_html();
358
        if (0 == $this->get_total_number_of_items()) {
359
            $cols = $this->getColCount();
360
            $this->setCellAttributes(
361
                1,
362
                0,
363
                'style="font-style: italic;text-align:center;" colspan='.$cols
364
            );
365
            $message_empty = api_xml_http_response_encode(get_lang('Empty'));
366
            $this->setCellContents(1, 0, $message_empty);
367
            $empty_table = true;
368
        }
369
370
        if ($empty_table) {
371
            return '';
372
        }
373
374
        $params = $this->get_sortable_table_param_string().'&amp;'.$this->get_additional_url_paramstring();
375
        $table_id = 'form_'.$this->table_name.'_id';
376
        $html = '';
377
        $form = '';
378
        $nav = '';
379
        if (false === $this->hideNavigation) {
380
            // Only show pagination if there are more than 1 page.
381
            if ($this->get_pager()->numPages() > 1) {
382
                $form = $this->get_page_select_form();
383
                $nav = $this->get_navigation_html();
384
                $html = '<div class="q-card sortable-buttons-actions">';
385
                $html .= '<div class="flex flex-row justify-between pager-bar">';
386
                $html .= '<div class="col">';
387
                $html .= '<div class="pb-2 pt-2 pager-select">'.$form.'</div>';
388
                $html .= '</div>';
389
                $html .= '<div class="col">';
390
                $html .= '<div class="row justify-center pager-counter">'.$this->get_table_title().'</div>';
391
                $html .= '</div>';
392
                $html .= '<div class="col">';
393
                $html .= '<div class="row justify-end pager-jumper">'.$nav.'</div>';
394
                $html .= '</div>';
395
                $html .= '</div>';
396
                $html .= '</div>';
397
            }
398
        }
399
400
        if (count($this->form_actions) > 0) {
401
            $params = $this->get_sortable_table_param_string().'&amp;'.$this->get_additional_url_paramstring();
402
            $html .= '<form
403
                id ="'.$table_id.'"
404
                class="form-search"
405
                method="post"
406
                action="'.api_get_self().'?'.$params.'"
407
                name="form_'.$this->table_name.'">';
408
        }
409
410
        //$html .= '<div class="table-responsive">'.$content.'</div>';
411
        $html .= '<div class="sortable-container">';
412
        $html .= $content.'</div>';
413
414
        if (!empty($this->additional_parameters)) {
415
            foreach ($this->additional_parameters as $key => $value) {
416
                $html .= '<input type="hidden" name="'.Security::remove_XSS($key).'" value ="'
417
                    .Security::remove_XSS($value).'" />';
418
            }
419
        }
420
        $html .= '<input type="hidden" name="action">';
421
        $html .= '<div class="flex q-card p-2 mb-4 sortable-buttons-actions">';
422
        $html .= '<div class="flex w-full items-center justify-between">';
423
424
        if (count($this->actionButtons) > 0) {
425
            $html .= '<div class="btn-toolbar flex space-x-2">';
426
            $html .= '<div class="btn-group">';
427
428
            foreach ($this->actionButtons as $action => $data) {
429
                $label = $data['label'];
430
                $icon = $data['icon'];
431
                $html .= '<a class="btn btn-default" href="?'.$params.'&action_table='.$action.'">'.$icon.'&nbsp;'.$label.'</a>';
432
            }
433
            $html .= '</div>';
434
            $html .= '</div>';
435
        }
436
437
        if (count($this->form_actions) > 0) {
438
            $html .= '<div class="flex space-x-2">';
439
            $html .= '<a class="btn btn--action mr-2" href="?'.$params.'&amp;'.$this->param_prefix.'selectall=1" onclick="javascript: setCheckbox(true, \''.$table_id.'\'); return false;">'
440
                .get_lang('Select all').'</a>';
441
            $html .= '<a class="btn btn--action mr-2" href="?'.$params.'" onclick="javascript: setCheckbox(false, \''.$table_id.'\'); return false;">'
442
                .get_lang('Deselect all').'</a>';
443
444
            $items = [];
445
            foreach ($this->form_actions as $action => $label) {
446
                $items[] = [
447
                    'href' => 'javascript:void();',
448
                    'onclick' => "javascript:action_click(this, '$table_id');",
449
                    'title' => $label,
450
                    'data-action' => $action,
451
                    'data-confirm' => addslashes(api_htmlentities(get_lang("Please confirm your choice"))),
452
                ];
453
            }
454
            $html .= Display::groupButtonWithDropDown(get_lang('Action'), $items);
455
        } else {
456
            $html .= $form;
457
        }
458
459
        $html .= '</div>';
460
461
        // Pagination
462
        if ($this->get_total_number_of_items() > $this->default_items_per_page) {
463
            $html .= '<div class="flex justify-end mt-4 w-full">';
464
            $html .= '<div class="page-nav pb-2 pt-2">'.$nav.'</div>';
465
            $html .= '</div>';
466
        }
467
468
        $html .= '</div>'; // btn-group
469
        $html .= '</div>'; // sortable-buttons-actions
470
        if (count($this->form_actions) > 0) {
471
            $html .= '</form>';
472
        }
473
474
        return $html;
475
    }
476
477
    /**
478
     * This function shows the content of a table in a grid.
479
     * Should not be use to edit information (edit/delete rows) only.
480
     */
481
    public function display_grid()
482
    {
483
        $empty_table = false;
484
        if (0 == $this->get_total_number_of_items()) {
485
            $message_empty = api_xml_http_response_encode(get_lang('Empty'));
486
            $this->setCellContents(1, 0, $message_empty);
487
            $empty_table = true;
488
        }
489
        $html = '';
490
        if (!$empty_table) {
491
            $form = $this->get_page_select_form();
492
            $nav = $this->get_navigation_html();
493
494
            // @todo This style css must be moved to default.css only for dev
495
            echo '<style>
496
                    .main-grid { width:100%;}
497
                    .sub-header { width:100%; padding-top: 10px; padding-right: 10px; padding-left: 10px; height:30px;}
498
                    .grid_container { width:100%;}
499
                    .grid_item { height: 120px; width:98px;  float:left; padding:5px; margin:8px;}
500
                    .grid_element_0 { width:100px; height: 100px; float:left; text-align:center; margin-bottom:5px;}
501
                    .grid_element_1 { width:100px; float:left; text-align:center;margin-bottom:5px;}
502
                    .grid_element_2 { width:150px; float:left;}
503
                    .grid_selectbox { width:30%; float:left;}
504
                    .grid_title     { width:30%; float:left;}
505
                    .grid_nav         { }
506
            </style>';
507
508
            // @todo  This also must be moved
509
            // Show only navigations if there are more than 1 page
510
            $my_pager = $this->get_pager();
511
            $html .= '<div class="main-grid">';
512
            if ($my_pager->numPages() > 1) {
513
                $html .= '<div class="sub-header">';
514
                $html .= '<div class="grid_selectbox">'.$form.'</div>';
515
                $html .= '<div class="grid_title">'.$this->get_table_title().'</div>';
516
                $html .= '<div class="grid_nav">'.$nav.'</div>';
517
                $html .= '</div>';
518
            }
519
520
            $html .= '<div class="clear"></div>';
521
            if (count($this->form_actions) > 0) {
522
                $params = $this->get_sortable_table_param_string().'&amp;'.$this->get_additional_url_paramstring();
523
                $html .= '<form method="post" action="'.api_get_self().'?'.$params
524
                    .'" name="form_'.$this->table_name.'">';
525
            }
526
        }
527
        // Getting the items of the table
528
        $items = $this->get_clean_html(false); //no sort
529
530
        // Generation of style classes must be improved. Maybe we need a a table name to create style on the fly:
531
        // i.e: .whoisonline_table_grid_container instead of  .grid_container
532
        // where whoisonline is the table's name like drupal's template engine
533
        $html .= '<div class="grid_container">';
534
        if (is_array($items) && count($items) > 0) {
535
            foreach ($items as &$row) {
536
                $html .= '<div class="grid_item">';
537
                $i = 0;
538
                foreach ($row as &$element) {
539
                    $html .= '<div class="grid_element_'.$i.'">'.$element.'</div>';
540
                    $i++;
541
                }
542
                $html .= '</div>';
543
            }
544
        }
545
        $html .= '</div>'; //close grid_container
546
        $html .= '</div>'; //close main grid
547
        $html .= '<div class="clear"></div>';
548
549
        echo $html;
550
    }
551
552
    /**
553
     * This function returns the content of a table in a grid
554
     * Should not be use to edit information (edit/delete rows) only.
555
     *
556
     * @param array      options of visibility
557
     * @param bool       hide navigation optionally
558
     * @param int        content per page when show navigation (optional)
559
     * @param bool       sort data optionally
560
     *
561
     * @return string grid html
562
     */
563
    public function display_simple_grid(
564
        $visibility_options,
565
        $hide_navigation = true,
566
        $per_page = 20,
567
        $sort_data = true,
568
        $grid_class = []
569
    ) {
570
        $empty_table = false;
571
        if (0 == $this->get_total_number_of_items()) {
572
            $message_empty = api_xml_http_response_encode(get_lang('Empty'));
573
            $this->setCellContents(1, 0, $message_empty);
574
            $empty_table = true;
575
        }
576
        $html = '';
577
        if (!$empty_table) {
578
            // If we show the pagination
579
            if (!$hide_navigation) {
580
                $form = '&nbsp;';
581
582
                if ($this->get_total_number_of_items() > $per_page) {
583
                    if ($per_page > 10) {
584
                        $form = $this->get_page_select_form();
585
                    }
586
                    $nav = $this->get_navigation_html();
587
                    // This also must be moved
588
                    $html = '<div class="sub-header">';
589
                    $html .= '<div class="grid_selectbox">'.$form.'</div>';
590
                    $html .= '<div class="grid_title">'.$this->get_table_title().'</div>';
591
                    $html .= '<div class="grid_nav">'.$nav.'</div>';
592
                    $html .= '</div>';
593
                }
594
            }
595
596
            $html .= '<div class="clear"></div>';
597
            if (count($this->form_actions) > 0) {
598
                $params = $this->get_sortable_table_param_string().'&amp;'.$this->get_additional_url_paramstring();
599
                $html .= '<form method="post" action="'.api_get_self().'?'.$params
600
                    .'" name="form_'.$this->table_name.'">';
601
            }
602
        }
603
604
        if ($hide_navigation) {
605
            $items = $this->table_data; // This is a faster way to get what we want
606
        } else {
607
            // The normal way
608
            $items = $this->get_clean_html($sort_data); // Getting the items of the table
609
        }
610
611
        // Generation of style classes must be improved. Maybe we need a a table name to create style on the fly:
612
        // i.e: .whoisonline_table_grid_container instead of  .grid_container
613
        // where whoisonline is the table's name like drupal's template engine
614
615
        if (is_array($visibility_options)) {
616
            $filter = false; // The 2nd condition of the if will be loaded
617
        } else {
618
            $filter = false !== $visibility_options;
619
        }
620
621
        $item_css_class = $item_css_style = $grid_css_class = $grid_css_style = '';
622
        if (!empty($grid_class)) {
623
            $grid_css_class = $grid_class['main']['class'];
624
            $item_css_class = $grid_class['item']['class'];
625
626
            $grid_css_style = isset($grid_class['main']['style']) ? $grid_class['main']['style'] : null;
627
            $item_css_style = isset($grid_class['item']['style']) ? $grid_class['item']['style'] : null;
628
        }
629
630
        $div = '';
631
        if (is_array($items) && count($items) > 0) {
632
            foreach ($items as &$row) {
633
                $i = 0;
634
                $rows = '';
635
                foreach ($row as &$element) {
636
                    if ($filter || isset($visibility_options[$i]) && $visibility_options[$i]
637
                    ) {
638
                        $rows .= '<div class="'.$this->table_name.'_grid_element_'.$i.'">'.$element.'</div>';
639
                    }
640
                    $i++;
641
                }
642
                $div .= Display::div(
643
                    $rows,
644
                    [
645
                        'class' => $item_css_class.' '.$this->table_name.'_grid_item',
646
                        'style' => $item_css_style,
647
                    ]
648
                );
649
            }
650
        }
651
652
        $html .= Display::div(
653
            $div,
654
            [
655
                'class' => $grid_css_class.' '.$this->table_name.'_grid_container',
656
                'style' => $grid_css_style,
657
            ]
658
        );
659
        $html .= '<div class="clear"></div>';
660
661
        return $html;
662
    }
663
664
    /**
665
     * Get the HTML-code with the navigational buttons to browse through the
666
     * data-pages.
667
     */
668
    public function get_navigation_html()
669
    {
670
        $pager = $this->get_pager();
671
        $pager_links = $pager->getLinks();
672
        $nav = $pager_links['first'].' '.$pager_links['back'];
673
        $nav .= ' '.$pager->getCurrentPageId().' / '.$pager->numPages().' ';
674
        $nav .= $pager_links['next'].' '.$pager_links['last'];
675
676
        return $nav;
677
    }
678
679
    /**
680
     * Get the HTML-code with the data-table.
681
     */
682
    public function get_table_html()
683
    {
684
        $pager = $this->get_pager();
685
        $offset = $pager->getOffsetByPageId();
686
        $from = $offset[0] - 1;
687
        $table_data = $this->get_table_data($from, $this->per_page, $this->column);
688
        $this->processHeaders();
689
        if (is_array($table_data)) {
690
            $count = 1;
691
            foreach ($table_data as &$row) {
692
                $row = $this->filter_data($row);
693
                $newRow = [];
694
                if (!empty($this->columnsToHide)) {
695
                    $counter = 0;
696
                    foreach ($row as $index => $rowInfo) {
697
                        if (!isset($this->columnsToHide[$index])) {
698
                            $newRow[$counter] = $rowInfo;
699
                            $counter++;
700
                        }
701
                    }
702
                    $row = $newRow;
703
                }
704
                $this->addRow($row);
705
                if (isset($row['child_of'])) {
706
                    $this->setRowAttributes(
707
                        $count,
708
                        ['class' => 'hidden hidden_'.$row['child_of']],
709
                        true
710
                    );
711
                }
712
                $count++;
713
            }
714
        }
715
716
        if (true == $this->odd_even_rows_enabled) {
0 ignored issues
show
Coding Style Best Practice introduced by
It seems like you are loosely comparing two booleans. Considering using the strict comparison === instead.

When comparing two booleans, it is generally considered safer to use the strict comparison operator.

Loading history...
717
            $this->altRowAttributes(
718
                0,
719
                ['class' => 'row_odd'],
720
                ['class' => 'row_even'],
721
                true
722
            );
723
        }
724
725
        foreach ($this->th_attributes as $column => $attributes) {
726
            $this->setCellAttributes(0, $column, $attributes);
727
        }
728
        foreach ($this->td_attributes as $column => $attributes) {
729
            $this->setColAttributes($column, $attributes);
730
        }
731
732
        return $this->toHTML();
733
    }
734
735
    /**
736
     * This function return the items of the table.
737
     *
738
     * @param bool true for sorting table data or false otherwise
739
     *
740
     * @return array table row items
741
     */
742
    public function get_clean_html($sort = true)
743
    {
744
        $pager = $this->get_pager();
745
        $offset = $pager->getOffsetByPageId();
746
        $from = $offset[0] - 1;
747
        $table_data = $this->get_table_data($from, null, null, null, $sort);
748
        $new_table_data = [];
749
        if (is_array($table_data)) {
750
            foreach ($table_data as $index => &$row) {
751
                $row = $this->filter_data($row);
752
                $new_table_data[] = $row;
753
            }
754
        }
755
756
        return $new_table_data;
757
    }
758
759
    /**
760
     * Get the HTML-code which represents a form to select how many items a page
761
     * should contain.
762
     */
763
    public function get_page_select_form(): string
764
    {
765
        $total_number_of_items = $this->get_total_number_of_items();
766
        if ($total_number_of_items <= $this->default_items_per_page) {
767
            return '';
768
        }
769
770
        if (true === $this->hideItemSelector) {
771
            return '';
772
        }
773
774
        $result[] = '<form method="GET" action="'.api_get_self().'" style="display:inline;">';
0 ignored issues
show
Comprehensibility Best Practice introduced by
$result was never initialized. Although not strictly required by PHP, it is generally a good practice to add $result = array(); before regardless.
Loading history...
775
        $param[$this->param_prefix.'direction'] = $this->direction;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$param was never initialized. Although not strictly required by PHP, it is generally a good practice to add $param = array(); before regardless.
Loading history...
776
        $param[$this->param_prefix.'page_nr'] = $this->page_nr;
777
        $param[$this->param_prefix.'column'] = $this->column;
778
779
        if (is_array($this->additional_parameters)) {
780
            $param = array_merge($param, $this->additional_parameters);
781
        }
782
783
        foreach ($param as $key => $value) {
784
            $result[] = '<input type="hidden" name="'.$key.'" value="'.$value.'"/>';
785
        }
786
        $result[] = '<select style="width: auto;" class="form-control" name="'.$this->param_prefix
787
            .'per_page" onchange="javascript: this.form.submit();">';
788
        $list = [10, 20, 50, 100, 500, 1000];
789
790
        $rowList = api_get_setting('display.table_row_list', true);
791
        if (is_array($rowList) && isset($rowList['options'])) {
792
            $list = $rowList['options'];
793
        }
794
795
        foreach ($list as $nr) {
796
            if ($total_number_of_items <= $nr) {
797
                break;
798
            }
799
            $result[] = '<option value="'.$nr.'" '.($nr == $this->per_page ? 'selected="selected"' : '').'>'.$nr
800
                .'</option>';
801
        }
802
803
        $result[] = '<option value="'.$total_number_of_items.'" '
804
            .($total_number_of_items == $this->per_page ? 'selected="selected"' : '')
805
            .'>'.api_ucfirst(get_lang('All')).'</option>';
806
807
        $result[] = '</select>';
808
        $result[] = '<noscript>';
809
        $result[] = '<button class="btn btn--success" type="submit">'.get_lang('Save').'</button>';
810
        $result[] = '</noscript>';
811
        $result[] = '</form>';
812
813
        return implode("\n", $result);
814
    }
815
816
    /**
817
     * Get the table title.
818
     */
819
    public function get_table_title()
820
    {
821
        $pager = $this->get_pager();
822
        $showed_items = $pager->getOffsetByPageId();
823
824
        return $showed_items[0].' - '.$showed_items[1].' / '.$this->get_total_number_of_items();
825
    }
826
827
    /**
828
     * @return array
829
     */
830
    public function getHeaders()
831
    {
832
        return $this->headers;
833
    }
834
835
    /**
836
     * Process headers.
837
     */
838
    public function processHeaders()
839
    {
840
        $counter = 0;
841
        foreach ($this->headers as $column => $columnInfo) {
842
            $label = $columnInfo['label'];
843
            $sortable = $columnInfo['sortable'];
844
            $th_attributes = $columnInfo['th_attributes'];
845
            $td_attributes = $columnInfo['td_attributes'];
846
847
            if (!empty($this->columnsToHide)) {
848
                if (isset($this->columnsToHide[$column])) {
849
                    continue;
850
                }
851
            }
852
853
            $column = $counter;
854
            $param['direction'] = 'ASC';
855
            if ($this->column == $column && 'ASC' == $this->direction) {
856
                $param['direction'] = 'DESC';
857
            }
858
859
            $param['page_nr'] = $this->page_nr;
860
            $param['per_page'] = $this->per_page;
861
            $param['column'] = $column;
862
            $link = $label;
863
            if ($sortable) {
864
                $link = '<a href="'.api_get_self().'?'.api_get_cidreq().'&amp;';
865
                foreach ($param as $key => &$value) {
866
                    $link .= $this->param_prefix.$key.'='.urlencode($value).'&amp;';
867
                }
868
                $link .= $this->get_additional_url_paramstring();
869
                $link .= '">'.$label.'</a>';
870
                if ($this->column == $column) {
871
                    $link .= 'ASC' == $this->direction ? ' &#8595;' : ' &#8593;';
872
                }
873
            }
874
            $this->setHeaderContents(0, $column, $link);
875
876
            if (!is_null($td_attributes)) {
877
                $this->td_attributes[$column] = $td_attributes;
878
            }
879
            if (!is_null($th_attributes)) {
880
                $this->th_attributes[$column] = $th_attributes;
881
            }
882
883
            $counter++;
884
        }
885
    }
886
887
    /**
888
     * Set the header-label.
889
     *
890
     * @param int    $column        The column number
891
     * @param string $label         The label
892
     * @param bool   $sortable      Is the table sortable by this column? (defatult
893
     *                              = true)
894
     * @param array  $th_attributes Additional attributes for the th-tag of the
895
     *                              table header
896
     * @param string $td_attributes Additional attributes for the td-tags of the
897
     *                              column
898
     */
899
    public function set_header(
900
        $column,
901
        $label,
902
        $sortable = true,
903
        $th_attributes = ['class' => 'th-header'],
904
        $td_attributes = null
905
    ) {
906
        $this->headers[$column] = [
907
            'label' => $label,
908
            'sortable' => $sortable,
909
            'th_attributes' => $th_attributes,
910
            'td_attributes' => $td_attributes,
911
        ];
912
    }
913
914
    /**
915
     * Get the parameter-string with additional parameters to use in the URLs
916
     * generated by this SortableTable.
917
     */
918
    public function get_additional_url_paramstring()
919
    {
920
        $param_string_parts = [];
921
        if (is_array($this->additional_parameters) && count($this->additional_parameters) > 0) {
922
            foreach ($this->additional_parameters as $key => $value) {
923
                $param_string_parts[] = urlencode($key).'='.urlencode($value);
924
            }
925
        }
926
        $result = implode('&amp;', $param_string_parts);
927
        foreach ($this->other_tables as $index => &$tablename) {
928
            $param = [];
929
            if (isset($_GET[$tablename.'_direction'])) {
930
                $my_get_direction = $_GET[$tablename.'_direction'];
931
                if (!in_array($my_get_direction, ['ASC', 'DESC'])) {
932
                    $param[$tablename.'_direction'] = 'ASC';
933
                } else {
934
                    $param[$tablename.'_direction'] = $my_get_direction;
935
                }
936
            }
937
            if (isset($_GET[$tablename.'_page_nr'])) {
938
                $param[$tablename.'_page_nr'] = intval($_GET[$tablename.'_page_nr']);
939
            }
940
            if (isset($_GET[$tablename.'_per_page'])) {
941
                $param[$tablename.'_per_page'] = intval($_GET[$tablename.'_per_page']);
942
            }
943
            if (isset($_GET[$tablename.'_column'])) {
944
                $param[$tablename.'_column'] = intval($_GET[$tablename.'_column']);
945
            }
946
            $param_string_parts = [];
947
            foreach ($param as $key => &$value) {
948
                $param_string_parts[] = urlencode($key).'='.urlencode($value);
949
            }
950
            if (count($param_string_parts) > 0) {
951
                $result .= '&amp;'.implode('&amp;', $param_string_parts);
952
            }
953
        }
954
955
        return $result;
956
    }
957
958
    /**
959
     * Get the parameter-string with the SortableTable-related parameters to use
960
     * in URLs.
961
     */
962
    public function get_sortable_table_param_string()
963
    {
964
        $param[$this->param_prefix.'direction'] = $this->direction;
0 ignored issues
show
Comprehensibility Best Practice introduced by
$param was never initialized. Although not strictly required by PHP, it is generally a good practice to add $param = array(); before regardless.
Loading history...
965
        $param[$this->param_prefix.'page_nr'] = $this->page_nr;
966
        $param[$this->param_prefix.'per_page'] = $this->per_page;
967
        $param[$this->param_prefix.'column'] = $this->column;
968
        $param_string_parts = [];
969
        foreach ($param as $key => &$value) {
970
            $param_string_parts[] = urlencode($key).'='.urlencode($value);
971
        }
972
        $res = implode('&amp;', $param_string_parts);
973
974
        return $res;
975
    }
976
977
    /**
978
     * Add a filter to a column. If another filter was already defined for the
979
     * given column, it will be overwritten.
980
     *
981
     * @param int    $column     The number of the column
982
     * @param callable $function The name of the filter-function. This should be a
983
     *                           function wich requires 1 parameter and returns the filtered value.
984
     */
985
    public function set_column_filter($column, $function)
986
    {
987
        $this->column_filters[$column] = $function;
988
    }
989
990
    /**
991
     * List of columns to hide.
992
     *
993
     * @param int $column
994
     */
995
    public function setHideColumn($column)
996
    {
997
        $this->columnsToHide[$column] = $column;
998
    }
999
1000
    /**
1001
     * Define a list of actions which can be performed on the table-date.
1002
     * If you define a list of actions, the first column of the table will be
1003
     * converted into checkboxes.
1004
     *
1005
     * @param array  $actions       A list of actions. The key is the name of the
1006
     *                              action. The value is the label to show in the select-box
1007
     * @param string $checkbox_name The name of the generated checkboxes. The
1008
     *                              value of the checkbox will be the value of the first column.
1009
     */
1010
    public function set_form_actions($actions, $checkbox_name = 'id')
1011
    {
1012
        $this->form_actions = $actions;
1013
        $this->checkbox_name = $checkbox_name;
1014
    }
1015
1016
    /**
1017
     * Define a list of additional parameters to use in the generated URLs
1018
     * <code>$parameters['action'] = 'test'; will be convert in
1019
     * <input type="hidden" name="action" value="test"></code>.
1020
     *
1021
     * @param array $parameters
1022
     */
1023
    public function set_additional_parameters(array $parameters)
1024
    {
1025
        $this->additional_parameters = $parameters;
1026
    }
1027
1028
    /**
1029
     * Set other tables on the same page.
1030
     * If you have other sortable tables on the page displaying this sortable
1031
     * tables, you can define those other tables with this function. If you
1032
     * don't define the other tables, there sorting and pagination will return
1033
     * to their default state when sorting this table.
1034
     *
1035
     * @param array $tablenames an array of table names
1036
     */
1037
    public function set_other_tables($tablenames)
1038
    {
1039
        $this->other_tables = $tablenames;
1040
    }
1041
1042
    /**
1043
     * Transform all data in a table-row, using the filters defined by the
1044
     * function set_column_filter(...) defined elsewhere in this class.
1045
     * If you've defined actions, the first element of the given row will be
1046
     * converted into a checkbox.
1047
     *
1048
     * @param array $row a row from the table
1049
     *
1050
     * @return array
1051
     */
1052
    public function filter_data($row)
1053
    {
1054
        $url_params = $this->get_sortable_table_param_string().'&'.$this->get_additional_url_paramstring();
1055
        foreach ($this->column_filters as $column => &$function) {
1056
            $firstParam = isset($row[$column]) ? $row[$column] : 0;
1057
            $row[$column] = call_user_func($function, $firstParam, $url_params, $row);
1058
        }
1059
        if (count($this->form_actions) > 0) {
1060
            if (strlen($row[0]) > 0) {
1061
                $row[0] = '<div class="checkbox" ><label><input type="checkbox" name="'.$this->checkbox_name.'[]" value="'.$row[0].'"';
1062
                if (isset($_GET[$this->param_prefix.'selectall'])) {
1063
                    $row[0] .= ' checked="checked"';
1064
                }
1065
                $row[0] .= '/><span class="checkbox-material"><span class="check"></span></span></label></div>';
1066
            }
1067
        }
1068
        if (is_array($row)) {
1069
            foreach ($row as &$value) {
1070
                if (empty($value)) {
1071
                    $value = '-';
1072
                }
1073
            }
1074
        }
1075
1076
        return $row;
1077
    }
1078
1079
    /**
1080
     * Get the total number of items. This function calls the function given as
1081
     * 2nd argument in the constructor of a SortableTable. Make sure your
1082
     * function has the same parameters as defined here.
1083
     */
1084
    public function get_total_number_of_items()
1085
    {
1086
        if (-1 == $this->total_number_of_items && !is_null($this->get_total_number_function)) {
1087
            $this->total_number_of_items = call_user_func(
1088
                $this->get_total_number_function,
1089
                $this->getDataFunctionParams()
1090
            );
1091
        }
1092
1093
        return $this->total_number_of_items;
1094
    }
1095
1096
    /**
1097
     * @param int $value
1098
     */
1099
    public function setTotalNumberOfItems($value)
1100
    {
1101
        $this->total_number_of_items = (int) $value;
1102
    }
1103
1104
    /**
1105
     * Get the data to display.  This function calls the function given as
1106
     * 2nd argument in the constructor of a SortableTable. Make sure your
1107
     * function has the same parameters as defined here.
1108
     *
1109
     * @param int    $from      index of the first item to return
1110
     * @param int    $perPage   The number of items to return
1111
     * @param int    $column    The number of the column on which the data should be
1112
     * @param bool   $sort      Whether to sort or not
1113
     *                          sorted
1114
     * @param string $direction In which order should the data be sorted (ASC
1115
     *                          or DESC)
1116
     *
1117
     * @return array
1118
     */
1119
    public function get_table_data(
1120
        $from = null,
1121
        $perPage = null,
1122
        $column = null,
1123
        $direction = null,
1124
        $sort = null
1125
    ) {
1126
        $data = [];
1127
        if (null !== $this->get_data_function) {
1128
            $data = call_user_func(
1129
                $this->get_data_function,
1130
                $from,
1131
                $this->per_page,
1132
                $this->column,
1133
                $this->direction,
1134
                $this->dataFunctionParams
1135
            );
1136
        }
1137
1138
        return $data;
1139
    }
1140
1141
    /**
1142
     * @param array $data
1143
     */
1144
    public function setTableData($data)
1145
    {
1146
        $this->table_data = $data;
1147
    }
1148
}
1149