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