PageRenderTime 52ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 1ms

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

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