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