MDL-56671 mod_scorm: fix styling issues on scorm module
[moodle.git] / mod / scorm / report / basic / classes / report.php
1 <?php
2 // This file is part of Moodle - http://moodle.org/
3 //
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
8 //
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12 // GNU General Public License for more details.
13 //
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle.  If not, see <http://www.gnu.org/licenses/>.
16 /**
17  * Core Report class of basic reporting plugin
18  * @package    scormreport
19  * @subpackage basic
20  * @author     Dan Marsden and Ankit Kumar Agarwal
21  * @license    http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22  */
24 namespace scormreport_basic;
26 defined('MOODLE_INTERNAL') || die();
27 require_once($CFG->libdir . '/csvlib.class.php');
29 class report extends \mod_scorm\report {
30     /**
31      * displays the full report
32      * @param \stdClass $scorm full SCORM object
33      * @param \stdClass $cm - full course_module object
34      * @param \stdClass $course - full course object
35      * @param string $download - type of download being requested
36      */
37     public function display($scorm, $cm, $course, $download) {
38         global $CFG, $DB, $OUTPUT, $PAGE;
40         $contextmodule = \context_module::instance($cm->id);
41         $action = optional_param('action', '', PARAM_ALPHA);
42         $attemptids = optional_param_array('attemptid', array(), PARAM_RAW);
43         $attemptsmode = optional_param('attemptsmode', SCORM_REPORT_ATTEMPTS_ALL_STUDENTS, PARAM_INT);
44         $PAGE->set_url(new \moodle_url($PAGE->url, array('attemptsmode' => $attemptsmode)));
46         if ($action == 'delete' && has_capability('mod/scorm:deleteresponses', $contextmodule) && confirm_sesskey()) {
47             if (scorm_delete_responses($attemptids, $scorm)) { // Delete responses.
48                 echo $OUTPUT->notification(get_string('scormresponsedeleted', 'scorm'), 'notifysuccess');
49             }
50         }
51         // Find out current groups mode.
52         $currentgroup = groups_get_activity_group($cm, true);
54         // Detailed report.
55         $mform = new \mod_scorm_report_settings($PAGE->url, compact('currentgroup'));
56         if ($fromform = $mform->get_data()) {
57             $detailedrep = $fromform->detailedrep;
58             $pagesize = $fromform->pagesize;
59             set_user_preference('scorm_report_detailed', $detailedrep);
60             set_user_preference('scorm_report_pagesize', $pagesize);
61         } else {
62             $detailedrep = get_user_preferences('scorm_report_detailed', false);
63             $pagesize = get_user_preferences('scorm_report_pagesize', 0);
64         }
65         if ($pagesize < 1) {
66             $pagesize = SCORM_REPORT_DEFAULT_PAGE_SIZE;
67         }
69         // Select group menu.
70         $displayoptions = array();
71         $displayoptions['attemptsmode'] = $attemptsmode;
72         if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used.
73             if (!$download) {
74                 groups_print_activity_menu($cm, new \moodle_url($PAGE->url, $displayoptions));
75             }
76         }
78         // We only want to show the checkbox to delete attempts
79         // if the user has permissions and if the report mode is showing attempts.
80         $candelete = has_capability('mod/scorm:deleteresponses', $contextmodule)
81                         && ($attemptsmode != SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO);
82         // Select the students.
83         $nostudents = false;
85         if (empty($currentgroup)) {
86             // All users who can attempt scoes.
87             if (!$students = get_users_by_capability($contextmodule, 'mod/scorm:savetrack', 'u.id', '', '', '', '', '', false)) {
88                 echo $OUTPUT->notification(get_string('nostudentsyet'));
89                 $nostudents = true;
90                 $allowedlist = '';
91             } else {
92                 $allowedlist = array_keys($students);
93             }
94             unset($students);
95         } else {
96             // All users who can attempt scoes and who are in the currently selected group.
97             if (!$groupstudents = get_users_by_capability($contextmodule, 'mod/scorm:savetrack', 'u.id', '', '', '',
98                                                             $currentgroup, '', false)) {
99                 echo $OUTPUT->notification(get_string('nostudentsingroup'));
100                 $nostudents = true;
101                 $groupstudents = array();
102             }
103             $allowedlist = array_keys($groupstudents);
104             unset($groupstudents);
105         }
107         if ( !$nostudents ) {
108             // Now check if asked download of data.
109             $coursecontext = \context_course::instance($course->id);
110             if ($download) {
111                 $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
112                 $filename = clean_filename("$shortname ".format_string($scorm->name, true));
113             }
115             // Define table columns.
116             $columns = array();
117             $headers = array();
118             if (!$download && $candelete) {
119                 $columns[] = 'checkbox';
120                 $headers[] = null;
121             }
122             if (!$download && $CFG->grade_report_showuserimage) {
123                 $columns[] = 'picture';
124                 $headers[] = '';
125             }
126             $columns[] = 'fullname';
127             $headers[] = get_string('name');
128             $extrafields = get_extra_user_fields($coursecontext);
129             foreach ($extrafields as $field) {
130                 $columns[] = $field;
131                 $headers[] = get_user_field_name($field);
132             }
134             $columns[] = 'attempt';
135             $headers[] = get_string('attempt', 'scorm');
136             $columns[] = 'start';
137             $headers[] = get_string('started', 'scorm');
138             $columns[] = 'finish';
139             $headers[] = get_string('last', 'scorm');
140             $columns[] = 'score';
141             $headers[] = get_string('score', 'scorm');
142             if ($detailedrep && $scoes = $DB->get_records('scorm_scoes', array("scorm" => $scorm->id), 'sortorder, id')) {
143                 foreach ($scoes as $sco) {
144                     if ($sco->launch != '') {
145                         $columns[] = 'scograde'.$sco->id;
146                         $headers[] = format_string($sco->title);
147                     }
148                 }
149             } else {
150                 $scoes = null;
151             }
153             if (!$download) {
154                 $table = new \flexible_table('mod-scorm-report');
156                 $table->define_columns($columns);
157                 $table->define_headers($headers);
158                 $table->define_baseurl($PAGE->url);
160                 $table->sortable(true);
161                 $table->collapsible(true);
163                 // This is done to prevent redundant data, when a user has multiple attempts.
164                 $table->column_suppress('picture');
165                 $table->column_suppress('fullname');
166                 foreach ($extrafields as $field) {
167                     $table->column_suppress($field);
168                 }
170                 $table->no_sorting('start');
171                 $table->no_sorting('finish');
172                 $table->no_sorting('score');
173                 $table->no_sorting('checkbox');
174                 $table->no_sorting('picture');
176                 if ( $scoes ) {
177                     foreach ($scoes as $sco) {
178                         if ($sco->launch != '') {
179                             $table->no_sorting('scograde'.$sco->id);
180                         }
181                     }
182                 }
184                 $table->column_class('picture', 'picture');
185                 $table->column_class('fullname', 'bold');
186                 $table->column_class('score', 'bold');
188                 $table->set_attribute('cellspacing', '0');
189                 $table->set_attribute('id', 'attempts');
190                 $table->set_attribute('class', 'generaltable generalbox');
192                 // Start working -- this is necessary as soon as the niceties are over.
193                 $table->setup();
194             } else if ($download == 'ODS') {
195                 require_once("$CFG->libdir/odslib.class.php");
197                 $filename .= ".ods";
198                 // Creating a workbook.
199                 $workbook = new \MoodleODSWorkbook("-");
200                 // Sending HTTP headers.
201                 $workbook->send($filename);
202                 // Creating the first worksheet.
203                 $sheettitle = get_string('report', 'scorm');
204                 $myxls = $workbook->add_worksheet($sheettitle);
205                 // Format types.
206                 $format = $workbook->add_format();
207                 $format->set_bold(0);
208                 $formatbc = $workbook->add_format();
209                 $formatbc->set_bold(1);
210                 $formatbc->set_align('center');
211                 $formatb = $workbook->add_format();
212                 $formatb->set_bold(1);
213                 $formaty = $workbook->add_format();
214                 $formaty->set_bg_color('yellow');
215                 $formatc = $workbook->add_format();
216                 $formatc->set_align('center');
217                 $formatr = $workbook->add_format();
218                 $formatr->set_bold(1);
219                 $formatr->set_color('red');
220                 $formatr->set_align('center');
221                 $formatg = $workbook->add_format();
222                 $formatg->set_bold(1);
223                 $formatg->set_color('green');
224                 $formatg->set_align('center');
225                 // Here starts workshhet headers.
227                 $colnum = 0;
228                 foreach ($headers as $item) {
229                     $myxls->write(0, $colnum, $item, $formatbc);
230                     $colnum++;
231                 }
232                 $rownum = 1;
233             } else if ($download == 'Excel') {
234                 require_once("$CFG->libdir/excellib.class.php");
236                 $filename .= ".xls";
237                 // Creating a workbook.
238                 $workbook = new \MoodleExcelWorkbook("-");
239                 // Sending HTTP headers.
240                 $workbook->send($filename);
241                 // Creating the first worksheet.
242                 $sheettitle = get_string('report', 'scorm');
243                 $myxls = $workbook->add_worksheet($sheettitle);
244                 // Format types.
245                 $format = $workbook->add_format();
246                 $format->set_bold(0);
247                 $formatbc = $workbook->add_format();
248                 $formatbc->set_bold(1);
249                 $formatbc->set_align('center');
250                 $formatb = $workbook->add_format();
251                 $formatb->set_bold(1);
252                 $formaty = $workbook->add_format();
253                 $formaty->set_bg_color('yellow');
254                 $formatc = $workbook->add_format();
255                 $formatc->set_align('center');
256                 $formatr = $workbook->add_format();
257                 $formatr->set_bold(1);
258                 $formatr->set_color('red');
259                 $formatr->set_align('center');
260                 $formatg = $workbook->add_format();
261                 $formatg->set_bold(1);
262                 $formatg->set_color('green');
263                 $formatg->set_align('center');
265                 $colnum = 0;
266                 foreach ($headers as $item) {
267                     $myxls->write(0, $colnum, $item, $formatbc);
268                     $colnum++;
269                 }
270                 $rownum = 1;
271             } else if ($download == 'CSV') {
272                 $csvexport = new \csv_export_writer("tab");
273                 $csvexport->set_filename($filename, ".txt");
274                 $csvexport->add_data($headers);
275             }
276             $params = array();
277             list($usql, $params) = $DB->get_in_or_equal($allowedlist, SQL_PARAMS_NAMED);
278             // Construct the SQL.
279             $select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, ';
280             $select .= 'st.scormid AS scormid, st.attempt AS attempt, ' .
281                     \user_picture::fields('u', array('idnumber'), 'userid') .
282                     get_extra_user_fields_sql($coursecontext, 'u', '', array('email', 'idnumber')) . ' ';
284             // This part is the same for all cases - join users and scorm_scoes_track tables.
285             $from = 'FROM {user} u ';
286             $from .= 'LEFT JOIN {scorm_scoes_track} st ON st.userid = u.id AND st.scormid = '.$scorm->id;
287             switch ($attemptsmode) {
288                 case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH:
289                     // Show only students with attempts.
290                     $where = ' WHERE u.id ' .$usql. ' AND st.userid IS NOT NULL';
291                     break;
292                 case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
293                     // Show only students without attempts.
294                     $where = ' WHERE u.id ' .$usql. ' AND st.userid IS NULL';
295                     break;
296                 case SCORM_REPORT_ATTEMPTS_ALL_STUDENTS:
297                     // Show all students with or without attempts.
298                     $where = ' WHERE u.id ' .$usql. ' AND (st.userid IS NOT NULL OR st.userid IS NULL)';
299                     break;
300             }
302             $countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').')) AS nbresults, ';
303             $countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'st.attempt').')) AS nbattempts, ';
304             $countsql .= 'COUNT(DISTINCT(u.id)) AS nbusers ';
305             $countsql .= $from.$where;
307             if (!$download) {
308                 $sort = $table->get_sql_sort();
309             } else {
310                 $sort = '';
311             }
312             // Fix some wired sorting.
313             if (empty($sort)) {
314                 $sort = ' ORDER BY uniqueid';
315             } else {
316                 $sort = ' ORDER BY '.$sort;
317             }
319             if (!$download) {
320                 // Add extra limits due to initials bar.
321                 list($twhere, $tparams) = $table->get_sql_where();
322                 if ($twhere) {
323                     $where .= ' AND '.$twhere; // Initial bar.
324                     $params = array_merge($params, $tparams);
325                 }
327                 if (!empty($countsql)) {
328                     $count = $DB->get_record_sql($countsql, $params);
329                     $totalinitials = $count->nbresults;
330                     if ($twhere) {
331                         $countsql .= ' AND '.$twhere;
332                     }
333                     $count = $DB->get_record_sql($countsql, $params);
334                     $total  = $count->nbresults;
335                 }
337                 $table->pagesize($pagesize, $total);
339                 echo \html_writer::start_div('scormattemptcounts');
340                 if ( $count->nbresults == $count->nbattempts ) {
341                     echo get_string('reportcountattempts', 'scorm', $count);
342                 } else if ( $count->nbattempts > 0 ) {
343                     echo get_string('reportcountallattempts', 'scorm', $count);
344                 } else {
345                     echo $count->nbusers.' '.get_string('users');
346                 }
347                 echo \html_writer::end_div();
348             }
350             // Fetch the attempts.
351             if (!$download) {
352                 $attempts = $DB->get_records_sql($select.$from.$where.$sort, $params,
353                 $table->get_page_start(), $table->get_page_size());
354                 echo \html_writer::start_div('', array('id' => 'scormtablecontainer'));
355                 if ($candelete) {
356                     // Start form.
357                     $strreallydel  = addslashes_js(get_string('deleteattemptcheck', 'scorm'));
358                     echo \html_writer::start_tag('form', array('id' => 'attemptsform', 'method' => 'post',
359                                                                 'action' => $PAGE->url->out(false),
360                                                                 'onsubmit' => 'return confirm("'.$strreallydel.'");'));
361                     echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'delete'));
362                     echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
363                     echo \html_writer::start_div('', array('style' => 'display: none;'));
364                     echo \html_writer::input_hidden_params($PAGE->url);
365                     echo \html_writer::end_div();
366                     echo \html_writer::start_div();
367                 }
368                 $table->initialbars($totalinitials > 20); // Build table rows.
369             } else {
370                 $attempts = $DB->get_records_sql($select.$from.$where.$sort, $params);
371             }
373             if ($attempts) {
374                 foreach ($attempts as $scouser) {
375                     $row = array();
376                     if (!empty($scouser->attempt)) {
377                         $timetracks = scorm_get_sco_runtime($scorm->id, false, $scouser->userid, $scouser->attempt);
378                     } else {
379                         $timetracks = '';
380                     }
381                     if (in_array('checkbox', $columns)) {
382                         if ($candelete && !empty($timetracks->start)) {
383                             $row[] = \html_writer::checkbox('attemptid[]', $scouser->userid . ':' . $scouser->attempt, false);
384                         } else if ($candelete) {
385                             $row[] = '';
386                         }
387                     }
388                     if (in_array('picture', $columns)) {
389                         $user = new \stdClass();
390                         $additionalfields = explode(',', \user_picture::fields());
391                         $user = username_load_fields_from_object($user, $scouser, null, $additionalfields);
392                         $user->id = $scouser->userid;
393                         $row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id));
394                     }
395                     if (!$download) {
396                         $url = new \moodle_url('/user/view.php', array('id' => $scouser->userid, 'course' => $course->id));
397                         $row[] = \html_writer::link($url, fullname($scouser));
398                     } else {
399                         $row[] = fullname($scouser);
400                     }
401                     foreach ($extrafields as $field) {
402                         $row[] = s($scouser->{$field});
403                     }
404                     if (empty($timetracks->start)) {
405                         $row[] = '-';
406                         $row[] = '-';
407                         $row[] = '-';
408                         $row[] = '-';
409                     } else {
410                         if (!$download) {
411                             $url = new \moodle_url('/mod/scorm/report/userreport.php', array('id' => $cm->id,
412                                     'user' => $scouser->userid, 'attempt' => $scouser->attempt));
413                             $row[] = \html_writer::link($url, $scouser->attempt);
414                         } else {
415                             $row[] = $scouser->attempt;
416                         }
417                         if ($download == 'ODS' || $download == 'Excel' ) {
418                             $row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
419                         } else {
420                             $row[] = userdate($timetracks->start);
421                         }
422                         if ($download == 'ODS' || $download == 'Excel' ) {
423                             $row[] = userdate($timetracks->finish, get_string('strftimedatetime', 'langconfig'));
424                         } else {
425                             $row[] = userdate($timetracks->finish);
426                         }
427                         $row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
428                     }
429                     // Print out all scores of attempt.
430                     if ($scoes) {
431                         foreach ($scoes as $sco) {
432                             if ($sco->launch != '') {
433                                 if ($trackdata = scorm_get_tracks($sco->id, $scouser->userid, $scouser->attempt)) {
434                                     if ($trackdata->status == '') {
435                                         $trackdata->status = 'notattempted';
436                                     }
437                                     $strstatus = get_string($trackdata->status, 'scorm');
438                                     // If raw score exists, print it.
439                                     if ($trackdata->score_raw != '') {
440                                         $score = $trackdata->score_raw;
441                                         // Add max score if it exists.
442                                         if (scorm_version_check($scorm->version, SCORM_13)) {
443                                             $maxkey = 'cmi.score.max';
444                                         } else {
445                                             $maxkey = 'cmi.core.score.max';
446                                         }
447                                         if (isset($trackdata->$maxkey)) {
448                                             $score .= '/'.$trackdata->$maxkey;
449                                         }
450                                         // Else print out status.
451                                     } else {
452                                         $score = $strstatus;
453                                     }
454                                     if (!$download) {
455                                         $url = new \moodle_url('/mod/scorm/report/userreporttracks.php', array('id' => $cm->id,
456                                             'scoid' => $sco->id, 'user' => $scouser->userid, 'attempt' => $scouser->attempt));
457                                         $row[] = \html_writer::img($OUTPUT->pix_url($trackdata->status, 'scorm'), $strstatus,
458                                             array('title' => $strstatus)) . \html_writer::empty_tag('br') .
459                                            \html_writer::link($url, $score, array('title' => get_string('details', 'scorm')));
460                                     } else {
461                                         $row[] = $score;
462                                     }
463                                 } else {
464                                     // If we don't have track data, we haven't attempted yet.
465                                     $strstatus = get_string('notattempted', 'scorm');
466                                     if (!$download) {
467                                         $row[] = \html_writer::img($OUTPUT->pix_url('notattempted', 'scorm'), $strstatus,
468                                                 array('title' => $strstatus)).\html_writer::empty_tag('br').$strstatus;
469                                     } else {
470                                         $row[] = $strstatus;
471                                     }
472                                 }
473                             }
474                         }
475                     }
477                     if (!$download) {
478                         $table->add_data($row);
479                     } else if ($download == 'Excel' or $download == 'ODS') {
480                         $colnum = 0;
481                         foreach ($row as $item) {
482                             $myxls->write($rownum, $colnum, $item, $format);
483                             $colnum++;
484                         }
485                         $rownum++;
486                     } else if ($download == 'CSV') {
487                         $csvexport->add_data($row);
488                     }
489                 }
490                 if (!$download) {
491                     $table->finish_output();
492                     if ($candelete) {
493                         echo \html_writer::start_tag('table', array('id' => 'commands'));
494                         echo \html_writer::start_tag('tr').\html_writer::start_tag('td');
495                         echo \html_writer::link('javascript:select_all_in(\'DIV\', null, \'scormtablecontainer\');',
496                                                     get_string('selectall', 'scorm')).' / ';
497                         echo \html_writer::link('javascript:deselect_all_in(\'DIV\', null, \'scormtablecontainer\');',
498                                                     get_string('selectnone', 'scorm'));
499                         echo '&nbsp;&nbsp;';
500                         echo \html_writer::empty_tag('input', array('type' => 'submit',
501                                                                     'value' => get_string('deleteselected', 'scorm'),
502                                                                     'class' => 'btn btn-secondary'));
503                         echo \html_writer::end_tag('td').\html_writer::end_tag('tr').\html_writer::end_tag('table');
504                         // Close form.
505                         echo \html_writer::end_tag('div');
506                         echo \html_writer::end_tag('form');
507                     }
508                     echo \html_writer::end_div();
509                     if (!empty($attempts)) {
510                         echo \html_writer::start_tag('table', array('class' => 'boxaligncenter')).\html_writer::start_tag('tr');
511                         echo \html_writer::start_tag('td');
512                         echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
513                                                                    array('download' => 'ODS') + $displayoptions),
514                                                                    get_string('downloadods'));
515                         echo \html_writer::end_tag('td');
516                         echo \html_writer::start_tag('td');
517                         echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
518                                                                    array('download' => 'Excel') + $displayoptions),
519                                                                    get_string('downloadexcel'));
520                         echo \html_writer::end_tag('td');
521                         echo \html_writer::start_tag('td');
522                         echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
523                                                                    array('download' => 'CSV') + $displayoptions),
524                                                                    get_string('downloadtext'));
525                         echo \html_writer::end_tag('td');
526                         echo \html_writer::start_tag('td');
527                         echo \html_writer::end_tag('td');
528                         echo \html_writer::end_tag('tr').\html_writer::end_tag('table');
529                     }
530                 }
531             } else {
532                 if ($candelete && !$download) {
533                     echo \html_writer::end_div();
534                     echo \html_writer::end_tag('form');
535                     $table->finish_output();
536                 }
537                 echo \html_writer::end_div();
538             }
539             // Show preferences form irrespective of attempts are there to report or not.
540             if (!$download) {
541                 $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
542                 $mform->display();
543             }
544             if ($download == 'Excel' or $download == 'ODS') {
545                 $workbook->close();
546                 exit;
547             } else if ($download == 'CSV') {
548                 $csvexport->download_file();
549                 exit;
550             }
551         } else {
552             echo $OUTPUT->notification(get_string('noactivity', 'scorm'));
553         }
554     }// Function ends.