SCORM MDL-21354 Add log to file option for AICC objects as we can't use the normal...
[moodle.git] / mod / scorm / lib.php
CommitLineData
28f672b2 1<?php
2
3// This file is part of Moodle - http://moodle.org/
4//
5// Moodle is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Moodle is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Moodle. If not, see <http://www.gnu.org/licenses/>.
e4aa175a 17
28f672b2 18/**
19 * @package mod-scorm
20 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
21 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
22 */
23
24/** SCORM_TYPE_LOCAL = local */
9528568b 25define('SCORM_TYPE_LOCAL', 'local');
28f672b2 26/** SCORM_TYPE_LOCALSYNC = localsync */
9528568b 27define('SCORM_TYPE_LOCALSYNC', 'localsync');
28f672b2 28/** SCORM_TYPE_EXTERNAL = external */
9528568b 29define('SCORM_TYPE_EXTERNAL', 'external');
28f672b2 30/** SCORM_TYPE_IMSREPOSITORY = imsrepository */
9528568b 31define('SCORM_TYPE_IMSREPOSITORY', 'imsrepository');
32
33
e4aa175a 34/**
28f672b2 35 * Given an object containing all the necessary data,
36 * (defined by the form in mod_form.php) this function
37 * will create a new instance and return the id number
38 * of the new instance.
39 *
40 * @global stdClass
41 * @global object
42 * @uses CONTEXT_MODULE
43 * @uses SCORM_TYPE_LOCAL
44 * @uses SCORM_TYPE_LOCALSYNC
45 * @uses SCORM_TYPE_EXTERNAL
46 * @uses SCORM_TYPE_IMSREPOSITORY
47 * @param object $scorm Form data
48 * @param object $mform
49 * @return int new instance id
50 */
9528568b 51function scorm_add_instance($scorm, $mform=null) {
c18269c7 52 global $CFG, $DB;
a679d64d 53
86996ffe 54 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
a679d64d 55
d54e2145 56 if (empty($scorm->timerestrict)) {
57 $scorm->timeopen = 0;
58 $scorm->timeclose = 0;
59 }
60
9528568b 61 $cmid = $scorm->coursemodule;
62 $cmidnumber = $scorm->cmidnumber;
63 $courseid = $scorm->course;
76ea4fb4 64
9528568b 65 $context = get_context_instance(CONTEXT_MODULE, $cmid);
a679d64d 66
9528568b 67 $scorm = scorm_option2text($scorm);
68 $scorm->width = (int)str_replace('%', '', $scorm->width);
69 $scorm->height = (int)str_replace('%', '', $scorm->height);
e4aa175a 70
9528568b 71 if (!isset($scorm->whatgrade)) {
72 $scorm->whatgrade = 0;
73 }
74 $scorm->grademethod = ($scorm->whatgrade * 10) + $scorm->grademethod;
b3659259 75
a8f3a651 76 $id = $DB->insert_record('scorm', $scorm);
e4aa175a 77
9528568b 78/// update course module record - from now on this instance properly exists and all function may be used
bf8e93d7 79 $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
e4aa175a 80
9528568b 81/// reload scorm instance
82 $scorm = $DB->get_record('scorm', array('id'=>$id));
83
84/// store the package and verify
85 if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
86 if ($mform) {
87 $filename = $mform->get_new_filename('packagefile');
88 if ($filename !== false) {
89 $fs = get_file_storage();
90 $fs->delete_area_files($context->id, 'scorm_package');
91 $mform->save_stored_file('packagefile', $context->id, 'scorm_package', 0, '/', $filename);
92 $scorm->reference = $filename;
8ba4f1e0 93 }
e4aa175a 94 }
95
9528568b 96 } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
97 $scorm->reference = $scorm->packageurl;
e4aa175a 98
9528568b 99 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
100 $scorm->reference = $scorm->packageurl;
101
102 } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
103 $scorm->reference = $scorm->packageurl;
531fa830 104
a679d64d 105 } else {
9528568b 106 return false;
e4aa175a 107 }
9528568b 108
109 // save reference
110 $DB->update_record('scorm', $scorm);
111
112
113/// extra fields required in grade related functions
114 $scorm->course = $courseid;
115 $scorm->cmidnumber = $cmidnumber;
116 $scorm->cmid = $cmid;
117
118 scorm_parse($scorm, true);
119
120 scorm_grade_item_update($scorm);
121
122 return $scorm->id;
e4aa175a 123}
124
125/**
28f672b2 126 * Given an object containing all the necessary data,
127 * (defined by the form in mod_form.php) this function
128 * will update an existing instance with new data.
129 *
130 * @global stdClass
131 * @global object
132 * @uses CONTEXT_MODULE
133 * @uses SCORM_TYPE_LOCAL
134 * @uses SCORM_TYPE_LOCALSYNC
135 * @uses SCORM_TYPE_EXTERNAL
136 * @uses SCORM_TYPE_IMSREPOSITORY
137 * @param object $scorm Form data
138 * @param object $mform
139 * @return bool
140 */
9528568b 141function scorm_update_instance($scorm, $mform=null) {
c18269c7 142 global $CFG, $DB;
e4aa175a 143
86996ffe 144 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
76ea4fb4 145
d54e2145 146 if (empty($scorm->timerestrict)) {
147 $scorm->timeopen = 0;
148 $scorm->timeclose = 0;
149 }
150
9528568b 151 $cmid = $scorm->coursemodule;
152 $cmidnumber = $scorm->cmidnumber;
153 $courseid = $scorm->course;
154
155 $scorm->id = $scorm->instance;
156
157 $context = get_context_instance(CONTEXT_MODULE, $cmid);
158
159 if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
160 if ($mform) {
161 $filename = $mform->get_new_filename('packagefile');
162 if ($filename !== false) {
163 $scorm->reference = $filename;
164 $fs = get_file_storage();
165 $fs->delete_area_files($context->id, 'scorm_package');
166 $mform->save_stored_file('packagefile', $context->id, 'scorm_package', 0, '/', $filename);
a679d64d 167 }
76ea4fb4 168 }
76ea4fb4 169
9528568b 170 } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
171 $scorm->reference = $scorm->packageurl;
172
173 } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
174 $scorm->reference = $scorm->packageurl;
175
176 } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
177 $scorm->reference = $scorm->packageurl;
178
179 } else {
180 return false;
181 }
bfe8c2f0 182
e4aa175a 183 $scorm = scorm_option2text($scorm);
9528568b 184 $scorm->width = (int)str_replace('%','',$scorm->width);
185 $scorm->height = (int)str_replace('%','',$scorm->height);
186 $scorm->timemodified = time();
e4aa175a 187
b3659259 188 if (!isset($scorm->whatgrade)) {
189 $scorm->whatgrade = 0;
190 }
9528568b 191 $scorm->grademethod = ($scorm->whatgrade * 10) + $scorm->grademethod;
a30b6819 192
a8c31db2 193 $DB->update_record('scorm', $scorm);
531fa830 194
9528568b 195 $scorm = $DB->get_record('scorm', array('id'=>$scorm->id));
196
197/// extra fields required in grade related functions
198 $scorm->course = $courseid;
199 $scorm->idnumber = $cmidnumber;
200 $scorm->cmid = $cmid;
201
202 scorm_parse($scorm, (bool)$scorm->updatefreq);
203
204 scorm_grade_item_update($scorm);
205
206 return true;
e4aa175a 207}
208
209/**
28f672b2 210 * Given an ID of an instance of this module,
211 * this function will permanently delete the instance
212 * and any data that depends on it.
213 *
214 * @global stdClass
215 * @global object
216 * @param int $id Scorm instance id
217 * @return boolean
218 */
e4aa175a 219function scorm_delete_instance($id) {
c18269c7 220 global $CFG, $DB;
e4aa175a 221
c18269c7 222 if (! $scorm = $DB->get_record('scorm', array('id'=>$id))) {
e4aa175a 223 return false;
224 }
225
226 $result = true;
227
e4aa175a 228 // Delete any dependent records
c18269c7 229 if (! $DB->delete_records('scorm_scoes_track', array('scormid'=>$scorm->id))) {
e4aa175a 230 $result = false;
231 }
c18269c7 232 if ($scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) {
b3659259 233 foreach ($scoes as $sco) {
c18269c7 234 if (! $DB->delete_records('scorm_scoes_data', array('scoid'=>$sco->id))) {
b3659259 235 $result = false;
236 }
9528568b 237 }
c18269c7 238 $DB->delete_records('scorm_scoes', array('scorm'=>$scorm->id));
b3659259 239 } else {
e4aa175a 240 $result = false;
241 }
c18269c7 242 if (! $DB->delete_records('scorm', array('id'=>$scorm->id))) {
e4aa175a 243 $result = false;
244 }
a30b6819 245
c18269c7 246 /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) {
e4aa175a 247 $result = false;
248 }
c18269c7 249 if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) {
e4aa175a 250 $result = false;
251 }
c18269c7 252 if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) {
e4aa175a 253 $result = false;
254 }
c18269c7 255 if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) {
e4aa175a 256 $result = false;
257 }
c18269c7 258 if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) {
e4aa175a 259 $result = false;
260 }
c18269c7 261 if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) {
e4aa175a 262 $result = false;
263 }
c18269c7 264 if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) {
e4aa175a 265 $result = false;
9528568b 266 }*/
531fa830 267
c18269c7 268 scorm_grade_item_delete($scorm);
9528568b 269
e4aa175a 270 return $result;
271}
272
273/**
28f672b2 274 * Return a small object with summary information about what a
275 * user has done with a given particular instance of this module
276 * Used for user activity reports.
277 *
278 * @global stdClass
279 * @param int $course Course id
280 * @param int $user User id
281 * @param int $mod
282 * @param int $scorm The scorm id
283 * @return mixed
284 */
9528568b 285function scorm_user_outline($course, $user, $mod, $scorm) {
531fa830 286 global $CFG;
86996ffe 287 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
a30b6819 288
1a96363a
NC
289 require_once("$CFG->libdir/gradelib.php");
290 $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
291 if (!empty($grades->items[0]->grades)) {
292 $grade = reset($grades->items[0]->grades);
293 $result = new object();
294 $result->info = get_string('grade') . ': '. $grade->str_long_grade;
295 $result->time = $grade->dategraded;
296 return $result;
297 }
298 return null;
e4aa175a 299}
300
301/**
28f672b2 302 * Print a detailed representation of what a user has done with
303 * a given particular instance of this module, for user activity reports.
304 *
305 * @global stdClass
306 * @global object
307 * @param object $course
308 * @param object $user
309 * @param object $mod
310 * @param object $scorm
311 * @return boolean
312 */
e4aa175a 313function scorm_user_complete($course, $user, $mod, $scorm) {
d436d197 314 global $CFG, $DB, $OUTPUT;
1a96363a 315 require_once("$CFG->libdir/gradelib.php");
e4aa175a 316
317 $liststyle = 'structlist';
e4aa175a 318 $now = time();
319 $firstmodify = $now;
320 $lastmodify = 0;
321 $sometoreport = false;
322 $report = '';
9895302c 323
c86a91d5 324 // First Access and Last Access dates for SCOs
86996ffe 325 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
c86a91d5
PH
326 $timetracks = scorm_get_sco_runtime($scorm->id, false, $user->id);
327 $firstmodify = $timetracks->start;
328 $lastmodify = $timetracks->finish;
9895302c 329
1a96363a
NC
330 $grades = grade_get_grades($course->id, 'mod', 'scorm', $scorm->id, $user->id);
331 if (!empty($grades->items[0]->grades)) {
332 $grade = reset($grades->items[0]->grades);
333 echo $OUTPUT->container(get_string('grade').': '.$grade->str_long_grade);
334 if ($grade->str_feedback) {
335 echo $OUTPUT->container(get_string('feedback').': '.$grade->str_feedback);
336 }
337 }
338
bf347041 339 if ($orgs = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id, 'organization'=>'', 'launch'=>''),'id','id,identifier,title')) {
e4aa175a 340 if (count($orgs) <= 1) {
341 unset($orgs);
342 $orgs[]->identifier = '';
343 }
344 $report .= '<div class="mod-scorm">'."\n";
345 foreach ($orgs as $org) {
bf347041 346 $conditions = array();
e4aa175a 347 $currentorg = '';
348 if (!empty($org->identifier)) {
349 $report .= '<div class="orgtitle">'.$org->title.'</div>';
350 $currentorg = $org->identifier;
bf347041 351 $conditions['organization'] = $currentorg;
e4aa175a 352 }
353 $report .= "<ul id='0' class='$liststyle'>";
bf347041 354 $conditions['scorm'] = $scorm->id;
355 if ($scoes = $DB->get_records('scorm_scoes', $conditions, "id ASC")){
9fb2de4e 356 // drop keys so that we can access array sequentially
9528568b 357 $scoes = array_values($scoes);
e4aa175a 358 $level=0;
359 $sublist=1;
360 $parents[$level]='/';
9fb2de4e 361 foreach ($scoes as $pos=>$sco) {
e4aa175a 362 if ($parents[$level]!=$sco->parent) {
363 if ($level>0 && $parents[$level-1]==$sco->parent) {
364 $report .= "\t\t</ul></li>\n";
365 $level--;
366 } else {
367 $i = $level;
368 $closelist = '';
369 while (($i > 0) && ($parents[$level] != $sco->parent)) {
370 $closelist .= "\t\t</ul></li>\n";
371 $i--;
372 }
373 if (($i == 0) && ($sco->parent != $currentorg)) {
374 $report .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
375 $level++;
376 } else {
377 $report .= $closelist;
378 $level = $i;
379 }
380 $parents[$level]=$sco->parent;
381 }
382 }
383 $report .= "\t\t<li>";
9fb2de4e 384 if (isset($scoes[$pos+1])) {
385 $nextsco = $scoes[$pos+1];
386 } else {
387 $nextsco = false;
388 }
e4aa175a 389 if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
390 $sublist++;
391 } else {
485f4ce6 392 $report .= '<img src="'.$OUTPUT->pix_url('spacer', 'scorm').'" alt="" />';
e4aa175a 393 }
394
395 if ($sco->launch) {
e4aa175a 396 $score = '';
397 $totaltime = '';
398 if ($usertrack=scorm_get_tracks($sco->id,$user->id)) {
399 if ($usertrack->status == '') {
400 $usertrack->status = 'notattempted';
401 }
402 $strstatus = get_string($usertrack->status,'scorm');
485f4ce6 403 $report .= "<img src='".$OUTPUT->pix_url($usertrack->status, 'scorm')."' alt='$strstatus' title='$strstatus' />";
e4aa175a 404 } else {
405 if ($sco->scormtype == 'sco') {
485f4ce6 406 $report .= '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
e4aa175a 407 } else {
485f4ce6 408 $report .= '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
e4aa175a 409 }
410 }
411 $report .= "&nbsp;$sco->title $score$totaltime</li>\n";
412 if ($usertrack !== false) {
413 $sometoreport = true;
414 $report .= "\t\t\t<li><ul class='$liststyle'>\n";
415 foreach($usertrack as $element => $value) {
416 if (substr($element,0,3) == 'cmi') {
417 $report .= '<li>'.$element.' => '.$value.'</li>';
418 }
419 }
420 $report .= "\t\t\t</ul></li>\n";
9528568b 421 }
e4aa175a 422 } else {
423 $report .= "&nbsp;$sco->title</li>\n";
424 }
425 }
426 for ($i=0;$i<$level;$i++) {
427 $report .= "\t\t</ul></li>\n";
428 }
429 }
430 $report .= "\t</ul><br />\n";
431 }
432 $report .= "</div>\n";
433 }
434 if ($sometoreport) {
435 if ($firstmodify < $now) {
436 $timeago = format_time($now - $firstmodify);
437 echo get_string('firstaccess','scorm').': '.userdate($firstmodify).' ('.$timeago.")<br />\n";
438 }
439 if ($lastmodify > 0) {
440 $timeago = format_time($now - $lastmodify);
441 echo get_string('lastaccess','scorm').': '.userdate($lastmodify).' ('.$timeago.")<br />\n";
442 }
443 echo get_string('report','scorm').":<br />\n";
444 echo $report;
445 } else {
dabfd0ed 446 print_string('noactivity','scorm');
e4aa175a 447 }
448
449 return true;
450}
451
e4aa175a 452/**
28f672b2 453 * Function to be run periodically according to the moodle cron
454 * This function searches for things that need to be done, such
455 * as sending out mail, toggling flags etc ...
456 *
457 * @global stdClass
458 * @global object
459 * @return boolean
460 */
e4aa175a 461function scorm_cron () {
bf347041 462 global $CFG, $DB;
a679d64d 463
86996ffe 464 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
a679d64d 465
466 $sitetimezone = $CFG->timezone;
9528568b 467 /// Now see if there are any scorm updates to be done
468
bfe8c2f0 469 if (!isset($CFG->scorm_updatetimelast)) { // To catch the first time
470 set_config('scorm_updatetimelast', 0);
471 }
472
a679d64d 473 $timenow = time();
c5b3ba6a 474 $updatetime = usergetmidnight($timenow, $sitetimezone);
a679d64d 475
476 if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) {
477
bfe8c2f0 478 set_config('scorm_updatetimelast', $timenow);
e4aa175a 479
376c9c70 480 mtrace('Updating scorm packages which require daily update');//We are updating
bfe8c2f0 481
bf347041 482 $scormsupdate = $DB->get_records('scorm', array('updatefreq'=>UPDATE_EVERYDAY));
9528568b 483 foreach($scormsupdate as $scormupdate) {
484 scorm_parse($scormupdate, true);
a679d64d 485 }
486 }
487
e4aa175a 488 return true;
489}
490
491/**
531fa830 492 * Return grade for given user or all users.
493 *
28f672b2 494 * @global stdClass
495 * @global object
531fa830 496 * @param int $scormid id of scorm
497 * @param int $userid optional user id, 0 means all users
498 * @return array array of grades, false if none
499 */
500function scorm_get_user_grades($scorm, $userid=0) {
bf347041 501 global $CFG, $DB;
86996ffe 502 require_once($CFG->dirroot.'/mod/scorm/locallib.php');
531fa830 503
504 $grades = array();
505 if (empty($userid)) {
bf347041 506 if ($scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid", array($scorm->id), "", "userid,null")) {
531fa830 507 foreach ($scousers as $scouser) {
508 $grades[$scouser->userid] = new object();
509 $grades[$scouser->userid]->id = $scouser->userid;
510 $grades[$scouser->userid]->userid = $scouser->userid;
511 $grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
512 }
513 } else {
514 return false;
515 }
e4aa175a 516
531fa830 517 } else {
bf347041 518 if (!$DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid", array($scorm->id, $userid), "", "userid,null")) {
531fa830 519 return false; //no attempt yet
520 }
521 $grades[$userid] = new object();
522 $grades[$userid]->id = $userid;
523 $grades[$userid]->userid = $userid;
524 $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
e4aa175a 525 }
e4aa175a 526
531fa830 527 return $grades;
528}
529
530/**
531 * Update grades in central gradebook
532 *
28f672b2 533 * @global stdClass
534 * @global object
775f811a 535 * @param object $scorm
531fa830 536 * @param int $userid specific user only, 0 mean all
28f672b2 537 * @param bool $nullifnone
531fa830 538 */
775f811a 539function scorm_update_grades($scorm, $userid=0, $nullifnone=true) {
bf347041 540 global $CFG, $DB;
775f811a 541 require_once($CFG->libdir.'/gradelib.php');
531fa830 542
775f811a 543 if ($grades = scorm_get_user_grades($scorm, $userid)) {
544 scorm_grade_item_update($scorm, $grades);
531fa830 545
775f811a 546 } else if ($userid and $nullifnone) {
547 $grade = new object();
548 $grade->userid = $userid;
549 $grade->rawgrade = NULL;
550 scorm_grade_item_update($scorm, $grade);
531fa830 551
e4aa175a 552 } else {
775f811a 553 scorm_grade_item_update($scorm);
554 }
555}
556
557/**
558 * Update all grades in gradebook.
28f672b2 559 *
560 * @global object
775f811a 561 */
562function scorm_upgrade_grades() {
563 global $DB;
564
565 $sql = "SELECT COUNT('x')
566 FROM {scorm} s, {course_modules} cm, {modules} m
567 WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
568 $count = $DB->count_records_sql($sql);
569
570 $sql = "SELECT s.*, cm.idnumber AS cmidnumber, s.course AS courseid
571 FROM {scorm} s, {course_modules} cm, {modules} m
572 WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
573 if ($rs = $DB->get_recordset_sql($sql)) {
775f811a 574 $pbar = new progress_bar('scormupgradegrades', 500, true);
575 $i=0;
576 foreach ($rs as $scorm) {
577 $i++;
578 upgrade_set_timeout(60*5); // set up timeout, may also abort execution
579 scorm_update_grades($scorm, 0, false);
580 $pbar->update($i, $count, "Updating Scorm grades ($i/$count).");
531fa830 581 }
775f811a 582 $rs->close();
e4aa175a 583 }
531fa830 584}
e4aa175a 585
531fa830 586/**
587 * Update/create grade item for given scorm
588 *
28f672b2 589 * @global stdClass
590 * @global object
591 * @uses GRADE_TYPE_VALUE
592 * @uses GRADE_TYPE_NONE
531fa830 593 * @param object $scorm object with extra cmidnumber
28f672b2 594 * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
531fa830 595 * @return object grade_item
596 */
0b5a80a1 597function scorm_grade_item_update($scorm, $grades=NULL) {
bf347041 598 global $CFG, $DB;
531fa830 599 if (!function_exists('grade_update')) { //workaround for buggy PHP versions
600 require_once($CFG->libdir.'/gradelib.php');
601 }
602
7ef16bf9 603 $params = array('itemname'=>$scorm->name);
604 if (isset($scorm->cmidnumber)) {
605 $params['idnumber'] = $scorm->cmidnumber;
606 }
9528568b 607
531fa830 608 if (($scorm->grademethod % 10) == 0) { // GRADESCOES
5b4b959b 609 if ($maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND launch <> ?', array($scorm->id, $DB->sql_empty()))) {
531fa830 610 $params['gradetype'] = GRADE_TYPE_VALUE;
611 $params['grademax'] = $maxgrade;
612 $params['grademin'] = 0;
613 } else {
614 $params['gradetype'] = GRADE_TYPE_NONE;
e4aa175a 615 }
531fa830 616 } else {
617 $params['gradetype'] = GRADE_TYPE_VALUE;
618 $params['grademax'] = $scorm->maxgrade;
619 $params['grademin'] = 0;
e4aa175a 620 }
531fa830 621
0b5a80a1 622 if ($grades === 'reset') {
623 $params['reset'] = true;
624 $grades = NULL;
625 }
626
627 return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params);
531fa830 628}
629
630/**
631 * Delete grade item for given scorm
632 *
28f672b2 633 * @global stdClass
531fa830 634 * @param object $scorm object
635 * @return object grade_item
636 */
637function scorm_grade_item_delete($scorm) {
638 global $CFG;
639 require_once($CFG->libdir.'/gradelib.php');
640
641 return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, NULL, array('deleted'=>1));
e4aa175a 642}
643
28f672b2 644/**
645 * @return array
646 */
e4aa175a 647function scorm_get_view_actions() {
648 return array('pre-view','view','view all','report');
649}
650
28f672b2 651/**
652 * @return array
653 */
e4aa175a 654function scorm_get_post_actions() {
655 return array();
656}
657
28f672b2 658/**
659 * @param object $scorm
660 * @return object $scorm
661 */
e4aa175a 662function scorm_option2text($scorm) {
1adc77e6 663 $scorm_popoup_options = scorm_get_popup_options_array();
e5dd8e3b 664
e4aa175a 665 if (isset($scorm->popup)) {
76ea4fb4 666 if ($scorm->popup == 1) {
e4aa175a 667 $optionlist = array();
1adc77e6 668 foreach ($scorm_popoup_options as $name => $option) {
e4aa175a 669 if (isset($scorm->$name)) {
670 $optionlist[] = $name.'='.$scorm->$name;
671 } else {
672 $optionlist[] = $name.'=0';
673 }
9528568b 674 }
e4aa175a 675 $scorm->options = implode(',', $optionlist);
676 } else {
677 $scorm->options = '';
9528568b 678 }
e4aa175a 679 } else {
680 $scorm->popup = 0;
681 $scorm->options = '';
682 }
683 return $scorm;
684}
685
0b5a80a1 686/**
687 * Implementation of the function for printing the form elements that control
688 * whether the course reset functionality affects the scorm.
e5dd8e3b 689 *
28f672b2 690 * @param object $mform form passed by reference
0b5a80a1 691 */
692function scorm_reset_course_form_definition(&$mform) {
693 $mform->addElement('header', 'scormheader', get_string('modulenameplural', 'scorm'));
694 $mform->addElement('advcheckbox', 'reset_scorm', get_string('deleteallattempts','scorm'));
695}
696
697/**
698 * Course reset form defaults.
28f672b2 699 *
700 * @return array
0b5a80a1 701 */
702function scorm_reset_course_form_defaults($course) {
703 return array('reset_scorm'=>1);
704}
705
706/**
707 * Removes all grades from gradebook
28f672b2 708 *
709 * @global stdClass
710 * @global object
0b5a80a1 711 * @param int $courseid
712 * @param string optional type
713 */
714function scorm_reset_gradebook($courseid, $type='') {
bf347041 715 global $CFG, $DB;
0b5a80a1 716
717 $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid
bf347041 718 FROM {scorm} s, {course_modules} cm, {modules} m
719 WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id AND s.course=?";
0b5a80a1 720
bf347041 721 if ($scorms = $DB->get_records_sql($sql, array($courseid))) {
0b5a80a1 722 foreach ($scorms as $scorm) {
723 scorm_grade_item_update($scorm, 'reset');
724 }
725 }
726}
727
728/**
729 * Actual implementation of the rest coures functionality, delete all the
730 * scorm attempts for course $data->courseid.
28f672b2 731 *
732 * @global stdClass
733 * @global object
734 * @param object $data the data submitted from the reset course.
0b5a80a1 735 * @return array status array
736 */
737function scorm_reset_userdata($data) {
bf347041 738 global $CFG, $DB;
0b5a80a1 739
740 $componentstr = get_string('modulenameplural', 'scorm');
741 $status = array();
742
743 if (!empty($data->reset_scorm)) {
744 $scormssql = "SELECT s.id
bf347041 745 FROM {scorm} s
746 WHERE s.course=?";
0b5a80a1 747
bf347041 748 $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
0b5a80a1 749
750 // remove all grades from gradebook
751 if (empty($data->reset_gradebook_grades)) {
752 scorm_reset_gradebook($data->courseid);
753 }
754
755 $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'scorm'), 'error'=>false);
756 }
757
758 // no dates to shift here
759
760 return $status;
761}
762
f432bebf 763/**
764 * Returns all other caps used in module
28f672b2 765 *
766 * @return array
f432bebf 767 */
768function scorm_get_extra_capabilities() {
769 return array('moodle/site:accessallgroups');
770}
771
9528568b 772/**
773 * Lists all file areas current user may browse
28f672b2 774 *
775 * @param object $course
776 * @param object $cm
777 * @param object $context
778 * @return array
9528568b 779 */
780function scorm_get_file_areas($course, $cm, $context) {
781 $areas = array();
782 if (has_capability('moodle/course:managefiles', $context)) {
9528568b 783 $areas['scorm_content'] = get_string('areacontent', 'scorm');
784 $areas['scorm_package'] = get_string('areapackage', 'scorm');
785 }
786 return $areas;
787}
788
789/**
9895302c 790 * File browsing support for SCORM file areas
e5dd8e3b 791 *
9895302c
DC
792 * @param stdclass $browser
793 * @param stdclass $areas
794 * @param stdclass $course
795 * @param stdclass $cm
796 * @param stdclass $context
797 * @param string $filearea
798 * @param int $itemid
799 * @param string $filepath
800 * @param string $filename
801 * @return stdclass file_info instance or null if not found
9528568b 802 */
803function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
804 global $CFG;
805
806 if (!has_capability('moodle/course:managefiles', $context)) {
807 return null;
808 }
809
810 // no writing for now!
811
812 $fs = get_file_storage();
813
814 if ($filearea === 'scorm_content') {
815
816 $filepath = is_null($filepath) ? '/' : $filepath;
817 $filename = is_null($filename) ? '.' : $filename;
e5dd8e3b 818
9528568b 819 $urlbase = $CFG->wwwroot.'/pluginfile.php';
820 if (!$storedfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) {
821 if ($filepath === '/' and $filename === '.') {
822 $storedfile = new virtual_root_file($context->id, $filearea, 0);
823 } else {
824 // not found
825 return null;
826 }
827 }
28f672b2 828 /**
829 * @package mod-scorm
830 * @copyright 1999 onwards Martin Dougiamas {@link http://moodle.com}
831 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
832 */
9528568b 833 class scorm_package_file_info extends file_info_stored {
834 public function get_parent() {
835 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
836 return $this->browser->get_file_info($this->context);
837 }
838 return parent::get_parent();
839 }
840 public function get_visible_name() {
841 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
3156b8ca 842 return $this->topvisiblename;
9528568b 843 }
844 return parent::get_visible_name();
845 }
846 }
8546def3 847 return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false);
9528568b 848
849 } else if ($filearea === 'scorm_package') {
850 $filepath = is_null($filepath) ? '/' : $filepath;
851 $filename = is_null($filename) ? '.' : $filename;
e5dd8e3b 852
9528568b 853 $urlbase = $CFG->wwwroot.'/pluginfile.php';
854 if (!$storedfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) {
855 if ($filepath === '/' and $filename === '.') {
856 $storedfile = new virtual_root_file($context->id, $filearea, 0);
857 } else {
858 // not found
859 return null;
860 }
861 }
9895302c 862 return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
9528568b 863 }
864
865 // scorm_intro handled in file_browser
866
867 return false;
868}
869
870/**
871 * Serves scorm content, introduction images and packages. Implements needed access control ;-)
28f672b2 872 *
28f672b2 873 * @param object $course
874 * @param object $cminfo
875 * @param object $context
876 * @param string $filearea
877 * @param array $args
98edf7b6 878 * @param bool $forcedownload
86900a93 879 * @return bool false if file not found, does not return if found - just send the file
9528568b 880 */
98edf7b6 881function scorm_pluginfile($course, $cminfo, $context, $filearea, $args, $forcedownload) {
9528568b 882 global $CFG;
883
884 if (!$cminfo->uservisible) {
885 return false; // probably hidden
886 }
887
888 $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
889
0a8a7b6c 890 if (!$cm = get_coursemodule_from_instance('scorm', $cminfo->instance, $course->id)) {
891 return false;
892 }
e5dd8e3b 893
0a8a7b6c 894 require_login($course, true, $cm);
895
ac3668bf 896 if ($filearea === 'scorm_content') {
9528568b 897 $revision = (int)array_shift($args); // prevents caching problems - ignored here
898 $relativepath = '/'.implode('/', $args);
899 $fullpath = $context->id.'scorm_content0'.$relativepath;
900 // TODO: add any other access restrictions here if needed!
901
902 } else if ($filearea === 'scorm_package') {
903 if (!has_capability('moodle/course:manageactivities', $context)) {
904 return false;
905 }
906 $relativepath = '/'.implode('/', $args);
907 $fullpath = $context->id.'scorm_package0'.$relativepath;
908 $lifetime = 0; // no caching here
909
910 } else {
911 return false;
912 }
913
914 $fs = get_file_storage();
915 if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
916 return false;
917 }
918
919 // finally send the file
920 send_stored_file($file, $lifetime, 0, false);
921}
922
42f103be 923/**
28f672b2 924 * @uses FEATURE_GROUPS
925 * @uses FEATURE_GROUPINGS
926 * @uses FEATURE_GROUPMEMBERSONLY
927 * @uses FEATURE_MOD_INTRO
928 * @uses FEATURE_COMPLETION_TRACKS_VIEWS
929 * @uses FEATURE_GRADE_HAS_GRADE
930 * @uses FEATURE_GRADE_OUTCOMES
42f103be 931 * @param string $feature FEATURE_xx constant for requested feature
28f672b2 932 * @return mixed True if module supports feature, false if not, null if doesn't know
42f103be 933 */
934function scorm_supports($feature) {
935 switch($feature) {
936 case FEATURE_GROUPS: return false;
937 case FEATURE_GROUPINGS: return false;
938 case FEATURE_GROUPMEMBERSONLY: return true;
dc5c2bd9 939 case FEATURE_MOD_INTRO: return true;
42f103be 940 case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
941 case FEATURE_GRADE_HAS_GRADE: return true;
942 case FEATURE_GRADE_OUTCOMES: return true;
02488b86 943 case FEATURE_BACKUP_MOODLE2: return true;
42f103be 944
945 default: return null;
946 }
947}
0a1f8f8f
SH
948
949/**
950 * This function extends the global navigaiton for the site.
951 * It is important to note that you should not rely on PAGE objects within this
952 * body of code as there is no guarantee that during an AJAX request they are
953 * available
954 *
955 * @param navigation_node $navigation The scorm node within the global navigation
956 * @param stdClass $course The course object returned from the DB
957 * @param stdClass $module The module object returned from the DB
958 * @param stdClass $cm The course module isntance returned from the DB
959 */
960function scorm_extend_navigation($navigation, $course, $module, $cm) {
961 /**
962 * This is currently just a stub so that it can be easily expanded upon.
963 * When expanding just remove this comment and the line below and then add
964 * you content.
965 */
966 $navigation->nodetype = navigation_node::NODETYPE_LEAF;
02488b86 967}
169a204c
DM
968/**
969 * writes log output to a temp log file
970 *
971 * @param string $type - type of log(aicc,scorm12,scorm13) used as prefix for filename
972 * @param string $text - text to be written to file.
973 * @param integer $scoid - scoid of object this log entry is for.
974 */
975function scorm_write_log($type, $text, $scoid) {
976 global $CFG, $USER;
977
978 $debugenablelog = debugging('', DEBUG_DEVELOPER);
979 if (!$debugenablelog || empty($text)) {
980 return ;
981 }
982 if (make_upload_directory('temp/scormlogs/')) {
983 $logpath = $CFG->dataroot.'/temp/scormlogs';
984
985 $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
986 @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND);
987 }
988}