PageRenderTime 101ms CodeModel.GetById 29ms RepoModel.GetById 2ms app.codeStats 0ms

/mod/scorm/report/basic/classes/report.php

https://bitbucket.org/moodle/moodle
PHP | 538 lines | 437 code | 34 blank | 67 comment | 122 complexity | 354ac7525c110410e6d770d3f4830c4b MD5 | raw file
Possible License(s): Apache-2.0, LGPL-2.1, BSD-3-Clause, MIT, GPL-3.0
  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. */
  23. namespace scormreport_basic;
  24. defined('MOODLE_INTERNAL') || die();
  25. require_once($CFG->libdir . '/csvlib.class.php');
  26. class report extends \mod_scorm\report {
  27. /**
  28. * displays the full report
  29. * @param \stdClass $scorm full SCORM object
  30. * @param \stdClass $cm - full course_module object
  31. * @param \stdClass $course - full course object
  32. * @param string $download - type of download being requested
  33. */
  34. public function display($scorm, $cm, $course, $download) {
  35. global $CFG, $DB, $OUTPUT, $PAGE;
  36. $contextmodule = \context_module::instance($cm->id);
  37. $action = optional_param('action', '', PARAM_ALPHA);
  38. $attemptids = optional_param_array('attemptid', array(), PARAM_RAW);
  39. $attemptsmode = optional_param('attemptsmode', SCORM_REPORT_ATTEMPTS_ALL_STUDENTS, PARAM_INT);
  40. $PAGE->set_url(new \moodle_url($PAGE->url, array('attemptsmode' => $attemptsmode)));
  41. if ($action == 'delete' && has_capability('mod/scorm:deleteresponses', $contextmodule) && confirm_sesskey()) {
  42. if (scorm_delete_responses($attemptids, $scorm)) { // Delete responses.
  43. echo $OUTPUT->notification(get_string('scormresponsedeleted', 'scorm'), 'notifysuccess');
  44. }
  45. }
  46. // Find out current groups mode.
  47. $currentgroup = groups_get_activity_group($cm, true);
  48. // Detailed report.
  49. $mform = new \mod_scorm_report_settings($PAGE->url, compact('currentgroup'));
  50. if ($fromform = $mform->get_data()) {
  51. $detailedrep = $fromform->detailedrep;
  52. $pagesize = $fromform->pagesize;
  53. set_user_preference('scorm_report_detailed', $detailedrep);
  54. set_user_preference('scorm_report_pagesize', $pagesize);
  55. } else {
  56. $detailedrep = get_user_preferences('scorm_report_detailed', false);
  57. $pagesize = get_user_preferences('scorm_report_pagesize', 0);
  58. }
  59. if ($pagesize < 1) {
  60. $pagesize = SCORM_REPORT_DEFAULT_PAGE_SIZE;
  61. }
  62. // Select group menu.
  63. $displayoptions = array();
  64. $displayoptions['attemptsmode'] = $attemptsmode;
  65. if ($groupmode = groups_get_activity_groupmode($cm)) { // Groups are being used.
  66. if (!$download) {
  67. groups_print_activity_menu($cm, new \moodle_url($PAGE->url, $displayoptions));
  68. }
  69. }
  70. // We only want to show the checkbox to delete attempts
  71. // if the user has permissions and if the report mode is showing attempts.
  72. $candelete = has_capability('mod/scorm:deleteresponses', $contextmodule)
  73. && ($attemptsmode != SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO);
  74. // Select the students.
  75. $nostudents = false;
  76. list($allowedlistsql, $params) = get_enrolled_sql($contextmodule, 'mod/scorm:savetrack', (int) $currentgroup);
  77. if (empty($currentgroup)) {
  78. // All users who can attempt scoes.
  79. if (!$DB->record_exists_sql($allowedlistsql, $params)) {
  80. echo $OUTPUT->notification(get_string('nostudentsyet'));
  81. $nostudents = true;
  82. }
  83. } else {
  84. // All users who can attempt scoes and who are in the currently selected group.
  85. if (!$DB->record_exists_sql($allowedlistsql, $params)) {
  86. echo $OUTPUT->notification(get_string('nostudentsingroup'));
  87. $nostudents = true;
  88. }
  89. }
  90. if ( !$nostudents ) {
  91. // Now check if asked download of data.
  92. $coursecontext = \context_course::instance($course->id);
  93. if ($download) {
  94. $shortname = format_string($course->shortname, true, array('context' => $coursecontext));
  95. $filename = clean_filename("$shortname ".format_string($scorm->name, true));
  96. }
  97. // Define table columns.
  98. $columns = array();
  99. $headers = array();
  100. if (!$download && $candelete) {
  101. $columns[] = 'checkbox';
  102. $headers[] = $this->generate_master_checkbox();
  103. }
  104. if (!$download && $CFG->grade_report_showuserimage) {
  105. $columns[] = 'picture';
  106. $headers[] = '';
  107. }
  108. $columns[] = 'fullname';
  109. $headers[] = get_string('name');
  110. // TODO Does not support custom user profile fields (MDL-70456).
  111. $extrafields = \core_user\fields::get_identity_fields($coursecontext, false);
  112. foreach ($extrafields as $field) {
  113. $columns[] = $field;
  114. $headers[] = \core_user\fields::get_display_name($field);
  115. }
  116. $columns[] = 'attempt';
  117. $headers[] = get_string('attempt', 'scorm');
  118. $columns[] = 'start';
  119. $headers[] = get_string('started', 'scorm');
  120. $columns[] = 'finish';
  121. $headers[] = get_string('last', 'scorm');
  122. $columns[] = 'score';
  123. $headers[] = get_string('score', 'scorm');
  124. if ($detailedrep && $scoes = $DB->get_records('scorm_scoes', array("scorm" => $scorm->id), 'sortorder, id')) {
  125. foreach ($scoes as $sco) {
  126. if ($sco->launch != '') {
  127. $columns[] = 'scograde'.$sco->id;
  128. $headers[] = format_string($sco->title);
  129. }
  130. }
  131. } else {
  132. $scoes = null;
  133. }
  134. if (!$download) {
  135. $table = new \flexible_table('mod-scorm-report');
  136. $table->define_columns($columns);
  137. $table->define_headers($headers);
  138. $table->define_baseurl($PAGE->url);
  139. $table->sortable(true);
  140. $table->collapsible(true);
  141. // This is done to prevent redundant data, when a user has multiple attempts.
  142. $table->column_suppress('picture');
  143. $table->column_suppress('fullname');
  144. foreach ($extrafields as $field) {
  145. $table->column_suppress($field);
  146. }
  147. $table->no_sorting('start');
  148. $table->no_sorting('finish');
  149. $table->no_sorting('score');
  150. $table->no_sorting('checkbox');
  151. $table->no_sorting('picture');
  152. if ( $scoes ) {
  153. foreach ($scoes as $sco) {
  154. if ($sco->launch != '') {
  155. $table->no_sorting('scograde'.$sco->id);
  156. }
  157. }
  158. }
  159. $table->column_class('picture', 'picture');
  160. $table->column_class('fullname', 'bold');
  161. $table->column_class('score', 'bold');
  162. $table->set_attribute('cellspacing', '0');
  163. $table->set_attribute('id', 'attempts');
  164. $table->set_attribute('class', 'generaltable generalbox');
  165. // Start working -- this is necessary as soon as the niceties are over.
  166. $table->setup();
  167. } else if ($download == 'ODS') {
  168. require_once("$CFG->libdir/odslib.class.php");
  169. $filename .= ".ods";
  170. // Creating a workbook.
  171. $workbook = new \MoodleODSWorkbook("-");
  172. // Sending HTTP headers.
  173. $workbook->send($filename);
  174. // Creating the first worksheet.
  175. $sheettitle = get_string('report', 'scorm');
  176. $myxls = $workbook->add_worksheet($sheettitle);
  177. // Format types.
  178. $format = $workbook->add_format();
  179. $format->set_bold(0);
  180. $formatbc = $workbook->add_format();
  181. $formatbc->set_bold(1);
  182. $formatbc->set_align('center');
  183. $formatb = $workbook->add_format();
  184. $formatb->set_bold(1);
  185. $formaty = $workbook->add_format();
  186. $formaty->set_bg_color('yellow');
  187. $formatc = $workbook->add_format();
  188. $formatc->set_align('center');
  189. $formatr = $workbook->add_format();
  190. $formatr->set_bold(1);
  191. $formatr->set_color('red');
  192. $formatr->set_align('center');
  193. $formatg = $workbook->add_format();
  194. $formatg->set_bold(1);
  195. $formatg->set_color('green');
  196. $formatg->set_align('center');
  197. // Here starts workshhet headers.
  198. $colnum = 0;
  199. foreach ($headers as $item) {
  200. $myxls->write(0, $colnum, $item, $formatbc);
  201. $colnum++;
  202. }
  203. $rownum = 1;
  204. } else if ($download == 'Excel') {
  205. require_once("$CFG->libdir/excellib.class.php");
  206. $filename .= ".xls";
  207. // Creating a workbook.
  208. $workbook = new \MoodleExcelWorkbook("-");
  209. // Sending HTTP headers.
  210. $workbook->send($filename);
  211. // Creating the first worksheet.
  212. $sheettitle = get_string('report', 'scorm');
  213. $myxls = $workbook->add_worksheet($sheettitle);
  214. // Format types.
  215. $format = $workbook->add_format();
  216. $format->set_bold(0);
  217. $formatbc = $workbook->add_format();
  218. $formatbc->set_bold(1);
  219. $formatbc->set_align('center');
  220. $formatb = $workbook->add_format();
  221. $formatb->set_bold(1);
  222. $formaty = $workbook->add_format();
  223. $formaty->set_bg_color('yellow');
  224. $formatc = $workbook->add_format();
  225. $formatc->set_align('center');
  226. $formatr = $workbook->add_format();
  227. $formatr->set_bold(1);
  228. $formatr->set_color('red');
  229. $formatr->set_align('center');
  230. $formatg = $workbook->add_format();
  231. $formatg->set_bold(1);
  232. $formatg->set_color('green');
  233. $formatg->set_align('center');
  234. $colnum = 0;
  235. foreach ($headers as $item) {
  236. $myxls->write(0, $colnum, $item, $formatbc);
  237. $colnum++;
  238. }
  239. $rownum = 1;
  240. } else if ($download == 'CSV') {
  241. $csvexport = new \csv_export_writer("tab");
  242. $csvexport->set_filename($filename, ".txt");
  243. $csvexport->add_data($headers);
  244. }
  245. // Construct the SQL.
  246. $select = 'SELECT DISTINCT '.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').' AS uniqueid, ';
  247. // TODO Does not support custom user profile fields (MDL-70456).
  248. $userfields = \core_user\fields::for_identity($coursecontext, false)->with_userpic()->including('idnumber');
  249. $selectfields = $userfields->get_sql('u', false, '', 'userid')->selects;
  250. $select .= 'st.scormid AS scormid, st.attempt AS attempt ' . $selectfields . ' ';
  251. // This part is the same for all cases - join users and scorm_scoes_track tables.
  252. $from = 'FROM {user} u ';
  253. $from .= 'LEFT JOIN {scorm_scoes_track} st ON st.userid = u.id AND st.scormid = '.$scorm->id;
  254. switch ($attemptsmode) {
  255. case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH:
  256. // Show only students with attempts.
  257. $where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NOT NULL";
  258. break;
  259. case SCORM_REPORT_ATTEMPTS_STUDENTS_WITH_NO:
  260. // Show only students without attempts.
  261. $where = " WHERE u.id IN ({$allowedlistsql}) AND st.userid IS NULL";
  262. break;
  263. case SCORM_REPORT_ATTEMPTS_ALL_STUDENTS:
  264. // Show all students with or without attempts.
  265. $where = " WHERE u.id IN ({$allowedlistsql}) AND (st.userid IS NOT NULL OR st.userid IS NULL)";
  266. break;
  267. }
  268. $countsql = 'SELECT COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'COALESCE(st.attempt, 0)').')) AS nbresults, ';
  269. $countsql .= 'COUNT(DISTINCT('.$DB->sql_concat('u.id', '\'#\'', 'st.attempt').')) AS nbattempts, ';
  270. $countsql .= 'COUNT(DISTINCT(u.id)) AS nbusers ';
  271. $countsql .= $from.$where;
  272. if (!$download) {
  273. $sort = $table->get_sql_sort();
  274. } else {
  275. $sort = '';
  276. }
  277. // Fix some wired sorting.
  278. if (empty($sort)) {
  279. $sort = ' ORDER BY uniqueid';
  280. } else {
  281. $sort = ' ORDER BY '.$sort;
  282. }
  283. if (!$download) {
  284. // Add extra limits due to initials bar.
  285. list($twhere, $tparams) = $table->get_sql_where();
  286. if ($twhere) {
  287. $where .= ' AND '.$twhere; // Initial bar.
  288. $params = array_merge($params, $tparams);
  289. }
  290. if (!empty($countsql)) {
  291. $count = $DB->get_record_sql($countsql, $params);
  292. $totalinitials = $count->nbresults;
  293. if ($twhere) {
  294. $countsql .= ' AND '.$twhere;
  295. }
  296. $count = $DB->get_record_sql($countsql, $params);
  297. $total = $count->nbresults;
  298. }
  299. $table->pagesize($pagesize, $total);
  300. echo \html_writer::start_div('scormattemptcounts');
  301. if ( $count->nbresults == $count->nbattempts ) {
  302. echo get_string('reportcountattempts', 'scorm', $count);
  303. } else if ( $count->nbattempts > 0 ) {
  304. echo get_string('reportcountallattempts', 'scorm', $count);
  305. } else {
  306. echo $count->nbusers.' '.get_string('users');
  307. }
  308. echo \html_writer::end_div();
  309. }
  310. // Fetch the attempts.
  311. if (!$download) {
  312. $attempts = $DB->get_records_sql($select.$from.$where.$sort, $params,
  313. $table->get_page_start(), $table->get_page_size());
  314. echo \html_writer::start_div('', array('id' => 'scormtablecontainer'));
  315. if ($candelete) {
  316. // Start form.
  317. $strreallydel = addslashes_js(get_string('deleteattemptcheck', 'scorm'));
  318. echo \html_writer::start_tag('form', array('id' => 'attemptsform', 'method' => 'post',
  319. 'action' => $PAGE->url->out(false),
  320. 'onsubmit' => 'return confirm("'.$strreallydel.'");'));
  321. echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'action', 'value' => 'delete'));
  322. echo \html_writer::empty_tag('input', array('type' => 'hidden', 'name' => 'sesskey', 'value' => sesskey()));
  323. echo \html_writer::start_div('', array('style' => 'display: none;'));
  324. echo \html_writer::input_hidden_params($PAGE->url);
  325. echo \html_writer::end_div();
  326. echo \html_writer::start_div();
  327. }
  328. $table->initialbars($totalinitials > 20); // Build table rows.
  329. } else {
  330. $attempts = $DB->get_records_sql($select.$from.$where.$sort, $params);
  331. }
  332. if ($attempts) {
  333. foreach ($attempts as $scouser) {
  334. $row = array();
  335. if (!empty($scouser->attempt)) {
  336. $timetracks = scorm_get_sco_runtime($scorm->id, false, $scouser->userid, $scouser->attempt);
  337. } else {
  338. $timetracks = '';
  339. }
  340. if (in_array('checkbox', $columns)) {
  341. if ($candelete && !empty($timetracks->start)) {
  342. $row[] = $this->generate_row_checkbox('attemptid[]', "{$scouser->userid}:{$scouser->attempt}");
  343. } else if ($candelete) {
  344. $row[] = '';
  345. }
  346. }
  347. if (in_array('picture', $columns)) {
  348. $user = new \stdClass();
  349. $additionalfields = explode(',', implode(',', \core_user\fields::get_picture_fields()));
  350. $user = username_load_fields_from_object($user, $scouser, null, $additionalfields);
  351. $user->id = $scouser->userid;
  352. $row[] = $OUTPUT->user_picture($user, array('courseid' => $course->id));
  353. }
  354. if (!$download) {
  355. $url = new \moodle_url('/user/view.php', array('id' => $scouser->userid, 'course' => $course->id));
  356. $row[] = \html_writer::link($url, fullname($scouser));
  357. } else {
  358. $row[] = fullname($scouser);
  359. }
  360. foreach ($extrafields as $field) {
  361. $row[] = s($scouser->{$field});
  362. }
  363. if (empty($timetracks->start)) {
  364. $row[] = '-';
  365. $row[] = '-';
  366. $row[] = '-';
  367. $row[] = '-';
  368. } else {
  369. if (!$download) {
  370. $url = new \moodle_url('/mod/scorm/report/userreport.php', array('id' => $cm->id,
  371. 'user' => $scouser->userid, 'attempt' => $scouser->attempt));
  372. $row[] = \html_writer::link($url, $scouser->attempt);
  373. } else {
  374. $row[] = $scouser->attempt;
  375. }
  376. if ($download == 'ODS' || $download == 'Excel' ) {
  377. $row[] = userdate($timetracks->start, get_string("strftimedatetime", "langconfig"));
  378. } else {
  379. $row[] = userdate($timetracks->start);
  380. }
  381. if ($download == 'ODS' || $download == 'Excel' ) {
  382. $row[] = userdate($timetracks->finish, get_string('strftimedatetime', 'langconfig'));
  383. } else {
  384. $row[] = userdate($timetracks->finish);
  385. }
  386. $row[] = scorm_grade_user_attempt($scorm, $scouser->userid, $scouser->attempt);
  387. }
  388. // Print out all scores of attempt.
  389. if ($scoes) {
  390. foreach ($scoes as $sco) {
  391. if ($sco->launch != '') {
  392. if ($trackdata = scorm_get_tracks($sco->id, $scouser->userid, $scouser->attempt)) {
  393. if ($trackdata->status == '') {
  394. $trackdata->status = 'notattempted';
  395. }
  396. $strstatus = get_string($trackdata->status, 'scorm');
  397. // If raw score exists, print it.
  398. if ($trackdata->score_raw != '') {
  399. $score = $trackdata->score_raw;
  400. // Add max score if it exists.
  401. if (scorm_version_check($scorm->version, SCORM_13)) {
  402. $maxkey = 'cmi.score.max';
  403. } else {
  404. $maxkey = 'cmi.core.score.max';
  405. }
  406. if (isset($trackdata->$maxkey)) {
  407. $score .= '/'.$trackdata->$maxkey;
  408. }
  409. // Else print out status.
  410. } else {
  411. $score = $strstatus;
  412. }
  413. if (!$download) {
  414. $url = new \moodle_url('/mod/scorm/report/userreporttracks.php', array('id' => $cm->id,
  415. 'scoid' => $sco->id, 'user' => $scouser->userid, 'attempt' => $scouser->attempt));
  416. $row[] = $OUTPUT->pix_icon($trackdata->status, $strstatus, 'scorm') . '<br>' .
  417. \html_writer::link($url, $score, array('title' => get_string('details', 'scorm')));
  418. } else {
  419. $row[] = $score;
  420. }
  421. } else {
  422. // If we don't have track data, we haven't attempted yet.
  423. $strstatus = get_string('notattempted', 'scorm');
  424. if (!$download) {
  425. $row[] = $OUTPUT->pix_icon('notattempted', $strstatus, 'scorm') . '<br>' . $strstatus;
  426. } else {
  427. $row[] = $strstatus;
  428. }
  429. }
  430. }
  431. }
  432. }
  433. if (!$download) {
  434. $table->add_data($row);
  435. } else if ($download == 'Excel' or $download == 'ODS') {
  436. $colnum = 0;
  437. foreach ($row as $item) {
  438. $myxls->write($rownum, $colnum, $item, $format);
  439. $colnum++;
  440. }
  441. $rownum++;
  442. } else if ($download == 'CSV') {
  443. $csvexport->add_data($row);
  444. }
  445. }
  446. if (!$download) {
  447. $table->finish_output();
  448. if ($candelete) {
  449. echo \html_writer::start_tag('table', array('id' => 'commands'));
  450. echo \html_writer::start_tag('tr').\html_writer::start_tag('td');
  451. echo $this->generate_delete_selected_button();
  452. echo \html_writer::end_tag('td').\html_writer::end_tag('tr').\html_writer::end_tag('table');
  453. // Close form.
  454. echo \html_writer::end_tag('div');
  455. echo \html_writer::end_tag('form');
  456. }
  457. echo \html_writer::end_div();
  458. if (!empty($attempts)) {
  459. echo \html_writer::start_tag('table', array('class' => 'boxaligncenter')).\html_writer::start_tag('tr');
  460. echo \html_writer::start_tag('td');
  461. echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
  462. array('download' => 'ODS') + $displayoptions),
  463. get_string('downloadods'));
  464. echo \html_writer::end_tag('td');
  465. echo \html_writer::start_tag('td');
  466. echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
  467. array('download' => 'Excel') + $displayoptions),
  468. get_string('downloadexcel'));
  469. echo \html_writer::end_tag('td');
  470. echo \html_writer::start_tag('td');
  471. echo $OUTPUT->single_button(new \moodle_url($PAGE->url,
  472. array('download' => 'CSV') + $displayoptions),
  473. get_string('downloadtext'));
  474. echo \html_writer::end_tag('td');
  475. echo \html_writer::start_tag('td');
  476. echo \html_writer::end_tag('td');
  477. echo \html_writer::end_tag('tr').\html_writer::end_tag('table');
  478. }
  479. }
  480. } else {
  481. if ($candelete && !$download) {
  482. echo \html_writer::end_div();
  483. echo \html_writer::end_tag('form');
  484. $table->finish_output();
  485. }
  486. echo \html_writer::end_div();
  487. }
  488. // Show preferences form irrespective of attempts are there to report or not.
  489. if (!$download) {
  490. $mform->set_data(compact('detailedrep', 'pagesize', 'attemptsmode'));
  491. $mform->display();
  492. }
  493. if ($download == 'Excel' or $download == 'ODS') {
  494. $workbook->close();
  495. exit;
  496. } else if ($download == 'CSV') {
  497. $csvexport->download_file();
  498. exit;
  499. }
  500. } else {
  501. echo $OUTPUT->notification(get_string('noactivity', 'scorm'));
  502. }
  503. }// Function ends.
  504. }