PageRenderTime 32ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/mod/scorm/lib.php

https://bitbucket.org/kudutest1/moodlegit
PHP | 1351 lines | 813 code | 172 blank | 366 comment | 204 complexity | b9dd5517229db260665ccb2be17620d9 MD5 | raw file
  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. * @package mod-scorm
  18. * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
  19. * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
  20. */
  21. /** SCORM_TYPE_LOCAL = local */
  22. define('SCORM_TYPE_LOCAL', 'local');
  23. /** SCORM_TYPE_LOCALSYNC = localsync */
  24. define('SCORM_TYPE_LOCALSYNC', 'localsync');
  25. /** SCORM_TYPE_EXTERNAL = external */
  26. define('SCORM_TYPE_EXTERNAL', 'external');
  27. /** SCORM_TYPE_IMSREPOSITORY = imsrepository */
  28. define('SCORM_TYPE_IMSREPOSITORY', 'imsrepository');
  29. /** SCORM_TYPE_AICCURL = external AICC url */
  30. define('SCORM_TYPE_AICCURL', 'aiccurl');
  31. define('SCORM_TOC_SIDE', 0);
  32. define('SCORM_TOC_HIDDEN', 1);
  33. define('SCORM_TOC_POPUP', 2);
  34. define('SCORM_TOC_DISABLED', 3);
  35. //used to check what SCORM version is being used.
  36. define('SCORM_12', 1);
  37. define('SCORM_13', 2);
  38. define('SCORM_AICC', 3);
  39. // List of possible attemptstatusdisplay options.
  40. define('SCORM_DISPLAY_ATTEMPTSTATUS_NO', 0);
  41. define('SCORM_DISPLAY_ATTEMPTSTATUS_ALL', 1);
  42. define('SCORM_DISPLAY_ATTEMPTSTATUS_MY', 2);
  43. define('SCORM_DISPLAY_ATTEMPTSTATUS_ENTRY', 3);
  44. /**
  45. * Return an array of status options
  46. *
  47. * Optionally with translated strings
  48. *
  49. * @param bool $with_strings (optional)
  50. * @return array
  51. */
  52. function scorm_status_options($with_strings = false) {
  53. // Id's are important as they are bits
  54. $options = array(
  55. 2 => 'passed',
  56. 4 => 'completed'
  57. );
  58. if ($with_strings) {
  59. foreach ($options as $key => $value) {
  60. $options[$key] = get_string('completionstatus_'.$value, 'scorm');
  61. }
  62. }
  63. return $options;
  64. }
  65. /**
  66. * Given an object containing all the necessary data,
  67. * (defined by the form in mod_form.php) this function
  68. * will create a new instance and return the id number
  69. * of the new instance.
  70. *
  71. * @global stdClass
  72. * @global object
  73. * @uses CONTEXT_MODULE
  74. * @uses SCORM_TYPE_LOCAL
  75. * @uses SCORM_TYPE_LOCALSYNC
  76. * @uses SCORM_TYPE_EXTERNAL
  77. * @uses SCORM_TYPE_IMSREPOSITORY
  78. * @param object $scorm Form data
  79. * @param object $mform
  80. * @return int new instance id
  81. */
  82. function scorm_add_instance($scorm, $mform=null) {
  83. global $CFG, $DB;
  84. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  85. if (empty($scorm->timeopen)) {
  86. $scorm->timeopen = 0;
  87. }
  88. if (empty($scorm->timeclose)) {
  89. $scorm->timeclose = 0;
  90. }
  91. $cmid = $scorm->coursemodule;
  92. $cmidnumber = $scorm->cmidnumber;
  93. $courseid = $scorm->course;
  94. $context = context_module::instance($cmid);
  95. $scorm = scorm_option2text($scorm);
  96. $scorm->width = (int)str_replace('%', '', $scorm->width);
  97. $scorm->height = (int)str_replace('%', '', $scorm->height);
  98. if (!isset($scorm->whatgrade)) {
  99. $scorm->whatgrade = 0;
  100. }
  101. $id = $DB->insert_record('scorm', $scorm);
  102. /// update course module record - from now on this instance properly exists and all function may be used
  103. $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
  104. /// reload scorm instance
  105. $record = $DB->get_record('scorm', array('id'=>$id));
  106. /// store the package and verify
  107. if ($record->scormtype === SCORM_TYPE_LOCAL) {
  108. if ($mform) {
  109. $filename = $mform->get_new_filename('packagefile');
  110. if ($filename !== false) {
  111. $fs = get_file_storage();
  112. $fs->delete_area_files($context->id, 'mod_scorm', 'package');
  113. $mform->save_stored_file('packagefile', $context->id, 'mod_scorm', 'package', 0, '/', $filename);
  114. $record->reference = $filename;
  115. }
  116. }
  117. } else if ($record->scormtype === SCORM_TYPE_LOCALSYNC) {
  118. $record->reference = $scorm->packageurl;
  119. } else if ($record->scormtype === SCORM_TYPE_EXTERNAL) {
  120. $record->reference = $scorm->packageurl;
  121. } else if ($record->scormtype === SCORM_TYPE_IMSREPOSITORY) {
  122. $record->reference = $scorm->packageurl;
  123. } else if ($record->scormtype === SCORM_TYPE_AICCURL) {
  124. $record->reference = $scorm->packageurl;
  125. } else {
  126. return false;
  127. }
  128. // save reference
  129. $DB->update_record('scorm', $record);
  130. /// extra fields required in grade related functions
  131. $record->course = $courseid;
  132. $record->cmidnumber = $cmidnumber;
  133. $record->cmid = $cmid;
  134. scorm_parse($record, true);
  135. scorm_grade_item_update($record);
  136. return $record->id;
  137. }
  138. /**
  139. * Given an object containing all the necessary data,
  140. * (defined by the form in mod_form.php) this function
  141. * will update an existing instance with new data.
  142. *
  143. * @global stdClass
  144. * @global object
  145. * @uses CONTEXT_MODULE
  146. * @uses SCORM_TYPE_LOCAL
  147. * @uses SCORM_TYPE_LOCALSYNC
  148. * @uses SCORM_TYPE_EXTERNAL
  149. * @uses SCORM_TYPE_IMSREPOSITORY
  150. * @param object $scorm Form data
  151. * @param object $mform
  152. * @return bool
  153. */
  154. function scorm_update_instance($scorm, $mform=null) {
  155. global $CFG, $DB;
  156. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  157. if (empty($scorm->timeopen)) {
  158. $scorm->timeopen = 0;
  159. }
  160. if (empty($scorm->timeclose)) {
  161. $scorm->timeclose = 0;
  162. }
  163. $cmid = $scorm->coursemodule;
  164. $cmidnumber = $scorm->cmidnumber;
  165. $courseid = $scorm->course;
  166. $scorm->id = $scorm->instance;
  167. $context = context_module::instance($cmid);
  168. if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
  169. if ($mform) {
  170. $filename = $mform->get_new_filename('packagefile');
  171. if ($filename !== false) {
  172. $scorm->reference = $filename;
  173. $fs = get_file_storage();
  174. $fs->delete_area_files($context->id, 'mod_scorm', 'package');
  175. $mform->save_stored_file('packagefile', $context->id, 'mod_scorm', 'package', 0, '/', $filename);
  176. }
  177. }
  178. } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
  179. $scorm->reference = $scorm->packageurl;
  180. } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
  181. $scorm->reference = $scorm->packageurl;
  182. } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
  183. $scorm->reference = $scorm->packageurl;
  184. } else if ($scorm->scormtype === SCORM_TYPE_AICCURL) {
  185. $scorm->reference = $scorm->packageurl;
  186. } else {
  187. return false;
  188. }
  189. $scorm = scorm_option2text($scorm);
  190. $scorm->width = (int)str_replace('%', '', $scorm->width);
  191. $scorm->height = (int)str_replace('%', '', $scorm->height);
  192. $scorm->timemodified = time();
  193. if (!isset($scorm->whatgrade)) {
  194. $scorm->whatgrade = 0;
  195. }
  196. $DB->update_record('scorm', $scorm);
  197. $scorm = $DB->get_record('scorm', array('id'=>$scorm->id));
  198. /// extra fields required in grade related functions
  199. $scorm->course = $courseid;
  200. $scorm->idnumber = $cmidnumber;
  201. $scorm->cmid = $cmid;
  202. scorm_parse($scorm, (bool)$scorm->updatefreq);
  203. scorm_grade_item_update($scorm);
  204. scorm_update_grades($scorm);
  205. return true;
  206. }
  207. /**
  208. * Given an ID of an instance of this module,
  209. * this function will permanently delete the instance
  210. * and any data that depends on it.
  211. *
  212. * @global stdClass
  213. * @global object
  214. * @param int $id Scorm instance id
  215. * @return boolean
  216. */
  217. function scorm_delete_instance($id) {
  218. global $CFG, $DB;
  219. if (! $scorm = $DB->get_record('scorm', array('id'=>$id))) {
  220. return false;
  221. }
  222. $result = true;
  223. // Delete any dependent records
  224. if (! $DB->delete_records('scorm_scoes_track', array('scormid'=>$scorm->id))) {
  225. $result = false;
  226. }
  227. if ($scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) {
  228. foreach ($scoes as $sco) {
  229. if (! $DB->delete_records('scorm_scoes_data', array('scoid'=>$sco->id))) {
  230. $result = false;
  231. }
  232. }
  233. $DB->delete_records('scorm_scoes', array('scorm'=>$scorm->id));
  234. }
  235. if (! $DB->delete_records('scorm', array('id'=>$scorm->id))) {
  236. $result = false;
  237. }
  238. /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) {
  239. $result = false;
  240. }
  241. if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) {
  242. $result = false;
  243. }
  244. if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) {
  245. $result = false;
  246. }
  247. if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) {
  248. $result = false;
  249. }
  250. if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) {
  251. $result = false;
  252. }
  253. if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) {
  254. $result = false;
  255. }
  256. if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) {
  257. $result = false;
  258. }*/
  259. scorm_grade_item_delete($scorm);
  260. return $result;
  261. }
  262. /**
  263. * Return a small object with summary information about what a
  264. * user has done with a given particular instance of this module
  265. * Used for user activity reports.
  266. *
  267. * @global stdClass
  268. * @param int $course Course id
  269. * @param int $user User id
  270. * @param int $mod
  271. * @param int $scorm The scorm id
  272. * @return mixed
  273. */
  274. function scorm_user_outline($course, $user, $mod, $scorm) {
  275. global $CFG;
  276. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  277. require_once("$CFG->libdir/gradelib.php");
  278. $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
  279. if (!empty($grades->items[0]->grades)) {
  280. $grade = reset($grades->items[0]->grades);
  281. $result = new stdClass();
  282. $result->info = get_string('grade') . ': '. $grade->str_long_grade;
  283. //datesubmitted == time created. dategraded == time modified or time overridden
  284. //if grade was last modified by the user themselves use date graded. Otherwise use date submitted
  285. //TODO: move this copied & pasted code somewhere in the grades API. See MDL-26704
  286. if ($grade->usermodified == $user->id || empty($grade->datesubmitted)) {
  287. $result->time = $grade->dategraded;
  288. } else {
  289. $result->time = $grade->datesubmitted;
  290. }
  291. return $result;
  292. }
  293. return null;
  294. }
  295. /**
  296. * Print a detailed representation of what a user has done with
  297. * a given particular instance of this module, for user activity reports.
  298. *
  299. * @global stdClass
  300. * @global object
  301. * @param object $course
  302. * @param object $user
  303. * @param object $mod
  304. * @param object $scorm
  305. * @return boolean
  306. */
  307. function scorm_user_complete($course, $user, $mod, $scorm) {
  308. global $CFG, $DB, $OUTPUT;
  309. require_once("$CFG->libdir/gradelib.php");
  310. $liststyle = 'structlist';
  311. $now = time();
  312. $firstmodify = $now;
  313. $lastmodify = 0;
  314. $sometoreport = false;
  315. $report = '';
  316. // First Access and Last Access dates for SCOs
  317. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  318. $timetracks = scorm_get_sco_runtime($scorm->id, false, $user->id);
  319. $firstmodify = $timetracks->start;
  320. $lastmodify = $timetracks->finish;
  321. $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
  322. if (!empty($grades->items[0]->grades)) {
  323. $grade = reset($grades->items[0]->grades);
  324. echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
  325. if ($grade->str_feedback) {
  326. echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
  327. }
  328. }
  329. if ($orgs = $DB->get_records_select('scorm_scoes', 'scorm = ? AND '.
  330. $DB->sql_isempty('scorm_scoes', 'launch', false, true).' AND '.
  331. $DB->sql_isempty('scorm_scoes', 'organization', false, false),
  332. array($scorm->id), 'id', 'id,identifier,title')) {
  333. if (count($orgs) <= 1) {
  334. unset($orgs);
  335. $orgs = array();
  336. $org = new stdClass();
  337. $org->identifier = '';
  338. $orgs[] = $org;
  339. }
  340. $report .= '<div class="mod-scorm">'."\n";
  341. foreach ($orgs as $org) {
  342. $conditions = array();
  343. $currentorg = '';
  344. if (!empty($org->identifier)) {
  345. $report .= '<div class="orgtitle">'.$org->title.'</div>';
  346. $currentorg = $org->identifier;
  347. $conditions['organization'] = $currentorg;
  348. }
  349. $report .= "<ul id='0' class='$liststyle'>";
  350. $conditions['scorm'] = $scorm->id;
  351. if ($scoes = $DB->get_records('scorm_scoes', $conditions, "id ASC")) {
  352. // drop keys so that we can access array sequentially
  353. $scoes = array_values($scoes);
  354. $level=0;
  355. $sublist=1;
  356. $parents[$level]='/';
  357. foreach ($scoes as $pos => $sco) {
  358. if ($parents[$level]!=$sco->parent) {
  359. if ($level>0 && $parents[$level-1]==$sco->parent) {
  360. $report .= "\t\t</ul></li>\n";
  361. $level--;
  362. } else {
  363. $i = $level;
  364. $closelist = '';
  365. while (($i > 0) && ($parents[$level] != $sco->parent)) {
  366. $closelist .= "\t\t</ul></li>\n";
  367. $i--;
  368. }
  369. if (($i == 0) && ($sco->parent != $currentorg)) {
  370. $report .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
  371. $level++;
  372. } else {
  373. $report .= $closelist;
  374. $level = $i;
  375. }
  376. $parents[$level]=$sco->parent;
  377. }
  378. }
  379. $report .= "\t\t<li>";
  380. if (isset($scoes[$pos+1])) {
  381. $nextsco = $scoes[$pos+1];
  382. } else {
  383. $nextsco = false;
  384. }
  385. if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
  386. $sublist++;
  387. } else {
  388. $report .= '<img src="'.$OUTPUT->pix_url('spacer', 'scorm').'" alt="" />';
  389. }
  390. if ($sco->launch) {
  391. $score = '';
  392. $totaltime = '';
  393. if ($usertrack=scorm_get_tracks($sco->id, $user->id)) {
  394. if ($usertrack->status == '') {
  395. $usertrack->status = 'notattempted';
  396. }
  397. $strstatus = get_string($usertrack->status, 'scorm');
  398. $report .= "<img src='".$OUTPUT->pix_url($usertrack->status, 'scorm')."' alt='$strstatus' title='$strstatus' />";
  399. } else {
  400. if ($sco->scormtype == 'sco') {
  401. $report .= '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted', 'scorm').'" title="'.get_string('notattempted', 'scorm').'" />';
  402. } else {
  403. $report .= '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset', 'scorm').'" title="'.get_string('asset', 'scorm').'" />';
  404. }
  405. }
  406. $report .= "&nbsp;$sco->title $score$totaltime</li>\n";
  407. if ($usertrack !== false) {
  408. $sometoreport = true;
  409. $report .= "\t\t\t<li><ul class='$liststyle'>\n";
  410. foreach ($usertrack as $element => $value) {
  411. if (substr($element, 0, 3) == 'cmi') {
  412. $report .= '<li>'.$element.' => '.s($value).'</li>';
  413. }
  414. }
  415. $report .= "\t\t\t</ul></li>\n";
  416. }
  417. } else {
  418. $report .= "&nbsp;$sco->title</li>\n";
  419. }
  420. }
  421. for ($i=0; $i<$level; $i++) {
  422. $report .= "\t\t</ul></li>\n";
  423. }
  424. }
  425. $report .= "\t</ul><br />\n";
  426. }
  427. $report .= "</div>\n";
  428. }
  429. if ($sometoreport) {
  430. if ($firstmodify < $now) {
  431. $timeago = format_time($now - $firstmodify);
  432. echo get_string('firstaccess', 'scorm').': '.userdate($firstmodify).' ('.$timeago.")<br />\n";
  433. }
  434. if ($lastmodify > 0) {
  435. $timeago = format_time($now - $lastmodify);
  436. echo get_string('lastaccess', 'scorm').': '.userdate($lastmodify).' ('.$timeago.")<br />\n";
  437. }
  438. echo get_string('report', 'scorm').":<br />\n";
  439. echo $report;
  440. } else {
  441. print_string('noactivity', 'scorm');
  442. }
  443. return true;
  444. }
  445. /**
  446. * Function to be run periodically according to the moodle cron
  447. * This function searches for things that need to be done, such
  448. * as sending out mail, toggling flags etc ...
  449. *
  450. * @global stdClass
  451. * @global object
  452. * @return boolean
  453. */
  454. function scorm_cron () {
  455. global $CFG, $DB;
  456. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  457. $sitetimezone = $CFG->timezone;
  458. /// Now see if there are any scorm updates to be done
  459. if (!isset($CFG->scorm_updatetimelast)) { // To catch the first time
  460. set_config('scorm_updatetimelast', 0);
  461. }
  462. $timenow = time();
  463. $updatetime = usergetmidnight($timenow, $sitetimezone);
  464. if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) {
  465. set_config('scorm_updatetimelast', $timenow);
  466. mtrace('Updating scorm packages which require daily update');//We are updating
  467. $scormsupdate = $DB->get_records_select('scorm', 'updatefreq = ? AND scormtype <> ?', array(SCORM_UPDATE_EVERYDAY, SCORM_TYPE_LOCAL));
  468. foreach ($scormsupdate as $scormupdate) {
  469. scorm_parse($scormupdate, true);
  470. }
  471. //now clear out AICC session table with old session data
  472. $cfg_scorm = get_config('scorm');
  473. if (!empty($cfg_scorm->allowaicchacp)) {
  474. $expiretime = time() - ($cfg_scorm->aicchacpkeepsessiondata*24*60*60);
  475. $DB->delete_records_select('scorm_aicc_session', 'timemodified < ?', array($expiretime));
  476. }
  477. }
  478. return true;
  479. }
  480. /**
  481. * Return grade for given user or all users.
  482. *
  483. * @global stdClass
  484. * @global object
  485. * @param int $scormid id of scorm
  486. * @param int $userid optional user id, 0 means all users
  487. * @return array array of grades, false if none
  488. */
  489. function scorm_get_user_grades($scorm, $userid=0) {
  490. global $CFG, $DB;
  491. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  492. $grades = array();
  493. if (empty($userid)) {
  494. if ($scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid", array($scorm->id), "", "userid,null")) {
  495. foreach ($scousers as $scouser) {
  496. $grades[$scouser->userid] = new stdClass();
  497. $grades[$scouser->userid]->id = $scouser->userid;
  498. $grades[$scouser->userid]->userid = $scouser->userid;
  499. $grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
  500. }
  501. } else {
  502. return false;
  503. }
  504. } else {
  505. if (!$DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid", array($scorm->id, $userid), "", "userid,null")) {
  506. return false; //no attempt yet
  507. }
  508. $grades[$userid] = new stdClass();
  509. $grades[$userid]->id = $userid;
  510. $grades[$userid]->userid = $userid;
  511. $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
  512. }
  513. return $grades;
  514. }
  515. /**
  516. * Update grades in central gradebook
  517. *
  518. * @category grade
  519. * @param object $scorm
  520. * @param int $userid specific user only, 0 mean all
  521. * @param bool $nullifnone
  522. */
  523. function scorm_update_grades($scorm, $userid=0, $nullifnone=true) {
  524. global $CFG;
  525. require_once($CFG->libdir.'/gradelib.php');
  526. require_once($CFG->libdir.'/completionlib.php');
  527. if ($grades = scorm_get_user_grades($scorm, $userid)) {
  528. scorm_grade_item_update($scorm, $grades);
  529. //set complete
  530. scorm_set_completion($scorm, $userid, COMPLETION_COMPLETE, $grades);
  531. } else if ($userid and $nullifnone) {
  532. $grade = new stdClass();
  533. $grade->userid = $userid;
  534. $grade->rawgrade = null;
  535. scorm_grade_item_update($scorm, $grade);
  536. //set incomplete.
  537. scorm_set_completion($scorm, $userid, COMPLETION_INCOMPLETE);
  538. } else {
  539. scorm_grade_item_update($scorm);
  540. }
  541. }
  542. /**
  543. * Update all grades in gradebook.
  544. *
  545. * @global object
  546. */
  547. function scorm_upgrade_grades() {
  548. global $DB;
  549. $sql = "SELECT COUNT('x')
  550. FROM {scorm} s, {course_modules} cm, {modules} m
  551. WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
  552. $count = $DB->count_records_sql($sql);
  553. $sql = "SELECT s.*, cm.idnumber AS cmidnumber, s.course AS courseid
  554. FROM {scorm} s, {course_modules} cm, {modules} m
  555. WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
  556. $rs = $DB->get_recordset_sql($sql);
  557. if ($rs->valid()) {
  558. $pbar = new progress_bar('scormupgradegrades', 500, true);
  559. $i=0;
  560. foreach ($rs as $scorm) {
  561. $i++;
  562. upgrade_set_timeout(60*5); // set up timeout, may also abort execution
  563. scorm_update_grades($scorm, 0, false);
  564. $pbar->update($i, $count, "Updating Scorm grades ($i/$count).");
  565. }
  566. }
  567. $rs->close();
  568. }
  569. /**
  570. * Update/create grade item for given scorm
  571. *
  572. * @category grade
  573. * @uses GRADE_TYPE_VALUE
  574. * @uses GRADE_TYPE_NONE
  575. * @param object $scorm object with extra cmidnumber
  576. * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
  577. * @return object grade_item
  578. */
  579. function scorm_grade_item_update($scorm, $grades=null) {
  580. global $CFG, $DB;
  581. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  582. if (!function_exists('grade_update')) { //workaround for buggy PHP versions
  583. require_once($CFG->libdir.'/gradelib.php');
  584. }
  585. $params = array('itemname'=>$scorm->name);
  586. if (isset($scorm->cmidnumber)) {
  587. $params['idnumber'] = $scorm->cmidnumber;
  588. }
  589. if ($scorm->grademethod == GRADESCOES) {
  590. if ($maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id))) {
  591. $params['gradetype'] = GRADE_TYPE_VALUE;
  592. $params['grademax'] = $maxgrade;
  593. $params['grademin'] = 0;
  594. } else {
  595. $params['gradetype'] = GRADE_TYPE_NONE;
  596. }
  597. } else {
  598. $params['gradetype'] = GRADE_TYPE_VALUE;
  599. $params['grademax'] = $scorm->maxgrade;
  600. $params['grademin'] = 0;
  601. }
  602. if ($grades === 'reset') {
  603. $params['reset'] = true;
  604. $grades = null;
  605. }
  606. return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params);
  607. }
  608. /**
  609. * Delete grade item for given scorm
  610. *
  611. * @category grade
  612. * @param object $scorm object
  613. * @return object grade_item
  614. */
  615. function scorm_grade_item_delete($scorm) {
  616. global $CFG;
  617. require_once($CFG->libdir.'/gradelib.php');
  618. return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, null, array('deleted'=>1));
  619. }
  620. /**
  621. * @return array
  622. */
  623. function scorm_get_view_actions() {
  624. return array('pre-view', 'view', 'view all', 'report');
  625. }
  626. /**
  627. * @return array
  628. */
  629. function scorm_get_post_actions() {
  630. return array();
  631. }
  632. /**
  633. * @param object $scorm
  634. * @return object $scorm
  635. */
  636. function scorm_option2text($scorm) {
  637. $scorm_popoup_options = scorm_get_popup_options_array();
  638. if (isset($scorm->popup)) {
  639. if ($scorm->popup == 1) {
  640. $optionlist = array();
  641. foreach ($scorm_popoup_options as $name => $option) {
  642. if (isset($scorm->$name)) {
  643. $optionlist[] = $name.'='.$scorm->$name;
  644. } else {
  645. $optionlist[] = $name.'=0';
  646. }
  647. }
  648. $scorm->options = implode(',', $optionlist);
  649. } else {
  650. $scorm->options = '';
  651. }
  652. } else {
  653. $scorm->popup = 0;
  654. $scorm->options = '';
  655. }
  656. return $scorm;
  657. }
  658. /**
  659. * Implementation of the function for printing the form elements that control
  660. * whether the course reset functionality affects the scorm.
  661. *
  662. * @param object $mform form passed by reference
  663. */
  664. function scorm_reset_course_form_definition(&$mform) {
  665. $mform->addElement('header', 'scormheader', get_string('modulenameplural', 'scorm'));
  666. $mform->addElement('advcheckbox', 'reset_scorm', get_string('deleteallattempts', 'scorm'));
  667. }
  668. /**
  669. * Course reset form defaults.
  670. *
  671. * @return array
  672. */
  673. function scorm_reset_course_form_defaults($course) {
  674. return array('reset_scorm'=>1);
  675. }
  676. /**
  677. * Removes all grades from gradebook
  678. *
  679. * @global stdClass
  680. * @global object
  681. * @param int $courseid
  682. * @param string optional type
  683. */
  684. function scorm_reset_gradebook($courseid, $type='') {
  685. global $CFG, $DB;
  686. $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid
  687. FROM {scorm} s, {course_modules} cm, {modules} m
  688. WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id AND s.course=?";
  689. if ($scorms = $DB->get_records_sql($sql, array($courseid))) {
  690. foreach ($scorms as $scorm) {
  691. scorm_grade_item_update($scorm, 'reset');
  692. }
  693. }
  694. }
  695. /**
  696. * Actual implementation of the reset course functionality, delete all the
  697. * scorm attempts for course $data->courseid.
  698. *
  699. * @global stdClass
  700. * @global object
  701. * @param object $data the data submitted from the reset course.
  702. * @return array status array
  703. */
  704. function scorm_reset_userdata($data) {
  705. global $CFG, $DB;
  706. $componentstr = get_string('modulenameplural', 'scorm');
  707. $status = array();
  708. if (!empty($data->reset_scorm)) {
  709. $scormssql = "SELECT s.id
  710. FROM {scorm} s
  711. WHERE s.course=?";
  712. $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
  713. // remove all grades from gradebook
  714. if (empty($data->reset_gradebook_grades)) {
  715. scorm_reset_gradebook($data->courseid);
  716. }
  717. $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'scorm'), 'error'=>false);
  718. }
  719. // no dates to shift here
  720. return $status;
  721. }
  722. /**
  723. * Returns all other caps used in module
  724. *
  725. * @return array
  726. */
  727. function scorm_get_extra_capabilities() {
  728. return array('moodle/site:accessallgroups');
  729. }
  730. /**
  731. * Lists all file areas current user may browse
  732. *
  733. * @param object $course
  734. * @param object $cm
  735. * @param object $context
  736. * @return array
  737. */
  738. function scorm_get_file_areas($course, $cm, $context) {
  739. $areas = array();
  740. $areas['content'] = get_string('areacontent', 'scorm');
  741. $areas['package'] = get_string('areapackage', 'scorm');
  742. return $areas;
  743. }
  744. /**
  745. * File browsing support for SCORM file areas
  746. *
  747. * @package mod_scorm
  748. * @category files
  749. * @param file_browser $browser file browser instance
  750. * @param array $areas file areas
  751. * @param stdClass $course course object
  752. * @param stdClass $cm course module object
  753. * @param stdClass $context context object
  754. * @param string $filearea file area
  755. * @param int $itemid item ID
  756. * @param string $filepath file path
  757. * @param string $filename file name
  758. * @return file_info instance or null if not found
  759. */
  760. function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
  761. global $CFG;
  762. if (!has_capability('moodle/course:managefiles', $context)) {
  763. return null;
  764. }
  765. // no writing for now!
  766. $fs = get_file_storage();
  767. if ($filearea === 'content') {
  768. $filepath = is_null($filepath) ? '/' : $filepath;
  769. $filename = is_null($filename) ? '.' : $filename;
  770. $urlbase = $CFG->wwwroot.'/pluginfile.php';
  771. if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'content', 0, $filepath, $filename)) {
  772. if ($filepath === '/' and $filename === '.') {
  773. $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'content', 0);
  774. } else {
  775. // not found
  776. return null;
  777. }
  778. }
  779. require_once("$CFG->dirroot/mod/scorm/locallib.php");
  780. return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false);
  781. } else if ($filearea === 'package') {
  782. $filepath = is_null($filepath) ? '/' : $filepath;
  783. $filename = is_null($filename) ? '.' : $filename;
  784. $urlbase = $CFG->wwwroot.'/pluginfile.php';
  785. if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, $filepath, $filename)) {
  786. if ($filepath === '/' and $filename === '.') {
  787. $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'package', 0);
  788. } else {
  789. // not found
  790. return null;
  791. }
  792. }
  793. return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
  794. }
  795. // scorm_intro handled in file_browser
  796. return false;
  797. }
  798. /**
  799. * Serves scorm content, introduction images and packages. Implements needed access control ;-)
  800. *
  801. * @package mod_scorm
  802. * @category files
  803. * @param stdClass $course course object
  804. * @param stdClass $cm course module object
  805. * @param stdClass $context context object
  806. * @param string $filearea file area
  807. * @param array $args extra arguments
  808. * @param bool $forcedownload whether or not force download
  809. * @param array $options additional options affecting the file serving
  810. * @return bool false if file not found, does not return if found - just send the file
  811. */
  812. function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload, array $options=array()) {
  813. global $CFG;
  814. if ($context->contextlevel != CONTEXT_MODULE) {
  815. return false;
  816. }
  817. require_login($course, true, $cm);
  818. $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
  819. if ($filearea === 'content') {
  820. $revision = (int)array_shift($args); // prevents caching problems - ignored here
  821. $relativepath = implode('/', $args);
  822. $fullpath = "/$context->id/mod_scorm/content/0/$relativepath";
  823. // TODO: add any other access restrictions here if needed!
  824. } else if ($filearea === 'package') {
  825. if (!has_capability('moodle/course:manageactivities', $context)) {
  826. return false;
  827. }
  828. $relativepath = implode('/', $args);
  829. $fullpath = "/$context->id/mod_scorm/package/0/$relativepath";
  830. $lifetime = 0; // no caching here
  831. } else {
  832. return false;
  833. }
  834. $fs = get_file_storage();
  835. if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
  836. if ($filearea === 'content') { //return file not found straight away to improve performance.
  837. send_header_404();
  838. die;
  839. }
  840. return false;
  841. }
  842. // finally send the file
  843. send_stored_file($file, $lifetime, 0, false, $options);
  844. }
  845. /**
  846. * @uses FEATURE_GROUPS
  847. * @uses FEATURE_GROUPINGS
  848. * @uses FEATURE_GROUPMEMBERSONLY
  849. * @uses FEATURE_MOD_INTRO
  850. * @uses FEATURE_COMPLETION_TRACKS_VIEWS
  851. * @uses FEATURE_COMPLETION_HAS_RULES
  852. * @uses FEATURE_GRADE_HAS_GRADE
  853. * @uses FEATURE_GRADE_OUTCOMES
  854. * @param string $feature FEATURE_xx constant for requested feature
  855. * @return mixed True if module supports feature, false if not, null if doesn't know
  856. */
  857. function scorm_supports($feature) {
  858. switch($feature) {
  859. case FEATURE_GROUPS: return false;
  860. case FEATURE_GROUPINGS: return false;
  861. case FEATURE_GROUPMEMBERSONLY: return true;
  862. case FEATURE_MOD_INTRO: return true;
  863. case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
  864. case FEATURE_COMPLETION_HAS_RULES: return true;
  865. case FEATURE_GRADE_HAS_GRADE: return true;
  866. case FEATURE_GRADE_OUTCOMES: return true;
  867. case FEATURE_BACKUP_MOODLE2: return true;
  868. case FEATURE_SHOW_DESCRIPTION: return true;
  869. default: return null;
  870. }
  871. }
  872. /**
  873. * Get the filename for a temp log file
  874. *
  875. * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
  876. * @param integer $scoid - scoid of object this log entry is for
  877. * @return string The filename as an absolute path
  878. */
  879. function scorm_debug_log_filename($type, $scoid) {
  880. global $CFG, $USER;
  881. $logpath = $CFG->tempdir.'/scormlogs';
  882. $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
  883. return $logfile;
  884. }
  885. /**
  886. * writes log output to a temp log file
  887. *
  888. * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
  889. * @param string $text - text to be written to file.
  890. * @param integer $scoid - scoid of object this log entry is for.
  891. */
  892. function scorm_debug_log_write($type, $text, $scoid) {
  893. $debugenablelog = get_config('scorm', 'allowapidebug');
  894. if (!$debugenablelog || empty($text)) {
  895. return;
  896. }
  897. if (make_temp_directory('scormlogs/')) {
  898. $logfile = scorm_debug_log_filename($type, $scoid);
  899. @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND);
  900. }
  901. }
  902. /**
  903. * Remove debug log file
  904. *
  905. * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
  906. * @param integer $scoid - scoid of object this log entry is for
  907. * @return boolean True if the file is successfully deleted, false otherwise
  908. */
  909. function scorm_debug_log_remove($type, $scoid) {
  910. $debugenablelog = get_config('scorm', 'allowapidebug');
  911. $logfile = scorm_debug_log_filename($type, $scoid);
  912. if (!$debugenablelog || !file_exists($logfile)) {
  913. return false;
  914. }
  915. return @unlink($logfile);
  916. }
  917. /**
  918. * writes overview info for course_overview block - displays upcoming scorm objects that have a due date
  919. *
  920. * @param object $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
  921. * @param array $htmlarray
  922. * @return mixed
  923. */
  924. function scorm_print_overview($courses, &$htmlarray) {
  925. global $USER, $CFG;
  926. if (empty($courses) || !is_array($courses) || count($courses) == 0) {
  927. return array();
  928. }
  929. if (!$scorms = get_all_instances_in_courses('scorm', $courses)) {
  930. return;
  931. }
  932. $strscorm = get_string('modulename', 'scorm');
  933. $strduedate = get_string('duedate', 'scorm');
  934. foreach ($scorms as $scorm) {
  935. $time = time();
  936. $showattemptstatus = false;
  937. if ($scorm->timeopen) {
  938. $isopen = ($scorm->timeopen <= $time && $time <= $scorm->timeclose);
  939. }
  940. if ($scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_ALL ||
  941. $scorm->displayattemptstatus == SCORM_DISPLAY_ATTEMPTSTATUS_MY) {
  942. $showattemptstatus = true;
  943. }
  944. if ($showattemptstatus || !empty($isopen) || !empty($scorm->timeclose)) {
  945. $str = '<div class="scorm overview"><div class="name">'.$strscorm. ': '.
  946. '<a '.($scorm->visible ? '':' class="dimmed"').
  947. 'title="'.$strscorm.'" href="'.$CFG->wwwroot.
  948. '/mod/scorm/view.php?id='.$scorm->coursemodule.'">'.
  949. $scorm->name.'</a></div>';
  950. if ($scorm->timeclose) {
  951. $str .= '<div class="info">'.$strduedate.': '.userdate($scorm->timeclose).'</div>';
  952. }
  953. if ($showattemptstatus) {
  954. require_once($CFG->dirroot.'/mod/scorm/locallib.php');
  955. $str .= '<div class="details">'.scorm_get_attempt_status($USER, $scorm).'</div>';
  956. }
  957. $str .= '</div>';
  958. if (empty($htmlarray[$scorm->course]['scorm'])) {
  959. $htmlarray[$scorm->course]['scorm'] = $str;
  960. } else {
  961. $htmlarray[$scorm->course]['scorm'] .= $str;
  962. }
  963. }
  964. }
  965. }
  966. /**
  967. * Return a list of page types
  968. * @param string $pagetype current page type
  969. * @param stdClass $parentcontext Block's parent context
  970. * @param stdClass $currentcontext Current context of block
  971. */
  972. function scorm_page_type_list($pagetype, $parentcontext, $currentcontext) {
  973. $module_pagetype = array('mod-scorm-*'=>get_string('page-mod-scorm-x', 'scorm'));
  974. return $module_pagetype;
  975. }
  976. /**
  977. * Returns the SCORM version used.
  978. * @param string $scormversion comes from $scorm->version
  979. * @param string $version one of the defined vars SCORM_12, SCORM_13, SCORM_AICC (or empty)
  980. * @return Scorm version.
  981. */
  982. function scorm_version_check($scormversion, $version='') {
  983. $scormversion = trim(strtolower($scormversion));
  984. if (empty($version) || $version==SCORM_12) {
  985. if ($scormversion == 'scorm_12' || $scormversion == 'scorm_1.2') {
  986. return SCORM_12;
  987. }
  988. if (!empty($version)) {
  989. return false;
  990. }
  991. }
  992. if (empty($version) || $version == SCORM_13) {
  993. if ($scormversion == 'scorm_13' || $scormversion == 'scorm_1.3') {
  994. return SCORM_13;
  995. }
  996. if (!empty($version)) {
  997. return false;
  998. }
  999. }
  1000. if (empty($version) || $version == SCORM_AICC) {
  1001. if (strpos($scormversion, 'aicc')) {
  1002. return SCORM_AICC;
  1003. }
  1004. if (!empty($version)) {
  1005. return false;
  1006. }
  1007. }
  1008. return false;
  1009. }
  1010. /**
  1011. * Obtains the automatic completion state for this scorm based on any conditions
  1012. * in scorm settings.
  1013. *
  1014. * @param object $course Course
  1015. * @param object $cm Course-module
  1016. * @param int $userid User ID
  1017. * @param bool $type Type of comparison (or/and; can be used as return value if no conditions)
  1018. * @return bool True if completed, false if not. (If no conditions, then return
  1019. * value depends on comparison type)
  1020. */
  1021. function scorm_get_completion_state($course, $cm, $userid, $type) {
  1022. global $DB;
  1023. $result = $type;
  1024. // Get scorm
  1025. if (!$scorm = $DB->get_record('scorm', array('id' => $cm->instance))) {
  1026. print_error('cannotfindscorm');
  1027. }
  1028. // Only check for existence of tracks and return false if completionstatusrequired or completionscorerequired
  1029. // this means that if only view is required we don't end up with a false state.
  1030. if ($scorm->completionstatusrequired !== null ||
  1031. $scorm->completionscorerequired !== null) {
  1032. // Get user's tracks data.
  1033. $tracks = $DB->get_records_sql(
  1034. "
  1035. SELECT
  1036. id,
  1037. element,
  1038. value
  1039. FROM
  1040. {scorm_scoes_track}
  1041. WHERE
  1042. scormid = ?
  1043. AND userid = ?
  1044. AND element IN
  1045. (
  1046. 'cmi.core.lesson_status',
  1047. 'cmi.completion_status',
  1048. 'cmi.success_status',
  1049. 'cmi.core.score.raw',
  1050. 'cmi.score.raw'
  1051. )
  1052. ",
  1053. array($scorm->id, $userid)
  1054. );
  1055. if (!$tracks) {
  1056. return completion_info::aggregate_completion_states($type, $result, false);
  1057. }
  1058. }
  1059. // Check for status
  1060. if ($scorm->completionstatusrequired !== null) {
  1061. // Get status
  1062. $statuses = array_flip(scorm_status_options());
  1063. $nstatus = 0;
  1064. foreach ($tracks as $track) {
  1065. if (!in_array($track->element, array('cmi.core.lesson_status', 'cmi.completion_status', 'cmi.success_status'))) {
  1066. continue;
  1067. }
  1068. if (array_key_exists($track->value, $statuses)) {
  1069. $nstatus |= $statuses[$track->value];
  1070. }
  1071. }
  1072. if ($scorm->completionstatusrequired & $nstatus) {
  1073. return completion_info::aggregate_completion_states($type, $result, true);
  1074. } else {
  1075. return completion_info::aggregate_completion_states($type, $result, false);
  1076. }
  1077. }
  1078. // Check for score
  1079. if ($scorm->completionscorerequired !== null) {
  1080. $maxscore = -1;
  1081. foreach ($tracks as $track) {
  1082. if (!in_array($track->element, array('cmi.core.score.raw', 'cmi.score.raw'))) {
  1083. continue;
  1084. }
  1085. if (strlen($track->value) && floatval($track->value) >= $maxscore) {
  1086. $maxscore = floatval($track->value);
  1087. }
  1088. }
  1089. if ($scorm->completionscorerequired <= $maxscore) {
  1090. return completion_info::aggregate_completion_states($type, $result, true);
  1091. } else {
  1092. return completion_info::aggregate_completion_states($type, $result, false);
  1093. }
  1094. }
  1095. return $result;
  1096. }
  1097. /**
  1098. * Register the ability to handle drag and drop file uploads
  1099. * @return array containing details of the files / types the mod can handle
  1100. */
  1101. function scorm_dndupload_register() {
  1102. return array('files' => array(
  1103. array('extension' => 'zip', 'message' => get_string('dnduploadscorm', 'scorm'))
  1104. ));
  1105. }
  1106. /**
  1107. * Handle a file that has been uploaded
  1108. * @param object $uploadinfo details of the file / content that has been uploaded
  1109. * @return int instance id of the newly created mod
  1110. */
  1111. function scorm_dndupload_handle($uploadinfo) {
  1112. $context = context_module::instance($uploadinfo->coursemodule);
  1113. file_save_draft_area_files($uploadinfo->draftitemid, $context->id, 'mod_scorm', 'package', 0);
  1114. $fs = get_file_storage();
  1115. $files = $fs->get_area_files($context->id, 'mod_scorm', 'package', 0, 'sortorder, itemid, filepath, filename', false);
  1116. $file = reset($files);
  1117. // Validate the file, make sure it's a valid SCORM package!
  1118. $packer = get_file_packer('application/zip');
  1119. $filelist = $file->list_files($packer);
  1120. if (!is_array($filelist)) {
  1121. return false;
  1122. } else {
  1123. $manifestpresent = false;
  1124. $aiccfound = false;
  1125. foreach ($filelist as $info) {
  1126. if ($info->pathname == 'imsmanifest.xml') {
  1127. $manifestpresent = true;
  1128. break;
  1129. }
  1130. if (preg_match('/\.cst$/', $info->pathname)) {
  1131. $aiccfound = true;
  1132. break;
  1133. }
  1134. }
  1135. if (!$manifestpresent && !$aiccfound) {
  1136. return false;
  1137. }
  1138. }
  1139. // Create a default scorm object to pass to scorm_add_instance()!
  1140. $scorm = get_config('scorm');
  1141. $scorm->course = $uploadinfo->course->id;
  1142. $scorm->coursemodule = $uploadinfo->coursemodule;
  1143. $scorm->cmidnumber = '';
  1144. $scorm->name = $uploadinfo->displayname;
  1145. $scorm->scormtype = SCORM_TYPE_LOCAL;
  1146. $scorm->reference = $file->get_filename();
  1147. $scorm->intro = '';
  1148. $scorm->width = $scorm->framewidth;
  1149. $scorm->height = $scorm->frameheight;
  1150. return scorm_add_instance($scorm, null);
  1151. }
  1152. /**
  1153. * Sets activity completion state
  1154. *
  1155. * @param object $scorm object
  1156. * @param int $userid User ID
  1157. * @param int $completionstate Completion state
  1158. * @param array $grades grades array of users with grades - used when $userid = 0
  1159. */
  1160. function scorm_set_completion($scorm, $userid, $completionstate = COMPLETION_COMPLETE, $grades = array()) {
  1161. $course = new stdClass();
  1162. $course->id = $scorm->course;
  1163. $completion = new completion_info($course);
  1164. // Check if completion is enabled site-wide, or for the course
  1165. if (!$completion->is_enabled()) {
  1166. return;
  1167. }
  1168. $cm = get_coursemodule_from_instance('scorm', $scorm->id, $scorm->course);
  1169. if (empty($cm) || !$completion->is_enabled($cm)) {
  1170. return;
  1171. }
  1172. if (empty($userid)) { //we need to get all the relevant users from $grades param.
  1173. foreach ($grades as $grade) {
  1174. $completion->update_state($cm, $completionstate, $grade->userid);
  1175. }
  1176. } else {
  1177. $completion->update_state($cm, $completionstate, $userid);
  1178. }
  1179. }