2 // This file is part of Moodle - http://moodle.org/
4 // Moodle is free software: you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation, either version 3 of the License, or
7 // (at your option) any later version.
9 // Moodle is distributed in the hope that it will be useful,
10 // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 // GNU General Public License for more details.
14 // You should have received a copy of the GNU General Public License
15 // along with Moodle. If not, see <http://www.gnu.org/licenses/>.
18 * SCORM module external API
22 * @copyright 2015 Juan Leyva <juan@moodle.com>
23 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
27 defined('MOODLE_INTERNAL') || die;
29 require_once($CFG->libdir . '/externallib.php');
30 require_once($CFG->dirroot . '/mod/scorm/lib.php');
31 require_once($CFG->dirroot . '/mod/scorm/locallib.php');
34 * SCORM module external functions
38 * @copyright 2015 Juan Leyva <juan@moodle.com>
39 * @license http://www.gnu.org/copyleft/gpl.html GNU GPL v3 or later
42 class mod_scorm_external extends external_api {
45 * Returns description of method parameters
47 * @return external_function_parameters
50 public static function view_scorm_parameters() {
51 return new external_function_parameters(
53 'scormid' => new external_value(PARAM_INT, 'scorm instance id')
59 * Trigger the course module viewed event.
61 * @param int $scormid the scorm instance id
62 * @return array of warnings and status result
64 * @throws moodle_exception
66 public static function view_scorm($scormid) {
68 require_once($CFG->dirroot . '/mod/scorm/lib.php');
70 $params = self::validate_parameters(self::view_scorm_parameters(),
76 // Request and permission validation.
77 $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
78 list($course, $cm) = get_course_and_cm_from_instance($scorm, 'scorm');
80 $context = context_module::instance($cm->id);
81 self::validate_context($context);
83 // Call the scorm/lib API.
84 scorm_view($scorm, $course, $cm, $context);
87 $result['status'] = true;
88 $result['warnings'] = $warnings;
93 * Returns description of method result value
95 * @return external_description
98 public static function view_scorm_returns() {
99 return new external_single_structure(
101 'status' => new external_value(PARAM_BOOL, 'status: true if success'),
102 'warnings' => new external_warnings()
108 * Describes the parameters for get_scorm_attempt_count.
110 * @return external_function_parameters
113 public static function get_scorm_attempt_count_parameters() {
114 return new external_function_parameters(
116 'scormid' => new external_value(PARAM_INT, 'SCORM instance id'),
117 'userid' => new external_value(PARAM_INT, 'User id'),
118 'ignoremissingcompletion' => new external_value(PARAM_BOOL,
119 'Ignores attempts that haven\'t reported a grade/completion',
120 VALUE_DEFAULT, false),
126 * Return the number of attempts done by a user in the given SCORM.
128 * @param int $scormid the scorm id
129 * @param int $userid the user id
130 * @param bool $ignoremissingcompletion ignores attempts that haven't reported a grade/completion
131 * @return array of warnings and the attempts count
134 public static function get_scorm_attempt_count($scormid, $userid, $ignoremissingcompletion = false) {
137 $params = self::validate_parameters(self::get_scorm_attempt_count_parameters(),
138 array('scormid' => $scormid, 'userid' => $userid,
139 'ignoremissingcompletion' => $ignoremissingcompletion));
144 $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
145 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
147 $context = context_module::instance($cm->id);
148 self::validate_context($context);
150 // Validate the user obtaining the context, it will fail if the user doesn't exists or have been deleted.
151 context_user::instance($params['userid']);
153 // Extra checks so only users with permissions can view other users attempts.
154 if ($USER->id != $params['userid']) {
155 require_capability('mod/scorm:viewreport', $context);
158 // If the SCORM is not open this function will throw exceptions.
159 scorm_require_available($scorm);
161 $attemptscount = scorm_get_attempt_count($params['userid'], $scorm, false, $params['ignoremissingcompletion']);
164 $result['attemptscount'] = $attemptscount;
165 $result['warnings'] = $warnings;
170 * Describes the get_scorm_attempt_count return value.
172 * @return external_single_structure
175 public static function get_scorm_attempt_count_returns() {
177 return new external_single_structure(
179 'attemptscount' => new external_value(PARAM_INT, 'Attempts count'),
180 'warnings' => new external_warnings(),
186 * Describes the parameters for get_scorms_by_courses.
188 * @return external_function_parameters
191 public static function get_scorm_scoes_parameters() {
192 return new external_function_parameters(
194 'scormid' => new external_value(PARAM_INT, 'scorm instance id'),
195 'organization' => new external_value(PARAM_RAW, 'organization id', VALUE_DEFAULT, '')
201 * Returns a list containing all the scoes data related to the given scorm id
203 * @param int $scormid the scorm id
204 * @param string $organization the organization id
205 * @return array warnings and the scoes data
208 public static function get_scorm_scoes($scormid, $organization = '') {
211 $params = self::validate_parameters(self::get_scorm_scoes_parameters(),
212 array('scormid' => $scormid, 'organization' => $organization));
217 $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
218 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
220 $context = context_module::instance($cm->id);
221 self::validate_context($context);
223 // Check settings / permissions to view the SCORM.
224 scorm_require_available($scorm, true, $context);
226 if (!$scoes = scorm_get_scoes($scorm->id, $params['organization'])) {
227 // Function scorm_get_scoes return false, not an empty array.
232 $result['scoes'] = $scoes;
233 $result['warnings'] = $warnings;
238 * Describes the get_scorm_scoes return value.
240 * @return external_single_structure
243 public static function get_scorm_scoes_returns() {
245 return new external_single_structure(
247 'scoes' => new external_multiple_structure(
248 new external_single_structure(
250 'id' => new external_value(PARAM_INT, 'sco id'),
251 'scorm' => new external_value(PARAM_INT, 'scorm id'),
252 'manifest' => new external_value(PARAM_NOTAGS, 'manifest id'),
253 'organization' => new external_value(PARAM_NOTAGS, 'organization id'),
254 'parent' => new external_value(PARAM_NOTAGS, 'parent'),
255 'identifier' => new external_value(PARAM_NOTAGS, 'identifier'),
256 'launch' => new external_value(PARAM_NOTAGS, 'launch file'),
257 'scormtype' => new external_value(PARAM_ALPHA, 'scorm type (asset, sco)'),
258 'title' => new external_value(PARAM_NOTAGS, 'sco title'),
259 'sortorder' => new external_value(PARAM_INT, 'sort order'),
263 'warnings' => new external_warnings(),
269 * Describes the parameters for get_scorm_user_data.
271 * @return external_function_parameters
274 public static function get_scorm_user_data_parameters() {
275 return new external_function_parameters(
277 'scormid' => new external_value(PARAM_INT, 'scorm instance id'),
278 'attempt' => new external_value(PARAM_INT, 'attempt number')
284 * Retrieves user tracking and SCO data and default SCORM values
286 * @param int $scormid the scorm id
287 * @param int $attempt the attempt number
288 * @return array warnings and the scoes data
289 * @throws moodle_exception
292 public static function get_scorm_user_data($scormid, $attempt) {
295 $params = self::validate_parameters(self::get_scorm_user_data_parameters(),
296 array('scormid' => $scormid, 'attempt' => $attempt));
301 $scorm = $DB->get_record('scorm', array('id' => $params['scormid']), '*', MUST_EXIST);
302 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
304 $context = context_module::instance($cm->id);
305 self::validate_context($context);
307 scorm_require_available($scorm, true, $context);
309 $scorm->version = strtolower(clean_param($scorm->version, PARAM_SAFEDIR));
310 if (!file_exists($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php')) {
311 $scorm->version = 'scorm_12';
313 require_once($CFG->dirroot.'/mod/scorm/datamodels/'.$scorm->version.'lib.php');
315 if ($scoes = scorm_get_scoes($scorm->id)) {
316 $def = new stdClass();
317 $user = new stdClass();
319 foreach ($scoes as $sco) {
320 $def->{$sco->id} = new stdClass();
321 $user->{$sco->id} = new stdClass();
322 // We force mode normal, this can be override by the client at any time.
323 $def->{$sco->id} = get_scorm_default($user->{$sco->id}, $scorm, $sco->id, $params['attempt'], 'normal');
326 $defaultdata = array();
328 foreach ((array) $user->{$sco->id} as $key => $val) {
334 foreach ($def->{$sco->id} as $key => $val) {
335 $defaultdata[] = array(
343 'userdata' => $userdata,
344 'defaultdata' => $defaultdata,
350 $result['data'] = $data;
351 $result['warnings'] = $warnings;
356 * Describes the get_scorm_user_data return value.
358 * @return external_single_structure
361 public static function get_scorm_user_data_returns() {
363 return new external_single_structure(
365 'data' => new external_multiple_structure(
366 new external_single_structure(
368 'scoid' => new external_value(PARAM_INT, 'sco id'),
369 'userdata' => new external_multiple_structure(
370 new external_single_structure(
372 'element' => new external_value(PARAM_RAW, 'element name'),
373 'value' => new external_value(PARAM_RAW, 'element value')
377 'defaultdata' => new external_multiple_structure(
378 new external_single_structure(
380 'element' => new external_value(PARAM_RAW, 'element name'),
381 'value' => new external_value(PARAM_RAW, 'element value')
388 'warnings' => new external_warnings(),
394 * Describes the parameters for insert_scorm_tracks.
396 * @return external_function_parameters
399 public static function insert_scorm_tracks_parameters() {
400 return new external_function_parameters(
402 'scoid' => new external_value(PARAM_INT, 'SCO id'),
403 'attempt' => new external_value(PARAM_INT, 'attempt number'),
404 'tracks' => new external_multiple_structure(
405 new external_single_structure(
407 'element' => new external_value(PARAM_RAW, 'element name'),
408 'value' => new external_value(PARAM_RAW, 'element value')
417 * Saves a SCORM tracking record.
418 * It will overwrite any existing tracking data for this attempt.
419 * Validation should be performed before running the function to ensure the user will not lose any existing attempt data.
421 * @param int $scoid the SCO id
422 * @param string $attempt the attempt number
423 * @param array $tracks the track records to be stored
424 * @return array warnings and the scoes data
425 * @throws moodle_exception
428 public static function insert_scorm_tracks($scoid, $attempt, $tracks) {
431 $params = self::validate_parameters(self::insert_scorm_tracks_parameters(),
432 array('scoid' => $scoid, 'attempt' => $attempt, 'tracks' => $tracks));
437 $sco = scorm_get_sco($params['scoid'], SCO_ONLY);
439 throw new moodle_exception('cannotfindsco', 'scorm');
442 $scorm = $DB->get_record('scorm', array('id' => $sco->scorm), '*', MUST_EXIST);
443 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
445 $context = context_module::instance($cm->id);
446 self::validate_context($context);
448 // Check settings / permissions to view the SCORM.
449 require_capability('mod/scorm:savetrack', $context);
451 // Check settings / permissions to view the SCORM.
452 scorm_require_available($scorm);
454 foreach ($params['tracks'] as $track) {
455 $element = $track['element'];
456 $value = $track['value'];
457 $trackid = scorm_insert_track($USER->id, $scorm->id, $sco->id, $params['attempt'], $element, $value,
458 $scorm->forcecompleted);
461 $trackids[] = $trackid;
465 'itemid' => $scorm->id,
467 'message' => 'Element: ' . $element . ' was not saved'
473 $result['trackids'] = $trackids;
474 $result['warnings'] = $warnings;
479 * Describes the insert_scorm_tracks return value.
481 * @return external_single_structure
484 public static function insert_scorm_tracks_returns() {
486 return new external_single_structure(
488 'trackids' => new external_multiple_structure(new external_value(PARAM_INT, 'track id')),
489 'warnings' => new external_warnings(),
495 * Describes the parameters for get_scorms_by_courses.
497 * @return external_function_parameters
500 public static function get_scorm_sco_tracks_parameters() {
501 return new external_function_parameters(
503 'scoid' => new external_value(PARAM_INT, 'sco id'),
504 'userid' => new external_value(PARAM_INT, 'user id'),
505 'attempt' => new external_value(PARAM_INT, 'attempt number (0 for last attempt)', VALUE_DEFAULT, 0)
511 * Retrieves SCO tracking data for the given user id and attempt number
513 * @param int $scoid the sco id
514 * @param int $userid the user id
515 * @param int $attempt the attempt number
516 * @return array warnings and the scoes data
519 public static function get_scorm_sco_tracks($scoid, $userid, $attempt = 0) {
522 $params = self::validate_parameters(self::get_scorm_sco_tracks_parameters(),
523 array('scoid' => $scoid, 'userid' => $userid, 'attempt' => $attempt));
528 $sco = scorm_get_sco($params['scoid'], SCO_ONLY);
530 throw new moodle_exception('cannotfindsco', 'scorm');
533 $scorm = $DB->get_record('scorm', array('id' => $sco->scorm), '*', MUST_EXIST);
534 $cm = get_coursemodule_from_instance('scorm', $scorm->id);
536 $context = context_module::instance($cm->id);
537 self::validate_context($context);
539 // Validate the user obtaining the context, it will fail if the user doesn't exists or have been deleted.
540 context_user::instance($params['userid']);
542 // Extra checks so only users with permissions can view other users attempts.
543 if ($USER->id != $params['userid']) {
544 require_capability('mod/scorm:viewreport', $context);
547 scorm_require_available($scorm, true, $context);
549 if (empty($params['attempt'])) {
550 $params['attempt'] = scorm_get_last_attempt($scorm->id, $params['userid']);
553 if ($scormtracks = scorm_get_tracks($sco->id, $params['userid'], $params['attempt'])) {
554 foreach ($scormtracks as $element => $value) {
556 'element' => $element,
563 $result['data']['attempt'] = $params['attempt'];
564 $result['data']['tracks'] = $tracks;
565 $result['warnings'] = $warnings;
570 * Describes the get_scorm_sco_tracks return value.
572 * @return external_single_structure
575 public static function get_scorm_sco_tracks_returns() {
577 return new external_single_structure(
579 'data' => new external_single_structure(
581 'attempt' => new external_value(PARAM_INT, 'Attempt number'),
582 'tracks' => new external_multiple_structure(
583 new external_single_structure(
585 'element' => new external_value(PARAM_RAW, 'Element name'),
586 'value' => new external_value(PARAM_RAW, 'Element value')
592 'warnings' => new external_warnings(),
598 * Describes the parameters for get_scorms_by_courses.
600 * @return external_function_parameters
603 public static function get_scorms_by_courses_parameters() {
604 return new external_function_parameters (
606 'courseids' => new external_multiple_structure(
607 new external_value(PARAM_INT, 'course id'), 'Array of course ids', VALUE_DEFAULT, array()
614 * Returns a list of scorms in a provided list of courses,
615 * if no list is provided all scorms that the user can view will be returned.
617 * @param array $courseids the course ids
618 * @return array the scorm details
621 public static function get_scorms_by_courses($courseids = array()) {
624 $returnedscorms = array();
627 $params = self::validate_parameters(self::get_scorms_by_courses_parameters(), array('courseids' => $courseids));
629 if (empty($params['courseids'])) {
630 $params['courseids'] = array_keys(enrol_get_my_courses());
633 // Ensure there are courseids to loop through.
634 if (!empty($params['courseids'])) {
636 list($courses, $warnings) = external_util::validate_courses($params['courseids']);
638 // Get the scorms in this course, this function checks users visibility permissions.
639 // We can avoid then additional validate_context calls.
640 $scorms = get_all_instances_in_courses("scorm", $courses);
642 $fs = get_file_storage();
643 foreach ($scorms as $scorm) {
645 $context = context_module::instance($scorm->coursemodule);
650 // First, we return information that any user can see in (or can deduce from) the web interface.
651 $module['id'] = $scorm->id;
652 $module['coursemodule'] = $scorm->coursemodule;
653 $module['course'] = $scorm->course;
654 $module['name'] = format_string($scorm->name, true, array('context' => $context));
655 list($module['intro'], $module['introformat']) =
656 external_format_text($scorm->intro, $scorm->introformat, $context->id, 'mod_scorm', 'intro', $scorm->id);
658 // Check if the SCORM open and return warnings if so.
659 list($open, $openwarnings) = scorm_get_availability_status($scorm, true, $context);
662 foreach ($openwarnings as $warningkey => $warningdata) {
665 'itemid' => $scorm->id,
666 'warningcode' => $warningkey,
667 'message' => get_string($warningkey, 'scorm', $warningdata)
671 $module['packagesize'] = 0;
673 if ($scorm->scormtype === SCORM_TYPE_LOCAL or $scorm->scormtype === SCORM_TYPE_LOCALSYNC) {
674 if ($packagefile = $fs->get_file($context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)) {
675 $module['packagesize'] = $packagefile->get_filesize();
677 $module['packageurl'] = moodle_url::make_webservice_pluginfile_url(
678 $context->id, 'mod_scorm', 'package', 0, '/', $scorm->reference)->out(false);
682 $viewablefields = array('version', 'maxgrade', 'grademethod', 'whatgrade', 'maxattempt', 'forcecompleted',
683 'forcenewattempt', 'lastattemptlock', 'displayattemptstatus', 'displaycoursestructure',
684 'sha1hash', 'md5hash', 'revision', 'launch', 'skipview', 'hidebrowse', 'hidetoc', 'nav',
685 'navpositionleft', 'navpositiontop', 'auto', 'popup', 'width', 'height', 'timeopen',
686 'timeclose', 'displayactivityname', 'scormtype', 'reference');
688 // Check additional permissions for returning optional private settings.
689 if (has_capability('moodle/course:manageactivities', $context)) {
691 $additionalfields = array('updatefreq', 'options', 'completionstatusrequired', 'completionscorerequired',
692 'autocommit', 'timemodified', 'section', 'visible', 'groupmode', 'groupingid');
693 $viewablefields = array_merge($viewablefields, $additionalfields);
697 foreach ($viewablefields as $field) {
698 $module[$field] = $scorm->{$field};
702 $returnedscorms[] = $module;
707 $result['scorms'] = $returnedscorms;
708 $result['warnings'] = $warnings;
713 * Describes the get_scorms_by_courses return value.
715 * @return external_single_structure
718 public static function get_scorms_by_courses_returns() {
720 return new external_single_structure(
722 'scorms' => new external_multiple_structure(
723 new external_single_structure(
725 'id' => new external_value(PARAM_INT, 'SCORM id'),
726 'coursemodule' => new external_value(PARAM_INT, 'Course module id'),
727 'course' => new external_value(PARAM_INT, 'Course id'),
728 'name' => new external_value(PARAM_TEXT, 'SCORM name'),
729 'intro' => new external_value(PARAM_RAW, 'The SCORM intro'),
730 'introformat' => new external_format_value('intro'),
731 'packagesize' => new external_value(PARAM_INT, 'SCORM zip package size', VALUE_OPTIONAL),
732 'packageurl' => new external_value(PARAM_URL, 'SCORM zip package URL', VALUE_OPTIONAL),
733 'version' => new external_value(PARAM_NOTAGS, 'SCORM version (SCORM_12, SCORM_13, SCORM_AICC)',
735 'maxgrade' => new external_value(PARAM_INT, 'Max grade', VALUE_OPTIONAL),
736 'grademethod' => new external_value(PARAM_INT, 'Grade method', VALUE_OPTIONAL),
737 'whatgrade' => new external_value(PARAM_INT, 'What grade', VALUE_OPTIONAL),
738 'maxattempt' => new external_value(PARAM_INT, 'Maximum number of attemtps', VALUE_OPTIONAL),
739 'forcecompleted' => new external_value(PARAM_BOOL, 'Status current attempt is forced to "completed"',
741 'forcenewattempt' => new external_value(PARAM_BOOL, 'Hides the "Start new attempt" checkbox',
743 'lastattemptlock' => new external_value(PARAM_BOOL, 'Prevents to launch new attempts once finished',
745 'displayattemptstatus' => new external_value(PARAM_BOOL, 'Display attempts status', VALUE_OPTIONAL),
746 'displaycoursestructure' => new external_value(PARAM_BOOL, 'Display contents structure',
748 'sha1hash' => new external_value(PARAM_NOTAGS, 'Package content or ext path hash', VALUE_OPTIONAL),
749 'md5hash' => new external_value(PARAM_NOTAGS, 'MD5 Hash of package file', VALUE_OPTIONAL),
750 'revision' => new external_value(PARAM_INT, 'Revison number', VALUE_OPTIONAL),
751 'launch' => new external_value(PARAM_INT, 'First content to launch', VALUE_OPTIONAL),
752 'skipview' => new external_value(PARAM_BOOL, 'Skip or not content structure page', VALUE_OPTIONAL),
753 'hidebrowse' => new external_value(PARAM_BOOL, 'Disable preview mode?', VALUE_OPTIONAL),
754 'hidetoc' => new external_value(PARAM_BOOL, 'Display or not course structure in player',
756 'nav' => new external_value(PARAM_INT, 'Show navigation buttons', VALUE_OPTIONAL),
757 'navpositionleft' => new external_value(PARAM_INT, 'Navigation position left', VALUE_OPTIONAL),
758 'navpositiontop' => new external_value(PARAM_INT, 'Navigation position top', VALUE_OPTIONAL),
759 'auto' => new external_value(PARAM_BOOL, 'Auto continue?', VALUE_OPTIONAL),
760 'popup' => new external_value(PARAM_INT, 'Display in current or new window', VALUE_OPTIONAL),
761 'width' => new external_value(PARAM_INT, 'Frame width', VALUE_OPTIONAL),
762 'height' => new external_value(PARAM_INT, 'Frame height', VALUE_OPTIONAL),
763 'timeopen' => new external_value(PARAM_INT, 'Available from', VALUE_OPTIONAL),
764 'timeclose' => new external_value(PARAM_INT, 'Available to', VALUE_OPTIONAL),
765 'displayactivityname' => new external_value(PARAM_BOOL, 'Display the activity name above the player?',
767 'scormtype' => new external_value(PARAM_ALPHA, 'SCORM type', VALUE_OPTIONAL),
768 'reference' => new external_value(PARAM_NOTAGS, 'Reference to the package', VALUE_OPTIONAL),
769 'updatefreq' => new external_value(PARAM_INT, 'Auto-update frequency for remote packages',
771 'options' => new external_value(PARAM_RAW, 'Additional options', VALUE_OPTIONAL),
772 'completionstatusrequired' => new external_value(PARAM_INT, 'Status passed/completed required?',
774 'completionscorerequired' => new external_value(PARAM_INT, 'Minimum score required', VALUE_OPTIONAL),
775 'autocommit' => new external_value(PARAM_BOOL, 'Save track data automatically?', VALUE_OPTIONAL),
776 'timemodified' => new external_value(PARAM_INT, 'Time of last modification', VALUE_OPTIONAL),
777 'section' => new external_value(PARAM_INT, 'Course section id', VALUE_OPTIONAL),
778 'visible' => new external_value(PARAM_BOOL, 'Visible', VALUE_OPTIONAL),
779 'groupmode' => new external_value(PARAM_INT, 'Group mode', VALUE_OPTIONAL),
780 'groupingid' => new external_value(PARAM_INT, 'Group id', VALUE_OPTIONAL),
784 'warnings' => new external_warnings(),