MDL-18910 moving modedit features to modname_supports()
[moodle.git] / mod / scorm / lib.php
1 <?php  // $Id$
3 define('SCORM_TYPE_LOCAL', 'local');
4 define('SCORM_TYPE_LOCALSYNC', 'localsync');
5 define('SCORM_TYPE_EXTERNAL', 'external');
6 define('SCORM_TYPE_IMSREPOSITORY', 'imsrepository');
9 /**
10 * Given an object containing all the necessary data,
11 * (defined by the form in mod_form.php) this function
12 * will create a new instance and return the id number
13 * of the new instance.
14 *
15 * @param object $scorm Form data
16 * @param object $mform
17 * @return int new instance id
18 */
20 function scorm_add_instance($scorm, $mform=null) {
21     global $CFG, $DB;
23     require_once('locallib.php');
25     if (empty($scorm->timerestrict)) {
26         $scorm->timeopen = 0;
27         $scorm->timeclose = 0;
28     }
30     $cmid       = $scorm->coursemodule;
31     $cmidnumber = $scorm->cmidnumber;
32     $courseid   = $scorm->course;
34     $context = get_context_instance(CONTEXT_MODULE, $cmid);
36     $scorm = scorm_option2text($scorm);
37     $scorm->width  = (int)str_replace('%', '', $scorm->width);
38     $scorm->height = (int)str_replace('%', '', $scorm->height);
40     if (!isset($scorm->whatgrade)) {
41         $scorm->whatgrade = 0;
42     }
43     $scorm->grademethod = ($scorm->whatgrade * 10) + $scorm->grademethod;
45     if (!$id = $DB->insert_record('scorm', $scorm)) {
46         return false;
47     }
49 /// update course module record - from now on this instance properly exists and all function may be used
50     if (!$DB->set_field('course_modules', 'instance', $id, array('id'=>$cmid))) {
51         print_error('cannotaddcoursemodule');
52     }
54 /// reload scorm instance
55     $scorm = $DB->get_record('scorm', array('id'=>$id));
57 /// store the package and verify
58     if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
59         if ($mform) {
60             $filename = $mform->get_new_filename('packagefile');
61             if ($filename !== false) {
62                 $fs = get_file_storage();
63                 $fs->delete_area_files($context->id, 'scorm_package');
64                 $mform->save_stored_file('packagefile', $context->id, 'scorm_package', 0, '/', $filename);
65                 $scorm->reference = $filename;
66             }
67         }
69     } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
70         $scorm->reference = $scorm->packageurl;
72     } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
73         $scorm->reference = $scorm->packageurl;
75     } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
76         $scorm->reference = $scorm->packageurl;
78     } else {
79         return false;
80     }
82     // save reference
83     $DB->update_record('scorm', $scorm);
86 /// extra fields required in grade related functions
87     $scorm->course     = $courseid;
88     $scorm->cmidnumber = $cmidnumber;
89     $scorm->cmid       = $cmid;
91     scorm_parse($scorm, true);
93     scorm_grade_item_update($scorm);
95     return $scorm->id;
96 }
98 /**
99 * Given an object containing all the necessary data,
100 * (defined by the form in mod_form.php) this function
101 * will update an existing instance with new data.
103 * @param object $scorm Form data
104 * @param object $mform
105 * @return bool success
106 */
107 function scorm_update_instance($scorm, $mform=null) {
108     global $CFG, $DB;
110     require_once('locallib.php');
112     if (empty($scorm->timerestrict)) {
113         $scorm->timeopen = 0;
114         $scorm->timeclose = 0;
115     }
117     $cmid       = $scorm->coursemodule;
118     $cmidnumber = $scorm->cmidnumber;
119     $courseid   = $scorm->course;
121     $scorm->id = $scorm->instance;
123     $context = get_context_instance(CONTEXT_MODULE, $cmid);
125     if ($scorm->scormtype === SCORM_TYPE_LOCAL) {
126         if ($mform) {
127             $filename = $mform->get_new_filename('packagefile');
128             if ($filename !== false) {
129                 $scorm->reference = $filename;
130                 $fs = get_file_storage();
131                 $fs->delete_area_files($context->id, 'scorm_package');
132                 $mform->save_stored_file('packagefile', $context->id, 'scorm_package', 0, '/', $filename);
133             }
134         }
136     } else if ($scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
137         $scorm->reference = $scorm->packageurl;
139     } else if ($scorm->scormtype === SCORM_TYPE_EXTERNAL) {
140         $scorm->reference = $scorm->packageurl;
142     } else if ($scorm->scormtype === SCORM_TYPE_IMSREPOSITORY) {
143         $scorm->reference = $scorm->packageurl;
145     } else {
146         return false;
147     }
149     $scorm = scorm_option2text($scorm);
150     $scorm->width        = (int)str_replace('%','',$scorm->width);
151     $scorm->height       = (int)str_replace('%','',$scorm->height);
152     $scorm->timemodified = time();
154     if (!isset($scorm->whatgrade)) {
155         $scorm->whatgrade = 0;
156     }
157     $scorm->grademethod  = ($scorm->whatgrade * 10) + $scorm->grademethod;
159     if (!$DB->update_record('scorm', $scorm)) {
160         return false;
161     }
163     $scorm = $DB->get_record('scorm', array('id'=>$scorm->id));
165 /// extra fields required in grade related functions
166     $scorm->course   = $courseid;
167     $scorm->idnumber = $cmidnumber;
168     $scorm->cmid     = $cmid;
170     scorm_parse($scorm, (bool)$scorm->updatefreq);
172     scorm_grade_item_update($scorm);
174     return true;
177 /**
178 * Given an ID of an instance of this module,
179 * this function will permanently delete the instance
180 * and any data that depends on it.
182 * @param int $id Scorm instance id
183 * @return boolean
184 */
185 function scorm_delete_instance($id) {
186     global $CFG, $DB;
188     if (! $scorm = $DB->get_record('scorm', array('id'=>$id))) {
189         return false;
190     }
192     $result = true;
194     // Delete any dependent records
195     if (! $DB->delete_records('scorm_scoes_track', array('scormid'=>$scorm->id))) {
196         $result = false;
197     }
198     if ($scoes = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id))) {
199         foreach ($scoes as $sco) {
200             if (! $DB->delete_records('scorm_scoes_data', array('scoid'=>$sco->id))) {
201                 $result = false;
202             }
203         }
204         $DB->delete_records('scorm_scoes', array('scorm'=>$scorm->id));
205     } else {
206         $result = false;
207     }
208     if (! $DB->delete_records('scorm', array('id'=>$scorm->id))) {
209         $result = false;
210     }
212     /*if (! $DB->delete_records('scorm_sequencing_controlmode', array('scormid'=>$scorm->id))) {
213         $result = false;
214     }
215     if (! $DB->delete_records('scorm_sequencing_rolluprules', array('scormid'=>$scorm->id))) {
216         $result = false;
217     }
218     if (! $DB->delete_records('scorm_sequencing_rolluprule', array('scormid'=>$scorm->id))) {
219         $result = false;
220     }
221     if (! $DB->delete_records('scorm_sequencing_rollupruleconditions', array('scormid'=>$scorm->id))) {
222         $result = false;
223     }
224     if (! $DB->delete_records('scorm_sequencing_rolluprulecondition', array('scormid'=>$scorm->id))) {
225         $result = false;
226     }
227     if (! $DB->delete_records('scorm_sequencing_rulecondition', array('scormid'=>$scorm->id))) {
228         $result = false;
229     }
230     if (! $DB->delete_records('scorm_sequencing_ruleconditions', array('scormid'=>$scorm->id))) {
231         $result = false;
232     }*/
234     scorm_grade_item_delete($scorm);
236     return $result;
239 /**
240 * Return a small object with summary information about what a
241 * user has done with a given particular instance of this module
242 * Used for user activity reports.
244 * @param int $course Course id
245 * @param int $user User id
246 * @param int $mod
247 * @param int $scorm The scorm id
248 * @return mixed
249 */
250 function scorm_user_outline($course, $user, $mod, $scorm) {
251     global $CFG;
252     require_once('locallib.php');
254     $return = scorm_grade_user($scorm, $user->id, true);
256     return $return;
259 /**
260 * Print a detailed representation of what a user has done with
261 * a given particular instance of this module, for user activity reports.
263 * @param int $course Course id
264 * @param int $user User id
265 * @param int $mod
266 * @param int $scorm The scorm id
267 * @return boolean
268 */
269 function scorm_user_complete($course, $user, $mod, $scorm) {
270     global $CFG, $DB;
272     $liststyle = 'structlist';
273     $scormpixdir = $CFG->modpixpath.'/scorm/pix';
274     $now = time();
275     $firstmodify = $now;
276     $lastmodify = 0;
277     $sometoreport = false;
278     $report = '';
280     if ($orgs = $DB->get_records('scorm_scoes', array('scorm'=>$scorm->id, 'organization'=>'', 'launch'=>''),'id','id,identifier,title')) {
281         if (count($orgs) <= 1) {
282             unset($orgs);
283             $orgs[]->identifier = '';
284         }
285         $report .= '<div class="mod-scorm">'."\n";
286         foreach ($orgs as $org) {
287             $conditions = array();
288             $currentorg = '';
289             if (!empty($org->identifier)) {
290                 $report .= '<div class="orgtitle">'.$org->title.'</div>';
291                 $currentorg = $org->identifier;
292                 $conditions['organization'] = $currentorg;
293             }
294             $report .= "<ul id='0' class='$liststyle'>";
295                 $conditions['scorm'] = $scorm->id;
296             if ($scoes = $DB->get_records('scorm_scoes', $conditions, "id ASC")){
297                 // drop keys so that we can access array sequentially
298                 $scoes = array_values($scoes);
299                 $level=0;
300                 $sublist=1;
301                 $parents[$level]='/';
302                 foreach ($scoes as $pos=>$sco) {
303                     if ($parents[$level]!=$sco->parent) {
304                         if ($level>0 && $parents[$level-1]==$sco->parent) {
305                             $report .= "\t\t</ul></li>\n";
306                             $level--;
307                         } else {
308                             $i = $level;
309                             $closelist = '';
310                             while (($i > 0) && ($parents[$level] != $sco->parent)) {
311                                 $closelist .= "\t\t</ul></li>\n";
312                                 $i--;
313                             }
314                             if (($i == 0) && ($sco->parent != $currentorg)) {
315                                 $report .= "\t\t<li><ul id='$sublist' class='$liststyle'>\n";
316                                 $level++;
317                             } else {
318                                 $report .= $closelist;
319                                 $level = $i;
320                             }
321                             $parents[$level]=$sco->parent;
322                         }
323                     }
324                     $report .= "\t\t<li>";
325                     if (isset($scoes[$pos+1])) {
326                         $nextsco = $scoes[$pos+1];
327                     } else {
328                         $nextsco = false;
329                     }
330                     if (($nextsco !== false) && ($sco->parent != $nextsco->parent) && (($level==0) || (($level>0) && ($nextsco->parent == $sco->identifier)))) {
331                         $sublist++;
332                     } else {
333                         $report .= '<img src="'.$scormpixdir.'/spacer.gif" alt="" />';
334                     }
336                     if ($sco->launch) {
337                         require_once('locallib.php');
338                         $score = '';
339                         $totaltime = '';
340                         if ($usertrack=scorm_get_tracks($sco->id,$user->id)) {
341                             if ($usertrack->status == '') {
342                                 $usertrack->status = 'notattempted';
343                             }
344                             $strstatus = get_string($usertrack->status,'scorm');
345                             $report .= "<img src='".$scormpixdir.'/'.$usertrack->status.".gif' alt='$strstatus' title='$strstatus' />";
346                             if ($usertrack->timemodified != 0) {
347                                 if ($usertrack->timemodified > $lastmodify) {
348                                     $lastmodify = $usertrack->timemodified;
349                                 }
350                                 if ($usertrack->timemodified < $firstmodify) {
351                                     $firstmodify = $usertrack->timemodified;
352                                 }
353                             }
354                         } else {
355                             if ($sco->scormtype == 'sco') {
356                                 $report .= '<img src="'.$scormpixdir.'/'.'notattempted.gif" alt="'.get_string('notattempted','scorm').'" title="'.get_string('notattempted','scorm').'" />';
357                             } else {
358                                 $report .= '<img src="'.$scormpixdir.'/'.'asset.gif" alt="'.get_string('asset','scorm').'" title="'.get_string('asset','scorm').'" />';
359                             }
360                         }
361                         $report .= "&nbsp;$sco->title $score$totaltime</li>\n";
362                         if ($usertrack !== false) {
363                             $sometoreport = true;
364                             $report .= "\t\t\t<li><ul class='$liststyle'>\n";
365                             foreach($usertrack as $element => $value) {
366                                 if (substr($element,0,3) == 'cmi') {
367                                     $report .= '<li>'.$element.' => '.$value.'</li>';
368                                 }
369                             }
370                             $report .= "\t\t\t</ul></li>\n";
371                         }
372                     } else {
373                         $report .= "&nbsp;$sco->title</li>\n";
374                     }
375                 }
376                 for ($i=0;$i<$level;$i++) {
377                     $report .= "\t\t</ul></li>\n";
378                 }
379             }
380             $report .= "\t</ul><br />\n";
381         }
382         $report .= "</div>\n";
383     }
384     if ($sometoreport) {
385         if ($firstmodify < $now) {
386             $timeago = format_time($now - $firstmodify);
387             echo get_string('firstaccess','scorm').': '.userdate($firstmodify).' ('.$timeago.")<br />\n";
388         }
389         if ($lastmodify > 0) {
390             $timeago = format_time($now - $lastmodify);
391             echo get_string('lastaccess','scorm').': '.userdate($lastmodify).' ('.$timeago.")<br />\n";
392         }
393         echo get_string('report','scorm').":<br />\n";
394         echo $report;
395     } else {
396         print_string('noactivity','scorm');
397     }
399     return true;
402 /**
403 * Function to be run periodically according to the moodle cron
404 * This function searches for things that need to be done, such
405 * as sending out mail, toggling flags etc ...
407 * @return boolean
408 */
409 function scorm_cron () {
410     global $CFG, $DB;
412     require_once('locallib.php');
414     $sitetimezone = $CFG->timezone;
415     /// Now see if there are any scorm updates to be done
417     if (!isset($CFG->scorm_updatetimelast)) {    // To catch the first time
418         set_config('scorm_updatetimelast', 0);
419     }
421     $timenow = time();
422     $updatetime = usergetmidnight($timenow, $sitetimezone) + ($CFG->scorm_updatetimelast * 3600);
424     if ($CFG->scorm_updatetimelast < $updatetime and $timenow > $updatetime) {
426         set_config('scorm_updatetimelast', $timenow);
428         mtrace('Updating scorm packages which require daily update');//We are updating
430         $scormsupdate = $DB->get_records('scorm', array('updatefreq'=>UPDATE_EVERYDAY));
431         foreach($scormsupdate as $scormupdate) {
432             scorm_parse($scormupdate, true);
433         }
434     }
436     return true;
439 /**
440  * Return grade for given user or all users.
441  *
442  * @param int $scormid id of scorm
443  * @param int $userid optional user id, 0 means all users
444  * @return array array of grades, false if none
445  */
446 function scorm_get_user_grades($scorm, $userid=0) {
447     global $CFG, $DB;
448     require_once('locallib.php');
450     $grades = array();
451     if (empty($userid)) {
452         if ($scousers = $DB->get_records_select('scorm_scoes_track', "scormid=? GROUP BY userid", array($scorm->id), "", "userid,null")) {
453             foreach ($scousers as $scouser) {
454                 $grades[$scouser->userid] = new object();
455                 $grades[$scouser->userid]->id         = $scouser->userid;
456                 $grades[$scouser->userid]->userid     = $scouser->userid;
457                 $grades[$scouser->userid]->rawgrade = scorm_grade_user($scorm, $scouser->userid);
458             }
459         } else {
460             return false;
461         }
463     } else {
464         if (!$DB->get_records_select('scorm_scoes_track', "scormid=? AND userid=? GROUP BY userid", array($scorm->id, $userid), "", "userid,null")) {
465             return false; //no attempt yet
466         }
467         $grades[$userid] = new object();
468         $grades[$userid]->id         = $userid;
469         $grades[$userid]->userid     = $userid;
470         $grades[$userid]->rawgrade = scorm_grade_user($scorm, $userid);
471     }
473     return $grades;
476 /**
477  * Update grades in central gradebook
478  *
479  * @param object $scorm
480  * @param int $userid specific user only, 0 mean all
481  */
482 function scorm_update_grades($scorm, $userid=0, $nullifnone=true) {
483     global $CFG, $DB;
484     require_once($CFG->libdir.'/gradelib.php');
486     if ($grades = scorm_get_user_grades($scorm, $userid)) {
487         scorm_grade_item_update($scorm, $grades);
489     } else if ($userid and $nullifnone) {
490         $grade = new object();
491         $grade->userid   = $userid;
492         $grade->rawgrade = NULL;
493         scorm_grade_item_update($scorm, $grade);
495     } else {
496         scorm_grade_item_update($scorm);
497     }
500 /**
501  * Update all grades in gradebook.
502  */
503 function scorm_upgrade_grades() {
504     global $DB;
506     $sql = "SELECT COUNT('x')
507               FROM {scorm} s, {course_modules} cm, {modules} m
508              WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
509     $count = $DB->count_records_sql($sql);
511     $sql = "SELECT s.*, cm.idnumber AS cmidnumber, s.course AS courseid
512               FROM {scorm} s, {course_modules} cm, {modules} m
513              WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id";
514     if ($rs = $DB->get_recordset_sql($sql)) {
515         $pbar = new progress_bar('scormupgradegrades', 500, true);
516         $i=0;
517         foreach ($rs as $scorm) {
518             $i++;
519             upgrade_set_timeout(60*5); // set up timeout, may also abort execution
520             scorm_update_grades($scorm, 0, false);
521             $pbar->update($i, $count, "Updating Scorm grades ($i/$count).");
522         }
523         $rs->close();
524     }
527 /**
528  * Update/create grade item for given scorm
529  *
530  * @param object $scorm object with extra cmidnumber
531  * @param mixed optional array/object of grade(s); 'reset' means reset grades in gradebook
532  * @return object grade_item
533  */
534 function scorm_grade_item_update($scorm, $grades=NULL) {
535     global $CFG, $DB;
536     if (!function_exists('grade_update')) { //workaround for buggy PHP versions
537         require_once($CFG->libdir.'/gradelib.php');
538     }
540     $params = array('itemname'=>$scorm->name);
541     if (isset($scorm->cmidnumber)) {
542         $params['idnumber'] = $scorm->cmidnumber;
543     }
545     if (($scorm->grademethod % 10) == 0) { // GRADESCOES
546         if ($maxgrade = $DB->count_records_select('scorm_scoes', 'scorm = ? AND launch <> ?', array($scorm->id, $DB->sql_empty()))) {
547             $params['gradetype'] = GRADE_TYPE_VALUE;
548             $params['grademax']  = $maxgrade;
549             $params['grademin']  = 0;
550         } else {
551             $params['gradetype'] = GRADE_TYPE_NONE;
552         }
553     } else {
554         $params['gradetype'] = GRADE_TYPE_VALUE;
555         $params['grademax']  = $scorm->maxgrade;
556         $params['grademin']  = 0;
557     }
559     if ($grades  === 'reset') {
560         $params['reset'] = true;
561         $grades = NULL;
562     }
564     return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, $grades, $params);
567 /**
568  * Delete grade item for given scorm
569  *
570  * @param object $scorm object
571  * @return object grade_item
572  */
573 function scorm_grade_item_delete($scorm) {
574     global $CFG;
575     require_once($CFG->libdir.'/gradelib.php');
577     return grade_update('mod/scorm', $scorm->course, 'mod', 'scorm', $scorm->id, 0, NULL, array('deleted'=>1));
580 function scorm_get_view_actions() {
581     return array('pre-view','view','view all','report');
584 function scorm_get_post_actions() {
585     return array();
588 function scorm_option2text($scorm) {
589     $scorm_popoup_options = scorm_get_popup_options_array();
590     
591     if (isset($scorm->popup)) {
592         if ($scorm->popup == 1) {
593             $optionlist = array();
594             foreach ($scorm_popoup_options as $name => $option) {
595                 if (isset($scorm->$name)) {
596                     $optionlist[] = $name.'='.$scorm->$name;
597                 } else {
598                     $optionlist[] = $name.'=0';
599                 }
600             }
601             $scorm->options = implode(',', $optionlist);
602         } else {
603             $scorm->options = '';
604         }
605     } else {
606         $scorm->popup = 0;
607         $scorm->options = '';
608     }
609     return $scorm;
612 /**
613  * Implementation of the function for printing the form elements that control
614  * whether the course reset functionality affects the scorm.
615  * @param $mform form passed by reference
616  */
617 function scorm_reset_course_form_definition(&$mform) {
618     $mform->addElement('header', 'scormheader', get_string('modulenameplural', 'scorm'));
619     $mform->addElement('advcheckbox', 'reset_scorm', get_string('deleteallattempts','scorm'));
622 /**
623  * Course reset form defaults.
624  */
625 function scorm_reset_course_form_defaults($course) {
626     return array('reset_scorm'=>1);
629 /**
630  * Removes all grades from gradebook
631  * @param int $courseid
632  * @param string optional type
633  */
634 function scorm_reset_gradebook($courseid, $type='') {
635     global $CFG, $DB;
637     $sql = "SELECT s.*, cm.idnumber as cmidnumber, s.course as courseid
638               FROM {scorm} s, {course_modules} cm, {modules} m
639              WHERE m.name='scorm' AND m.id=cm.module AND cm.instance=s.id AND s.course=?";
641     if ($scorms = $DB->get_records_sql($sql, array($courseid))) {
642         foreach ($scorms as $scorm) {
643             scorm_grade_item_update($scorm, 'reset');
644         }
645     }
648 /**
649  * Actual implementation of the rest coures functionality, delete all the
650  * scorm attempts for course $data->courseid.
651  * @param $data the data submitted from the reset course.
652  * @return array status array
653  */
654 function scorm_reset_userdata($data) {
655     global $CFG, $DB;
657     $componentstr = get_string('modulenameplural', 'scorm');
658     $status = array();
660     if (!empty($data->reset_scorm)) {
661         $scormssql = "SELECT s.id
662                          FROM {scorm} s
663                         WHERE s.course=?";
665         $DB->delete_records_select('scorm_scoes_track', "scormid IN ($scormssql)", array($data->courseid));
667         // remove all grades from gradebook
668         if (empty($data->reset_gradebook_grades)) {
669             scorm_reset_gradebook($data->courseid);
670         }
672         $status[] = array('component'=>$componentstr, 'item'=>get_string('deleteallattempts', 'scorm'), 'error'=>false);
673     }
675     // no dates to shift here
677     return $status;
680 /**
681  * Returns all other caps used in module
682  */
683 function scorm_get_extra_capabilities() {
684     return array('moodle/site:accessallgroups');
687 /**
688  * Lists all file areas current user may browse
689  */
690 function scorm_get_file_areas($course, $cm, $context) {
691     $areas = array();
692     if (has_capability('moodle/course:managefiles', $context)) {
693         $areas['scorm_intro']   = get_string('areaintro', 'scorm');
694         $areas['scorm_content'] = get_string('areacontent', 'scorm');
695         $areas['scorm_package'] = get_string('areapackage', 'scorm');
696     }
697     return $areas;
700 /**
701  * File browsing support
702  */
703 function scorm_get_file_info($browser, $areas, $course, $cm, $context, $filearea, $itemid, $filepath, $filename) {
704     global $CFG;
706     if (!has_capability('moodle/course:managefiles', $context)) {
707         return null;
708     }
710     // no writing for now!
712     $fs = get_file_storage();
714     if ($filearea === 'scorm_content') {
716         $filepath = is_null($filepath) ? '/' : $filepath;
717         $filename = is_null($filename) ? '.' : $filename;
718     
719         $urlbase = $CFG->wwwroot.'/pluginfile.php';
720         if (!$storedfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) {
721             if ($filepath === '/' and $filename === '.') {
722                 $storedfile = new virtual_root_file($context->id, $filearea, 0);
723             } else {
724                 // not found
725                 return null;
726             }
727         }
728         class scorm_package_file_info extends file_info_stored {
729             public function get_parent() {
730                 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
731                     return $this->browser->get_file_info($this->context);
732                 }
733                 return parent::get_parent();
734             }
735             public function get_visible_name() {
736                 if ($this->lf->get_filepath() === '/' and $this->lf->get_filename() === '.') {
737                     return $this->topvisiblename;
738                 }
739                 return parent::get_visible_name();
740             }
741         }
742         return new scorm_package_file_info($browser, $context, $storedfile, $urlbase, $areas[$filearea], true, true, false, false);
744     } else if ($filearea === 'scorm_package') {
745         $filepath = is_null($filepath) ? '/' : $filepath;
746         $filename = is_null($filename) ? '.' : $filename;
747     
748         $urlbase = $CFG->wwwroot.'/pluginfile.php';
749         if (!$storedfile = $fs->get_file($context->id, $filearea, 0, $filepath, $filename)) {
750             if ($filepath === '/' and $filename === '.') {
751                 $storedfile = new virtual_root_file($context->id, $filearea, 0);
752             } else {
753                 // not found
754                 return null;
755             }
756         }
757         return new file_info_stored($browser, $context, $storedfile, $urlbase, $areas[$filearea], false, true, false);
758     }
760     // scorm_intro handled in file_browser
762     return false;
765 /**
766  * Serves scorm content, introduction images and packages. Implements needed access control ;-)
767  */
768 function scorm_pluginfile($course, $cminfo, $context, $filearea, $args) {
769     global $CFG;
771     if (!$cminfo->uservisible) {
772         return false; // probably hidden
773     }
775     $lifetime = isset($CFG->filelifetime) ? $CFG->filelifetime : 86400;
777     if ($filearea === 'scorm_intro') {
778         // all users may access it
779         $relativepath = '/'.implode('/', $args);
780         $fullpath = $context->id.'scorm_intro0'.$relativepath;
782         $fs = get_file_storage();
783         if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
784             return false;
785         }
787     } else if ($filearea === 'scorm_content') {
788         $revision = (int)array_shift($args); // prevents caching problems - ignored here
789         $relativepath = '/'.implode('/', $args);
790         $fullpath = $context->id.'scorm_content0'.$relativepath;
791         // TODO: add any other access restrictions here if needed!
793     } else if ($filearea === 'scorm_package') {
794         if (!has_capability('moodle/course:manageactivities', $context)) {
795             return false;
796         }
797         $relativepath = '/'.implode('/', $args);
798         $fullpath = $context->id.'scorm_package0'.$relativepath;
799         $lifetime = 0; // no caching here
801     } else {
802         return false;
803     }
805     $fs = get_file_storage();
806     if (!$file = $fs->get_file_by_hash(sha1($fullpath)) or $file->is_directory()) {
807         return false;
808     }
810     // finally send the file
811     send_stored_file($file, $lifetime, 0, false);
814 /**
815  * @param string $feature FEATURE_xx constant for requested feature
816  * @return mixed True if module supports feature, null if doesn't know
817  */
818 function scorm_supports($feature) {
819     switch($feature) {
820         case FEATURE_GROUPS:                  return false;
821         case FEATURE_GROUPINGS:               return false;
822         case FEATURE_GROUPMEMBERSONLY:        return true;
823         case FEATURE_MODEDIT_INTRO_EDITOR:    return true;
824         case FEATURE_COMPLETION_TRACKS_VIEWS: return true;
825         case FEATURE_GRADE_HAS_GRADE:         return true;
826         case FEATURE_GRADE_OUTCOMES:          return true;
828         default: return null;
829     }