Merge branch 'm20_MDL-26176_cleanup' of github.com:danmarsden/moodle
[moodle.git] / mod / scorm / lib.php
1 <?php
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/>.
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  */
24 /** SCORM_TYPE_LOCAL = local */
25 define('SCORM_TYPE_LOCAL', 'local');
26 /** SCORM_TYPE_LOCALSYNC = localsync */
27 define('SCORM_TYPE_LOCALSYNC', 'localsync');
28 /** SCORM_TYPE_EXTERNAL = external */
29 define('SCORM_TYPE_EXTERNAL', 'external');
30 /** SCORM_TYPE_IMSREPOSITORY = imsrepository */
31 define('SCORM_TYPE_IMSREPOSITORY', 'imsrepository');
34 /**
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  */
51 function scorm_add_instance($scorm, $mform=null) {
52     global $CFG, $DB;
54     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
56     if (empty($scorm->timeopen)) {
57         $scorm->timeopen = 0;
58     }
59     if (empty($scorm->timeclose)) {
60         $scorm->timeclose = 0;
61     }
62     $cmid       = $scorm->coursemodule;
63     $cmidnumber = $scorm->cmidnumber;
64     $courseid   = $scorm->course;
66     $context = get_context_instance(CONTEXT_MODULE, $cmid);
68     $scorm = scorm_option2text($scorm);
69     $scorm->width  = (int)str_replace('%', '', $scorm->width);
70     $scorm->height = (int)str_replace('%', '', $scorm->height);
72     if (!isset($scorm->whatgrade)) {
73         $scorm->whatgrade = 0;
74     }
76     $id = $DB->insert_record('scorm', $scorm);
78 /// update course module record - from now on this instance properly exists and all function may be used
79     $DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid));
81 /// reload scorm instance
82     $record = $DB->get_record('scorm', array('id'=>$id));
84 /// store the package and verify
85     if ($record->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, 'mod_scorm', 'package');
91                 $mform->save_stored_file('packagefile', $context->id, 'mod_scorm', 'package', 0, '/', $filename);
92                 $record->reference = $filename;
93             }
94         }
96     } else if ($record->scormtype === SCORM_TYPE_LOCALSYNC) {
97         $record->reference = $scorm->packageurl;
99     } else if ($record->scormtype === SCORM_TYPE_EXTERNAL) {
100         $record->reference = $scorm->packageurl;
102     } else if ($record->scormtype === SCORM_TYPE_IMSREPOSITORY) {
103         $record->reference = $scorm->packageurl;
105     } else {
106         return false;
107     }
109     // save reference
110     $DB->update_record('scorm', $record);
113 /// extra fields required in grade related functions
114     $record->course     = $courseid;
115     $record->cmidnumber = $cmidnumber;
116     $record->cmid       = $cmid;
118     scorm_parse($record, true);
120     scorm_grade_item_update($record);
122     return $record->id;
125 /**
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  */
141 function scorm_update_instance($scorm, $mform=null) {
142     global $CFG, $DB;
144     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
146     if (empty($scorm->timeopen)) {
147         $scorm->timeopen = 0;
148     }
149     if (empty($scorm->timeclose)) {
150         $scorm->timeclose = 0;
151     }
153     $cmid       = $scorm->coursemodule;
154     $cmidnumber = $scorm->cmidnumber;
155     $courseid   = $scorm->course;
157     $scorm->id = $scorm->instance;
159     $context = get_context_instance(CONTEXT_MODULE, $cmid);
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();
167                 $fs->delete_area_files($context->id, 'mod_scorm', 'package');
168                 $mform->save_stored_file('packagefile', $context->id, 'mod_scorm', 'package', 0, '/', $filename);
169             }
170         }
172     } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
173         $scorm->reference = $scorm->packageurl;
175     } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
176         $scorm->reference = $scorm->packageurl;
178     } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
179         $scorm->reference = $scorm->packageurl;
181     } else {
182         return false;
183     }
185     $scorm = scorm_option2text($scorm);
186     $scorm->width        = (int)str_replace('%','',$scorm->width);
187     $scorm->height       = (int)str_replace('%','',$scorm->height);
188     $scorm->timemodified = time();
190     if (!isset($scorm->whatgrade)) {
191         $scorm->whatgrade = 0;
192     }
194     $DB->update_record('scorm', $scorm);
196     $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;
203     scorm_parse($scorm, (bool)$scorm->updatefreq);
205     scorm_grade_item_update($scorm);
206     scorm_update_grades($scorm);
208     return true;
211 /**
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  */
221 function scorm_delete_instance($id) {
222     global $CFG, $DB;
224     if (! $scorm = $DB->get_record('scorm', array('id'=>$id))) {
225         return false;
226     }
228     $result = true;
230     // Delete any dependent records
231     if (! $DB->delete_records('scorm_scoes_track', array('scormid'=>$scorm->id))) {
232         $result = false;
233     }
234     if ($scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) {
235         foreach ($scoes as $sco) {
236             if (! $DB->delete_records('scorm_scoes_data', array('scoid'=>$sco->id))) {
237                 $result = false;
238             }
239         }
240         $DB->delete_records('scorm_scoes', array('scorm'=>$scorm->id));
241     } else {
242         $result = false;
243     }
244     if (! $DB->delete_records('scorm', array('id'=>$scorm->id))) {
245         $result = false;
246     }
248     /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) {
249         $result = false;
250     }
251     if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) {
252         $result = false;
253     }
254     if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) {
255         $result = false;
256     }
257     if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) {
258         $result = false;
259     }
260     if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) {
261         $result = false;
262     }
263     if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) {
264         $result = false;
265     }
266     if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) {
267         $result = false;
268     }*/
270     scorm_grade_item_delete($scorm);
272     return $result;
275 /**
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  */
287 function scorm_user_outline($course, $user, $mod, $scorm) {
288     global $CFG;
289     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
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);
295         $result = new stdClass();
296         $result->info = get_string('grade') . ': '. $grade->str_long_grade;
297         $result->time = $grade->dategraded;
298         return $result;
299     }
300     return null;
303 /**
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  */
315 function scorm_user_complete($course, $user, $mod, $scorm) {
316     global $CFG, $DB, $OUTPUT;
317     require_once("$CFG->libdir/gradelib.php");
319     $liststyle = 'structlist';
320     $now = time();
321     $firstmodify = $now;
322     $lastmodify = 0;
323     $sometoreport = false;
324     $report = '';
326     // First Access and Last Access dates for SCOs
327     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
328     $timetracks = scorm_get_sco_runtime($scorm->id, false, $user->id);
329     $firstmodify = $timetracks->start;
330     $lastmodify = $timetracks->finish;
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     }
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')) {
345         if (count($orgs) <= 1) {
346             unset($orgs);
347             $orgs[]->identifier = '';
348         }
349         $report .= '<div class="mod-scorm">'."\n";
350         foreach ($orgs as $org) {
351             $conditions = array();
352             $currentorg = '';
353             if (!empty($org->identifier)) {
354                 $report .= '<div class="orgtitle">'.$org->title.'</div>';
355                 $currentorg = $org->identifier;
356                 $conditions['organization'] = $currentorg;
357             }
358             $report .= "<ul id='0' class='$liststyle'>";
359                 $conditions['scorm'] = $scorm->id;
360             if ($scoes = $DB->get_records('scorm_scoes', $conditions, "id ASC")){
361                 // drop keys so that we can access array sequentially
362                 $scoes = array_values($scoes);
363                 $level=0;
364                 $sublist=1;
365                 $parents[$level]='/';
366                 foreach ($scoes as $pos=>$sco) {
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>";
389                     if (isset($scoes[$pos+1])) {
390                         $nextsco = $scoes[$pos+1];
391                     } else {
392                         $nextsco = false;
393                     }
394                     if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
395                         $sublist++;
396                     } else {
397                         $report .= '<img src="'.$OUTPUT->pix_url('spacer', 'scorm').'" alt="" />';
398                     }
400                     if ($sco->launch) {
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');
408                             $report .= "<img src='".$OUTPUT->pix_url($usertrack->status, 'scorm')."' alt='$strstatus' title='$strstatus' />";
409                         } else {
410                             if ($sco->scormtype == 'sco') {
411                                 $report .= '<img src="'.$OUTPUT->pix_url('notattempted', 'scorm').'" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
412                             } else {
413                                 $report .= '<img src="'.$OUTPUT->pix_url('asset', 'scorm').'" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
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.' => '.s($value).'</li>';
423                                 }
424                             }
425                             $report .= "\t\t\t</ul></li>\n";
426                         }
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 {
451         print_string('noactivity','scorm');
452     }
454     return true;
457 /**
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  */
466 function scorm_cron () {
467     global $CFG, $DB;
469     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
471     $sitetimezone = $CFG->timezone;
472     /// Now see if there are any scorm updates to be done
474     if (!isset($CFG->scorm_updatetimelast)) {    // To catch the first time
475         set_config('scorm_updatetimelast', 0);
476     }
478     $timenow = time();
479     $updatetime = usergetmidnight($timenow, $sitetimezone);
481     if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) {
483         set_config('scorm_updatetimelast', $timenow);
485         mtrace('Updating scorm packages which require daily update');//We are updating
487         $scormsupdate = $DB->get_records('scorm', array('updatefreq'=>UPDATE_EVERYDAY));
488         foreach($scormsupdate as $scormupdate) {
489             scorm_parse($scormupdate, true);
490         }
491     }
493     return true;
496 /**
497  * Return grade for given user or all users.
498  *
499  * @global stdClass
500  * @global object
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  */
505 function scorm_get_user_grades($scorm, $userid=0) {
506     global $CFG, $DB;
507     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
509     $grades = array();
510     if (empty($userid)) {
511         if ($scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid", array($scorm->id), "", "userid,null")) {
512             foreach ($scousers as $scouser) {
513                 $grades[$scouser->userid] = new stdClass();
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         }
522     } else {
523         if (!$DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid", array($scorm->id, $userid), "", "userid,null")) {
524             return false; //no attempt yet
525         }
526         $grades[$userid] = new stdClass();
527         $grades[$userid]->id         = $userid;
528         $grades[$userid]->userid     = $userid;
529         $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
530     }
532     return $grades;
535 /**
536  * Update grades in central gradebook
537  *
538  * @global stdClass
539  * @global object
540  * @param object $scorm
541  * @param int $userid specific user only, 0 mean all
542  * @param bool $nullifnone
543  */
544 function scorm_update_grades($scorm, $userid=0, $nullifnone=true) {
545     global $CFG, $DB;
546     require_once($CFG->libdir.'/gradelib.php');
548     if ($grades = scorm_get_user_grades($scorm, $userid)) {
549         scorm_grade_item_update($scorm, $grades);
551     } else if ($userid and $nullifnone) {
552         $grade = new stdClass();
553         $grade->userid   = $userid;
554         $grade->rawgrade = NULL;
555         scorm_grade_item_update($scorm, $grade);
557     } else {
558         scorm_grade_item_update($scorm);
559     }
562 /**
563  * Update all grades in gradebook.
564  *
565  * @global object
566  */
567 function scorm_upgrade_grades() {
568     global $DB;
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);
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)) {
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).");
586         }
587         $rs->close();
588     }
591 /**
592  * Update/create grade item for given scorm
593  *
594  * @global stdClass
595  * @global object
596  * @uses GRADE_TYPE_VALUE
597  * @uses GRADE_TYPE_NONE
598  * @param object $scorm object with extra cmidnumber
599  * @param mixed $grades optional array/object of grade(s); 'reset' means reset grades in gradebook
600  * @return object grade_item
601  */
602 function scorm_grade_item_update($scorm, $grades=NULL) {
603     global $CFG, $DB;
604     require_once($CFG->dirroot.'/mod/scorm/locallib.php');
605     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
606         require_once($CFG->libdir.'/gradelib.php');
607     }
609     $params = array('itemname'=>$scorm->name);
610     if (isset($scorm->cmidnumber)) {
611         $params['idnumber'] = $scorm->cmidnumber;
612     }
614     if ($scorm->grademethod == GRADESCOES) {
615         if ($maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND '.$DB->sql_isnotempty('scorm_scoes', 'launch', false, true), array($scorm->id))) {
616             $params['gradetype'] = GRADE_TYPE_VALUE;
617             $params['grademax']  = $maxgrade;
618             $params['grademin']  = 0;
619         } else {
620             $params['gradetype'] = GRADE_TYPE_NONE;
621         }
622     } else {
623         $params['gradetype'] = GRADE_TYPE_VALUE;
624         $params['grademax']  = $scorm->maxgrade;
625         $params['grademin']  = 0;
626     }
628     if ($grades  === 'reset') {
629         $params['reset'] = true;
630         $grades = NULL;
631     }
633     return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params);
636 /**
637  * Delete grade item for given scorm
638  *
639  * @global stdClass
640  * @param object $scorm object
641  * @return object grade_item
642  */
643 function scorm_grade_item_delete($scorm) {
644     global $CFG;
645     require_once($CFG->libdir.'/gradelib.php');
647     return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, NULL, array('deleted'=>1));
650 /**
651  * @return array
652  */
653 function scorm_get_view_actions() {
654     return array('pre-view','view','view all','report');
657 /**
658  * @return array
659  */
660 function scorm_get_post_actions() {
661     return array();
664 /**
665  * @param object $scorm
666  * @return object $scorm
667  */
668 function scorm_option2text($scorm) {
669     $scorm_popoup_options = scorm_get_popup_options_array();
671     if (isset($scorm->popup)) {
672         if ($scorm->popup == 1) {
673             $optionlist = array();
674             foreach ($scorm_popoup_options as $name => $option) {
675                 if (isset($scorm->$name)) {
676                     $optionlist[] = $name.'='.$scorm->$name;
677                 } else {
678                     $optionlist[] = $name.'=0';
679                 }
680             }
681             $scorm->options = implode(',', $optionlist);
682         } else {
683             $scorm->options = '';
684         }
685     } else {
686         $scorm->popup = 0;
687         $scorm->options = '';
688     }
689     return $scorm;
692 /**
693  * Implementation of the function for printing the form elements that control
694  * whether the course reset functionality affects the scorm.
695  *
696  * @param object $mform form passed by reference
697  */
698 function 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'));
703 /**
704  * Course reset form defaults.
705  *
706  * @return array
707  */
708 function scorm_reset_course_form_defaults($course) {
709     return array('reset_scorm'=>1);
712 /**
713  * Removes all grades from gradebook
714  *
715  * @global stdClass
716  * @global object
717  * @param int $courseid
718  * @param string optional type
719  */
720 function scorm_reset_gradebook($courseid, $type='') {
721     global $CFG, $DB;
723     $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid
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=?";
727     if ($scorms = $DB->get_records_sql($sql, array($courseid))) {
728         foreach ($scorms as $scorm) {
729             scorm_grade_item_update($scorm, 'reset');
730         }
731     }
734 /**
735  * Actual implementation of the reset course functionality, delete all the
736  * scorm attempts for course $data->courseid.
737  *
738  * @global stdClass
739  * @global object
740  * @param object $data the data submitted from the reset course.
741  * @return array status array
742  */
743 function scorm_reset_userdata($data) {
744     global $CFG, $DB;
746     $componentstr = get_string('modulenameplural', 'scorm');
747     $status = array();
749     if (!empty($data->reset_scorm)) {
750         $scormssql = "SELECT s.id
751                          FROM {scorm} s
752                         WHERE s.course=?";
754         $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
756         // remove all grades from gradebook
757         if (empty($data->reset_gradebook_grades)) {
758             scorm_reset_gradebook($data->courseid);
759         }
761         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'scorm'), 'error'=>false);
762     }
764     // no dates to shift here
766     return $status;
769 /**
770  * Returns all other caps used in module
771  *
772  * @return array
773  */
774 function scorm_get_extra_capabilities() {
775     return array('moodle/site:accessallgroups');
778 /**
779  * Lists all file areas current user may browse
780  *
781  * @param object $course
782  * @param object $cm
783  * @param object $context
784  * @return array
785  */
786 function scorm_get_file_areas($course, $cm, $context) {
787     $areas = array();
788     $areas['content'] = get_string('areacontent', 'scorm');
789     $areas['package'] = get_string('areapackage', 'scorm');
790     return $areas;
793 /**
794  * File browsing support for SCORM file areas
795  *
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
806  */
807 function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
808     global $CFG;
810     if (!has_capability('moodle/course:managefiles', $context)) {
811         return null;
812     }
814     // no writing for now!
816     $fs = get_file_storage();
818     if ($filearea === 'content') {
820         $filepath = is_null($filepath) ? '/' : $filepath;
821         $filename = is_null($filename) ? '.' : $filename;
823         $urlbase = $CFG->wwwroot.'/pluginfile.php';
824         if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'content', 0, $filepath, $filename)) {
825             if ($filepath === '/' and $filename === '.') {
826                 $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'content', 0);
827             } else {
828                 // not found
829                 return null;
830             }
831         }
832         require_once("$CFG->dirroot/mod/scorm/locallib.php");
833         return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false);
835     } else if ($filearea === 'package') {
836         $filepath = is_null($filepath) ? '/' : $filepath;
837         $filename = is_null($filename) ? '.' : $filename;
839         $urlbase = $CFG->wwwroot.'/pluginfile.php';
840         if (!$storedfile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, $filepath, $filename)) {
841             if ($filepath === '/' and $filename === '.') {
842                 $storedfile = new virtual_root_file($context->id, 'mod_scorm', 'package', 0);
843             } else {
844                 // not found
845                 return null;
846             }
847         }
848         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false, false);
849     }
851     // scorm_intro handled in file_browser
853     return false;
856 /**
857  * Serves scorm content, introduction images and packages. Implements needed access control ;-)
858  *
859  * @param object $course
860  * @param object $cm
861  * @param object $context
862  * @param string $filearea
863  * @param array $args
864  * @param bool $forcedownload
865  * @return bool false if file not found, does not return if found - just send the file
866  */
867 function scorm_pluginfile($course, $cm, $context, $filearea, $args, $forcedownload) {
868     global $CFG;
870     if ($context->contextlevel != CONTEXT_MODULE) {
871         return false;
872     }
874     require_login($course, true, $cm);
876     $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
878     if ($filearea === 'content') {
879         $revision = (int)array_shift($args); // prevents caching problems - ignored here
880         $relativepath = implode('/', $args);
881         $fullpath = "/$context->id/mod_scorm/content/0/$relativepath";
882         // TODO: add any other access restrictions here if needed!
884     } else if ($filearea === 'package') {
885         if (!has_capability('moodle/course:manageactivities', $context)) {
886             return false;
887         }
888         $relativepath = implode('/', $args);
889         $fullpath = "/$context->id/mod_scorm/package/0/$relativepath";
890         $lifetime = 0; // no caching here
892     } else {
893         return false;
894     }
896     $fs = get_file_storage();
897     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
898         return false;
899     }
901     // finally send the file
902     send_stored_file($file, $lifetime, 0, false);
905 /**
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
913  * @param string $feature FEATURE_xx constant for requested feature
914  * @return mixed True if module supports feature, false if not, null if doesn't know
915  */
916 function scorm_supports($feature) {
917     switch($feature) {
918         case FEATURE_GROUPS:                  return false;
919         case FEATURE_GROUPINGS:               return false;
920         case FEATURE_GROUPMEMBERSONLY:        return true;
921         case FEATURE_MOD_INTRO:               return true;
922         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
923         case FEATURE_GRADE_HAS_GRADE:         return true;
924         case FEATURE_GRADE_OUTCOMES:          return true;
925         case FEATURE_BACKUP_MOODLE2:          return true;
927         default: return null;
928     }
931 /**
932  * This function extends the global navigation for the site.
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
940  * @param stdClass $cm The course module instance returned from the DB
941  */
942 function 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;
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
956  * @return string The filename as an absolute path
957 */
958 function scorm_debug_log_filename($type, $scoid) {
959     global $CFG, $USER;
961     $logpath = $CFG->dataroot.'/temp/scormlogs';
962     $logfile = $logpath.'/'.$type.'debug_'.$USER->id.'_'.$scoid.'.log';
963     return $logfile;
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  */
973 function scorm_debug_log_write($type, $text, $scoid) {
975     $debugenablelog = get_config('scorm', 'allowapidebug');
976     if (!$debugenablelog || empty($text)) {
977         return ;
978     }
979     if (make_upload_directory('temp/scormlogs/')) {
980         $logfile = scorm_debug_log_filename($type, $scoid);
981         @file_put_contents($logfile, date('Y/m/d H:i:s O')." DEBUG $text\r\n", FILE_APPEND);
982     }
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  */
992 function scorm_debug_log_remove($type, $scoid) {
994     $debugenablelog = get_config('scorm', 'allowapidebug');
995     $logfile = scorm_debug_log_filename($type, $scoid);
996     if (!$debugenablelog || !file_exists($logfile)) {
997         return false;
998     }
1000     return @unlink($logfile);
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
1008  * @return mixed
1009  */
1010 function scorm_print_overview($courses, &$htmlarray) {
1011     global $USER, $CFG, $DB;
1013     if (empty($courses) || !is_array($courses) || count($courses) == 0) {
1014         return array();
1015     }
1017     if (!$scorms = get_all_instances_in_courses('scorm',$courses)) {
1018         return;
1019     }
1021     $scormids = array();
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     }
1036     if(empty($scormids)){
1037         // no scorms to look at - we're done
1038         return true;
1039     }
1040     $strscorm   = get_string('modulename', 'scorm');
1041     $strduedate = get_string('duedate', 'scorm');
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         }
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     }