3 ////////////////////////////////////////////////////////////////////
4 /// Default class for report plugins
6 /// Doesn't do anything on it's own -- it needs to be extended.
7 /// This class displays quiz reports. Because it is called from
8 /// within /mod/quiz/report.php you can assume that the page header
9 /// and footer are taken care of.
11 /// This file can refer to itself as report.php to pass variables
12 /// to itself - all these will also be globally available. You must
13 /// pass "id=$cm->id" or q=$quiz->id", and "mode=reportname".
14 ////////////////////////////////////////////////////////////////////
16 // Included by ../report.php
18 class hotpot_default_report
{
20 function display($hotpot, $cm, $course, $users, $attempts, $questions, $options) {
21 /// This function just displays the report
22 // it is replaced by the "display" functions in the scripts in the "report" folder
26 function add_question_headings(&$questions, &$table, $align='center', $size=50, $wrap=false, $fontsize=0) {
27 $count = count($questions);
28 for ($i=0; $i<$count; $i++
) {
29 $table->head
[] = get_string('questionshort', 'hotpot', $i+
1);
30 if (isset($table->align
)) {
31 $table->align
[] = $align;
33 if (isset($table->size
)) {
34 $table->size
[] = $size;
36 if (isset($table->wrap
)) {
37 $table->wrap
[] = $wrap;
39 if (isset($table->fontsize
)) {
40 $table->fontsize
[] = $fontsize;
46 function set_legend(&$table, &$q, &$value, &$question) {
47 // $q is the question number
48 // $value is the value (=text) of the answer
50 // check the legend is required
51 if (isset($table->legend
) && isset($value)) {
53 // create question details array, if necessary
54 if (empty($table->legend
[$q])) {
55 $table->legend
[$q] = array(
56 'name' => hotpot_get_question_name($question),
61 // search for this $value in answers array for this $q(uestion)
62 $i_max = count($table->legend
[$q]['answers']);
63 for ($i=0; $i<$i_max; $i++
) {
64 if ($table->legend
[$q]['answers'][$i]==$value) {
69 // add $value to answers array, if it was not there
71 $table->legend
[$q]['answers'][$i] = $value;
74 // convert $value to alphabetic index (A, B ... AA, AB ...)
75 $value = $this->dec_to_ALPHA($i);
78 function create_legend_table(&$tables, &$table) {
80 if (isset($table->legend
)) {
83 $legend->tablealign
= '*';
84 $legend->border
= isset($table->border
) ?
$table->border
: NULL;
85 $legend->cellpadding
= isset($table->cellpadding
) ?
$table->cellpadding
: NULL;
86 $legend->cellspacing
= isset($table->cellspacing
) ?
$table->cellspacing
: NULL;
87 $legend->tableclass
= isset($table->tableclass
) ?
$table->tableclass
: NULL;
89 $legend->caption
= get_string('reportlegend', 'hotpot');
90 $legend->align
= array('right', 'left');
91 $legend->statheadercols
= array(0);
93 $legend->stat
= array();
95 // put the questions in order
96 ksort($table->legend
);
98 foreach($table->legend
as $q=>$question) {
100 $legend->stat
[] = array(
101 get_string('questionshort', 'hotpot', $q+
1),
104 foreach($question['answers'] as $a=>$answer) {
105 $legend->stat
[] = array(
106 $this->dec_to_ALPHA($a),
112 unset($table->legend
);
116 function dec_to_ALPHA($dec) {
118 return chr(ord('A') +
$dec);
120 return $this->dec_to_ALPHA(intval($dec/26)-1).$this->dec_to_ALPHA($dec %
26);
123 function remove_column(&$table, $target_col) {
125 if (is_array($table)) {
126 unset($table[$target_col]);
127 $table = array_values($table);
129 } else if (is_object($table)) {
130 $vars = get_object_vars($table);
131 foreach ($vars as $name=>$value) {
137 $cells = &$table->$name;
139 $row_max = count($cells);
140 for ($row=0; $row<$row_max; $row++
) {
143 $col_max = count($cells[$row]);
146 while ($current_col<$target_col && $col<$col_max) {
148 if (empty($skipcol[$current_col])) {
150 $cell = $cells[$row][$col++
];
151 if (is_object($cell)) {
152 if (isset($cell->rowspan
) && is_numeric($cell->rowspan
) && ($cell->rowspan
>0)) {
153 // skip cells below this one
154 $skipcol[$current_col] = $cell->rowspan
-1;
156 if (isset($cell->colspan
) && is_numeric($cell->colspan
) && ($cell->colspan
>0)) {
157 // skip cells to the right of this one
158 for ($c=1; $c<$cell->colspan
; $c++
) {
159 if (empty($skipcol[$current_col+
$c])) {
160 $skipcol[$current_col+
$c] = 1;
162 $skipcol[$current_col+
$c] ++
;
168 $skipcol[$current_col]--;
172 if ($current_col==$target_col && $col<$col_max) {
173 $this->remove_column($cells[$row], $col);
183 $this->remove_column($table->$name, $target_col);
185 case 'statheadercols' :
186 $array = &$table->$name;
187 $count = count($array);
188 for ($i=0; $i<$count; $i++
) {
189 if ($array[$i]>=$target_col) {
200 function expand_spans(&$table, $zone) {
201 // expand multi-column and multi-row cells in a specified $zone of a $table
203 // do nothing if this $zone is empty
204 if (empty($table->$zone)) return;
206 // shortcut to rows in this $table $zone
207 $rows = &$table->{$zone};
209 // loop through the rows
210 foreach ($rows as $row=>$cells) {
212 // check this is an array
213 if (is_array($cells)) {
215 // loop through the cells in this row
216 foreach ($cells as $col=>$cell) {
218 if (is_object($cell)) {
219 if (isset($cell->rowspan
) && is_numeric($cell->rowspan
) && ($cell->rowspan
>1)) {
220 // fill in cells below this one
221 $new_cell = array($cell->text
);
222 for ($r=1; $r<$cell->rowspan
; $r++
) {
223 array_splice($rows[$row+
$r], $col, 0, $new_cell);
226 if (isset($cell->colspan
) && is_numeric($cell->colspan
) && ($cell->colspan
>1)) {
227 // fill in cells to the right of this one
228 $new_cells = array();
229 for ($c=1; $c<$cell->colspan
; $c++
) {
230 $new_cells[] = $cell->text
;
232 array_splice($rows[$row], $col, 0, $new_cells);
234 // replace $cell object with plain text
235 $rows[$row][$col] = $cell->text
;
242 /////////////////////////////////////////////////
243 /// print a report in html, text or Excel format
244 /////////////////////////////////////////////////
246 // the stuff to print is contained in $table
247 // which has the following properties:
249 // $table->border border width for the table
250 // $table->cellpadding padding on each cell
251 // $table->cellspacing spacing between cells
252 // $table->tableclass class for table
253 // $table->width table width
255 // $table->align is an array of column alignments
256 // $table->class is an array of column classes
257 // $table->size is an array of column sizes
258 // $table->wrap is an array of column wrap/nowrap switches
259 // $table->fontsize is an array of fontsizes
261 // $table->caption is a caption (=title) for the report
262 // $table->head is an array of headings (all TH cells)
263 // $table->data[] is an array of arrays containing the data (all TD cells)
264 // if a row is given as "hr", a "tabledivider" is inserted
265 // if a cell is a string, it is assumed to be the cell content
266 // a cell can also be an object, thus:
267 // $cell->text : the content of the cell
268 // $cell->rowspan : the row span of this cell
269 // $cell->colspan : the column span of this cell
270 // if rowspan or colspan are specified, neighboring cells are shifted accordingly
271 // $table->stat[] is an array of arrays containing the statistics rows (TD and TH cells)
272 // $table->foot[] is an array of arrays containing the footer rows (all TH cells)
274 // $table->statheadercols is an array of column numbers which are headers
277 //////////////////////////////////////////
280 function print_report(&$course, &$hotpot, &$tables, &$options) {
281 switch ($options['reportformat']) {
283 $this->print_text_report($course, $hotpot, $tables, $options);
286 $this->print_excel_report($course, $hotpot, $tables, $options);
288 default: // 'htm' (and anything else)
289 $this->print_html_report($tables);
294 function print_report_start(&$course, &$hotpot, &$options, &$table) {
295 switch ($options['reportformat']) {
297 $this->print_text_start($course, $hotpot, $options);
300 $this->print_excel_start($course, $hotpot, $options);
304 $this->print_html_start($course, $hotpot, $options);
309 function print_report_cells(&$table, &$options, $zone) {
310 switch ($options['reportformat']) {
317 default: // 'htm' (and anything else)
321 $fn = "print_{$fmt}_{$zone}";
322 $this->$fn($table, $options);
325 function print_report_finish(&$course, &$hotpot, &$options) {
326 switch ($options['reportformat']) {
331 $this->print_excel_finish($course, $hotpot, $options);
334 $this->print_html_finish($course, $hotpot, $options);
339 //////////////////////////////////////////
340 /// print an html report
342 function print_html_report(&$tables) {
343 $count = count($tables);
344 foreach($tables as $i=>$table) {
346 $this->print_html_start($table);
347 $this->print_html_head($table);
348 $this->print_html_data($table);
349 $this->print_html_stat($table);
350 $this->print_html_foot($table);
351 $this->print_html_finish($table);
354 print_spacer(30, 10, true);
358 function print_html_start(&$table) {
360 // default class for the table
361 if (empty($table->tableclass
)) {
362 $table->tableclass
= 'generaltable';
365 // default classes for TD and TH
366 $d = $table->tableclass
.'cell';
367 $h = $table->tableclass
.'header';
369 $table->th_side
= '<th valign="top" align="right" class="'.$h.'" scope="col">';
371 $table->td
= array();
372 $table->th_top
= array();
374 if (empty($table->colspan
)) {
375 if (isset($table->head
)) {
376 $table->colspan
= count($table->head
);
377 } else if (isset($table->data
)) {
378 $table->colspan
= count($table->data
[0]);
379 } else if (isset($table->stat
)) {
380 $table->colspan
= count($table->stat
);
381 } else if (isset($table->foot
)) {
382 $table->colspan
= count($table->foot
);
388 for ($i=0; $i<$table->colspan
; $i++
) {
390 $align = empty($table->align
[$i]) ?
'' : ' align="'.$table->align
[$i].'"';
391 $class = empty($table->class[$i]) ?
$d : ' class="'.$table->class[$i].'"';
392 $size = empty($table->size
[$i]) ?
'' : ' width="'.$table->size
[$i].'"';
393 $wrap = empty($table->wrap
[$i]) ?
'' : ' nowrap="nowrap"';
395 $table->th_top
[$i] = '<th align="center"'.$size.' class="'.$h.'" nowrap="nowrap" scope="col">';
397 $table->td
[$i] = '<td valign="top"'.$align.$class.$wrap.'>';
399 if (!empty($table->fontsize
[$i])) {
400 $table->td
[$i] .= '<font size="'.$table->fontsize
[$i].'">';
404 if (empty($table->border
)) {
407 if (empty($table->cellpadding
)) {
408 $table->cellpadding
= 5;
410 if (empty($table->cellspacing
)) {
411 $table->cellspacing
= 1;
413 if (empty($table->width
)) {
414 $table->width
= "80%"; // actually the width of the "simple box"
416 if (empty($table->tablealign
)) {
417 $table->tablealign
= "center";
420 if (isset($table->start
)) {
421 print $table->start
."\n";
424 print_simple_box_start("$table->tablealign", "$table->width", "#ffffff", 0);
425 print '<table width="100%" border="'.$table->border
.'" valign="top" align="center" cellpadding="'.$table->cellpadding
.'" cellspacing="'.$table->cellspacing
.'" class="'.$table->tableclass
.'">'."\n";
427 if (isset($table->caption
)) {
428 print '<tr><td colspan="'.$table->colspan
.'" class="'.$table->tableclass
.'header"><b>'.$table->caption
.'</b></td></tr>'."\n";
432 function print_html_head(&$table) {
433 if (isset($table->head
)) {
435 foreach ($table->head
as $i=>$cell) {
436 $th = $table->th_top
[$i];
437 print $th.$cell."</th>\n";
442 function print_html_data(&$table) {
443 if (isset($table->data
)) {
445 foreach ($table->data
as $cells) {
447 if (is_array($cells)) {
448 $i = 0; // index on $cells
449 $col = 0; // column index
450 while ($col<$table->colspan
&& isset($cells[$i])) {
451 if (empty($skipcol[$col])) {
452 $cell = &$cells[$i++
];
453 $td = $table->td
[$col];
454 if (is_object($cell)) {
456 if (isset($cell->rowspan
) && is_numeric($cell->rowspan
) && ($cell->rowspan
>0)) {
457 $td = '<td rowspan="'.$cell->rowspan
.'"'.substr($td, 3);
458 // skip cells below this one
459 $skipcol[$col] = $cell->rowspan
-1;
461 if (isset($cell->colspan
) && is_numeric($cell->colspan
) && ($cell->colspan
>0)) {
462 $td = '<td colspan="'.$cell->colspan
.'"'.substr($td, 3);
463 // skip cells to the right of this one
464 for ($c=1; $c<$cell->colspan
; $c++
) {
465 if (empty($skipcol[$col+
$c])) {
466 $skipcol[$col+
$c] = 1;
468 $skipcol[$col+
$c] ++
;
472 } else { // $cell is a string
475 print $td.$text."</td>\n";
481 } else if ($cells=='hr') {
482 print '<td colspan="'.$table->colspan
.'"><div class="tabledivider"></div></td>'."\n";
488 function print_html_stat(&$table) {
489 if (isset($table->stat
)) {
490 if (empty($table->statheadercols
)) {
491 $table->statheadercols
= array();
493 foreach ($table->stat
as $cells) {
495 foreach ($cells as $i => $cell) {
496 if (in_array($i, $table->statheadercols
)) {
497 $th = $table->th_side
;
498 print $th.$cell."</th>\n";
500 $td = $table->td
[$i];
501 print $td.$cell."</td>\n";
508 function print_html_foot(&$table) {
509 if (isset($table->foot
)) {
510 foreach ($table->foot
as $cells) {
512 foreach ($cells as $i => $cell) {
514 $th = $table->th_side
;
515 print $th.$cell."</th>\n";
517 $th = $table->th_top
[$i];
518 print $th.$cell."</th>\n";
525 function print_html_finish(&$table) {
527 print_simple_box_end();
529 if (isset($table->finish
)) {
530 print $table->finish
."\n";
534 //////////////////////////////////////////
535 /// print a text report
537 function print_text_report(&$course, &$hotpot, &$tables, &$options) {
538 $this->print_text_start($course, $hotpot, $options);
539 foreach ($tables as $table) {
540 $this->print_text_head($table, $options);
541 $this->print_text_data($table, $options);
542 $this->print_text_stat($table, $options);
543 $this->print_text_foot($table, $options);
546 function print_text_start(&$course, &$hotpot, &$options) {
547 $downloadfilename = clean_filename("$course->shortname $hotpot->name.txt");
548 header("Content-Type: application/download\n");
549 header("Content-Disposition: attachment; filename=$downloadfilename");
550 header("Expires: 0");
551 header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
552 header("Pragma: public");
554 function print_text_head(&$table, &$options) {
555 if (isset($table->caption
)) {
556 $i = strlen($table->caption
);
558 array(str_repeat('=', $i)),
559 array($table->caption
),
560 array(str_repeat('=', $i)),
562 foreach($data as $cells) {
563 $this->print_text_cells($cells, $options);
566 if (isset($table->head
)) {
567 $this->expand_spans($table, 'head');
568 $this->print_text_cells($table->head
, $options);
571 function print_text_data(&$table, &$options) {
572 if (isset($table->data
)) {
573 $this->expand_spans($table, 'data');
574 foreach ($table->data
as $cells) {
575 $this->print_text_cells($cells, $options);
579 function print_text_stat(&$table, &$options) {
580 if (isset($table->stat
)) {
581 $this->expand_spans($table, 'stat');
582 foreach ($table->stat
as $cells) {
583 $this->print_text_cells($cells, $options);
587 function print_text_foot(&$table, &$options) {
588 if (isset($table->foot
)) {
589 $this->expand_spans($table, 'foot');
590 foreach ($table->foot
as $cells) {
591 $this->print_text_cells($cells, $options);
595 function print_text_cells(&$cells, &$options) {
597 // do nothing if there are no cells
598 if (empty($cells) ||
is_string($cells)) return;
600 // convert to tab-delimted string
601 $str = implode("\t", $cells);
603 // replace newlines in string
604 $str = preg_replace("/\n/", ",", $str);
606 // set best newline for this browser (if it hasn't been done already)
607 if (empty($this->nl
)) {
608 $s = &$_SERVER['HTTP_USER_AGENT'];
609 $win = is_numeric(strpos($s, 'Win'));
610 $mac = is_numeric(strpos($s, 'Mac')) && !is_numeric(strpos($s, 'OS X'));
611 $this->nl
= $win ?
"\r\n" : ($mac ?
"\r" : "\n");
614 print $str.$this->nl
;
617 //////////////////////////////////////////
618 /// print an Excel report
620 function print_excel_report(&$course, &$hotpot, &$tables, &$options) {
623 // create Excel workbook
624 if (file_exists("$CFG->libdir/excellib.class.php")) {
626 require_once("$CFG->libdir/excellib.class.php");
627 $wb = new MoodleExcelWorkbook("-");
628 $wsnamelimit = 0; // no limit
631 require_once("$CFG->libdir/excel/Worksheet.php");
632 require_once("$CFG->libdir/excel/Workbook.php");
633 $wb = new Workbook("-");
634 $wsnamelimit = 31; // max length in chars
638 $this->print_excel_headers($wb, $course, $hotpot);
640 // create one worksheet for each table
641 foreach($tables as $table) {
643 if (empty($table->caption
)) {
646 $wsname = strip_tags($table->caption
);
647 if ($wsnamelimit && strlen($wsname) > $wsnamelimit) {
648 $wsname = substr($wsname, -$wsnamelimit); // end of string
649 // $wsname = substr($wsname, 0, $wsnamelimit); // start of string
652 $ws = &$wb->add_worksheet($wsname);
655 $this->print_excel_head($wb, $ws, $table, $row, $options);
656 $this->print_excel_data($wb, $ws, $table, $row, $options);
657 $this->print_excel_stat($wb, $ws, $table, $row, $options);
658 $this->print_excel_foot($wb, $ws, $table, $row, $options);
661 // close the workbook (and send it to the browser)
664 function print_excel_headers(&$wb, &$course, &$hotpot) {
665 $downloadfilename = clean_filename("$course->shortname $hotpot->name.xls");
666 if (method_exists($wb, 'send')) {
668 $wb->send($downloadfilename);
671 header("Content-type: application/vnd.ms-excel");
672 header("Content-Disposition: attachment; filename=$downloadfilename" );
673 header("Expires: 0");
674 header("Cache-Control: must-revalidate, post-check=0,pre-check=0");
675 header("Pragma: public");
678 function print_excel_head(&$wb, &$ws, &$table, &$row, &$options) {
679 // define format properties
687 // expand multi-column and multi-row cells
688 $this->expand_spans($table, 'head');
690 // print the headings
691 $this->print_excel_cells($wb, $ws, $table, $row, $properties, $table->head
, $options);
693 function print_excel_data(&$wb, &$ws, &$table, &$row, &$options) {
694 // do nothing if there are no cells
695 if (empty($table->data
)) return;
697 // define format properties
698 $properties = array('text_wrap' => (empty($options['reportwrapdata']) ?
0 : 1));
700 // expand multi-column and multi-row cells
701 $this->expand_spans($table, 'data');
704 foreach ($table->data
as $cells) {
705 $this->print_excel_cells($wb, $ws, $table, $row, $properties, $cells, $options);
708 function print_excel_stat(&$wb, &$ws, &$table, &$row, &$options) {
709 // do nothing if there are no cells
710 if (empty($table->stat
)) return;
712 // define format properties
713 $properties = array('align'=>'right');
715 // expand multi-column and multi-row cells
716 $this->expand_spans($table, 'stat');
719 $i_count = count($table->stat
);
720 foreach ($table->stat
as $i => $cells) {
722 // set border on top and bottom row
723 $properties['top'] = ($i==0) ?
1 : 0;
724 $properties['bottom'] = ($i==($i_count-1)) ?
1 : 0;
727 $this->print_excel_cells($wb, $ws, $table, $row, $properties, $cells, $options, $table->statheadercols
);
730 function print_excel_foot(&$wb, &$ws, &$table, &$row, &$options) {
731 // do nothing if there are no cells
732 if (empty($table->foot
)) return;
734 // define format properties
735 $properties = array('bold'=>1, 'align'=>'center');
737 // expand multi-column and multi-row cells
738 $this->expand_spans($table, 'foot');
741 $i_count = count($table->foot
);
742 foreach ($table->foot
as $i => $cells) {
744 // set border on top and bottom row
745 $properties['top'] = ($i==0) ?
1 : 0;
746 $properties['bottom'] = ($i==($i_count-1)) ?
1 : 0;
748 // print this footer row
749 $this->print_excel_cells($wb, $ws, $table, $row, $properties, $cells, $options);
753 function print_excel_cells(&$wb, &$ws, &$table, &$row, &$properties, &$cells, &$options, $statheadercols=NULL) {
754 // do nothing if there are no cells
755 if (empty($cells) ||
is_string($cells)) return;
758 foreach($cells as $col => $cell) {
760 unset($fmt_properties);
761 $fmt_properties = $properties;
763 if (empty($fmt_properties['text_wrap'])) {
764 if (strlen("$cell")>=9) {
766 $fmt_properties['align'] = 'left';
769 if (strlen("$cell")<9 && strpos("$cell", "\n")===false) {
770 // short cell value (wrapping not required)
771 $fmt_properties['text_wrap'] = 0;
775 // set bold, if required (for stat)
776 if (isset($statheadercols)) {
777 $fmt_properties['bold'] = in_array($col, $statheadercols) ?
1 : 0;
778 $fmt_properties['align'] = in_array($col, $statheadercols) ?
'right' : $table->align
[$col];
781 // set align, if required
782 if (isset($table->align
[$col]) && empty($fmt_properties['align'])) {
783 $fmt_properties['align'] = $table->align
[$col];
786 // check to see that an identical format object has not already been created
789 if (isset($wb->pear_excel_workbook
)) {
791 $fmt_properties_obj = (object)$fmt_properties;
792 foreach ($wb->pear_excel_workbook
->_formats
as $id=>$format) {
793 if ($format==$fmt_properties_obj) {
794 $fmt = &$wb->pear_excel_workbook
->_formats
[$id];
800 foreach ($wb->formats
as $id=>$format) {
801 if (isset($format->properties
) && $format->properties
==$fmt_properties) {
802 $fmt = &$wb->formats
[$id];
806 if (is_numeric($cell) ||
empty($options['reportencoding'])) {
810 if (function_exists('mb_convert_encoding')) {
811 $in_charset = mb_detect_encoding($cell, 'auto');
813 if (empty($in_charset)) {
814 $in_charset = get_string('thischarset');
816 if ($in_charset != 'ASCII' && function_exists('mb_convert_encoding')) {
817 $cell = mb_convert_encoding($cell, $options['reportencoding'], $in_charset);
822 // create new format object, if necessary (to avoid "too many cell formats" error)
824 $fmt = &$wb->add_format($fmt_properties);
825 $fmt->properties
= &$fmt_properties;
827 // set vertical alignment
828 if (isset($fmt->properties
['v_align'])) {
829 $fmt->set_align($fmt->properties
['v_align']);
831 $fmt->set_align('top'); // default
836 if (is_numeric($cell) && !preg_match("/^0./", $cell)) {
837 $ws->write_number($row, $col, $cell, $fmt);
839 $ws->write_string($row, $col, $cell, $fmt);
841 } // end foreach $col