Merge branch 'MDL-43804-master' of git://github.com/ankitagarwal/moodle
[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     private $column_textsort = array();
64     var $setup           = false;
65     var $sess            = NULL;
66     var $baseurl         = NULL;
67     var $request         = array();
69     var $is_collapsible = false;
70     var $is_sortable    = false;
71     var $use_pages      = false;
72     var $use_initials   = false;
74     var $maxsortkeys = 2;
75     var $pagesize    = 30;
76     var $currpage    = 0;
77     var $totalrows   = 0;
78     var $currentrow  = 0;
79     var $sort_default_column = NULL;
80     var $sort_default_order  = SORT_ASC;
82     /**
83      * Array of positions in which to display download controls.
84      */
85     var $showdownloadbuttonsat= array(TABLE_P_TOP);
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';
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  = '';
100     /**
101      * @var bool whether data is downloadable from table. Determines whether
102      * to display download buttons. Set by method downloadable().
103      */
104     var $downloadable = false;
106     /**
107      * @var string which download plugin to use. Default '' means none - print
108      * html table with paging.
109      */
110     var $defaultdownloadformat  = 'csv';
112     /**
113      * @var bool Has start output been called yet?
114      */
115     var $started_output = false;
117     var $exportclass = null;
119     /**
120      * Constructor
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.
123      */
124     function __construct($uniqueid) {
125         $this->uniqueid = $uniqueid;
126         $this->request  = array(
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',
133         );
134     }
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      */
149     function is_downloading($download = null, $filename='', $sheettitle='') {
150         if ($download!==null) {
151             $this->sheettitle = $sheettitle;
152             $this->is_downloadable(true);
153             $this->download = $download;
154             $this->filename = clean_filename($filename);
155             $this->export_class_instance();
156         }
157         return $this->download;
158     }
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) {
166         if (!is_null($exportclass)) {
167             $this->started_output = true;
168             $this->exportclass = $exportclass;
169             $this->exportclass->table = $this;
170         } else if (is_null($this->exportclass) && !empty($this->download)) {
171             $classname = 'table_'.$this->download.'_export_format';
172             $this->exportclass = new $classname($this);
173             if (!$this->exportclass->document_started()) {
174                 $this->exportclass->start_document($this->filename);
175             }
176         }
177         return $this->exportclass;
178     }
180     /**
181      * Probably don't need to call this directly. Calling is_downloading with a
182      * param automatically sets table as downloadable.
183      *
184      * @param bool $downloadable optional param to set whether data from
185      * table is downloadable. If ommitted this function can be used to get
186      * current state of table.
187      * @return bool whether table data is set to be downloadable.
188      */
189     function is_downloadable($downloadable = null) {
190         if ($downloadable !== null) {
191             $this->downloadable = $downloadable;
192         }
193         return $this->downloadable;
194     }
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      */
201     function show_download_buttons_at($showat) {
202         $this->showdownloadbuttonsat = $showat;
203     }
205     /**
206      * Sets the is_sortable variable to the given boolean, sort_default_column to
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      */
213     function sortable($bool, $defaultcolumn = NULL, $defaultorder = SORT_ASC) {
214         $this->is_sortable = $bool;
215         $this->sort_default_column = $defaultcolumn;
216         $this->sort_default_order  = $defaultorder;
217     }
219     /**
220      * Use text sorting functions for this column (required for text columns with Oracle).
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.
223      * @param string column name
224      */
225     function text_sorting($column) {
226         $this->column_textsort[] = $column;
227     }
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     }
237     /**
238      * Is the column sortable?
239      * @param string column name, null means table
240      * @return bool
241      */
242     function is_sortable($column = null) {
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     }
252     /**
253      * Sets the is_collapsible variable to the given boolean.
254      * @param bool $bool
255      * @return void
256      */
257     function collapsible($bool) {
258         $this->is_collapsible = $bool;
259     }
261     /**
262      * Sets the use_pages variable to the given boolean.
263      * @param bool $bool
264      * @return void
265      */
266     function pageable($bool) {
267         $this->use_pages = $bool;
268     }
270     /**
271      * Sets the use_initials variable to the given boolean.
272      * @param bool $bool
273      * @return void
274      */
275     function initialbars($bool) {
276         $this->use_initials = $bool;
277     }
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      */
286     function pagesize($perpage, $total) {
287         $this->pagesize  = $perpage;
288         $this->totalrows = $total;
289         $this->use_pages = true;
290     }
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      */
298     function set_control_variables($variables) {
299         foreach ($variables as $what => $variable) {
300             if (isset($this->request[$what])) {
301                 $this->request[$what] = $variable;
302             }
303         }
304     }
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      */
312     function set_attribute($attribute, $value) {
313         $this->attributes[$attribute] = $value;
314     }
316     /**
317      * What this method does is set the column so that if the same data appears in
318      * consecutive rows, then it is not repeated.
319      *
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.
323      * @param int $column the index of a column.
324      */
325     function column_suppress($column) {
326         if (isset($this->column_suppress[$column])) {
327             $this->column_suppress[$column] = true;
328         }
329     }
331     /**
332      * Sets the given $column index to the given $classname in $this->column_class.
333      * @param int $column
334      * @param string $classname
335      * @return void
336      */
337     function column_class($column, $classname) {
338         if (isset($this->column_class[$column])) {
339             $this->column_class[$column] = ' '.$classname; // This space needed so that classnames don't run together in the HTML
340         }
341     }
343     /**
344      * Sets the given $column index and $property index to the given $value in $this->column_style.
345      * @param int $column
346      * @param string $property
347      * @param mixed $value
348      * @return void
349      */
350     function column_style($column, $property, $value) {
351         if (isset($this->column_style[$column])) {
352             $this->column_style[$column][$property] = $value;
353         }
354     }
356     /**
357      * Sets all columns' $propertys to the given $value in $this->column_style.
358      * @param int $property
359      * @param string $value
360      * @return void
361      */
362     function column_style_all($property, $value) {
363         foreach (array_keys($this->columns) as $column) {
364             $this->column_style[$column][$property] = $value;
365         }
366     }
368     /**
369      * Sets $this->baseurl.
370      * @param moodle_url|string $url the url with params needed to call up this page
371      */
372     function define_baseurl($url) {
373         $this->baseurl = new moodle_url($url);
374     }
376     /**
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.
379      */
380     function define_columns($columns) {
381         $this->columns = array();
382         $this->column_style = array();
383         $this->column_class = array();
384         $colnum = 0;
386         foreach ($columns as $column) {
387             $this->columns[$column]         = $colnum++;
388             $this->column_style[$column]    = array();
389             $this->column_class[$column]    = '';
390             $this->column_suppress[$column] = false;
391         }
392     }
394     /**
395      * @param array $headers numerical keyed array of displayed string titles
396      * for each column.
397      */
398     function define_headers($headers) {
399         $this->headers = $headers;
400     }
402     /**
403      * Must be called after table is defined. Use methods above first. Cannot
404      * use functions below till after calling this method.
405      * @return type?
406      */
407     function setup() {
408         global $SESSION, $CFG;
410         if (empty($this->columns) || empty($this->uniqueid)) {
411             return false;
412         }
414         if (!isset($SESSION->flextable)) {
415             $SESSION->flextable = array();
416         }
418         if (!isset($SESSION->flextable[$this->uniqueid])) {
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   = '';
425             $SESSION->flextable[$this->uniqueid]->textsort = $this->column_textsort;
426         }
428         $this->sess = &$SESSION->flextable[$this->uniqueid];
430         if (($showcol = optional_param($this->request[TABLE_VAR_SHOW], '', PARAM_ALPHANUMEXT)) &&
431                 isset($this->columns[$showcol])) {
432             $this->sess->collapse[$showcol] = false;
434         } else if (($hidecol = optional_param($this->request[TABLE_VAR_HIDE], '', PARAM_ALPHANUMEXT)) &&
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]);
439             }
440         }
442         // Now, update the column attributes for collapsed columns
443         foreach (array_keys($this->columns) as $column) {
444             if (!empty($this->sess->collapse[$column])) {
445                 $this->column_style[$column]['width'] = '10px';
446             }
447         }
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']))) {
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);
461             }
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);
465         }
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             }
474         }
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;
479         }
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;
484         }
486         if (empty($this->baseurl)) {
487             debugging('You should set baseurl when using flexible_table.');
488             global $PAGE;
489             $this->baseurl = $PAGE->url;
490         }
492         $this->currpage = optional_param($this->request[TABLE_VAR_PAGE], 0, PARAM_INT);
493         $this->setup = true;
495         // Always introduce the "flexible" class for the table if not specified
496         if (empty($this->attributes)) {
497             $this->attributes['class'] = 'flexible';
498         } else if (!isset($this->attributes['class'])) {
499             $this->attributes['class'] = 'flexible';
500         } else if (!in_array('flexible', explode(' ', $this->attributes['class']))) {
501             $this->attributes['class'] = trim('flexible ' . $this->attributes['class']);
502         }
503     }
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;
512         if (empty($SESSION->flextable[$uniqueid])) {
513            return '';
514         }
516         $sess = &$SESSION->flextable[$uniqueid];
517         if (empty($sess->sortby)) {
518             return '';
519         }
520         if (empty($sess->textsort)) {
521             $sess->textsort = array();
522         }
524         return self::construct_order_by($sess->sortby, $sess->textsort);
525     }
527     /**
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.
531      */
532     public static function construct_order_by($cols, $textsortcols=array()) {
533         global $DB;
534         $bits = array();
536         foreach ($cols as $column => $order) {
537             if (in_array($column, $textsortcols)) {
538                 $column = $DB->sql_order_by_text($column);
539             }
540             if ($order == SORT_ASC) {
541                 $bits[] = $column . ' ASC';
542             } else {
543                 $bits[] = $column . ' DESC';
544             }
545         }
547         return implode(', ', $bits);
548     }
550     /**
551      * @return SQL fragment that can be used in an ORDER BY clause.
552      */
553     public function get_sql_sort() {
554         return self::construct_order_by($this->get_sort_columns(), $this->column_textsort);
555     }
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.');
564         }
566         if (empty($this->sess->sortby)) {
567             return array();
568         }
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         }
582         return $this->sess->sortby;
583     }
585     /**
586      * @return int the offset for LIMIT clause of SQL
587      */
588     function get_page_start() {
589         if (!$this->use_pages) {
590             return '';
591         }
592         return $this->currpage * $this->pagesize;
593     }
595     /**
596      * @return int the pagesize for LIMIT clause of SQL
597      */
598     function get_page_size() {
599         if (!$this->use_pages) {
600             return '';
601         }
602         return $this->pagesize;
603     }
605     /**
606      * @return string sql to add to where statement.
607      */
608     function get_sql_where() {
609         global $DB;
611         $conditions = array();
612         $params = array();
614         if (isset($this->columns['fullname'])) {
615             static $i = 0;
616             $i++;
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             }
626         }
628         return array(implode(" AND ", $conditions), $params);
629     }
631     /**
632      * Add a row of data to the table. This function takes an array with
633      * column names as keys.
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
639      * @param string $classname CSS class name to add to this row's tr tag.
640      */
641     function add_data_keyed($rowwithkeys, $classname = '') {
642         $this->add_data($this->get_row_from_keyed($rowwithkeys), $classname);
643     }
645     /**
646      * Add a seperator line to table.
647      */
648     function add_separator() {
649         if (!$this->setup) {
650             return false;
651         }
652         $this->add_data(NULL);
653     }
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.
661      *
662      * @param array $row a numerically keyed row of data to add to the table.
663      * @param string $classname CSS class name to add to this row's tr tag.
664      * @return bool success.
665      */
666     function add_data($row, $classname = '') {
667         if (!$this->setup) {
668             return false;
669         }
670         if (!$this->started_output) {
671             $this->start_output();
672         }
673         if ($this->exportclass!==null) {
674             if ($row === null) {
675                 $this->exportclass->add_seperator();
676             } else {
677                 $this->exportclass->add_data($row);
678             }
679         } else {
680             $this->print_row($row, $classname);
681         }
682         return true;
683     }
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.
688      *
689      */
690     function finish_output($closeexportclassdoc = true) {
691         if ($this->exportclass!==null) {
692             $this->exportclass->finish_table();
693             if ($closeexportclassdoc) {
694                 $this->exportclass->finish_document();
695             }
696         } else {
697             $this->finish_html();
698         }
699     }
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      */
706     function wrap_html_start() {
707     }
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      */
714     function wrap_html_finish() {
715     }
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      */
722     function format_row($row) {
723         $formattedrow = array();
724         foreach (array_keys($this->columns) as $column) {
725             $colmethodname = 'col_'.$column;
726             if (method_exists($this, $colmethodname)) {
727                 $formattedcolumn = $this->$colmethodname($row);
728             } else {
729                 $formattedcolumn = $this->other_cols($column, $row);
730                 if ($formattedcolumn===NULL) {
731                     $formattedcolumn = $row->$column;
732                 }
733             }
734             $formattedrow[$column] = $formattedcolumn;
735         }
736         return $formattedrow;
737     }
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      */
747     function col_fullname($row) {
748         global $COURSE, $CFG;
750         $name = fullname($row);
751         if ($this->download) {
752             return $name;
753         }
755         $userid = $row->{$this->useridfield};
756         if ($COURSE->id == SITEID) {
757             $profileurl = new moodle_url('/user/profile.php', array('id' => $userid));
758         } else {
759             $profileurl = new moodle_url('/user/view.php',
760                     array('id' => $userid, 'course' => $COURSE->id));
761         }
762         return html_writer::link($profileurl, $name);
763     }
765     /**
766      * You can override this method in a child class. See the description of
767      * build_table which calls this method.
768      */
769     function other_cols($column, $row) {
770         return NULL;
771     }
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      */
780     function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
781         if (!$this->is_downloading()) {
782             if (is_null($options)) {
783                 $options = new stdClass;
784             }
785             //some sensible defaults
786             if (!isset($options->para)) {
787                 $options->para = false;
788             }
789             if (!isset($options->newlines)) {
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     }
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() {
809         if (!$this->setup) {
810             return false;
811         }
812         $this->finish_html();
813     }
815     /**
816      * This function is not part of the public api.
817      * @return string initial of first name we are currently filtering by
818      */
819     function get_initial_first() {
820         if (!$this->use_initials) {
821             return NULL;
822         }
824         return $this->sess->i_first;
825     }
827     /**
828      * This function is not part of the public api.
829      * @return string initial of last name we are currently filtering by
830      */
831     function get_initial_last() {
832         if (!$this->use_initials) {
833             return NULL;
834         }
836         return $this->sess->i_last;
837     }
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      */
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         }
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         }
864         echo html_writer::end_tag('div');
865     }
867     /**
868      * This function is not part of the public api.
869      */
870     function print_initials_bar() {
871         if ((!empty($this->sess->i_last) || !empty($this->sess->i_first) ||$this->use_initials)
872                     && isset($this->columns['fullname'])) {
874             $alpha  = explode(',', get_string('alphabet', 'langconfig'));
876             // Bar of first initials
877             if (!empty($this->sess->i_first)) {
878                 $ifirst = $this->sess->i_first;
879             } else {
880                 $ifirst = '';
881             }
882             $this->print_one_initials_bar($alpha, $ifirst, 'firstinitial',
883                     get_string('firstname'), $this->request[TABLE_VAR_IFIRST]);
885             // Bar of last initials
886             if (!empty($this->sess->i_last)) {
887                 $ilast = $this->sess->i_last;
888             } else {
889                 $ilast = '';
890             }
891             $this->print_one_initials_bar($alpha, $ilast, 'lastinitial',
892                     get_string('lastname'), $this->request[TABLE_VAR_ILAST]);
893         }
894     }
896     /**
897      * This function is not part of the public api.
898      */
899     function print_nothing_to_display() {
900         global $OUTPUT;
901         $this->print_initials_bar();
903         echo $OUTPUT->heading(get_string('nothingtodisplay'));
904     }
906     /**
907      * This function is not part of the public api.
908      */
909     function get_row_from_keyed($rowwithkeys) {
910         if (is_object($rowwithkeys)) {
911             $rowwithkeys = (array)$rowwithkeys;
912         }
913         $row = array();
914         foreach (array_keys($this->columns) as $column) {
915             if (isset($rowwithkeys[$column])) {
916                 $row [] = $rowwithkeys[$column];
917             } else {
918                 $row[] ='';
919             }
920         }
921         return $row;
922     }
923     /**
924      * This function is not part of the public api.
925      */
926     function get_download_menu() {
927         $allclasses= get_declared_classes();
928         $exportclasses = array();
929         foreach ($allclasses as $class) {
930             $matches = array();
931             if (preg_match('/^table\_([a-z]+)\_export\_format$/', $class, $matches)) {
932                 $type = $matches[1];
933                 $exportclasses[$type]= get_string("download$type", 'table');
934             }
935         }
936         return $exportclasses;
937     }
939     /**
940      * This function is not part of the public api.
941      */
942     function download_buttons() {
943         if ($this->is_downloadable() && !$this->is_downloading()) {
944             $downloadoptions = $this->get_download_menu();
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').'"/>';
951             $html = '<form action="'. $this->baseurl .'" method="post">';
952             $html .= '<div class="mdl-align">';
953             $html .= html_writer::tag('label', get_string('downloadas', 'table', $downloadelements));
954             $html .= '</div></form>';
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.
965      *
966      */
967     function start_output() {
968         $this->started_output = true;
969         if ($this->exportclass!==null) {
970             $this->exportclass->start_table($this->sheettitle);
971             $this->exportclass->output_headers($this->headers);
972         } else {
973             $this->start_html();
974             $this->print_headers();
975             echo html_writer::start_tag('tbody');
976         }
977     }
979     /**
980      * This function is not part of the public api.
981      *
982      * Please do not use .r0/.r1 for css, as they will be removed in Moodle 2.9.
983      * @todo MDL-43902 , remove r0 and r1 from tr classes.
984      */
985     function print_row($row, $classname = '') {
986         static $suppress_lastrow = NULL;
987         $oddeven = $this->currentrow % 2;
988         $rowclasses = array('r' . $oddeven);
990         if ($classname) {
991             $rowclasses[] = $classname;
992         }
994         $rowid = $this->uniqueid . '_r' . $this->currentrow;
996         echo html_writer::start_tag('tr', array('class' => implode(' ', $rowclasses), 'id' => $rowid));
998         // If we have a separator, print it
999         if ($row === NULL) {
1000             $colcount = count($this->columns);
1001             echo html_writer::tag('td', html_writer::tag('div', '',
1002                     array('class' => 'tabledivider')), array('colspan' => $colcount));
1004         } else {
1005             $colbyindex = array_flip($this->columns);
1006             foreach ($row as $index => $data) {
1007                 $column = $colbyindex[$index];
1009                 if (empty($this->sess->collapse[$column])) {
1010                     if ($this->column_suppress[$column] && $suppress_lastrow !== NULL && $suppress_lastrow[$index] === $data) {
1011                         $content = '&nbsp;';
1012                     } else {
1013                         $content = $data;
1014                     }
1015                 } else {
1016                     $content = '&nbsp;';
1017                 }
1019                 echo html_writer::tag('td', $content, array(
1020                         'class' => 'cell c' . $index . $this->column_class[$column],
1021                         'id' => $rowid . '_c' . $index,
1022                         'style' => $this->make_styles_string($this->column_style[$column])));
1023             }
1024         }
1026         echo html_writer::end_tag('tr');
1028         $suppress_enabled = array_sum($this->column_suppress);
1029         if ($suppress_enabled) {
1030             $suppress_lastrow = $row;
1031         }
1032         $this->currentrow++;
1033     }
1035     /**
1036      * This function is not part of the public api.
1037      */
1038     function finish_html() {
1039         global $OUTPUT;
1040         if (!$this->started_output) {
1041             //no data has been added to the table.
1042             $this->print_nothing_to_display();
1044         } else {
1045             // Print empty rows to fill the table to the current pagesize.
1046             // This is done so the header aria-controls attributes do not point to
1047             // non existant elements.
1048             $emptyrow = array_fill(0, count($this->columns), '');
1049             while ($this->currentrow < $this->pagesize) {
1050                 $this->print_row($emptyrow, 'emptyrow');
1051             }
1053             echo html_writer::end_tag('tbody');
1054             echo html_writer::end_tag('table');
1055             echo html_writer::end_tag('div');
1056             $this->wrap_html_finish();
1058             // Paging bar
1059             if(in_array(TABLE_P_BOTTOM, $this->showdownloadbuttonsat)) {
1060                 echo $this->download_buttons();
1061             }
1063             if($this->use_pages) {
1064                 $pagingbar = new paging_bar($this->totalrows, $this->currpage, $this->pagesize, $this->baseurl);
1065                 $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
1066                 echo $OUTPUT->render($pagingbar);
1067             }
1068         }
1069     }
1071     /**
1072      * Generate the HTML for the collapse/uncollapse icon. This is a helper method
1073      * used by {@link print_headers()}.
1074      * @param string $column the column name, index into various names.
1075      * @param int $index numerical index of the column.
1076      * @return string HTML fragment.
1077      */
1078     protected function show_hide_link($column, $index) {
1079         global $OUTPUT;
1080         // Some headers contain <br /> tags, do not include in title, hence the
1081         // strip tags.
1083         $ariacontrols = '';
1084         for ($i = 0; $i < $this->pagesize; $i++) {
1085             $ariacontrols .= $this->uniqueid . '_r' . $i . '_c' . $index . ' ';
1086         }
1088         $ariacontrols = trim($ariacontrols);
1090         if (!empty($this->sess->collapse[$column])) {
1091             $linkattributes = array('title' => get_string('show') . ' ' . strip_tags($this->headers[$index]),
1092                                     'aria-expanded' => 'false',
1093                                     'aria-controls' => $ariacontrols);
1094             return html_writer::link($this->baseurl->out(false, array($this->request[TABLE_VAR_SHOW] => $column)),
1095                     html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/switch_plus'), 'alt' => get_string('show'))),
1096                     $linkattributes);
1098         } else if ($this->headers[$index] !== NULL) {
1099             $linkattributes = array('title' => get_string('hide') . ' ' . strip_tags($this->headers[$index]),
1100                                     'aria-expanded' => 'true',
1101                                     'aria-controls' => $ariacontrols);
1102             return html_writer::link($this->baseurl->out(false, array($this->request[TABLE_VAR_HIDE] => $column)),
1103                     html_writer::empty_tag('img', array('src' => $OUTPUT->pix_url('t/switch_minus'), 'alt' => get_string('hide'))),
1104                     $linkattributes);
1105         }
1106     }
1108     /**
1109      * This function is not part of the public api.
1110      */
1111     function print_headers() {
1112         global $CFG, $OUTPUT;
1114         echo html_writer::start_tag('thead');
1115         echo html_writer::start_tag('tr');
1116         foreach ($this->columns as $column => $index) {
1118             $icon_hide = '';
1119             if ($this->is_collapsible) {
1120                 $icon_hide = $this->show_hide_link($column, $index);
1121             }
1123             $primary_sort_column = '';
1124             $primary_sort_order  = '';
1125             if (reset($this->sess->sortby)) {
1126                 $primary_sort_column = key($this->sess->sortby);
1127                 $primary_sort_order  = current($this->sess->sortby);
1128             }
1130             switch ($column) {
1132                 case 'fullname':
1133                 // Check the full name display for sortable fields.
1134                 $nameformat = $CFG->fullnamedisplay;
1135                 if ($nameformat == 'language') {
1136                     $nameformat = get_string('fullnamedisplay');
1137                 }
1138                 $requirednames = order_in_string(array('firstname', 'lastname'), $nameformat);
1140                 if (!empty($requirednames)) {
1141                     if ($this->is_sortable($column)) {
1142                         // Done this way for the possibility of more than two sortable full name display fields.
1143                         $this->headers[$index] = '';
1144                         foreach ($requirednames as $name) {
1145                             $sortname = $this->sort_link(get_string($name),
1146                                     $name, $primary_sort_column === $name, $primary_sort_order);
1147                             $this->headers[$index] .= $sortname . ' / ';
1148                         }
1149                         $this->headers[$index] = substr($this->headers[$index], 0, -3);
1150                     }
1151                 }
1152                 break;
1154                 case 'userpic':
1155                     // do nothing, do not display sortable links
1156                 break;
1158                 default:
1159                 if ($this->is_sortable($column)) {
1160                     $this->headers[$index] = $this->sort_link($this->headers[$index],
1161                             $column, $primary_sort_column == $column, $primary_sort_order);
1162                 }
1163             }
1165             $attributes = array(
1166                 'class' => 'header c' . $index . $this->column_class[$column],
1167                 'scope' => 'col',
1168             );
1169             if ($this->headers[$index] === NULL) {
1170                 $content = '&nbsp;';
1171             } else if (!empty($this->sess->collapse[$column])) {
1172                 $content = $icon_hide;
1173             } else {
1174                 if (is_array($this->column_style[$column])) {
1175                     $attributes['style'] = $this->make_styles_string($this->column_style[$column]);
1176                 }
1177                 $content = $this->headers[$index] . html_writer::tag('div',
1178                         $icon_hide, array('class' => 'commands'));
1179             }
1180             echo html_writer::tag('th', $content, $attributes);
1181         }
1183         echo html_writer::end_tag('tr');
1184         echo html_writer::end_tag('thead');
1185     }
1187     /**
1188      * Generate the HTML for the sort icon. This is a helper method used by {@link sort_link()}.
1189      * @param bool $isprimary whether an icon is needed (it is only needed for the primary sort column.)
1190      * @param int $order SORT_ASC or SORT_DESC
1191      * @return string HTML fragment.
1192      */
1193     protected function sort_icon($isprimary, $order) {
1194         global $OUTPUT;
1196         if (!$isprimary) {
1197             return '';
1198         }
1200         if ($order == SORT_ASC) {
1201             return html_writer::empty_tag('img',
1202                     array('src' => $OUTPUT->pix_url('t/sort_asc'), 'alt' => get_string('asc'), 'class' => 'iconsort'));
1203         } else {
1204             return html_writer::empty_tag('img',
1205                     array('src' => $OUTPUT->pix_url('t/sort_desc'), 'alt' => get_string('desc'), 'class' => 'iconsort'));
1206         }
1207     }
1209     /**
1210      * Generate the correct tool tip for changing the sort order. This is a
1211      * helper method used by {@link sort_link()}.
1212      * @param bool $isprimary whether the is column is the current primary sort column.
1213      * @param int $order SORT_ASC or SORT_DESC
1214      * @return string the correct title.
1215      */
1216     protected function sort_order_name($isprimary, $order) {
1217         if ($isprimary && $order != SORT_ASC) {
1218             return get_string('desc');
1219         } else {
1220             return get_string('asc');
1221         }
1222     }
1224     /**
1225      * Generate the HTML for the sort link. This is a helper method used by {@link print_headers()}.
1226      * @param string $text the text for the link.
1227      * @param string $column the column name, may be a fake column like 'firstname' or a real one.
1228      * @param bool $isprimary whether the is column is the current primary sort column.
1229      * @param int $order SORT_ASC or SORT_DESC
1230      * @return string HTML fragment.
1231      */
1232     protected function sort_link($text, $column, $isprimary, $order) {
1233         return html_writer::link($this->baseurl->out(false,
1234                 array($this->request[TABLE_VAR_SORT] => $column)),
1235                 $text . get_accesshide(get_string('sortby') . ' ' .
1236                 $text . ' ' . $this->sort_order_name($isprimary, $order))) . ' ' .
1237                 $this->sort_icon($isprimary, $order);
1238     }
1240     /**
1241      * This function is not part of the public api.
1242      */
1243     function start_html() {
1244         global $OUTPUT;
1245         // Do we need to print initial bars?
1246         $this->print_initials_bar();
1248         // Paging bar
1249         if ($this->use_pages) {
1250             $pagingbar = new paging_bar($this->totalrows, $this->currpage, $this->pagesize, $this->baseurl);
1251             $pagingbar->pagevar = $this->request[TABLE_VAR_PAGE];
1252             echo $OUTPUT->render($pagingbar);
1253         }
1255         if (in_array(TABLE_P_TOP, $this->showdownloadbuttonsat)) {
1256             echo $this->download_buttons();
1257         }
1259         $this->wrap_html_start();
1260         // Start of main data table
1262         echo html_writer::start_tag('div', array('class' => 'no-overflow'));
1263         echo html_writer::start_tag('table', $this->attributes);
1265     }
1267     /**
1268      * This function is not part of the public api.
1269      * @param array $styles CSS-property => value
1270      * @return string values suitably to go in a style="" attribute in HTML.
1271      */
1272     function make_styles_string($styles) {
1273         if (empty($styles)) {
1274             return null;
1275         }
1277         $string = '';
1278         foreach($styles as $property => $value) {
1279             $string .= $property . ':' . $value . ';';
1280         }
1281         return $string;
1282     }
1286 /**
1287  * @package   moodlecore
1288  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1289  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1290  */
1291 class table_sql extends flexible_table {
1293     public $countsql = NULL;
1294     public $countparams = NULL;
1295     /**
1296      * @var object sql for querying db. Has fields 'fields', 'from', 'where', 'params'.
1297      */
1298     public $sql = NULL;
1299     /**
1300      * @var array Data fetched from the db.
1301      */
1302     public $rawdata = NULL;
1304     /**
1305      * @var bool Overriding default for this.
1306      */
1307     public $is_sortable    = true;
1308     /**
1309      * @var bool Overriding default for this.
1310      */
1311     public $is_collapsible = true;
1313     /**
1314      * @param string $uniqueid a string identifying this table.Used as a key in
1315      *                          session  vars.
1316      */
1317     function __construct($uniqueid) {
1318         parent::__construct($uniqueid);
1319         // some sensible defaults
1320         $this->set_attribute('cellspacing', '0');
1321         $this->set_attribute('class', 'generaltable generalbox');
1322     }
1324     /**
1325      * Take the data returned from the db_query and go through all the rows
1326      * processing each col using either col_{columnname} method or other_cols
1327      * method or if other_cols returns NULL then put the data straight into the
1328      * table.
1329      */
1330     function build_table() {
1331         if ($this->rawdata) {
1332             foreach ($this->rawdata as $row) {
1333                 $formattedrow = $this->format_row($row);
1334                 $this->add_data_keyed($formattedrow,
1335                         $this->get_row_class($row));
1336             }
1337         }
1338     }
1340     /**
1341      * Get any extra classes names to add to this row in the HTML.
1342      * @param $row array the data for this row.
1343      * @return string added to the class="" attribute of the tr.
1344      */
1345     function get_row_class($row) {
1346         return '';
1347     }
1349     /**
1350      * This is only needed if you want to use different sql to count rows.
1351      * Used for example when perhaps all db JOINS are not needed when counting
1352      * records. You don't need to call this function the count_sql
1353      * will be generated automatically.
1354      *
1355      * We need to count rows returned by the db seperately to the query itself
1356      * as we need to know how many pages of data we have to display.
1357      */
1358     function set_count_sql($sql, array $params = NULL) {
1359         $this->countsql = $sql;
1360         $this->countparams = $params;
1361     }
1363     /**
1364      * Set the sql to query the db. Query will be :
1365      *      SELECT $fields FROM $from WHERE $where
1366      * Of course you can use sub-queries, JOINS etc. by putting them in the
1367      * appropriate clause of the query.
1368      */
1369     function set_sql($fields, $from, $where, array $params = NULL) {
1370         $this->sql = new stdClass();
1371         $this->sql->fields = $fields;
1372         $this->sql->from = $from;
1373         $this->sql->where = $where;
1374         $this->sql->params = $params;
1375     }
1377     /**
1378      * Query the db. Store results in the table object for use by build_table.
1379      *
1380      * @param int $pagesize size of page for paginated displayed table.
1381      * @param bool $useinitialsbar do you want to use the initials bar. Bar
1382      * will only be used if there is a fullname column defined for the table.
1383      */
1384     function query_db($pagesize, $useinitialsbar=true) {
1385         global $DB;
1386         if (!$this->is_downloading()) {
1387             if ($this->countsql === NULL) {
1388                 $this->countsql = 'SELECT COUNT(1) FROM '.$this->sql->from.' WHERE '.$this->sql->where;
1389                 $this->countparams = $this->sql->params;
1390             }
1391             $grandtotal = $DB->count_records_sql($this->countsql, $this->countparams);
1392             if ($useinitialsbar && !$this->is_downloading()) {
1393                 $this->initialbars($grandtotal > $pagesize);
1394             }
1396             list($wsql, $wparams) = $this->get_sql_where();
1397             if ($wsql) {
1398                 $this->countsql .= ' AND '.$wsql;
1399                 $this->countparams = array_merge($this->countparams, $wparams);
1401                 $this->sql->where .= ' AND '.$wsql;
1402                 $this->sql->params = array_merge($this->sql->params, $wparams);
1404                 $total  = $DB->count_records_sql($this->countsql, $this->countparams);
1405             } else {
1406                 $total = $grandtotal;
1407             }
1409             $this->pagesize($pagesize, $total);
1410         }
1412         // Fetch the attempts
1413         $sort = $this->get_sql_sort();
1414         if ($sort) {
1415             $sort = "ORDER BY $sort";
1416         }
1417         $sql = "SELECT
1418                 {$this->sql->fields}
1419                 FROM {$this->sql->from}
1420                 WHERE {$this->sql->where}
1421                 {$sort}";
1423         if (!$this->is_downloading()) {
1424             $this->rawdata = $DB->get_records_sql($sql, $this->sql->params, $this->get_page_start(), $this->get_page_size());
1425         } else {
1426             $this->rawdata = $DB->get_records_sql($sql, $this->sql->params);
1427         }
1428     }
1430     /**
1431      * Convenience method to call a number of methods for you to display the
1432      * table.
1433      */
1434     function out($pagesize, $useinitialsbar, $downloadhelpbutton='') {
1435         global $DB;
1436         if (!$this->columns) {
1437             $onerow = $DB->get_record_sql("SELECT {$this->sql->fields} FROM {$this->sql->from} WHERE {$this->sql->where}", $this->sql->params);
1438             //if columns is not set then define columns as the keys of the rows returned
1439             //from the db.
1440             $this->define_columns(array_keys((array)$onerow));
1441             $this->define_headers(array_keys((array)$onerow));
1442         }
1443         $this->setup();
1444         $this->query_db($pagesize, $useinitialsbar);
1445         $this->build_table();
1446         $this->finish_output();
1447     }
1451 /**
1452  * @package   moodlecore
1453  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1454  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1455  */
1456 class table_default_export_format_parent {
1457     /**
1458      * @var flexible_table or child class reference pointing to table class
1459      * object from which to export data.
1460      */
1461     var $table;
1463     /**
1464      * @var bool output started. Keeps track of whether any output has been
1465      * started yet.
1466      */
1467     var $documentstarted = false;
1468     function table_default_export_format_parent(&$table) {
1469         $this->table =& $table;
1470     }
1472     function set_table(&$table) {
1473         $this->table =& $table;
1474     }
1476     function add_data($row) {
1477         return false;
1478     }
1480     function add_seperator() {
1481         return false;
1482     }
1484     function document_started() {
1485         return $this->documentstarted;
1486     }
1487     /**
1488      * Given text in a variety of format codings, this function returns
1489      * the text as safe HTML or as plain text dependent on what is appropriate
1490      * for the download format. The default removes all tags.
1491      */
1492     function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
1493         //use some whitespace to indicate where there was some line spacing.
1494         $text = str_replace(array('</p>', "\n", "\r"), '   ', $text);
1495         return strip_tags($text);
1496     }
1500 /**
1501  * @package   moodlecore
1502  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1503  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1504  */
1505 class table_spreadsheet_export_format_parent extends table_default_export_format_parent {
1506     var $currentrow;
1507     var $workbook;
1508     var $worksheet;
1509     /**
1510      * @var object format object - format for normal table cells
1511      */
1512     var $formatnormal;
1513     /**
1514      * @var object format object - format for header table cells
1515      */
1516     var $formatheaders;
1518     /**
1519      * should be overriden in child class.
1520      */
1521     var $fileextension;
1523     /**
1524      * This method will be overridden in the child class.
1525      */
1526     function define_workbook() {
1527     }
1529     function start_document($filename) {
1530         $filename = $filename.'.'.$this->fileextension;
1531         $this->define_workbook();
1532         // format types
1533         $this->formatnormal = $this->workbook->add_format();
1534         $this->formatnormal->set_bold(0);
1535         $this->formatheaders = $this->workbook->add_format();
1536         $this->formatheaders->set_bold(1);
1537         $this->formatheaders->set_align('center');
1538         // Sending HTTP headers
1539         $this->workbook->send($filename);
1540         $this->documentstarted = true;
1541     }
1543     function start_table($sheettitle) {
1544         $this->worksheet = $this->workbook->add_worksheet($sheettitle);
1545         $this->currentrow=0;
1546     }
1548     function output_headers($headers) {
1549         $colnum = 0;
1550         foreach ($headers as $item) {
1551             $this->worksheet->write($this->currentrow,$colnum,$item,$this->formatheaders);
1552             $colnum++;
1553         }
1554         $this->currentrow++;
1555     }
1557     function add_data($row) {
1558         $colnum = 0;
1559         foreach ($row as $item) {
1560             $this->worksheet->write($this->currentrow,$colnum,$item,$this->formatnormal);
1561             $colnum++;
1562         }
1563         $this->currentrow++;
1564         return true;
1565     }
1567     function add_seperator() {
1568         $this->currentrow++;
1569         return true;
1570     }
1572     function finish_table() {
1573     }
1575     function finish_document() {
1576         $this->workbook->close();
1577         exit;
1578     }
1582 /**
1583  * @package   moodlecore
1584  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1585  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1586  */
1587 class table_excel_export_format extends table_spreadsheet_export_format_parent {
1588     var $fileextension = 'xls';
1590     function define_workbook() {
1591         global $CFG;
1592         require_once("$CFG->libdir/excellib.class.php");
1593         // Creating a workbook
1594         $this->workbook = new MoodleExcelWorkbook("-");
1595     }
1600 /**
1601  * @package   moodlecore
1602  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1603  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1604  */
1605 class table_ods_export_format extends table_spreadsheet_export_format_parent {
1606     var $fileextension = 'ods';
1607     function define_workbook() {
1608         global $CFG;
1609         require_once("$CFG->libdir/odslib.class.php");
1610         // Creating a workbook
1611         $this->workbook = new MoodleODSWorkbook("-");
1612     }
1616 /**
1617  * @package   moodlecore
1618  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1619  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1620  */
1621 class table_text_export_format_parent extends table_default_export_format_parent {
1622     protected $seperator = "tab";
1623     protected $mimetype = 'text/tab-separated-values';
1624     protected $ext = '.txt';
1625     protected $myexporter;
1627     public function __construct() {
1628         $this->myexporter = new csv_export_writer($this->seperator, '"', $this->mimetype);
1629     }
1631     public function start_document($filename) {
1632         $this->filename = $filename;
1633         $this->documentstarted = true;
1634         $this->myexporter->set_filename($filename, $this->ext);
1635     }
1637     public function start_table($sheettitle) {
1638         //nothing to do here
1639     }
1641     public function output_headers($headers) {
1642         $this->myexporter->add_data($headers);
1643     }
1645     public function add_data($row) {
1646         $this->myexporter->add_data($row);
1647         return true;
1648     }
1650     public function finish_table() {
1651         //nothing to do here
1652     }
1654     public function finish_document() {
1655         $this->myexporter->download_file();
1656         exit;
1657     }
1659     /**
1660      * Format a row of data.
1661      * @param array $data
1662      */
1663     protected function format_row($data) {
1664         $escapeddata = array();
1665         foreach ($data as $value) {
1666             $escapeddata[] = '"' . str_replace('"', '""', $value) . '"';
1667         }
1668         return implode($this->seperator, $escapeddata) . "\n";
1669     }
1673 /**
1674  * @package   moodlecore
1675  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1676  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1677  */
1678 class table_tsv_export_format extends table_text_export_format_parent {
1679     protected $seperator = "tab";
1680     protected $mimetype = 'text/tab-separated-values';
1681     protected $ext = '.txt';
1684 require_once($CFG->libdir . '/csvlib.class.php');
1685 /**
1686  * @package   moodlecore
1687  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1688  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1689  */
1690 class table_csv_export_format extends table_text_export_format_parent {
1691     protected $seperator = "comma";
1692     protected $mimetype = 'text/csv';
1693     protected $ext = '.csv';
1696 /**
1697  * @package   moodlecore
1698  * @copyright 1999 onwards Martin Dougiamas  {@link http://moodle.com}
1699  * @license   http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
1700  */
1701 class table_xhtml_export_format extends table_default_export_format_parent {
1702     function start_document($filename) {
1703         header("Content-Type: application/download\n");
1704         header("Content-Disposition: attachment; filename=\"$filename.html\"");
1705         header("Expires: 0");
1706         header("Cache-Control: must-revalidate,post-check=0,pre-check=0");
1707         header("Pragma: public");
1708         //html headers
1709         echo <<<EOF
1710 <?xml version="1.0" encoding="UTF-8"?>
1711 <!DOCTYPE html
1712   PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
1713   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
1715 <html xmlns="http://www.w3.org/1999/xhtml"
1716   xml:lang="en" lang="en">
1717 <head>
1718 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
1719 <style type="text/css">/*<![CDATA[*/
1721 .flexible th {
1722 white-space:normal;
1724 th.header, td.header, div.header {
1725 border-color:#DDDDDD;
1726 background-color:lightGrey;
1728 .flexible th {
1729 white-space:nowrap;
1731 th {
1732 font-weight:bold;
1735 .generaltable {
1736 border-style:solid;
1738 .generalbox {
1739 border-style:solid;
1741 body, table, td, th {
1742 font-family:Arial,Verdana,Helvetica,sans-serif;
1743 font-size:100%;
1745 td {
1746     border-style:solid;
1747     border-width:1pt;
1749 table {
1750     border-collapse:collapse;
1751     border-spacing:0pt;
1752     width:80%;
1753     margin:auto;
1756 h1, h2 {
1757     text-align:center;
1759 .bold {
1760 font-weight:bold;
1762 .mdl-align {
1763     text-align:center;
1765 /*]]>*/</style>
1766 <title>$filename</title>
1767 </head>
1768 <body>
1769 EOF;
1770         $this->documentstarted = true;
1771     }
1773     function start_table($sheettitle) {
1774         $this->table->sortable(false);
1775         $this->table->collapsible(false);
1776         echo "<h2>{$sheettitle}</h2>";
1777         $this->table->start_html();
1778     }
1780     function output_headers($headers) {
1781         $this->table->print_headers();
1782         echo html_writer::start_tag('tbody');
1783     }
1785     function add_data($row) {
1786         $this->table->print_row($row);
1787         return true;
1788     }
1790     function add_seperator() {
1791         $this->table->print_row(NULL);
1792         return true;
1793     }
1795     function finish_table() {
1796         $this->table->finish_html();
1797     }
1799     function finish_document() {
1800         echo "</body>\n</html>";
1801         exit;
1802     }
1804     function format_text($text, $format=FORMAT_MOODLE, $options=NULL, $courseid=NULL) {
1805         if (is_null($options)) {
1806             $options = new stdClass;
1807         }
1808         //some sensible defaults
1809         if (!isset($options->para)) {
1810             $options->para = false;
1811         }
1812         if (!isset($options->newlines)) {
1813             $options->newlines = false;
1814         }
1815         if (!isset($options->smiley)) {
1816             $options->smiley = false;
1817         }
1818         if (!isset($options->filter)) {
1819             $options->filter = false;
1820         }
1821         return format_text($text, $format, $options);
1822     }