MDL-40481 quiz responses report needs cols need text_sorting
[moodle.git] / lib / tablelib.php
CommitLineData
72fb21b6 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
17
18/**
78bfb562
PS
19 * @package core
20 * @subpackage lib
21 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
22 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
72fb21b6 23 */
24
9683db71 25
78bfb562
PS
26defined('MOODLE_INTERNAL') || die();
27
4b7079c1
TH
28/**#@+
29 * These constants relate to the table's handling of URL parameters.
30 */
e2ba85e9 31define('TABLE_VAR_SORT', 1);
32define('TABLE_VAR_HIDE', 2);
33define('TABLE_VAR_SHOW', 3);
34define('TABLE_VAR_IFIRST', 4);
35define('TABLE_VAR_ILAST', 5);
36define('TABLE_VAR_PAGE', 6);
4b7079c1 37/**#@-*/
e2ba85e9 38
4b7079c1
TH
39/**#@+
40 * Constants that indicate whether the paging bar for the table
41 * appears above or below the table.
42 */
43define('TABLE_P_TOP', 1);
44define('TABLE_P_BOTTOM', 2);
45/**#@-*/
20e1f1c0 46
9683db71 47
72fb21b6 48/**
49 * @package moodlecore
50 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
51 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
52 */
7270d81f 53class flexible_table {
54
55 var $uniqueid = NULL;
56 var $attributes = array();
57 var $headers = array();
58 var $columns = array();
59 var $column_style = array();
554906f6 60 var $column_class = array();
7270d81f 61 var $column_suppress = array();
353f3f20 62 var $column_nosort = array('userpic');
1e4bd9fe 63 private $column_textsort = array();
7270d81f 64 var $setup = false;
65 var $sess = NULL;
66 var $baseurl = NULL;
e2ba85e9 67 var $request = array();
7270d81f 68
69 var $is_collapsible = false;
70 var $is_sortable = false;
71 var $use_pages = false;
72 var $use_initials = false;
73
73e4f589 74 var $maxsortkeys = 2;
75 var $pagesize = 30;
76 var $currpage = 0;
77 var $totalrows = 0;
fdf70473 78 var $currentrow = 0;
a5a3f48a 79 var $sort_default_column = NULL;
80 var $sort_default_order = SORT_ASC;
9cf4a18b 81
20e1f1c0 82 /**
83 * Array of positions in which to display download controls.
84 */
85 var $showdownloadbuttonsat= array(TABLE_P_TOP);
86
1f9ca535 87 /**
88 * @var string Key of field returned by db query that is the id field of the
89 * user table or equivalent.
90 */
91 public $useridfield = 'id';
92
20e1f1c0 93 /**
94 * @var string which download plugin to use. Default '' means none - print
95 * html table with paging. Property set by is_downloading which typically
96 * passes in cleaned data from $
97 */
98 var $download = '';
9cf4a18b 99
20e1f1c0 100 /**
ba9164e5 101 * @var bool whether data is downloadable from table. Determines whether
20e1f1c0 102 * to display download buttons. Set by method downloadable().
103 */
104 var $downloadable = false;
9cf4a18b 105
20e1f1c0 106 /**
107 * @var string which download plugin to use. Default '' means none - print
108 * html table with paging.
109 */
110 var $defaultdownloadformat = 'csv';
9cf4a18b 111
20e1f1c0 112 /**
ba9164e5 113 * @var bool Has start output been called yet?
20e1f1c0 114 */
115 var $started_output = false;
9cf4a18b 116
20e1f1c0 117 var $exportclass = null;
9cf4a18b 118
561d406b 119 /**
120 * Constructor
ba9164e5
TH
121 * @param int $uniqueid all tables have to have a unique id, this is used
122 * as a key when storing table properties like sort order in the session.
561d406b 123 */
a49c17b4 124 function __construct($uniqueid) {
7270d81f 125 $this->uniqueid = $uniqueid;
e2ba85e9 126 $this->request = array(
ba9164e5
TH
127 TABLE_VAR_SORT => 'tsort',
128 TABLE_VAR_HIDE => 'thide',
129 TABLE_VAR_SHOW => 'tshow',
130 TABLE_VAR_IFIRST => 'tifirst',
131 TABLE_VAR_ILAST => 'tilast',
132 TABLE_VAR_PAGE => 'page',
e2ba85e9 133 );
7270d81f 134 }
20e1f1c0 135
136 /**
137 * Call this to pass the download type. Use :
138 * $download = optional_param('download', '', PARAM_ALPHA);
139 * To get the download type. We assume that if you call this function with
140 * params that this table's data is downloadable, so we call is_downloadable
141 * for you (even if the param is '', which means no download this time.
142 * Also you can call this method with no params to get the current set
143 * download type.
144 * @param string $download download type. One of csv, tsv, xhtml, ods, etc
145 * @param string $filename filename for downloads without file extension.
146 * @param string $sheettitle title for downloaded data.
147 * @return string download type. One of csv, tsv, xhtml, ods, etc
148 */
9683db71
TH
149 function is_downloading($download = null, $filename='', $sheettitle='') {
150 if ($download!==null) {
20e1f1c0 151 $this->sheettitle = $sheettitle;
152 $this->is_downloadable(true);
153 $this->download = $download;
43ec99aa 154 $this->filename = clean_filename($filename);
155 $this->export_class_instance();
20e1f1c0 156 }
157 return $this->download;
158 }
117bd748 159
ba9164e5
TH
160 /**
161 * Get, and optionally set, the export class.
162 * @param $exportclass (optional) if passed, set the table to use this export class.
163 * @return table_default_export_format_parent the export class in use (after any set).
164 */
165 function export_class_instance($exportclass = null) {
9683db71 166 if (!is_null($exportclass)) {
869309b8 167 $this->started_output = true;
ba9164e5
TH
168 $this->exportclass = $exportclass;
169 $this->exportclass->table = $this;
170 } else if (is_null($this->exportclass) && !empty($this->download)) {
43ec99aa 171 $classname = 'table_'.$this->download.'_export_format';
172 $this->exportclass = new $classname($this);
9683db71 173 if (!$this->exportclass->document_started()) {
43ec99aa 174 $this->exportclass->start_document($this->filename);
175 }
176 }
177 return $this->exportclass;
178 }
117bd748 179
20e1f1c0 180 /**
181 * Probably don't need to call this directly. Calling is_downloading with a
182 * param automatically sets table as downloadable.
9cf4a18b 183 *
ba9164e5 184 * @param bool $downloadable optional param to set whether data from
20e1f1c0 185 * table is downloadable. If ommitted this function can be used to get
186 * current state of table.
ba9164e5 187 * @return bool whether table data is set to be downloadable.
20e1f1c0 188 */
9683db71
TH
189 function is_downloadable($downloadable = null) {
190 if ($downloadable !== null) {
20e1f1c0 191 $this->downloadable = $downloadable;
192 }
193 return $this->downloadable;
194 }
9cf4a18b 195
20e1f1c0 196 /**
197 * Where to show download buttons.
198 * @param array $showat array of postions in which to show download buttons.
199 * Containing TABLE_P_TOP and/or TABLE_P_BOTTOM
200 */
9683db71 201 function show_download_buttons_at($showat) {
20e1f1c0 202 $this->showdownloadbuttonsat = $showat;
203 }
9cf4a18b 204
561d406b 205 /**
9cf4a18b 206 * Sets the is_sortable variable to the given boolean, sort_default_column to
561d406b 207 * the given string, and the sort_default_order to the given integer.
208 * @param bool $bool
209 * @param string $defaultcolumn
210 * @param int $defaultorder
211 * @return void
212 */
a5a3f48a 213 function sortable($bool, $defaultcolumn = NULL, $defaultorder = SORT_ASC) {
7270d81f 214 $this->is_sortable = $bool;
a5a3f48a 215 $this->sort_default_column = $defaultcolumn;
216 $this->sort_default_order = $defaultorder;
7270d81f 217 }
218
1e4bd9fe
DW
219 /**
220 * Use text sorting functions for this column (required for text columns with Oracle).
94ffbef1
TH
221 * Be warned that you cannot use this with column aliases. You can only do this
222 * with real columns. See MDL-40481 for an example.
1e4bd9fe
DW
223 * @param string column name
224 */
225 function text_sorting($column) {
226 $this->column_textsort[] = $column;
227 }
228
353f3f20 229 /**
230 * Do not sort using this column
231 * @param string column name
232 */
233 function no_sorting($column) {
234 $this->column_nosort[] = $column;
235 }
236
237 /**
238 * Is the column sortable?
239 * @param string column name, null means table
240 * @return bool
241 */
9683db71 242 function is_sortable($column = null) {
353f3f20 243 if (empty($column)) {
244 return $this->is_sortable;
245 }
246 if (!$this->is_sortable) {
247 return false;
248 }
249 return !in_array($column, $this->column_nosort);
250 }
9683db71 251
561d406b 252 /**
253 * Sets the is_collapsible variable to the given boolean.
254 * @param bool $bool
255 * @return void
256 */
7270d81f 257 function collapsible($bool) {
258 $this->is_collapsible = $bool;
259 }
260
561d406b 261 /**
262 * Sets the use_pages variable to the given boolean.
263 * @param bool $bool
264 * @return void
265 */
7270d81f 266 function pageable($bool) {
267 $this->use_pages = $bool;
268 }
269
561d406b 270 /**
271 * Sets the use_initials variable to the given boolean.
272 * @param bool $bool
273 * @return void
274 */
7270d81f 275 function initialbars($bool) {
276 $this->use_initials = $bool;
277 }
278
561d406b 279 /**
280 * Sets the pagesize variable to the given integer, the totalrows variable
281 * to the given integer, and the use_pages variable to true.
282 * @param int $perpage
283 * @param int $total
284 * @return void
285 */
7270d81f 286 function pagesize($perpage, $total) {
287 $this->pagesize = $perpage;
288 $this->totalrows = $total;
289 $this->use_pages = true;
290 }
291
561d406b 292 /**
293 * Assigns each given variable in the array to the corresponding index
294 * in the request class variable.
295 * @param array $variables
296 * @return void
297 */
e2ba85e9 298 function set_control_variables($variables) {
9683db71
TH
299 foreach ($variables as $what => $variable) {
300 if (isset($this->request[$what])) {
e2ba85e9 301 $this->request[$what] = $variable;
302 }
303 }
304 }
305
561d406b 306 /**
307 * Gives the given $value to the $attribute index of $this->attributes.
308 * @param string $attribute
309 * @param mixed $value
310 * @return void
311 */
7270d81f 312 function set_attribute($attribute, $value) {
313 $this->attributes[$attribute] = $value;
314 }
315
561d406b 316 /**
9cf4a18b 317 * What this method does is set the column so that if the same data appears in
901e25d4 318 * consecutive rows, then it is not repeated.
9cf4a18b 319 *
901e25d4 320 * For example, in the quiz overview report, the fullname column is set to be suppressed, so
321 * that when one student has made multiple attempts, their name is only printed in the row
322 * for their first attempt.
ba9164e5 323 * @param int $column the index of a column.
561d406b 324 */
7270d81f 325 function column_suppress($column) {
9683db71 326 if (isset($this->column_suppress[$column])) {
7270d81f 327 $this->column_suppress[$column] = true;
328 }
329 }
330
561d406b 331 /**
332 * Sets the given $column index to the given $classname in $this->column_class.
ba9164e5 333 * @param int $column
561d406b 334 * @param string $classname
335 * @return void
336 */
554906f6 337 function column_class($column, $classname) {
9683db71 338 if (isset($this->column_class[$column])) {
554906f6 339 $this->column_class[$column] = ' '.$classname; // This space needed so that classnames don't run together in the HTML
340 }
341 }
342
561d406b 343 /**
344 * Sets the given $column index and $property index to the given $value in $this->column_style.
ba9164e5 345 * @param int $column
561d406b 346 * @param string $property
347 * @param mixed $value
348 * @return void
349 */
7270d81f 350 function column_style($column, $property, $value) {
9683db71 351 if (isset($this->column_style[$column])) {
7270d81f 352 $this->column_style[$column][$property] = $value;
353 }
354 }
355
561d406b 356 /**
20e1f1c0 357 * Sets all columns' $propertys to the given $value in $this->column_style.
ba9164e5 358 * @param int $property
561d406b 359 * @param string $value
360 * @return void
361 */
7270d81f 362 function column_style_all($property, $value) {
9683db71 363 foreach (array_keys($this->columns) as $column) {
7270d81f 364 $this->column_style[$column][$property] = $value;
365 }
366 }
367
561d406b 368 /**
59f392b3
TH
369 * Sets $this->baseurl.
370 * @param moodle_url|string $url the url with params needed to call up this page
561d406b 371 */
7270d81f 372 function define_baseurl($url) {
59f392b3 373 $this->baseurl = new moodle_url($url);
7270d81f 374 }
375
561d406b 376 /**
20e1f1c0 377 * @param array $columns an array of identifying names for columns. If
378 * columns are sorted then column names must correspond to a field in sql.
561d406b 379 */
7270d81f 380 function define_columns($columns) {
381 $this->columns = array();
382 $this->column_style = array();
554906f6 383 $this->column_class = array();
7270d81f 384 $colnum = 0;
385
9683db71 386 foreach ($columns as $column) {
7270d81f 387 $this->columns[$column] = $colnum++;
388 $this->column_style[$column] = array();
554906f6 389 $this->column_class[$column] = '';
7270d81f 390 $this->column_suppress[$column] = false;
391 }
392 }
393
561d406b 394 /**
20e1f1c0 395 * @param array $headers numerical keyed array of displayed string titles
396 * for each column.
561d406b 397 */
7270d81f 398 function define_headers($headers) {
399 $this->headers = $headers;
400 }
9cf4a18b 401
561d406b 402 /**
20e1f1c0 403 * Must be called after table is defined. Use methods above first. Cannot
404 * use functions below till after calling this method.
561d406b 405 * @return type?
406 */
7270d81f 407 function setup() {
408 global $SESSION, $CFG;
409
9683db71 410 if (empty($this->columns) || empty($this->uniqueid)) {
7270d81f 411 return false;
412 }
1e42af27 413
0ce8ba58 414 if (!isset($SESSION->flextable)) {
415 $SESSION->flextable = array();
7270d81f 416 }
1e42af27 417
9683db71 418 if (!isset($SESSION->flextable[$this->uniqueid])) {
0ce8ba58 419 $SESSION->flextable[$this->uniqueid] = new stdClass;
420 $SESSION->flextable[$this->uniqueid]->uniqueid = $this->uniqueid;
421 $SESSION->flextable[$this->uniqueid]->collapse = array();
422 $SESSION->flextable[$this->uniqueid]->sortby = array();
423 $SESSION->flextable[$this->uniqueid]->i_first = '';
424 $SESSION->flextable[$this->uniqueid]->i_last = '';
d495f2d7 425 $SESSION->flextable[$this->uniqueid]->textsort = $this->column_textsort;
0ce8ba58 426 }
427
428 $this->sess = &$SESSION->flextable[$this->uniqueid];
7270d81f 429
59f392b3
TH
430 if (($showcol = optional_param($this->request[TABLE_VAR_SHOW], '', PARAM_ALPHANUMEXT)) &&
431 isset($this->columns[$showcol])) {
432 $this->sess->collapse[$showcol] = false;
433
8b05c5e2 434 } else if (($hidecol = optional_param($this->request[TABLE_VAR_HIDE], '', PARAM_ALPHANUMEXT)) &&
59f392b3
TH
435 isset($this->columns[$hidecol])) {
436 $this->sess->collapse[$hidecol] = true;
437 if (array_key_exists($hidecol, $this->sess->sortby)) {
438 unset($this->sess->sortby[$hidecol]);
7270d81f 439 }
440 }
1e42af27 441
7270d81f 442 // Now, update the column attributes for collapsed columns
9683db71
TH
443 foreach (array_keys($this->columns) as $column) {
444 if (!empty($this->sess->collapse[$column])) {
7270d81f 445 $this->column_style[$column]['width'] = '10px';
446 }
447 }
448
59f392b3
TH
449 if (($sortcol = optional_param($this->request[TABLE_VAR_SORT], '', PARAM_ALPHANUMEXT)) &&
450 $this->is_sortable($sortcol) && empty($this->sess->collapse[$sortcol]) &&
451 (isset($this->columns[$sortcol]) || in_array($sortcol, array('firstname', 'lastname')) && isset($this->columns['fullname']))) {
452
453 if (array_key_exists($sortcol, $this->sess->sortby)) {
454 // This key already exists somewhere. Change its sortorder and bring it to the top.
455 $sortorder = $this->sess->sortby[$sortcol] == SORT_ASC ? SORT_DESC : SORT_ASC;
456 unset($this->sess->sortby[$sortcol]);
457 $this->sess->sortby = array_merge(array($sortcol => $sortorder), $this->sess->sortby);
458 } else {
459 // Key doesn't exist, so just add it to the beginning of the array, ascending order
460 $this->sess->sortby = array_merge(array($sortcol => SORT_ASC), $this->sess->sortby);
7270d81f 461 }
59f392b3
TH
462
463 // Finally, make sure that no more than $this->maxsortkeys are present into the array
464 $this->sess->sortby = array_slice($this->sess->sortby, 0, $this->maxsortkeys);
7270d81f 465 }
466
f9e62127
DW
467 // MDL-35375 - If a default order is defined and it is not in the current list of order by columns, add it at the end.
468 // This prevents results from being returned in a random order if the only order by column contains equal values.
469 if (!empty($this->sort_default_column)) {
470 if (!array_key_exists($this->sort_default_column, $this->sess->sortby)) {
471 $defaultsort = array($this->sort_default_column => $this->sort_default_order);
472 $this->sess->sortby = array_merge($this->sess->sortby, $defaultsort);
473 }
a5a3f48a 474 }
475
59f392b3
TH
476 $ilast = optional_param($this->request[TABLE_VAR_ILAST], null, PARAM_RAW);
477 if (!is_null($ilast) && ($ilast ==='' || strpos(get_string('alphabet', 'langconfig'), $ilast) !== false)) {
478 $this->sess->i_last = $ilast;
7270d81f 479 }
480
59f392b3
TH
481 $ifirst = optional_param($this->request[TABLE_VAR_IFIRST], null, PARAM_RAW);
482 if (!is_null($ifirst) && ($ifirst === '' || strpos(get_string('alphabet', 'langconfig'), $ifirst) !== false)) {
483 $this->sess->i_first = $ifirst;
7270d81f 484 }
485
9683db71 486 if (empty($this->baseurl)) {
59f392b3
TH
487 debugging('You should set baseurl when using flexible_table.');
488 global $PAGE;
489 $this->baseurl = $PAGE->url;
7270d81f 490 }
491
92ebcabe 492 $this->currpage = optional_param($this->request[TABLE_VAR_PAGE], 0, PARAM_INT);
7270d81f 493 $this->setup = true;
d1b523b7 494
59f392b3 495 // Always introduce the "flexible" class for the table if not specified
d1b523b7 496 if (empty($this->attributes)) {
497 $this->attributes['class'] = 'flexible';
d1b523b7 498 } else if (!isset($this->attributes['class'])) {
499 $this->attributes['class'] = 'flexible';
d1b523b7 500 } else if (!in_array('flexible', explode(' ', $this->attributes['class']))) {
501 $this->attributes['class'] = trim('flexible ' . $this->attributes['class']);
502 }
9baf2670
TH
503 }
504
505 /**
506 * Get the order by clause from the session, for the table with id $uniqueid.
507 * @param string $uniqueid the identifier for a table.
508 * @return SQL fragment that can be used in an ORDER BY clause.
509 */
510 public static function get_sort_for_table($uniqueid) {
511 global $SESSION;
9683db71 512 if (empty($SESSION->flextable[$uniqueid])) {
9baf2670
TH
513 return '';
514 }
d1b523b7 515
9baf2670
TH
516 $sess = &$SESSION->flextable[$uniqueid];
517 if (empty($sess->sortby)) {
518 return '';
519 }
d495f2d7
DW
520 if (empty($sess->textsort)) {
521 $sess->textsort = array();
522 }
9baf2670 523
d495f2d7 524 return self::construct_order_by($sess->sortby, $sess->textsort);
7270d81f 525 }
526
561d406b 527 /**
9baf2670
TH
528 * Prepare an an order by clause from the list of columns to be sorted.
529 * @param array $cols column name => SORT_ASC or SORT_DESC
530 * @return SQL fragment that can be used in an ORDER BY clause.
561d406b 531 */
1e4bd9fe
DW
532 public static function construct_order_by($cols, $textsortcols=array()) {
533 global $DB;
9baf2670
TH
534 $bits = array();
535
9683db71 536 foreach ($cols as $column => $order) {
1e4bd9fe
DW
537 if (in_array($column, $textsortcols)) {
538 $column = $DB->sql_order_by_text($column);
539 }
9baf2670
TH
540 if ($order == SORT_ASC) {
541 $bits[] = $column . ' ASC';
542 } else {
543 $bits[] = $column . ' DESC';
9013e4ad 544 }
9013e4ad 545 }
9baf2670
TH
546
547 return implode(', ', $bits);
548 }
549
550 /**
551 * @return SQL fragment that can be used in an ORDER BY clause.
552 */
553 public function get_sql_sort() {
1e4bd9fe 554 return self::construct_order_by($this->get_sort_columns(), $this->column_textsort);
9baf2670
TH
555 }
556
557 /**
558 * Get the columns to sort by, in the form required by {@link construct_order_by()}.
559 * @return array column name => SORT_... constant.
560 */
561 public function get_sort_columns() {
562 if (!$this->setup) {
563 throw new coding_exception('Cannot call get_sort_columns until you have called setup.');
7270d81f 564 }
9013e4ad 565
9baf2670
TH
566 if (empty($this->sess->sortby)) {
567 return array();
7270d81f 568 }
9baf2670 569
e81eed54
TH
570 foreach ($this->sess->sortby as $column => $notused) {
571 if (isset($this->columns[$column])) {
572 continue; // This column is OK.
573 }
574 if (in_array($column, array('firstname', 'lastname')) &&
575 isset($this->columns['fullname'])) {
576 continue; // This column is OK.
577 }
578 // This column is not OK.
579 unset($this->sess->sortby[$column]);
580 }
581
9baf2670 582 return $this->sess->sortby;
7270d81f 583 }
584
561d406b 585 /**
ba9164e5 586 * @return int the offset for LIMIT clause of SQL
561d406b 587 */
7270d81f 588 function get_page_start() {
9683db71 589 if (!$this->use_pages) {
7270d81f 590 return '';
591 }
592 return $this->currpage * $this->pagesize;
593 }
594
561d406b 595 /**
ba9164e5 596 * @return int the pagesize for LIMIT clause of SQL
561d406b 597 */
7270d81f 598 function get_page_size() {
9683db71 599 if (!$this->use_pages) {
7270d81f 600 return '';
601 }
602 return $this->pagesize;
603 }
604
561d406b 605 /**
e81eed54 606 * @return string sql to add to where statement.
561d406b 607 */
7270d81f 608 function get_sql_where() {
7e60297f 609 global $DB;
7270d81f 610
20a79b43
PS
611 $conditions = array();
612 $params = array();
613
ee70439a
PS
614 if (isset($this->columns['fullname'])) {
615 static $i = 0;
616 $i++;
617
618 if (!empty($this->sess->i_first)) {
619 $conditions[] = $DB->sql_like('firstname', ':ifirstc'.$i, false, false);
620 $params['ifirstc'.$i] = $this->sess->i_first.'%';
621 }
622 if (!empty($this->sess->i_last)) {
623 $conditions[] = $DB->sql_like('lastname', ':ilastc'.$i, false, false);
624 $params['ilastc'.$i] = $this->sess->i_last.'%';
625 }
7270d81f 626 }
627
20a79b43 628 return array(implode(" AND ", $conditions), $params);
7270d81f 629 }
630
561d406b 631 /**
9cf4a18b 632 * Add a row of data to the table. This function takes an array with
633 * column names as keys.
20e1f1c0 634 * It ignores any elements with keys that are not defined as columns. It
635 * puts in empty strings into the row when there is no element in the passed
636 * array corresponding to a column in the table. It puts the row elements in
637 * the proper order.
638 * @param $rowwithkeys array
c7ecf78e 639 * @param string $classname CSS class name to add to this row's tr tag.
20e1f1c0 640 */
9683db71 641 function add_data_keyed($rowwithkeys, $classname = '') {
c7ecf78e 642 $this->add_data($this->get_row_from_keyed($rowwithkeys), $classname);
20e1f1c0 643 }
9cf4a18b 644
20e1f1c0 645 /**
646 * Add a seperator line to table.
647 */
648 function add_separator() {
9683db71 649 if (!$this->setup) {
20e1f1c0 650 return false;
651 }
652 $this->add_data(NULL);
653 }
9cf4a18b 654
20e1f1c0 655 /**
656 * This method actually directly echoes the row passed to it now or adds it
657 * to the download. If this is the first row and start_output has not
658 * already been called this method also calls start_output to open the table
659 * or send headers for the downloaded.
660 * Can be used as before. print_html now calls finish_html to close table.
9cf4a18b 661 *
20e1f1c0 662 * @param array $row a numerically keyed row of data to add to the table.
c7ecf78e 663 * @param string $classname CSS class name to add to this row's tr tag.
ba9164e5 664 * @return bool success.
20e1f1c0 665 */
c7ecf78e 666 function add_data($row, $classname = '') {
9683db71 667 if (!$this->setup) {
20e1f1c0 668 return false;
669 }
9683db71 670 if (!$this->started_output) {
20e1f1c0 671 $this->start_output();
672 }
9683db71
TH
673 if ($this->exportclass!==null) {
674 if ($row === null) {
20e1f1c0 675 $this->exportclass->add_seperator();
676 } else {
677 $this->exportclass->add_data($row);
678 }
679 } else {
c7ecf78e 680 $this->print_row($row, $classname);
20e1f1c0 681 }
682 return true;
683 }
684
20e1f1c0 685 /**
686 * You should call this to finish outputting the table data after adding
687 * data to the table with add_data or add_data_keyed.
9cf4a18b 688 *
20e1f1c0 689 */
9683db71
TH
690 function finish_output($closeexportclassdoc = true) {
691 if ($this->exportclass!==null) {
43ec99aa 692 $this->exportclass->finish_table();
9683db71 693 if ($closeexportclassdoc) {
43ec99aa 694 $this->exportclass->finish_document();
695 }
9683db71 696 } else {
20e1f1c0 697 $this->finish_html();
698 }
699 }
9cf4a18b 700
20e1f1c0 701 /**
702 * Hook that can be overridden in child classes to wrap a table in a form
703 * for example. Called only when there is data to display and not
704 * downloading.
705 */
9683db71 706 function wrap_html_start() {
20e1f1c0 707 }
708
709 /**
710 * Hook that can be overridden in child classes to wrap a table in a form
711 * for example. Called only when there is data to display and not
712 * downloading.
713 */
9683db71 714 function wrap_html_finish() {
20e1f1c0 715 }
9cf4a18b 716
1f9ca535 717 /**
718 *
719 * @param array $row row of data from db used to make one row of the table.
720 * @return array one row for the table, added using add_data_keyed method.
721 */
9683db71 722 function format_row($row) {
1f9ca535 723 $formattedrow = array();
9683db71 724 foreach (array_keys($this->columns) as $column) {
1f9ca535 725 $colmethodname = 'col_'.$column;
9683db71 726 if (method_exists($this, $colmethodname)) {
1f9ca535 727 $formattedcolumn = $this->$colmethodname($row);
728 } else {
729 $formattedcolumn = $this->other_cols($column, $row);
9683db71 730 if ($formattedcolumn===NULL) {
1f9ca535 731 $formattedcolumn = $row->$column;
732 }
733 }
734 $formattedrow[$column] = $formattedcolumn;
735 }
736 return $formattedrow;
737 }
738
739 /**
740 * Fullname is treated as a special columname in tablelib and should always
741 * be treated the same as the fullname of a user.
742 * @uses $this->useridfield if the userid field is not expected to be id
743 * then you need to override $this->useridfield to point at the correct
744 * field for the user id.
745 *
746 */
9683db71 747 function col_fullname($row) {
1f9ca535 748 global $COURSE, $CFG;
1f9ca535 749
4726ad4f
TH
750 $name = fullname($row);
751 if ($this->download) {
752 return $name;
753 }
abf72969 754
4726ad4f
TH
755 $userid = $row->{$this->useridfield};
756 if ($COURSE->id == SITEID) {
757 $profileurl = new moodle_url('/user/profile.php', array('id' => $userid));
1f9ca535 758 } else {
4726ad4f
TH
759 $profileurl = new moodle_url('/user/view.php',
760 array('id' => $userid, 'course' => $COURSE->id));
1f9ca535 761 }
4726ad4f 762 return html_writer::link($profileurl, $name);
1f9ca535 763 }
764
765 /**
766 * You can override this method in a child class. See the description of
767 * build_table which calls this method.
768 */
9683db71 769 function other_cols($column, $row) {
1f9ca535 770 return NULL;
771 }
772
ef27e742 773 /**
774 * Used from col_* functions when text is to be displayed. Does the
775 * right thing - either converts text to html or strips any html tags
776 * depending on if we are downloading and what is the download type. Params
777 * are the same as format_text function in weblib.php but some default
778 * options are changed.
779 */
9683db71
TH
780 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
781 if (!$this->is_downloading()) {
782 if (is_null($options)) {
ef27e742 783 $options = new stdClass;
784 }
785 //some sensible defaults
9683db71 786 if (!isset($options->para)) {
ef27e742 787 $options->para = false;
788 }
9683db71 789 if (!isset($options->newlines)) {
ef27e742 790 $options->newlines = false;
791 }
792 if (!isset($options->smiley)) {
793 $options->smiley = false;
794 }
795 if (!isset($options->filter)) {
796 $options->filter = false;
797 }
798 return format_text($text, $format, $options);
799 } else {
800 $eci =& $this->export_class_instance();
801 return $eci->format_text($text, $format, $options, $courseid);
802 }
803 }
20e1f1c0 804 /**
805 * This method is deprecated although the old api is still supported.
806 * @deprecated 1.9.2 - Jun 2, 2008
807 */
808 function print_html() {
9683db71 809 if (!$this->setup) {
20e1f1c0 810 return false;
811 }
812 $this->finish_html();
813 }
814
815 /**
816 * This function is not part of the public api.
817 * @return string initial of first name we are currently filtering by
561d406b 818 */
91bb59f4 819 function get_initial_first() {
9683db71 820 if (!$this->use_initials) {
91bb59f4 821 return NULL;
822 }
823
824 return $this->sess->i_first;
825 }
826
561d406b 827 /**
20e1f1c0 828 * This function is not part of the public api.
829 * @return string initial of last name we are currently filtering by
561d406b 830 */
91bb59f4 831 function get_initial_last() {
9683db71 832 if (!$this->use_initials) {
91bb59f4 833 return NULL;
834 }
835
836 return $this->sess->i_last;
837 }
9cf4a18b 838
ba9164e5
TH
839 /**
840 * Helper function, used by {@link print_initials_bar()} to output one initial bar.
841 * @param array $alpha of letters in the alphabet.
842 * @param string $current the currently selected letter.
843 * @param string $class class name to add to this initial bar.
844 * @param string $title the name to put in front of this initial bar.
845 * @param string $urlvar URL parameter name for this initial.
846 */
6f0d3480
TH
847 protected function print_one_initials_bar($alpha, $current, $class, $title, $urlvar) {
848 echo html_writer::start_tag('div', array('class' => 'initialbar ' . $class)) .
849 $title . ' : ';
850 if ($current) {
851 echo html_writer::link($this->baseurl->out(false, array($urlvar => '')), get_string('all'));
852 } else {
853 echo html_writer::tag('strong', get_string('all'));
854 }
855
856 foreach ($alpha as $letter) {
857 if ($letter === $current) {
858 echo html_writer::tag('strong', $letter);
859 } else {
860 echo html_writer::link($this->baseurl->out(false, array($urlvar => $letter)), $letter);
861 }
862 }
863
864 echo html_writer::end_tag('div');
865 }
866
561d406b 867 /**
20e1f1c0 868 * This function is not part of the public api.
561d406b 869 */
9683db71 870 function print_initials_bar() {
6f0d3480 871 if ((!empty($this->sess->i_last) || !empty($this->sess->i_first) ||$this->use_initials)
db16ac50 872 && isset($this->columns['fullname'])) {
9cf4a18b 873
61bb07c2 874 $alpha = explode(',', get_string('alphabet', 'langconfig'));
9cf4a18b 875
7270d81f 876 // Bar of first initials
9683db71 877 if (!empty($this->sess->i_first)) {
6f0d3480 878 $ifirst = $this->sess->i_first;
7270d81f 879 } else {
6f0d3480 880 $ifirst = '';
7270d81f 881 }
6f0d3480
TH
882 $this->print_one_initials_bar($alpha, $ifirst, 'firstinitial',
883 get_string('firstname'), $this->request[TABLE_VAR_IFIRST]);
9cf4a18b 884
7270d81f 885 // Bar of last initials
9683db71 886 if (!empty($this->sess->i_last)) {
6f0d3480 887 $ilast = $this->sess->i_last;
7270d81f 888 } else {
6f0d3480 889 $ilast = '';
7270d81f 890 }
6f0d3480
TH
891 $this->print_one_initials_bar($alpha, $ilast, 'lastinitial',
892 get_string('lastname'), $this->request[TABLE_VAR_ILAST]);
20e1f1c0 893 }
894 }
7270d81f 895
20e1f1c0 896 /**
897 * This function is not part of the public api.
898 */
9683db71 899 function print_nothing_to_display() {
3c159385 900 global $OUTPUT;
db16ac50 901 $this->print_initials_bar();
7270d81f 902
3c159385 903 echo $OUTPUT->heading(get_string('nothingtodisplay'));
20e1f1c0 904 }
7270d81f 905
20e1f1c0 906 /**
907 * This function is not part of the public api.
908 */
9683db71
TH
909 function get_row_from_keyed($rowwithkeys) {
910 if (is_object($rowwithkeys)) {
20e1f1c0 911 $rowwithkeys = (array)$rowwithkeys;
7270d81f 912 }
f36a3308 913 $row = array();
9683db71
TH
914 foreach (array_keys($this->columns) as $column) {
915 if (isset($rowwithkeys[$column])) {
20e1f1c0 916 $row [] = $rowwithkeys[$column];
917 } else {
918 $row[] ='';
919 }
7270d81f 920 }
20e1f1c0 921 return $row;
922 }
923 /**
924 * This function is not part of the public api.
925 */
9683db71 926 function get_download_menu() {
20e1f1c0 927 $allclasses= get_declared_classes();
928 $exportclasses = array();
9683db71 929 foreach ($allclasses as $class) {
20e1f1c0 930 $matches = array();
9683db71 931 if (preg_match('/^table\_([a-z]+)\_export\_format$/', $class, $matches)) {
20e1f1c0 932 $type = $matches[1];
933 $exportclasses[$type]= get_string("download$type", 'table');
934 }
935 }
936 return $exportclasses;
937 }
9cf4a18b 938
20e1f1c0 939 /**
940 * This function is not part of the public api.
941 */
9683db71 942 function download_buttons() {
9683db71 943 if ($this->is_downloadable() && !$this->is_downloading()) {
20e1f1c0 944 $downloadoptions = $this->get_download_menu();
0465ef6e
RT
945
946 $downloadelements = new stdClass();
947 $downloadelements->formatsmenu = html_writer::select($downloadoptions,
948 'download', $this->defaultdownloadformat, false);
949 $downloadelements->downloadbutton = '<input type="submit" value="'.
950 get_string('download').'"/>';
20e1f1c0 951 $html = '<form action="'. $this->baseurl .'" method="post">';
952 $html .= '<div class="mdl-align">';
0465ef6e 953 $html .= html_writer::tag('label', get_string('downloadas', 'table', $downloadelements));
20e1f1c0 954 $html .= '</div></form>';
955
956 return $html;
957 } else {
958 return '';
959 }
960 }
961 /**
962 * This function is not part of the public api.
963 * You don't normally need to call this. It is called automatically when
964 * needed when you start adding data to the table.
9cf4a18b 965 *
20e1f1c0 966 */
9683db71 967 function start_output() {
20e1f1c0 968 $this->started_output = true;
9683db71 969 if ($this->exportclass!==null) {
43ec99aa 970 $this->exportclass->start_table($this->sheettitle);
20e1f1c0 971 $this->exportclass->output_headers($this->headers);
972 } else {
973 $this->start_html();
974 $this->print_headers();
46254044 975 echo html_writer::start_tag('tbody');
20e1f1c0 976 }
977 }
7270d81f 978
20e1f1c0 979 /**
980 * This function is not part of the public api.
981 */
c7ecf78e 982 function print_row($row, $classname = '') {
20e1f1c0 983 static $suppress_lastrow = NULL;
fdf70473 984 $oddeven = $this->currentrow % 2;
c7ecf78e 985 $rowclasses = array('r' . $oddeven);
c7ecf78e 986
987 if ($classname) {
988 $rowclasses[] = $classname;
989 }
990
fdf70473
DW
991 $rowid = $this->uniqueid . '_r' . $this->currentrow;
992
993 echo html_writer::start_tag('tr', array('class' => implode(' ', $rowclasses), 'id' => $rowid));
7270d81f 994
20e1f1c0 995 // If we have a separator, print it
eba40bfe 996 if ($row === NULL) {
c7ecf78e 997 $colcount = count($this->columns);
76dc1e25
TH
998 echo html_writer::tag('td', html_writer::tag('div', '',
999 array('class' => 'tabledivider')), array('colspan' => $colcount));
1000
c7ecf78e 1001 } else {
1002 $colbyindex = array_flip($this->columns);
1003 foreach ($row as $index => $data) {
20e1f1c0 1004 $column = $colbyindex[$index];
76dc1e25 1005
c7ecf78e 1006 if (empty($this->sess->collapse[$column])) {
1007 if ($this->column_suppress[$column] && $suppress_lastrow !== NULL && $suppress_lastrow[$index] === $data) {
76dc1e25 1008 $content = '&nbsp;';
c7ecf78e 1009 } else {
76dc1e25 1010 $content = $data;
20e1f1c0 1011 }
c7ecf78e 1012 } else {
76dc1e25 1013 $content = '&nbsp;';
20e1f1c0 1014 }
76dc1e25
TH
1015
1016 echo html_writer::tag('td', $content, array(
1017 'class' => 'cell c' . $index . $this->column_class[$column],
fdf70473 1018 'id' => $rowid . '_c' . $index,
76dc1e25 1019 'style' => $this->make_styles_string($this->column_style[$column])));
20e1f1c0 1020 }
1021 }
c7ecf78e 1022
76dc1e25 1023 echo html_writer::end_tag('tr');
c7ecf78e 1024
7270d81f 1025 $suppress_enabled = array_sum($this->column_suppress);
c7ecf78e 1026 if ($suppress_enabled) {
20e1f1c0 1027 $suppress_lastrow = $row;
1028 }
fdf70473 1029 $this->currentrow++;
20e1f1c0 1030 }
9683db71 1031
20e1f1c0 1032 /**
1033 * This function is not part of the public api.
1034 */
9683db71 1035 function finish_html() {
25911860 1036 global $OUTPUT;
20e1f1c0 1037 if (!$this->started_output) {
1038 //no data has been added to the table.
1039 $this->print_nothing_to_display();
76dc1e25 1040
20e1f1c0 1041 } else {
fdf70473
DW
1042 // Print empty rows to fill the table to the current pagesize.
1043 // This is done so the header aria-controls attributes do not point to
1044 // non existant elements.
3e227172 1045 $emptyrow = array_fill(0, count($this->columns), '');
fdf70473
DW
1046 while ($this->currentrow < $this->pagesize) {
1047 $this->print_row($emptyrow, 'emptyrow');
1048 }
1049
46254044 1050 echo html_writer::end_tag('tbody');
76dc1e25 1051 echo html_writer::end_tag('table');
f4136193 1052 echo html_writer::end_tag('div');
20e1f1c0 1053 $this->wrap_html_finish();
76dc1e25 1054
20e1f1c0 1055 // Paging bar
76dc1e25 1056 if(in_array(TABLE_P_BOTTOM, $this->showdownloadbuttonsat)) {
20e1f1c0 1057 echo $this->download_buttons();
1058 }
76dc1e25
TH
1059
1060 if($this->use_pages) {
929d7a83 1061 $pagingbar = new paging_bar($this->totalrows, $this->currpage, $this->pagesize, $this->baseurl);
b28c784f 1062 $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
929d7a83 1063 echo $OUTPUT->render($pagingbar);
20e1f1c0 1064 }
1065 }
9cf4a18b 1066 }
76dc1e25 1067
ba9164e5
TH
1068 /**
1069 * Generate the HTML for the collapse/uncollapse icon. This is a helper method
1070 * used by {@link print_headers()}.
1071 * @param string $column the column name, index into various names.
1072 * @param int $index numerical index of the column.
1073 * @return string HTML fragment.
1074 */
76dc1e25
TH
1075 protected function show_hide_link($column, $index) {
1076 global $OUTPUT;
1077 // Some headers contain <br /> tags, do not include in title, hence the
1078 // strip tags.
1079
fdf70473
DW
1080 $ariacontrols = '';
1081 for ($i = 0; $i < $this->pagesize; $i++) {
1082 $ariacontrols .= $this->uniqueid . '_r' . $i . '_c' . $index . ' ';
1083 }
1084
1085 $ariacontrols = trim($ariacontrols);
1086
76dc1e25 1087 if (!empty($this->sess->collapse[$column])) {
fdf70473
DW
1088 $linkattributes = array('title' => get_string('show') . ' ' . strip_tags($this->headers[$index]),
1089 'aria-expanded' => 'false',
1090 'aria-controls' => $ariacontrols);
76dc1e25
TH
1091 return html_writer::link($this->baseurl->out(false, array($this->request[TABLE_VAR_SHOW] => $column)),
1092 html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/switch_plus'), 'alt' => get_string('show'))),
fdf70473 1093 $linkattributes);
76dc1e25
TH
1094
1095 } else if ($this->headers[$index] !== NULL) {
fdf70473
DW
1096 $linkattributes = array('title' => get_string('hide') . ' ' . strip_tags($this->headers[$index]),
1097 'aria-expanded' => 'true',
1098 'aria-controls' => $ariacontrols);
76dc1e25
TH
1099 return html_writer::link($this->baseurl->out(false, array($this->request[TABLE_VAR_HIDE] => $column)),
1100 html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/switch_minus'), 'alt' => get_string('hide'))),
fdf70473 1101 $linkattributes);
76dc1e25
TH
1102 }
1103 }
1104
20e1f1c0 1105 /**
1106 * This function is not part of the public api.
1107 */
9683db71 1108 function print_headers() {
f2a1963c 1109 global $CFG, $OUTPUT;
7270d81f 1110
28932052 1111 echo html_writer::start_tag('thead');
76dc1e25 1112 echo html_writer::start_tag('tr');
9683db71 1113 foreach ($this->columns as $column => $index) {
7270d81f 1114
76dc1e25 1115 $icon_hide = '';
9683db71 1116 if ($this->is_collapsible) {
76dc1e25 1117 $icon_hide = $this->show_hide_link($column, $index);
7270d81f 1118 }
1119
73e4f589 1120 $primary_sort_column = '';
1121 $primary_sort_order = '';
9683db71 1122 if (reset($this->sess->sortby)) {
73e4f589 1123 $primary_sort_column = key($this->sess->sortby);
1124 $primary_sort_order = current($this->sess->sortby);
1125 }
1126
9683db71 1127 switch ($column) {
7270d81f 1128
1129 case 'fullname':
a327f25e
AG
1130 // Check the full name display for sortable fields.
1131 $nameformat = $CFG->fullnamedisplay;
1132 if ($nameformat == 'language') {
1133 $nameformat = get_string('fullnamedisplay');
1134 }
1135 $requirednames = order_in_string(array('firstname', 'lastname'), $nameformat);
1136
1137 if (!empty($requirednames)) {
1138 if ($this->is_sortable($column)) {
1139 // Done this way for the possibility of more than two sortable full name display fields.
1140 $this->headers[$index] = '';
1141 foreach ($requirednames as $name) {
1142 $sortname = $this->sort_link(get_string($name),
1143 $name, $primary_sort_column === $name, $primary_sort_order);
1144 $this->headers[$index] .= $sortname . ' / ';
1145 }
1146 $this->headers[$index] = substr($this->headers[$index], 0, -3);
78bfb562 1147 }
7270d81f 1148 }
1149 break;
1150
1d40c70b 1151 case 'userpic':
9cf4a18b 1152 // do nothing, do not display sortable links
1d40c70b 1153 break;
1154
7270d81f 1155 default:
9683db71 1156 if ($this->is_sortable($column)) {
76dc1e25
TH
1157 $this->headers[$index] = $this->sort_link($this->headers[$index],
1158 $column, $primary_sort_column == $column, $primary_sort_order);
7270d81f 1159 }
1160 }
1e42af27 1161
76dc1e25
TH
1162 $attributes = array(
1163 'class' => 'header c' . $index . $this->column_class[$column],
1164 'scope' => 'col',
1165 );
9683db71 1166 if ($this->headers[$index] === NULL) {
76dc1e25
TH
1167 $content = '&nbsp;';
1168 } else if (!empty($this->sess->collapse[$column])) {
1169 $content = $icon_hide;
9683db71 1170 } else {
76dc1e25
TH
1171 if (is_array($this->column_style[$column])) {
1172 $attributes['style'] = $this->make_styles_string($this->column_style[$column]);
1173 }
1174 $content = $this->headers[$index] . html_writer::tag('div',
1175 $icon_hide, array('class' => 'commands'));
7270d81f 1176 }
76dc1e25
TH
1177 echo html_writer::tag('th', $content, $attributes);
1178 }
1179
1180 echo html_writer::end_tag('tr');
28932052 1181 echo html_writer::end_tag('thead');
76dc1e25
TH
1182 }
1183
ba9164e5
TH
1184 /**
1185 * Generate the HTML for the sort icon. This is a helper method used by {@link sort_link()}.
1186 * @param bool $isprimary whether an icon is needed (it is only needed for the primary sort column.)
1187 * @param int $order SORT_ASC or SORT_DESC
1188 * @return string HTML fragment.
1189 */
76dc1e25
TH
1190 protected function sort_icon($isprimary, $order) {
1191 global $OUTPUT;
7270d81f 1192
76dc1e25
TH
1193 if (!$isprimary) {
1194 return '';
1195 }
1196
1197 if ($order == SORT_ASC) {
1198 return html_writer::empty_tag('img',
06b7ed0a 1199 array('src' => $OUTPUT->pix_url('t/sort_asc'), 'alt' => get_string('asc'), 'class' => 'iconsort'));
76dc1e25
TH
1200 } else {
1201 return html_writer::empty_tag('img',
06b7ed0a 1202 array('src' => $OUTPUT->pix_url('t/sort_desc'), 'alt' => get_string('desc'), 'class' => 'iconsort'));
76dc1e25
TH
1203 }
1204 }
1205
ba9164e5
TH
1206 /**
1207 * Generate the correct tool tip for changing the sort order. This is a
1208 * helper method used by {@link sort_link()}.
1209 * @param bool $isprimary whether the is column is the current primary sort column.
1210 * @param int $order SORT_ASC or SORT_DESC
1211 * @return string the correct title.
1212 */
76dc1e25
TH
1213 protected function sort_order_name($isprimary, $order) {
1214 if ($isprimary && $order != SORT_ASC) {
1215 return get_string('desc');
1216 } else {
1217 return get_string('asc');
7270d81f 1218 }
76dc1e25
TH
1219 }
1220
ba9164e5
TH
1221 /**
1222 * Generate the HTML for the sort link. This is a helper method used by {@link print_headers()}.
1223 * @param string $text the text for the link.
1224 * @param string $column the column name, may be a fake column like 'firstname' or a real one.
1225 * @param bool $isprimary whether the is column is the current primary sort column.
1226 * @param int $order SORT_ASC or SORT_DESC
1227 * @return string HTML fragment.
1228 */
76dc1e25
TH
1229 protected function sort_link($text, $column, $isprimary, $order) {
1230 return html_writer::link($this->baseurl->out(false,
1231 array($this->request[TABLE_VAR_SORT] => $column)),
1232 $text . get_accesshide(get_string('sortby') . ' ' .
1233 $text . ' ' . $this->sort_order_name($isprimary, $order))) . ' ' .
1234 $this->sort_icon($isprimary, $order);
20e1f1c0 1235 }
9cf4a18b 1236
20e1f1c0 1237 /**
1238 * This function is not part of the public api.
1239 */
9683db71 1240 function start_html() {
b28c784f 1241 global $OUTPUT;
20e1f1c0 1242 // Do we need to print initial bars?
1243 $this->print_initials_bar();
7270d81f 1244
1e42af27 1245 // Paging bar
9683db71 1246 if ($this->use_pages) {
929d7a83 1247 $pagingbar = new paging_bar($this->totalrows, $this->currpage, $this->pagesize, $this->baseurl);
b28c784f 1248 $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
929d7a83 1249 echo $OUTPUT->render($pagingbar);
1e42af27 1250 }
9cf4a18b 1251
9683db71 1252 if (in_array(TABLE_P_TOP, $this->showdownloadbuttonsat)) {
20e1f1c0 1253 echo $this->download_buttons();
1254 }
9cf4a18b 1255
20e1f1c0 1256 $this->wrap_html_start();
1257 // Start of main data table
1258
76dc1e25
TH
1259 echo html_writer::start_tag('div', array('class' => 'no-overflow'));
1260 echo html_writer::start_tag('table', $this->attributes);
9cf4a18b 1261
7270d81f 1262 }
1263
561d406b 1264 /**
20e1f1c0 1265 * This function is not part of the public api.
76dc1e25
TH
1266 * @param array $styles CSS-property => value
1267 * @return string values suitably to go in a style="" attribute in HTML.
561d406b 1268 */
76dc1e25 1269 function make_styles_string($styles) {
9683db71 1270 if (empty($styles)) {
76dc1e25 1271 return null;
20e1f1c0 1272 }
1273
76dc1e25
TH
1274 $string = '';
1275 foreach($styles as $property => $value) {
1276 $string .= $property . ':' . $value . ';';
7270d81f 1277 }
20e1f1c0 1278 return $string;
1279 }
1280}
1281
9683db71 1282
72fb21b6 1283/**
1284 * @package moodlecore
1285 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1286 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1287 */
9683db71 1288class table_sql extends flexible_table {
9cf4a18b 1289
1290 public $countsql = NULL;
1291 public $countparams = NULL;
20e1f1c0 1292 /**
9cf4a18b 1293 * @var object sql for querying db. Has fields 'fields', 'from', 'where', 'params'.
20e1f1c0 1294 */
9cf4a18b 1295 public $sql = NULL;
20e1f1c0 1296 /**
1297 * @var array Data fetched from the db.
1298 */
9cf4a18b 1299 public $rawdata = NULL;
1300
20e1f1c0 1301 /**
ba9164e5 1302 * @var bool Overriding default for this.
20e1f1c0 1303 */
9cf4a18b 1304 public $is_sortable = true;
20e1f1c0 1305 /**
ba9164e5 1306 * @var bool Overriding default for this.
20e1f1c0 1307 */
9cf4a18b 1308 public $is_collapsible = true;
1309
20e1f1c0 1310 /**
1311 * @param string $uniqueid a string identifying this table.Used as a key in
1312 * session vars.
1313 */
a49c17b4
TH
1314 function __construct($uniqueid) {
1315 parent::__construct($uniqueid);
20e1f1c0 1316 // some sensible defaults
1317 $this->set_attribute('cellspacing', '0');
1318 $this->set_attribute('class', 'generaltable generalbox');
7270d81f 1319 }
9cf4a18b 1320
20e1f1c0 1321 /**
1322 * Take the data returned from the db_query and go through all the rows
1323 * processing each col using either col_{columnname} method or other_cols
1324 * method or if other_cols returns NULL then put the data straight into the
1325 * table.
1326 */
9683db71
TH
1327 function build_table() {
1328 if ($this->rawdata) {
1329 foreach ($this->rawdata as $row) {
20e1f1c0 1330 $formattedrow = $this->format_row($row);
b08fb128
TH
1331 $this->add_data_keyed($formattedrow,
1332 $this->get_row_class($row));
20e1f1c0 1333 }
1334 }
1335 }
1336
b08fb128
TH
1337 /**
1338 * Get any extra classes names to add to this row in the HTML.
1339 * @param $row array the data for this row.
1340 * @return string added to the class="" attribute of the tr.
1341 */
1342 function get_row_class($row) {
1343 return '';
1344 }
1345
20e1f1c0 1346 /**
1347 * This is only needed if you want to use different sql to count rows.
1348 * Used for example when perhaps all db JOINS are not needed when counting
1349 * records. You don't need to call this function the count_sql
1350 * will be generated automatically.
9cf4a18b 1351 *
20e1f1c0 1352 * We need to count rows returned by the db seperately to the query itself
1353 * as we need to know how many pages of data we have to display.
1354 */
9683db71 1355 function set_count_sql($sql, array $params = NULL) {
20e1f1c0 1356 $this->countsql = $sql;
9cf4a18b 1357 $this->countparams = $params;
20e1f1c0 1358 }
9cf4a18b 1359
20e1f1c0 1360 /**
1361 * Set the sql to query the db. Query will be :
1362 * SELECT $fields FROM $from WHERE $where
1363 * Of course you can use sub-queries, JOINS etc. by putting them in the
1364 * appropriate clause of the query.
1365 */
9683db71 1366 function set_sql($fields, $from, $where, array $params = NULL) {
365a5941 1367 $this->sql = new stdClass();
20e1f1c0 1368 $this->sql->fields = $fields;
1369 $this->sql->from = $from;
1370 $this->sql->where = $where;
9cf4a18b 1371 $this->sql->params = $params;
20e1f1c0 1372 }
9cf4a18b 1373
20e1f1c0 1374 /**
1375 * Query the db. Store results in the table object for use by build_table.
9cf4a18b 1376 *
ba9164e5
TH
1377 * @param int $pagesize size of page for paginated displayed table.
1378 * @param bool $useinitialsbar do you want to use the initials bar. Bar
20e1f1c0 1379 * will only be used if there is a fullname column defined for the table.
1380 */
9683db71 1381 function query_db($pagesize, $useinitialsbar=true) {
9cf4a18b 1382 global $DB;
20e1f1c0 1383 if (!$this->is_downloading()) {
9683db71 1384 if ($this->countsql === NULL) {
20e1f1c0 1385 $this->countsql = 'SELECT COUNT(1) FROM '.$this->sql->from.' WHERE '.$this->sql->where;
71811083 1386 $this->countparams = $this->sql->params;
20e1f1c0 1387 }
d8a3b87c 1388 $grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
20e1f1c0 1389 if ($useinitialsbar && !$this->is_downloading()) {
d8a3b87c 1390 $this->initialbars($grandtotal > $pagesize);
20e1f1c0 1391 }
9cf4a18b 1392
20a79b43
PS
1393 list($wsql, $wparams) = $this->get_sql_where();
1394 if ($wsql) {
1395 $this->countsql .= ' AND '.$wsql;
23040521 1396 $this->countparams = array_merge($this->countparams, $wparams);
20a79b43
PS
1397
1398 $this->sql->where .= ' AND '.$wsql;
1399 $this->sql->params = array_merge($this->sql->params, $wparams);
1400
0fa4107d 1401 $total = $DB->count_records_sql($this->countsql, $this->countparams);
1402 } else {
d8a3b87c 1403 $total = $grandtotal;
20e1f1c0 1404 }
20e1f1c0 1405
20e1f1c0 1406 $this->pagesize($pagesize, $total);
1407 }
1408
1409 // Fetch the attempts
1410 $sort = $this->get_sql_sort();
e81eed54
TH
1411 if ($sort) {
1412 $sort = "ORDER BY $sort";
1413 }
1414 $sql = "SELECT
1415 {$this->sql->fields}
1416 FROM {$this->sql->from}
1417 WHERE {$this->sql->where}
1418 {$sort}";
1419
20e1f1c0 1420 if (!$this->is_downloading()) {
9cf4a18b 1421 $this->rawdata = $DB->get_records_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
20e1f1c0 1422 } else {
9cf4a18b 1423 $this->rawdata = $DB->get_records_sql($sql, $this->sql->params);
20e1f1c0 1424 }
1425 }
9cf4a18b 1426
20e1f1c0 1427 /**
1428 * Convenience method to call a number of methods for you to display the
1429 * table.
1430 */
9683db71 1431 function out($pagesize, $useinitialsbar, $downloadhelpbutton='') {
9cf4a18b 1432 global $DB;
9683db71 1433 if (!$this->columns) {
9cf4a18b 1434 $onerow = $DB->get_record_sql("SELECT {$this->sql->fields} FROM {$this->sql->from} WHERE {$this->sql->where}", $this->sql->params);
20e1f1c0 1435 //if columns is not set then define columns as the keys of the rows returned
1436 //from the db.
1437 $this->define_columns(array_keys((array)$onerow));
1438 $this->define_headers(array_keys((array)$onerow));
1439 }
1440 $this->setup();
1441 $this->query_db($pagesize, $useinitialsbar);
1442 $this->build_table();
1443 $this->finish_output();
1444 }
1445}
1446
9683db71 1447
72fb21b6 1448/**
1449 * @package moodlecore
1450 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1451 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1452 */
9683db71 1453class table_default_export_format_parent {
20e1f1c0 1454 /**
1455 * @var flexible_table or child class reference pointing to table class
1456 * object from which to export data.
1457 */
1458 var $table;
117bd748 1459
43ec99aa 1460 /**
ba9164e5 1461 * @var bool output started. Keeps track of whether any output has been
43ec99aa 1462 * started yet.
1463 */
1464 var $documentstarted = false;
9683db71 1465 function table_default_export_format_parent(&$table) {
20e1f1c0 1466 $this->table =& $table;
1467 }
117bd748 1468
9683db71 1469 function set_table(&$table) {
43ec99aa 1470 $this->table =& $table;
1471 }
20e1f1c0 1472
1473 function add_data($row) {
1474 return false;
1475 }
9683db71 1476
20e1f1c0 1477 function add_seperator() {
1478 return false;
1479 }
9683db71
TH
1480
1481 function document_started() {
43ec99aa 1482 return $this->documentstarted;
20e1f1c0 1483 }
ef27e742 1484 /**
1485 * Given text in a variety of format codings, this function returns
1486 * the text as safe HTML or as plain text dependent on what is appropriate
1487 * for the download format. The default removes all tags.
1488 */
9683db71 1489 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
ef27e742 1490 //use some whitespace to indicate where there was some line spacing.
1491 $text = str_replace(array('</p>', "\n", "\r"), ' ', $text);
1492 return strip_tags($text);
1493 }
7270d81f 1494}
1495
9683db71 1496
72fb21b6 1497/**
1498 * @package moodlecore
1499 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1500 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1501 */
9683db71 1502class table_spreadsheet_export_format_parent extends table_default_export_format_parent {
fdf70473 1503 var $currentrow;
20e1f1c0 1504 var $workbook;
1505 var $worksheet;
1506 /**
1507 * @var object format object - format for normal table cells
1508 */
1509 var $formatnormal;
1510 /**
1511 * @var object format object - format for header table cells
1512 */
1513 var $formatheaders;
1514
9cf4a18b 1515 /**
20e1f1c0 1516 * should be overriden in child class.
1517 */
1518 var $fileextension;
9cf4a18b 1519
20e1f1c0 1520 /**
1521 * This method will be overridden in the child class.
1522 */
9683db71 1523 function define_workbook() {
20e1f1c0 1524 }
9683db71
TH
1525
1526 function start_document($filename) {
43ec99aa 1527 $filename = $filename.'.'.$this->fileextension;
20e1f1c0 1528 $this->define_workbook();
20e1f1c0 1529 // format types
1530 $this->formatnormal =& $this->workbook->add_format();
1531 $this->formatnormal->set_bold(0);
1532 $this->formatheaders =& $this->workbook->add_format();
1533 $this->formatheaders->set_bold(1);
1534 $this->formatheaders->set_align('center');
20e1f1c0 1535 // Sending HTTP headers
43ec99aa 1536 $this->workbook->send($filename);
1537 $this->documentstarted = true;
1538 }
9683db71
TH
1539
1540 function start_table($sheettitle) {
bf404559 1541 $this->worksheet = $this->workbook->add_worksheet($sheettitle);
fdf70473 1542 $this->currentrow=0;
20e1f1c0 1543 }
9683db71
TH
1544
1545 function output_headers($headers) {
20e1f1c0 1546 $colnum = 0;
1547 foreach ($headers as $item) {
fdf70473 1548 $this->worksheet->write($this->currentrow,$colnum,$item,$this->formatheaders);
20e1f1c0 1549 $colnum++;
1550 }
fdf70473 1551 $this->currentrow++;
20e1f1c0 1552 }
9683db71
TH
1553
1554 function add_data($row) {
20e1f1c0 1555 $colnum = 0;
9683db71 1556 foreach ($row as $item) {
fdf70473 1557 $this->worksheet->write($this->currentrow,$colnum,$item,$this->formatnormal);
20e1f1c0 1558 $colnum++;
1559 }
fdf70473 1560 $this->currentrow++;
20e1f1c0 1561 return true;
1562 }
9683db71 1563
20e1f1c0 1564 function add_seperator() {
fdf70473 1565 $this->currentrow++;
20e1f1c0 1566 return true;
1567 }
43ec99aa 1568
9683db71 1569 function finish_table() {
43ec99aa 1570 }
9683db71
TH
1571
1572 function finish_document() {
20e1f1c0 1573 $this->workbook->close();
1574 exit;
1575 }
1576}
1577
9683db71 1578
72fb21b6 1579/**
1580 * @package moodlecore
1581 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1582 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1583 */
9683db71 1584class table_excel_export_format extends table_spreadsheet_export_format_parent {
20e1f1c0 1585 var $fileextension = 'xls';
1586
9683db71 1587 function define_workbook() {
20e1f1c0 1588 global $CFG;
1589 require_once("$CFG->libdir/excellib.class.php");
1590 // Creating a workbook
1591 $this->workbook = new MoodleExcelWorkbook("-");
1592 }
1593
1594}
1595
9683db71 1596
72fb21b6 1597/**
1598 * @package moodlecore
1599 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1600 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1601 */
9683db71 1602class table_ods_export_format extends table_spreadsheet_export_format_parent {
20e1f1c0 1603 var $fileextension = 'ods';
9683db71 1604 function define_workbook() {
20e1f1c0 1605 global $CFG;
1606 require_once("$CFG->libdir/odslib.class.php");
1607 // Creating a workbook
1608 $this->workbook = new MoodleODSWorkbook("-");
1609 }
1610}
1611
9683db71 1612
72fb21b6 1613/**
1614 * @package moodlecore
1615 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1616 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1617 */
6944b5e4 1618class table_text_export_format_parent extends table_default_export_format_parent {
9a5abd1b 1619 protected $seperator = "tab";
6944b5e4
TH
1620 protected $mimetype = 'text/tab-separated-values';
1621 protected $ext = '.txt';
9a5abd1b
AG
1622 protected $myexporter;
1623
1624 public function __construct() {
1625 $this->myexporter = new csv_export_writer($this->seperator, '"', $this->mimetype);
1626 }
6944b5e4
TH
1627
1628 public function start_document($filename) {
9a5abd1b 1629 $this->filename = $filename;
43ec99aa 1630 $this->documentstarted = true;
9a5abd1b 1631 $this->myexporter->set_filename($filename, $this->ext);
43ec99aa 1632 }
6944b5e4
TH
1633
1634 public function start_table($sheettitle) {
43ec99aa 1635 //nothing to do here
20e1f1c0 1636 }
6944b5e4
TH
1637
1638 public function output_headers($headers) {
9a5abd1b 1639 $this->myexporter->add_data($headers);
20e1f1c0 1640 }
6944b5e4
TH
1641
1642 public function add_data($row) {
9a5abd1b 1643 $this->myexporter->add_data($row);
20e1f1c0 1644 return true;
1645 }
6944b5e4
TH
1646
1647 public function finish_table() {
9a5abd1b 1648 //nothing to do here
43ec99aa 1649 }
6944b5e4
TH
1650
1651 public function finish_document() {
9a5abd1b 1652 $this->myexporter->download_file();
20e1f1c0 1653 exit;
1654 }
6944b5e4
TH
1655
1656 /**
1657 * Format a row of data.
1658 * @param array $data
1659 */
1660 protected function format_row($data) {
1661 $escapeddata = array();
1662 foreach ($data as $value) {
1663 $escapeddata[] = '"' . str_replace('"', '""', $value) . '"';
1664 }
1665 return implode($this->seperator, $escapeddata) . "\n";
1666 }
20e1f1c0 1667}
1668
9683db71 1669
72fb21b6 1670/**
1671 * @package moodlecore
1672 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1673 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1674 */
9683db71 1675class table_tsv_export_format extends table_text_export_format_parent {
9a5abd1b 1676 protected $seperator = "tab";
6944b5e4
TH
1677 protected $mimetype = 'text/tab-separated-values';
1678 protected $ext = '.txt';
20e1f1c0 1679}
1680
9a5abd1b 1681require_once($CFG->libdir . '/csvlib.class.php');
72fb21b6 1682/**
1683 * @package moodlecore
1684 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1685 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1686 */
9683db71 1687class table_csv_export_format extends table_text_export_format_parent {
9a5abd1b 1688 protected $seperator = "comma";
6944b5e4
TH
1689 protected $mimetype = 'text/csv';
1690 protected $ext = '.csv';
20e1f1c0 1691}
1692
72fb21b6 1693/**
1694 * @package moodlecore
1695 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
1696 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1697 */
9683db71
TH
1698class table_xhtml_export_format extends table_default_export_format_parent {
1699 function start_document($filename) {
20e1f1c0 1700 header("Content-Type: application/download\n");
43ec99aa 1701 header("Content-Disposition: attachment; filename=\"$filename.html\"");
20e1f1c0 1702 header("Expires: 0");
1703 header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
1704 header("Pragma: public");
20e1f1c0 1705 //html headers
20e1f1c0 1706 echo <<<EOF
1707<?xml version="1.0" encoding="UTF-8"?>
1708<!DOCTYPE html
1709 PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
9cf4a18b 1710 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
20e1f1c0 1711
9cf4a18b 1712<html xmlns="http://www.w3.org/1999/xhtml"
20e1f1c0 1713 xml:lang="en" lang="en">
869309b8 1714<head>
60bfa046 1715<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
20e1f1c0 1716<style type="text/css">/*<![CDATA[*/
1717
1718.flexible th {
1719white-space:normal;
1720}
1721th.header, td.header, div.header {
1722border-color:#DDDDDD;
1723background-color:lightGrey;
1724}
1725.flexible th {
1726white-space:nowrap;
1727}
1728th {
1729font-weight:bold;
1730}
1731
1732.generaltable {
1733border-style:solid;
1734}
1735.generalbox {
1736border-style:solid;
1737}
1738body, table, td, th {
1739font-family:Arial,Verdana,Helvetica,sans-serif;
1740font-size:100%;
1741}
1742td {
1743 border-style:solid;
1744 border-width:1pt;
1745}
1746table {
1747 border-collapse:collapse;
1748 border-spacing:0pt;
1749 width:80%;
1750 margin:auto;
1751}
1752
9683db71 1753h1, h2 {
20e1f1c0 1754 text-align:center;
1755}
1756.bold {
1757font-weight:bold;
1758}
869309b8 1759.mdl-align {
1760 text-align:center;
1761}
20e1f1c0 1762/*]]>*/</style>
869309b8 1763<title>$filename</title>
1764</head>
20e1f1c0 1765<body>
20e1f1c0 1766EOF;
43ec99aa 1767 $this->documentstarted = true;
1768 }
9683db71
TH
1769
1770 function start_table($sheettitle) {
43ec99aa 1771 $this->table->sortable(false);
1772 $this->table->collapsible(false);
1773 echo "<h2>{$sheettitle}</h2>";
20e1f1c0 1774 $this->table->start_html();
1775 }
43ec99aa 1776
9683db71 1777 function output_headers($headers) {
20e1f1c0 1778 $this->table->print_headers();
46254044 1779 echo html_writer::start_tag('tbody');
20e1f1c0 1780 }
9683db71
TH
1781
1782 function add_data($row) {
20e1f1c0 1783 $this->table->print_row($row);
1784 return true;
1785 }
9683db71 1786
20e1f1c0 1787 function add_seperator() {
1788 $this->table->print_row(NULL);
1789 return true;
1790 }
9683db71
TH
1791
1792 function finish_table() {
20e1f1c0 1793 $this->table->finish_html();
43ec99aa 1794 }
9683db71
TH
1795
1796 function finish_document() {
869309b8 1797 echo "</body>\n</html>";
20e1f1c0 1798 exit;
1799 }
9683db71
TH
1800
1801 function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
1802 if (is_null($options)) {
ef27e742 1803 $options = new stdClass;
1804 }
1805 //some sensible defaults
9683db71 1806 if (!isset($options->para)) {
ef27e742 1807 $options->para = false;
1808 }
9683db71 1809 if (!isset($options->newlines)) {
ef27e742 1810 $options->newlines = false;
1811 }
1812 if (!isset($options->smiley)) {
1813 $options->smiley = false;
1814 }
1815 if (!isset($options->filter)) {
1816 $options->filter = false;
1817 }
1818 return format_text($text, $format, $options);
1819 }
20e1f1c0 1820}