diff --git a/upgrade.txt b/upgrade.txt index 494f4504fcd..fef158a0dfe 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in the Moodle Mobile app, information provided here is intended especially for developers. +=== 2.9 === + + * Most of the functions from $mmaModScormOnline, $mmaModScormOffline, $mmaModScorm, $mmaModScormSync and $mmaModScormHelper now have a new param siteId to determine the site to affect. If you use any of these services please make sure you still pass the right parameters. + === 2.8 === * $mmModuleActionsDelegate has been renamed to $mmContentLinksDelegate to make it more generic. The specification of this delegate has changed, please look at its documentation to adapt your handlers. diff --git a/www/addons/mod_scorm/controllers/player.js b/www/addons/mod_scorm/controllers/player.js index 64d440aa32a..7ef28ad088f 100644 --- a/www/addons/mod_scorm/controllers/player.js +++ b/www/addons/mod_scorm/controllers/player.js @@ -40,7 +40,7 @@ angular.module('mm.addons.mod_scorm') $scope.loadingToc = true; if (scorm.popup) { - // Fix for bug in WS not returning %. If we receive a value <= 100 we'll assume it's a percentage. + // If we receive a value <= 100 we need to assume it's a percentage. if (scorm.width <= 100) { scorm.width = scorm.width + '%'; } @@ -98,7 +98,8 @@ angular.module('mm.addons.mod_scorm') promise = $mmaModScormHelper.createOfflineAttempt(scorm, result.attempt, attemptsData.online.length); } else { // Last attempt was online, verify that we can create a new online attempt. We ignore cache. - promise = $mmaModScorm.getScormUserData(scorm.id, result.attempt, false, undefined, true).catch(function() { + promise = $mmaModScorm.getScormUserData(scorm.id, result.attempt, false, undefined, undefined, true) + .catch(function() { // Cannot communicate with the server, create an offline attempt. offline = true; return $mmaModScormHelper.createOfflineAttempt(scorm, result.attempt, attemptsData.online.length); @@ -245,7 +246,7 @@ angular.module('mm.addons.mod_scorm') return $mmaModScorm.saveTracks(scoId, attempt, tracks, offline, scorm).then(function() { if (!offline) { // New online attempt created, update cached data about online attempts. - $mmaModScorm.getAttemptCount(scorm.id, undefined, false, true); + $mmaModScorm.getAttemptCount(scorm.id, undefined, undefined, false, true); } }); } diff --git a/www/addons/mod_scorm/main.js b/www/addons/mod_scorm/main.js index 9971989d20d..bcc82fa313b 100644 --- a/www/addons/mod_scorm/main.js +++ b/www/addons/mod_scorm/main.js @@ -64,13 +64,18 @@ angular.module('mm.addons.mod_scorm', ['mm.core']) $mmCoursePrefetchDelegateProvider.registerPrefetchHandler('mmaModScorm', 'scorm', '$mmaModScormPrefetchHandler'); }) -.run(function($timeout, $mmaModScormSync, $mmApp, $mmEvents, $mmaModScormOnline, $mmaModScormOffline, mmCoreEventLogin, - mmCoreEventLogout) { +.run(function($timeout, $mmaModScormSync, $mmApp, $mmEvents, $mmSite, mmCoreEventLogin) { var lastExecution = 0, - executing = false; + executing = false, + allSitesCalled = false; - function syncScorms() { + function syncScorms(allSites) { var now = new Date().getTime(); + + if (!allSites && !$mmSite.isLoggedIn()) { + return; + } + // Prevent consecutive and simultaneous executions. A sync process shouldn't take more than a few minutes, // so if it's been more than 5 minutes since the last execution we'll ignore the executing value. if (now - 5000 > lastExecution && (!executing || now - 300000 > lastExecution)) { @@ -78,7 +83,7 @@ angular.module('mm.addons.mod_scorm', ['mm.core']) executing = true; $timeout(function() { // Minor delay just to make sure network is fully established. - $mmaModScormSync.syncAllScorms().finally(function() { + $mmaModScormSync.syncAllScorms(allSites ? undefined : $mmSite.getId()).finally(function() { executing = false; }); }, 1000); @@ -86,19 +91,33 @@ angular.module('mm.addons.mod_scorm', ['mm.core']) } $mmApp.ready().then(function() { - document.addEventListener('online', syncScorms, false); // Cordova event. - window.addEventListener('online', syncScorms, false); // HTML5 event. + document.addEventListener('online', function() { + syncScorms(false); + }, false); // Cordova event. + window.addEventListener('online', function() { + syncScorms(false); + }, false); // HTML5 event. + + if (!$mmSite.isLoggedIn()) { + // App was started without any site logged in. Try to sync all sites. + allSitesCalled = true; + if ($mmApp.isOnline()) { + syncScorms(true); + } + } }); $mmEvents.on(mmCoreEventLogin, function() { - if ($mmApp.isOnline()) { - syncScorms(); + var allSites = false; + if (!allSitesCalled) { + // App started with a site logged in. Try to sync all sites. + allSitesCalled = true; + allSites = true; } - }); - $mmEvents.on(mmCoreEventLogout, function() { - $mmaModScormOnline.clearBlockedScorms(); - $mmaModScormOffline.clearBlockedScorms(); + if ($mmApp.isOnline()) { + syncScorms(allSites); + } }); }); diff --git a/www/addons/mod_scorm/services/helper.js b/www/addons/mod_scorm/services/helper.js index 65547ade62a..d885659dbe5 100644 --- a/www/addons/mod_scorm/services/helper.js +++ b/www/addons/mod_scorm/services/helper.js @@ -21,7 +21,7 @@ angular.module('mm.addons.mod_scorm') * @ngdoc service * @name $mmCourseHelper */ -.factory('$mmaModScormHelper', function($mmaModScorm, $mmUtil, $translate, $q, $mmaModScormOffline, $mmaModScormSync) { +.factory('$mmaModScormHelper', function($mmaModScorm, $mmUtil, $translate, $q, $mmaModScormOffline, $mmaModScormSync, $mmSite) { var self = {}, elementsToIgnore = ['status', 'score_raw', 'total_time', 'session_time', 'student_id', 'student_name', 'credit', @@ -54,14 +54,17 @@ angular.module('mm.addons.mod_scorm') * @name $mmaModScormHelper#convertAttemptToOffline * @param {Object} scorm SCORM. * @param {Number} attempt Number of the online attempt. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the attempt is created. */ - self.convertAttemptToOffline = function(scorm, attempt) { + self.convertAttemptToOffline = function(scorm, attempt, siteId) { + siteId = siteId || $mmSite.getId(); + // Get data from the online attempt. - return $mmaModScorm.getScormUserData(scorm.id, attempt).then(function(onlineData) { + return $mmaModScorm.getScormUserData(scorm.id, attempt, false, siteId).then(function(onlineData) { // The SCORM API might have written some data to the offline attempt already. // We don't want to override it with cached online data. - return $mmaModScormOffline.getScormUserData(scorm.id, attempt).catch(function() { + return $mmaModScormOffline.getScormUserData(siteId, scorm.id, attempt).catch(function() { // Ignore errors. }).then(function(offlineData) { var dataToStore = angular.copy(onlineData); @@ -85,7 +88,7 @@ angular.module('mm.addons.mod_scorm') } }); - return $mmaModScormOffline.createNewAttempt(scorm, undefined, attempt, dataToStore, onlineData); + return $mmaModScormOffline.createNewAttempt(siteId, scorm, undefined, attempt, dataToStore, onlineData); }); }).catch(function() { // Shouldn't happen. @@ -102,11 +105,13 @@ angular.module('mm.addons.mod_scorm') * @param {Object} scorm SCORM. * @param {Number} newAttempt Number of the new attempt. * @param {Number} lastOnline Number of the last online attempt. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the attempt is created. */ - self.createOfflineAttempt = function(scorm, newAttempt, lastOnline) { + self.createOfflineAttempt = function(scorm, newAttempt, lastOnline, siteId) { + siteId = siteId || $mmSite.getId(); // Try to get data from online attempts. - return self.searchOnlineAttemptUserData(scorm.id, lastOnline).then(function(userData) { + return self.searchOnlineAttemptUserData(scorm.id, lastOnline, siteId).then(function(userData) { // We're creating a new attempt, remove all the user data that is not needed for a new attempt. // We need to get the SCO data from here because WS get_scoes doesn't return sco_data in Moodle 3.0. angular.forEach(userData, function(sco) { @@ -119,7 +124,7 @@ angular.module('mm.addons.mod_scorm') }); sco.userdata = filtered; }); - return $mmaModScormOffline.createNewAttempt(scorm, undefined, newAttempt, userData); + return $mmaModScormOffline.createNewAttempt(siteId, scorm, undefined, newAttempt, userData); }).catch(function() { return $q.reject($translate.instant('mma.mod_scorm.errorcreateofflineattempt')); }); @@ -162,9 +167,11 @@ angular.module('mm.addons.mod_scorm') * @name $mmaModScormHelper#determineAttemptToContinue * @param {Object} scorm SCORM. * @param {Object} attempts Result of $mmaModScorm#getAttemptCount. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with an object with 2 properties: 'number' and 'offline'. */ - self.determineAttemptToContinue = function(scorm, attempts) { + self.determineAttemptToContinue = function(scorm, attempts, siteId) { + siteId = siteId || $mmSite.getId(); var lastOnline, result = { number: 0, @@ -190,7 +197,7 @@ angular.module('mm.addons.mod_scorm') if (lastOnline) { // Check if last online incomplete. var hasOffline = attempts.offline.indexOf(lastOnline) > -1; - return $mmaModScorm.isAttemptIncomplete(scorm.id, lastOnline, hasOffline).then(function(incomplete) { + return $mmaModScorm.isAttemptIncomplete(scorm.id, lastOnline, hasOffline, false, siteId).then(function(incomplete) { if (incomplete) { result.number = lastOnline; result.offline = hasOffline; @@ -212,20 +219,22 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormHelper#getFirstSco - * @param {String} scormid Scorm ID. + * @param {String} scormId Scorm ID. * @param {Object[]} [toc] SCORM's TOC. * @param {String} [organization] Organization to use. * @param {Number} attempt Attempt number. * @param {Boolean} offline True if attempt is offline, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the first SCO. */ - self.getFirstSco = function(scormid, toc, organization, attempt, offline) { + self.getFirstSco = function(scormId, toc, organization, attempt, offline, siteId) { + siteId = siteId || $mmSite.getId(); var promise; if (toc && toc.length) { promise = $q.when(toc); } else { // SCORM doesn't have a TOC. Get all the scos. - promise = $mmaModScorm.getScosWithData(scormid, organization, attempt, offline); + promise = $mmaModScorm.getScosWithData(scormId, organization, attempt, offline, false, siteId); } return promise.then(function(scos) { @@ -312,10 +321,11 @@ angular.module('mm.addons.mod_scorm') * @ngdoc method * @name $mmaModScormHelper#getScormReadableSyncTime * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the readable time. */ - self.getScormReadableSyncTime = function(scormId) { - return $mmaModScormSync.getScormSyncTime(scormId).then(function(time) { + self.getScormReadableSyncTime = function(scormId, siteId) { + return $mmaModScormSync.getScormSyncTime(scormId, siteId).then(function(time) { if (time == 0) { return $translate('mm.core.none'); } else { @@ -331,15 +341,17 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormHelper#searchOnlineAttemptUserData - * @param {Number} scormId SCORM ID. - * @param {Number} attempt Online attempt to get the data. - * @return {Promise} Promise resolved with user data. + * @param {Number} scormId SCORM ID. + * @param {Number} attempt Online attempt to get the data. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with user data. */ - self.searchOnlineAttemptUserData = function(scormId, attempt) { - return $mmaModScorm.getScormUserData(scormId, attempt).catch(function() { + self.searchOnlineAttemptUserData = function(scormId, attempt, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmaModScorm.getScormUserData(scormId, attempt, false, siteId).catch(function() { if (attempt > 0) { // We couldn't retrieve the data. Try again with the previous online attempt. - return self.searchOnlineAttemptUserData(scormId, attempt - 1); + return self.searchOnlineAttemptUserData(scormId, attempt - 1, siteId); } else { // No more attempts to try. Reject return $q.reject(); diff --git a/www/addons/mod_scorm/services/scorm.js b/www/addons/mod_scorm/services/scorm.js index c3f25567f50..82840e9185b 100644 --- a/www/addons/mod_scorm/services/scorm.js +++ b/www/addons/mod_scorm/services/scorm.js @@ -14,19 +14,6 @@ angular.module('mm.addons.mod_scorm') -.constant('mmaModScormSynchronizationStore', 'mod_scorm_sync') - -.config(function($mmSitesFactoryProvider, mmaModScormSynchronizationStore) { - var stores = [ - { - name: mmaModScormSynchronizationStore, - keyPath: 'scormid', - indexes: [] - } - ]; - $mmSitesFactoryProvider.registerStores(stores); -}) - /** * SCORM service. * @@ -35,7 +22,7 @@ angular.module('mm.addons.mod_scorm') * @name $mmaModScorm */ .factory('$mmaModScorm', function($mmSite, $q, $translate, $mmLang, $mmFilepool, $mmFS, $mmWS, $sce, $mmaModScormOnline, $state, - $mmaModScormOffline, $mmUtil, $log, mmaModScormComponent, mmCoreNotDownloaded) { + $mmaModScormOffline, $mmUtil, $log, $mmSitesManager, mmaModScormComponent, mmCoreNotDownloaded) { $log = $log.getInstance('$mmaModScorm'); var self = {}, @@ -258,16 +245,16 @@ angular.module('mm.addons.mod_scorm') */ self._downloadOrPrefetch = function(scorm, prefetch) { var result = self.isScormSupported(scorm), - siteid = $mmSite.getId(); + siteId = $mmSite.getId(); if (result !== true) { return $mmLang.translateAndReject(result); } - if (downloadPromises[siteid] && downloadPromises[siteid][scorm.id]) { + if (downloadPromises[siteId] && downloadPromises[siteId][scorm.id]) { // There's already a download ongoing for this package, return the promise. - return downloadPromises[siteid][scorm.id]; - } else if (!downloadPromises[siteid]) { - downloadPromises[siteid] = {}; + return downloadPromises[siteId][scorm.id]; + } else if (!downloadPromises[siteId]) { + downloadPromises[siteId] = {}; } var files = self.getScormFileList(scorm), @@ -276,7 +263,7 @@ angular.module('mm.addons.mod_scorm') deferred = $q.defer(), // We use a deferred to be able to notify. fn = prefetch ? $mmFilepool.prefetchPackage : $mmFilepool.downloadPackage; - downloadPromises[siteid][scorm.id] = deferred.promise; // Store promise to be able to restore it later. + downloadPromises[siteId][scorm.id] = deferred.promise; // Store promise to be able to restore it later. // Get the folder where the unzipped files will be. self.getScormFolder(scorm.moduleurl).then(function(path) { @@ -284,7 +271,7 @@ angular.module('mm.addons.mod_scorm') // Download the ZIP file to the filepool. // Using undefined for success & fail will pass the success/failure to the parent promise. deferred.notify({message: 'mm.core.downloading'}); - return fn(siteid, files, mmaModScormComponent, scorm.coursemodule, revision, 0) + return fn(siteId, files, mmaModScormComponent, scorm.coursemodule, revision, 0) .then(undefined, undefined, deferred.notify); }).then(function() { // Remove the destination folder to prevent having old unused files. @@ -293,23 +280,23 @@ angular.module('mm.addons.mod_scorm') }); }).then(function() { // Get the ZIP file path. - return $mmFilepool.getFilePathByUrl(siteid, self.getPackageUrl(scorm)); + return $mmFilepool.getFilePathByUrl(siteId, self.getPackageUrl(scorm)); }).then(function(zippath) { // Unzip and delete the zip when finished. deferred.notify({message: 'mm.core.unzipping'}); return $mmFS.unzipFile(zippath, dirPath).then(function() { - return $mmFilepool.removeFileByUrl(siteid, self.getPackageUrl(scorm)).catch(function() { + return $mmFilepool.removeFileByUrl(siteId, self.getPackageUrl(scorm)).catch(function() { // Ignore errors. }); }, function(error) { // Error unzipping. Set status as not downloaded and reject. - return $mmFilepool.storePackageStatus(siteid, mmaModScormComponent, scorm.coursemodule, + return $mmFilepool.storePackageStatus(siteId, mmaModScormComponent, scorm.coursemodule, mmCoreNotDownloaded, revision, 0).then(function() { return $q.reject(error); }); }, deferred.notify); }).then(deferred.resolve, deferred.reject).finally(function() { - delete downloadPromises[siteid][scorm.id]; // Delete stored promise. + delete downloadPromises[siteId][scorm.id]; // Delete stored promise. }); return deferred.promise; @@ -482,58 +469,64 @@ angular.module('mm.addons.mod_scorm') * @ngdoc method * @name $mmaModScorm#getAttemptCount * @param {Number} scormId SCORM ID. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Boolean} ignoreMissing True if it should ignore attempts without grade/completion. Only for online attempts. * @param {Boolean} ignoreCache True if it should ignore cached data for online attempts. * @return {Promise} Promise resolved when the attempt count is retrieved. It returns an object with * online attempts, offline attempts, total number of attempts and last attempt. */ - self.getAttemptCount = function(scormId, userId, ignoreMissing, ignoreCache) { - userId = userId || $mmSite.getUserId(); + self.getAttemptCount = function(scormId, siteId, userId, ignoreMissing, ignoreCache) { + siteId = siteId || $mmSite.getId(); - var result = { - lastAttempt: { - number: 0, - offline: false - } - }, - promises = []; - - promises.push($mmaModScormOnline.getAttemptCount(scormId, userId, ignoreMissing, ignoreCache).then(function(count) { - // Calculate numbers of offline attempts. - result.online = []; - for (var i = 1; i <= count; i++) { - result.online.push(i); - } - // Calculate last attempt. - if (count > result.lastAttempt.number) { - result.lastAttempt.number = count; - result.lastAttempt.offline = false; - } - })); + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); - promises.push($mmaModScormOffline.getAttempts(scormId, userId).then(function(attempts) { - // Get only attempt numbers. - result.offline = attempts.map(function(entry) { - // Calculate last attempt. We use >= to prioritize offline events if an attempt is both online and offline. - if (entry.attempt >= result.lastAttempt.number) { - result.lastAttempt.number = entry.attempt; - result.lastAttempt.offline = true; + var result = { + lastAttempt: { + number: 0, + offline: false + } + }, + promises = []; + + promises.push($mmaModScormOnline.getAttemptCount(siteId, scormId, userId, ignoreMissing, ignoreCache) + .then(function(count) { + // Calculate numbers of online attempts. + result.online = []; + for (var i = 1; i <= count; i++) { + result.online.push(i); } - return entry.attempt; - }); - })); - - return $q.all(promises).then(function() { - var total = result.online.length; - result.offline.forEach(function(attempt) { - // Check if this attempt also exists in online, it might have been copied to local. - if (result.online.indexOf(attempt) == -1) { - total++; + // Calculate last attempt. + if (count > result.lastAttempt.number) { + result.lastAttempt.number = count; + result.lastAttempt.offline = false; } + })); + + promises.push($mmaModScormOffline.getAttempts(siteId, scormId, userId).then(function(attempts) { + // Get only attempt numbers. + result.offline = attempts.map(function(entry) { + // Calculate last attempt. We use >= to prioritize offline events if an attempt is both online and offline. + if (entry.attempt >= result.lastAttempt.number) { + result.lastAttempt.number = entry.attempt; + result.lastAttempt.offline = true; + } + return entry.attempt; + }); + })); + + return $q.all(promises).then(function() { + var total = result.online.length; + result.offline.forEach(function(attempt) { + // Check if this attempt also exists in online, it might have been copied to local. + if (result.online.indexOf(attempt) == -1) { + total++; + } + }); + result.total = total; + return result; }); - result.total = total; - return result; }); }; @@ -547,9 +540,10 @@ angular.module('mm.addons.mod_scorm') * @param {Number} scormid SCORM ID. * @param {Number} attempt Attempt number. * @param {Boolean} offline True if attempt is offline, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the grade. If the attempt hasn't reported grade/completion, grade will be -1. */ - self.getAttemptGrade = function(scorm, attempt, offline) { + self.getAttemptGrade = function(scorm, attempt, offline, siteId) { var attemptscore = { scos: 0, values: 0, @@ -557,7 +551,7 @@ angular.module('mm.addons.mod_scorm') sum: 0 }; - return self.getScormUserData(scorm.id, attempt, offline).then(function(data) { + return self.getScormUserData(scorm.id, attempt, offline, siteId).then(function(data) { angular.forEach(data, function(scodata) { var userdata = scodata.userdata; if (userdata.status == 'completed' || userdata.status == 'passed') { @@ -605,11 +599,12 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getOrganizations - * @param {Number} scormid SCORM ID. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the list of organizations. */ - self.getOrganizations = function(scormid) { - return self.getScos(scormid).then(function(scos) { + self.getOrganizations = function(scormId, siteId) { + return self.getScos(scormId, siteId).then(function(scos) { var organizations = []; angular.forEach(scos, function(sco) { // Is an organization entry? @@ -631,15 +626,16 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getOrganizationToc - * @param {Number} scormid SCORM ID. + * @param {Number} scormId SCORM ID. * @param {String} organization Organization identifier. * @param {Number} attempt The attempt number (to populate SCO track data). * @param {Boolean} offline True if attempt is offline, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the toc object. */ - self.getOrganizationToc = function(scormid, organization, attempt, offline) { + self.getOrganizationToc = function(scormId, organization, attempt, offline, siteId) { - return self.getScosWithData(scormid, organization, attempt, offline).then(function(scos) { + return self.getScosWithData(scormId, organization, attempt, offline, false, siteId).then(function(scos) { var map = {}, rootScos = []; @@ -689,29 +685,31 @@ angular.module('mm.addons.mod_scorm') * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. * @param {Boolean} offline True if attempt is offline, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @param {Object[]} [scos] SCOs returned by $mmaModScorm#getScos. Recommended if offline=true. * @param {Boolean} ignoreCache True if it should ignore cached data for online attempts. * @return {Promise} Promise resolved when the user data is retrieved. */ - self.getScormUserData = function(scormId, attempt, offline, scos, ignoreCache) { + self.getScormUserData = function(scormId, attempt, offline, siteId, scos, ignoreCache) { + siteId = siteId || $mmSite.getId(); if (offline) { - var promise = scos ? $q.when(scos) : self.getScos(scormId); + var promise = scos ? $q.when(scos) : self.getScos(scormId, siteId); return promise.then(function(scos) { - return $mmaModScormOffline.getScormUserData(scormId, attempt, undefined, scos); + return $mmaModScormOffline.getScormUserData(siteId, scormId, attempt, undefined, scos); }); } else { - return $mmaModScormOnline.getScormUserData(scormId, attempt, ignoreCache); + return $mmaModScormOnline.getScormUserData(siteId, scormId, attempt, ignoreCache); } }; /** * Get cache key for get SCORM scos WS calls. * - * @param {Number} scormid SCORM ID. + * @param {Number} scormId SCORM ID. * @return {String} Cache key. */ - function getScosCacheKey(scormid) { - return 'mmaModScorm:scos:' + scormid; + function getScosCacheKey(scormId) { + return 'mmaModScorm:scos:' + scormId; } /** @@ -720,47 +718,48 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScos - * @param {Number} scormid SCORM ID. - * @param {String} organization Organization ID. - * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail if offline or server down). - * @return {Promise} Promise resolved with a list of SCO objects. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {String} [organization] Organization ID. + * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail if offline or server down). + * @return {Promise} Promise resolved with a list of SCO objects. */ - self.getScos = function(scormid, organization, ignoreCache) { - organization = organization || ''; + self.getScos = function(scormId, siteId, organization, ignoreCache) { + siteId = siteId || $mmSite.getId(); - if (!$mmSite.isLoggedIn()) { - return $q.reject(); - } + return $mmSitesManager.getSite(siteId).then(function(site) { + organization = organization || ''; - // Don't send the organization to the WS, we'll filter them locally. - var params = { - scormid: scormid - }, - preSets = { - cacheKey: getScosCacheKey(scormid) - }; + // Don't send the organization to the WS, we'll filter them locally. + var params = { + scormid: scormId + }, + preSets = { + cacheKey: getScosCacheKey(scormId) + }; - if (ignoreCache) { - preSets.getFromCache = 0; - preSets.emergencyCache = 0; - } + if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; + } - return $mmSite.read('mod_scorm_get_scorm_scoes', params, preSets).then(function(response) { - if (response && response.scoes) { - var scos = []; - if (organization) { - // Filter SCOs by organization. - angular.forEach(response.scoes, function(sco) { - if (sco.organization == organization) { - scos.push(sco); - } - }); - } else { - scos = response.scoes; + return site.read('mod_scorm_get_scorm_scoes', params, preSets).then(function(response) { + if (response && response.scoes) { + var scos = []; + if (organization) { + // Filter SCOs by organization. + angular.forEach(response.scoes, function(sco) { + if (sco.organization == organization) { + scos.push(sco); + } + }); + } else { + scos = response.scoes; + } + return scos; } - return scos; - } - return $q.reject(); + return $q.reject(); + }); }); }; @@ -771,19 +770,20 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScosWithData - * @param {Number} scormid SCORM ID. + * @param {Number} scormId SCORM ID. * @param {String} organization Organization ID. * @param {Number} attempt Attempt number. * @param {Boolean} offline True if attempt is offline, false otherwise. * @param {Boolean} ignoreCache True if it should ignore cached data for online attempts. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with a list of SCO objects. */ - self.getScosWithData = function(scormid, organization, attempt, offline, ignoreCache) { + self.getScosWithData = function(scormId, organization, attempt, offline, ignoreCache, siteId) { // Get organization SCOs. - return self.getScos(scormid, organization, ignoreCache).then(function(scos) { + return self.getScos(scormId, siteId, organization, ignoreCache).then(function(scos) { // Get the track data for all the SCOs in the organization for the given attempt. // We'll use this data to set SCO data like isvisible, status and so. - return self.getScormUserData(scormid, attempt, offline, scos, ignoreCache).then(function(data) { + return self.getScormUserData(scormId, attempt, offline, siteId, scos, ignoreCache).then(function(data) { var trackDataBySCO = {}; @@ -823,17 +823,20 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScoSrc - * @param {Object} scorm SCORM. - * @param {Object} sco SCO. - * @return {Promise} Promise resolved with the URL. + * @param {Object} scorm SCORM. + * @param {Object} sco SCO. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the URL. */ - self.getScoSrc = function(scorm, sco) { + self.getScoSrc = function(scorm, sco, siteId) { if (sco.launch.match(/http(s)?:\/\//)) { // It's an online URL. return $q.when($sce.trustAsResourceUrl(sco.launch)); } - return $mmFilepool.getDirectoryUrlByUrl($mmSite.getId(), scorm.moduleurl).then(function(dirPath) { + siteId = siteId || $mmSite.getId(); + + return $mmFilepool.getDirectoryUrlByUrl(siteId, scorm.moduleurl).then(function(dirPath) { // This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser. return $sce.trustAsResourceUrl($mmFS.concatenatePaths(dirPath, sco.launch)); }); @@ -845,11 +848,13 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScormFolder - * @param {String} moduleurl Module URL (returned by get_course_contents). + * @param {String} moduleUrl Module URL (returned by get_course_contents). + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the folder path. */ - self.getScormFolder = function(moduleurl) { - return $mmFilepool.getFilePathByUrl($mmSite.getId(), moduleurl); + self.getScormFolder = function(moduleUrl, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmFilepool.getFilePathByUrl(siteId, moduleUrl); }; /** @@ -925,56 +930,55 @@ angular.module('mm.addons.mod_scorm') /** * Get cache key for SCORM data WS calls. * - * @param {Number} courseid Course ID. + * @param {Number} courseId Course ID. * @return {String} Cache key. */ - function getScormDataCacheKey(courseid) { - return 'mmaModScorm:scorm:' + courseid; + function getScormDataCacheKey(courseId) { + return 'mmaModScorm:scorm:' + courseId; } /** * Get a SCORM with key=value. If more than one is found, only the first will be returned. * - * @param {Number} courseid Course ID. + * @param {String} siteId Site ID. + * @param {Number} courseId Course ID. * @param {String} key Name of the property to check. * @param {Mixed} value Value to search. - * @param {String} moduleurl Module URL. + * @param {String} moduleUrl Module URL. * @return {Promise} Promise resolved when the SCORM is retrieved. */ - function getScorm(courseid, key, value, moduleurl) { - var params = { - courseids: [courseid] - }, - preSets = { - cacheKey: getScormDataCacheKey(courseid) - }; - - if (!$mmSite.isLoggedIn()) { - return $q.reject(); - } + function getScorm(siteId, courseId, key, value, moduleUrl) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + courseids: [courseId] + }, + preSets = { + cacheKey: getScormDataCacheKey(courseId) + }; - return $mmSite.read('mod_scorm_get_scorms_by_courses', params, preSets).then(function(response) { - if (response && response.scorms) { - var currentScorm; - angular.forEach(response.scorms, function(scorm) { - if (!currentScorm && scorm[key] == value) { - currentScorm = scorm; - } - }); - if (currentScorm) { - // If the SCORM isn't available the WS returns a warning and it doesn't return timeopen and timeclosed. - if (typeof currentScorm.timeopen == 'undefined') { - angular.forEach(response.warnings, function(warning) { - if (warning.itemid === currentScorm.id) { - currentScorm.warningmessage = warning.message; - } - }); + return site.read('mod_scorm_get_scorms_by_courses', params, preSets).then(function(response) { + if (response && response.scorms) { + var currentScorm; + angular.forEach(response.scorms, function(scorm) { + if (!currentScorm && scorm[key] == value) { + currentScorm = scorm; + } + }); + if (currentScorm) { + // If the SCORM isn't available the WS returns a warning and it doesn't return timeopen and timeclosed. + if (typeof currentScorm.timeopen == 'undefined') { + angular.forEach(response.warnings, function(warning) { + if (warning.itemid === currentScorm.id) { + currentScorm.warningmessage = warning.message; + } + }); + } + currentScorm.moduleurl = moduleUrl; + return currentScorm; } - currentScorm.moduleurl = moduleurl; - return currentScorm; } - } - return $q.reject(); + return $q.reject(); + }); }); } @@ -984,13 +988,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScorm - * @param {Number} courseid Course ID. + * @param {Number} courseId Course ID. * @param {Number} cmid Course module ID. - * @parma {String} moduleurl Module URL. + * @parma {String} moduleUrl Module URL. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the SCORM is retrieved. */ - self.getScorm = function(courseid, cmid, moduleurl) { - return getScorm(courseid, 'coursemodule', cmid, moduleurl); + self.getScorm = function(courseId, cmid, moduleUrl, siteId) { + siteId = siteId || $mmSite.getId(); + return getScorm(siteId, courseId, 'coursemodule', cmid, moduleUrl); }; /** @@ -999,13 +1005,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#getScormById - * @param {Number} courseid Course ID. + * @param {Number} courseId Course ID. * @param {Number} cmid Course module ID. - * @parma {String} moduleurl Module URL. + * @parma {String} moduleUrl Module URL. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the SCORM is retrieved. */ - self.getScormById = function(courseid, id, moduleurl) { - return getScorm(courseid, 'id', id, moduleurl); + self.getScormById = function(courseId, id, moduleUrl, siteId) { + siteId = siteId || $mmSite.getId(); + return getScorm(siteId, courseId, 'id', id, moduleUrl); }; /** @@ -1049,15 +1057,17 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#invalidateAllScormData - * @param {Number} scormid SCORM ID. - * @param {Number} [userid] User ID. If not defined, current user. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when the data is invalidated. */ - self.invalidateAllScormData = function(scormid, userid) { + self.invalidateAllScormData = function(scormId, siteId, userId) { + siteId = siteId || $mmSite.getId(); var promises = []; - promises.push($mmaModScormOnline.invalidateAttemptCount(scormid, userid)); - promises.push(self.invalidateScos(scormid)); - promises.push($mmaModScormOnline.invalidateScormUserData(scormid)); + promises.push($mmaModScormOnline.invalidateAttemptCount(siteId, scormId, userId)); + promises.push(self.invalidateScos(scormId, siteId)); + promises.push($mmaModScormOnline.invalidateScormUserData(siteId, scormId)); return $q.all(promises); }; @@ -1068,10 +1078,12 @@ angular.module('mm.addons.mod_scorm') * @ngdoc method * @name $mmaModScorm#invalidateContent * @param {Object} moduleId The module ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} */ - self.invalidateContent = function(moduleId) { - return $mmFilepool.invalidateFilesByComponent($mmSite.getId(), mmaModScormComponent, moduleId); + self.invalidateContent = function(moduleId, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmFilepool.invalidateFilesByComponent(siteId, mmaModScormComponent, moduleId); }; /** @@ -1080,11 +1092,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#invalidateScos - * @param {Number} scormid SCORM ID. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - self.invalidateScos = function(scormid) { - return $mmSite.invalidateWsCacheForKey(getScosCacheKey(scormid)); + self.invalidateScos = function(scormId, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKey(getScosCacheKey(scormId)); + }); }; /** @@ -1093,11 +1109,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#invalidateScormData - * @param {Number} courseid Course ID. + * @param {Number} courseId Course ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - self.invalidateScormData = function(courseid) { - return $mmSite.invalidateWsCacheForKey(getScormDataCacheKey(courseid)); + self.invalidateScormData = function(courseId, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKey(getScormDataCacheKey(courseId)); + }); }; /** @@ -1110,10 +1130,11 @@ angular.module('mm.addons.mod_scorm') * @param {Number} attempt Attempt. * @param {Boolean} offline True if attempt is offline, false otherwise. * @param {Boolean} ignoreCache True if it should ignore cached data for online attempts. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with a boolean: true if incomplete, false otherwise. */ - self.isAttemptIncomplete = function(scormId, attempt, offline, ignoreCache) { - return self.getScosWithData(scormId, undefined, attempt, offline, ignoreCache).then(function(scos) { + self.isAttemptIncomplete = function(scormId, attempt, offline, ignoreCache, siteId) { + return self.getScosWithData(scormId, undefined, attempt, offline, ignoreCache, siteId).then(function(scos) { var incomplete = false; angular.forEach(scos, function(sco) { @@ -1153,10 +1174,13 @@ angular.module('mm.addons.mod_scorm') * @ngdoc method * @name $mmaModScorm#isScormBeingPlayed * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if it's being played, false otherwise. */ - self.isScormBeingPlayed = function(scormId) { - return $state.current.name == 'site.mod_scorm-player' && $state.params.scorm && $state.params.scorm.id == scormId; + self.isScormBeingPlayed = function(scormId, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmSite.getId() == siteId && $state.current.name == 'site.mod_scorm-player' && + $state.params.scorm && $state.params.scorm.id == scormId; }; /** @@ -1259,14 +1283,14 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#isValidPackageUrl - * @param {String} packageurl Package URL. + * @param {String} packageUrl Package URL. * @return {Boolean} True if valid, false otherwise. */ - self.isValidPackageUrl = function(packageurl) { - if (!packageurl) { + self.isValidPackageUrl = function(packageUrl) { + if (!packageUrl) { return false; } - if (packageurl.indexOf('imsmanifest.xml') > -1) { + if (packageUrl.indexOf('imsmanifest.xml') > -1) { return false; } return true; @@ -1278,15 +1302,19 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#logView - * @param {String} id Module ID. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {String} id Module ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - self.logView = function(id) { + self.logView = function(id, siteId) { + siteId = siteId || $mmSite.getId(); if (id) { - var params = { - scormid: id - }; - return $mmSite.write('mod_scorm_view_scorm', params); + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + scormid: id + }; + return site.write('mod_scorm_view_scorm', params); + }); } return $q.reject(); }; @@ -1297,19 +1325,23 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#logLaunchSco - * @param {Number} scormId SCORM ID. - * @param {Number} scoId SCO ID. - * @return {Promise} Promise resolved when the WS call is successful. + * @param {Number} scormId SCORM ID. + * @param {Number} scoId SCO ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. */ - self.logLaunchSco = function(scormId, scoId) { + self.logLaunchSco = function(scormId, scoId, siteId) { + siteId = siteId || $mmSite.getId(); var params = { scormid: scormId, scoid: scoId }; - return $mmSite.write('mod_scorm_launch_sco', params).then(function(response) { - if (!response || !response.status) { - return $q.reject(); - } + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.write('mod_scorm_launch_sco', params).then(function(response) { + if (!response || !response.status) { + return $q.reject(); + } + }); }); }; @@ -1330,7 +1362,7 @@ angular.module('mm.addons.mod_scorm') promises.push(self.prefetchData(scorm).catch(function() { // If prefetchData fails we don't want to fail the whole downloaded, so we'll ignore the error for now. - // TODO: Implement a warning system so the user knows which SCORMs have failed. + // @todo Implement a warning system so the user knows which SCORMs have failed. })); return $q.all(promises); @@ -1342,14 +1374,16 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScorm#prefetchData - * @param {Object} scorm SCORM object returned by $mmaModScorm#getScorm. - * @return {Promise} Promise resolved when the data is prefetched. + * @param {Object} scorm SCORM object returned by $mmaModScorm#getScorm. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is prefetched. */ - self.prefetchData = function(scorm) { + self.prefetchData = function(scorm, siteId) { + siteId = siteId || $mmSite.getId(); var promises = []; // Prefetch number of attempts (including not completed). - promises.push($mmaModScormOnline.getAttemptCount(scorm.id).catch(function() { + promises.push($mmaModScormOnline.getAttemptCount(siteId, scorm.id).catch(function() { // If it fails, assume we have no attempts. return 0; }).then(function(numAttempts) { @@ -1364,7 +1398,7 @@ angular.module('mm.addons.mod_scorm') } attempts.forEach(function(attempt) { - datapromises.push($mmaModScormOnline.getScormUserData(scorm.id, attempt).catch(function(err) { + datapromises.push($mmaModScormOnline.getScormUserData(siteId, scorm.id, attempt).catch(function(err) { // Ignore failures of all the attempts that aren't the last one. if (attempt == numAttempts) { return $q.reject(err); @@ -1375,12 +1409,12 @@ angular.module('mm.addons.mod_scorm') return $q.all(datapromises); } else { // No attempts. We'll still try to get user data to be able to identify SCOs not visible and so. - return $mmaModScormOnline.getScormUserData(scorm.id, 0); + return $mmaModScormOnline.getScormUserData(siteId, scorm.id, 0); } })); // Prefetch SCOs. - promises.push(self.getScos(scorm.id)); + promises.push(self.getScos(scorm.id, siteId)); return $q.all(promises); }; @@ -1411,16 +1445,18 @@ angular.module('mm.addons.mod_scorm') * @param {Boolean} offline True if attempt is offline, false otherwise. * @param {Object} scorm SCORM. * @param {Object} [userData] User data for this attempt and SCO. If not defined, it will be retrieved from DB. Recommended. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when data is saved. */ - self.saveTracks = function(scoId, attempt, tracks, offline, scorm, userData) { + self.saveTracks = function(scoId, attempt, tracks, offline, scorm, userData, siteId) { + siteId = siteId || $mmSite.getId(); if (offline) { - var promise = userData ? $q.when(userData) : self.getScormUserData(scorm.id, attempt, offline); + var promise = userData ? $q.when(userData) : self.getScormUserData(scorm.id, attempt, offline, siteId); return promise.then(function(userData) { - return $mmaModScormOffline.saveTracks(scorm, scoId, attempt, tracks, userData); + return $mmaModScormOffline.saveTracks(siteId, scorm, scoId, attempt, tracks, userData); }); } else { - return $mmaModScormOnline.saveTracks(scorm.id, scoId, attempt, tracks); + return $mmaModScormOnline.saveTracks(siteId, scorm.id, scoId, attempt, tracks); } }; diff --git a/www/addons/mod_scorm/services/scorm_offline.js b/www/addons/mod_scorm/services/scorm_offline.js index 4430900695c..35bd0995aec 100644 --- a/www/addons/mod_scorm/services/scorm_offline.js +++ b/www/addons/mod_scorm/services/scorm_offline.js @@ -96,7 +96,7 @@ angular.module('mm.addons.mod_scorm') * @ngdoc service * @name $mmaModScormOffline */ -.factory('$mmaModScormOffline', function($mmSite, $mmUtil, $q, $log, mmaModScormOfflineAttemptsStore, +.factory('$mmaModScormOffline', function($mmSite, $mmUtil, $q, $log, $mmSitesManager, mmaModScormOfflineAttemptsStore, mmaModScormOfflineTracksStore) { $log = $log.getInstance('$mmaModScormOffline'); @@ -111,60 +111,65 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#changeAttemptNumber + * @param {String} siteId Site ID. * @param {Object} scormId SCORM ID. * @param {Number} attempt Number of the attempt to change. * @param {Number} newAttempt New attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when the attempt number changes. */ - self.changeAttemptNumber = function(scormId, attempt, newAttempt, userId) { - $log.debug('Change attempt number from ' + attempt + ' to ' + newAttempt + ' in SCORM ' + scormId); - userId = userId || $mmSite.getUserId(); - - var db = $mmSite.getDb(), - newEntry = { - scormid: scormId, - userid: userId, - attempt: newAttempt, - timemodified: $mmUtil.timestamp() - }; - - blockedScorms[scormId] = true; // Block the SCORM so it can't be synced. - - // Get current data. - return db.get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).then(function(entry) { - newEntry.timecreated = entry.timecreated; - newEntry.courseid = entry.courseid; - - // Insert new attempt. - return db.insert(mmaModScormOfflineAttemptsStore, newEntry).then(function() { - // Copy tracking data to the new attempt. - return self.getScormStoredData(scormId, attempt, userId).then(function(entries) { - var promises = []; - angular.forEach(entries, function(entry) { - entry.attempt = newAttempt; - entry.synced = 0; - promises.push(db.insert(mmaModScormOfflineTracksStore, entry)); - }); + self.changeAttemptNumber = function(siteId, scormId, attempt, newAttempt, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + $log.debug('Change attempt number from ' + attempt + ' to ' + newAttempt + ' in SCORM ' + scormId); + userId = userId || site.getUserId(); + + var db = site.getDb(), + newEntry = { + scormid: scormId, + userid: userId, + attempt: newAttempt, + timemodified: $mmUtil.timestamp() + }; + + if (!blockedScorms[siteId]) { + blockedScorms[siteId] = {}; + } + blockedScorms[siteId][scormId] = true; // Block the SCORM so it can't be synced. + + // Get current data. + return db.get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).then(function(entry) { + newEntry.timecreated = entry.timecreated; + newEntry.courseid = entry.courseid; + + // Insert new attempt. + return db.insert(mmaModScormOfflineAttemptsStore, newEntry).then(function() { + // Copy tracking data to the new attempt. + return self.getScormStoredData(siteId, scormId, attempt, userId).then(function(entries) { + var promises = []; + angular.forEach(entries, function(entry) { + entry.attempt = newAttempt; + entry.synced = 0; + promises.push(db.insert(mmaModScormOfflineTracksStore, entry)); + }); - return $mmUtil.allPromises(promises).then(function() { - // All entries inserted. Delete the old attempt. - return self.deleteAttempt(scormId, attempt).catch(function() { - // The delete failed, it shouldn't happen. Let's retry once. - return self.deleteAttempt(scormId, attempt).catch(function() {}); + return $mmUtil.allPromises(promises).then(function() { + // All entries inserted. Delete the old attempt. + return self.deleteAttempt(siteId, scormId, attempt).catch(function() { + // The delete failed, it shouldn't happen. Let's retry once. + return self.deleteAttempt(siteId, scormId, attempt).catch(function() {}); + }); + }); + }).catch(function() { + // Failed to get the data, remove the new attempt. + return self.deleteAttempt(siteId, scormId, newAttempt).then(function() { + return $q.reject(); }); - }); - }).catch(function() { - // Failed to get the data, remove the new attempt. - return self.deleteAttempt(scormId, newAttempt).then(function() { - return $q.reject(); }); }); + }).finally(function() { + blockedScorms[siteId][scormId] = false; // Unblock the SCORM. }); - }).finally(function() { - blockedScorms[scormId] = false; // Unblock the SCORM. }); - }; /** @@ -173,10 +178,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#clearBlockedScorms + * @param {String} [siteId] If set, clear the blocked SCORMs only for this site. Otherwise clear all SCORMs. * @return {Void} */ - self.clearBlockedScorms = function() { - blockedScorms = {}; + self.clearBlockedScorms = function(siteId) { + if (siteId) { + delete blockedScorms[siteId]; + } else { + blockedScorms = {}; + } }; /** @@ -185,49 +195,55 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#createNewAttempt + * @param {String} siteId Site ID. * @param {Object} scorm SCORM. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Number} attempt Number of the new attempt. * @param {Object} userData User data to store in the attempt. * @param {Object} [snapshot] Optional. Snapshot to store in the attempt. * @return {Promise} Promise resolved when the new attempt is created. */ - self.createNewAttempt = function(scorm, userId, attempt, userData, snapshot) { - $log.debug('Creating new offline attempt ' + attempt + ' in SCORM ' + scorm.id); - userId = userId || $mmSite.getUserId(); - - blockedScorms[scorm.id] = true; // Block the SCORM so it can't be synced. - - // Create attempt in DB. - var db = $mmSite.getDb(), - entry = { - scormid: scorm.id, - userid: userId, - attempt: attempt, - courseid: scorm.course, - timecreated: $mmUtil.timestamp(), - timemodified: $mmUtil.timestamp() - }; - - if (snapshot) { - // Save a snapshot of the data we had when we created the attempt. - // Remove the default data, we don't want to store it. - entry.snapshot = removeDefaultData(snapshot); - } + self.createNewAttempt = function(siteId, scorm, userId, attempt, userData, snapshot) { + return $mmSitesManager.getSite(siteId).then(function(site) { + $log.debug('Creating new offline attempt ' + attempt + ' in SCORM ' + scorm.id); + userId = userId || site.getUserId(); - return db.insert(mmaModScormOfflineAttemptsStore, entry).then(function() { - // Store all the data in userData. - var promises = []; - angular.forEach(userData, function(sco) { - var tracks = []; - angular.forEach(sco.userdata, function(value, element) { - tracks.push({element: element, value: value}); + if (!blockedScorms[siteId]) { + blockedScorms[siteId] = {}; + } + blockedScorms[siteId][scorm.id] = true; // Block the SCORM so it can't be synced. + + // Create attempt in DB. + var db = site.getDb(), + entry = { + scormid: scorm.id, + userid: userId, + attempt: attempt, + courseid: scorm.course, + timecreated: $mmUtil.timestamp(), + timemodified: $mmUtil.timestamp() + }; + + if (snapshot) { + // Save a snapshot of the data we had when we created the attempt. + // Remove the default data, we don't want to store it. + entry.snapshot = removeDefaultData(snapshot); + } + + return db.insert(mmaModScormOfflineAttemptsStore, entry).then(function() { + // Store all the data in userData. + var promises = []; + angular.forEach(userData, function(sco) { + var tracks = []; + angular.forEach(sco.userdata, function(value, element) { + tracks.push({element: element, value: value}); + }); + promises.push(self.saveTracks(siteId, scorm, sco.scoid, attempt, tracks, userData)); }); - promises.push(self.saveTracks(scorm, sco.scoid, attempt, tracks, userData)); + return $q.all(promises); + }).finally(function() { + blockedScorms[siteId][scorm.id] = false; // Unblock the SCORM. }); - return $q.all(promises); - }).finally(function() { - blockedScorms[scorm.id] = false; // Unblock the SCORM. }); }; @@ -237,29 +253,32 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#deleteAttempt + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when all the data has been deleted. */ - self.deleteAttempt = function(scormId, attempt, userId) { - $log.debug('Delete offline attempt ' + attempt + ' in SCORM ' + scormId); - userId = userId || $mmSite.getUserId(); + self.deleteAttempt = function(siteId, scormId, attempt, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + $log.debug('Delete offline attempt ' + attempt + ' in SCORM ' + scormId); + userId = userId || site.getUserId(); + + return self.getScormStoredData(siteId, scormId, attempt, userId).then(function(entries) { + var promises = [], + db = site.getDb(); + + // Delete all the tracks. + angular.forEach(entries, function(entry) { + var entryId = [entry.userid, entry.scormid, entry.scoid, entry.attempt, entry.element]; + promises.push(db.remove(mmaModScormOfflineTracksStore, entryId)); + }); - return self.getScormStoredData(scormId, attempt, userId).then(function(entries) { - var promises = [], - db = $mmSite.getDb(); + // Delete the attempt. + promises.push(db.remove(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt])); - // Delete all the tracks. - angular.forEach(entries, function(entry) { - var entryId = [entry.userid, entry.scormid, entry.scoid, entry.attempt, entry.element]; - promises.push(db.remove(mmaModScormOfflineTracksStore, entryId)); + return $q.all(promises); }); - - // Delete the attempt. - promises.push(db.remove(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt])); - - return $q.all(promises); }); }; @@ -327,21 +346,24 @@ angular.module('mm.addons.mod_scorm') } /** - * Get all the offline attempts in current site. + * Get all the offline attempts in a certain site. * * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getAllAttempts - * @return {Promise} Promise resolved when the offline attempts are retrieved. + * @param {String} [siteId] Site ID. If not set, use current site. + * @return {Promise} Promise resolved when the offline attempts are retrieved. */ - self.getAllAttempts = function() { - var db = $mmSite.getDb(); - if (!db) { - // No current site, return empty array. - return $q.when([]); - } + self.getAllAttempts = function(siteId) { + siteId = siteId || $mmSite.getId(); + + return $mmSitesManager.getSiteDb(siteId).then(function(db) { + if (!db) { + return $q.reject(); + } - return db.getAll(mmaModScormOfflineAttemptsStore); + return db.getAll(mmaModScormOfflineAttemptsStore); + }); }; /** @@ -350,16 +372,19 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getAttempts + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when the offline attempts are retrieved. */ - self.getAttempts = function(scormId, userId) { - userId = userId || $mmSite.getUserId(); + self.getAttempts = function(siteId, scormId, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); - var db = $mmSite.getDb(); - return db.whereEqual(mmaModScormOfflineAttemptsStore, 'scormAndUser', [scormId, userId]).then(function(attempts) { - return attempts; + var db = site.getDb(); + return db.whereEqual(mmaModScormOfflineAttemptsStore, 'scormAndUser', [scormId, userId]).then(function(attempts) { + return attempts; + }); }); }; @@ -369,18 +394,21 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getAttemptSnapshot + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved with the snapshot or undefined if no snapshot. */ - self.getAttemptSnapshot = function(scormId, attempt, userId) { - userId = userId || $mmSite.getUserId(); - - return $mmSite.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).catch(function() { - return {}; // Attempt not found. - }).then(function(entry) { - return entry.snapshot; + self.getAttemptSnapshot = function(siteId, scormId, attempt, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + + return site.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).catch(function() { + return {}; // Attempt not found. + }).then(function(entry) { + return entry.snapshot; + }); }); }; @@ -390,18 +418,21 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getAttemptCreationTime + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved with time the attempt was created. */ - self.getAttemptCreationTime = function(scormId, attempt, userId) { - userId = userId || $mmSite.getUserId(); - - return $mmSite.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).catch(function() { - return {}; // Attempt not found. - }).then(function(entry) { - return entry.timecreated; + self.getAttemptCreationTime = function(siteId, scormId, attempt, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + + return site.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).catch(function() { + return {}; // Attempt not found. + }).then(function(entry) { + return entry.timecreated; + }); }); }; @@ -411,26 +442,29 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getScormStoredData + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. - * @param {Number} [userId] User ID. If not defined, current user. * @param {Number} attempt Attempt number. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Boolean} excludeSynced True if it should only return not synced entries. * @param {Boolean} excludeNotSynced True if it should only return synced entries. * @return {Promise} Promise resolved with the entries. */ - self.getScormStoredData = function(scormId, attempt, userId, excludeSynced, excludeNotSynced) { - userId = userId || $mmSite.getUserId(); - - var where; - - if (excludeSynced && excludeNotSynced) { - return $q.when([]); - } else if (excludeSynced || excludeNotSynced) { - where = ['scormUserAttemptSynced', '=', [scormId, userId, attempt, excludeNotSynced ? 1 : 0]]; - } else { - where = ['scormUserAttempt', '=', [scormId, userId, attempt]]; - } - return $mmSite.getDb().query(mmaModScormOfflineTracksStore, where); + self.getScormStoredData = function(siteId, scormId, attempt, userId, excludeSynced, excludeNotSynced) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + + var where; + + if (excludeSynced && excludeNotSynced) { + return $q.when([]); + } else if (excludeSynced || excludeNotSynced) { + where = ['scormUserAttemptSynced', '=', [scormId, userId, attempt, excludeNotSynced ? 1 : 0]]; + } else { + where = ['scormUserAttempt', '=', [scormId, userId, attempt]]; + } + return site.getDb().query(mmaModScormOfflineTracksStore, where); + }); }; /** @@ -439,110 +473,114 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#getScormUserData + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Object[]} scos SCOs returned by $mmaModScorm#getScos. Required. * @return {Promise} Promise resolved when the user data is retrieved. */ - self.getScormUserData = function(scormId, attempt, userId, scos) { - userId = userId || $mmSite.getUserId(); - - // Get user data. Ordering when using a compound index is complex, so we won't order by scoid. - return self.getScormStoredData(scormId, attempt, userId).then(function(entries) { - var response = {}, - launchUrls = getLaunchUrlsFromScos(scos), - userId = $mmSite.getUserId(), - username = $mmSite.getInfo().username, - fullName = $mmSite.getInfo().fullname; - - // Gather user data retrieved from DB, grouping it by scoid. - angular.forEach(entries, function(entry) { - var scoid = entry.scoid; - if (!response[scoid]) { - // Initialize SCO. - response[scoid] = { - scoid: scoid, - userdata: { - userid: userId, + self.getScormUserData = function(siteId, scormId, attempt, userId, scos) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + + // Get user data. Ordering when using a compound index is complex, so we won't order by scoid. + return self.getScormStoredData(siteId, scormId, attempt, userId).then(function(entries) { + var response = {}, + launchUrls = getLaunchUrlsFromScos(scos), + userId = site.getUserId(), + username = site.getInfo().username, + fullName = site.getInfo().fullname; + + // Gather user data retrieved from DB, grouping it by scoid. + angular.forEach(entries, function(entry) { + var scoid = entry.scoid; + if (!response[scoid]) { + // Initialize SCO. + response[scoid] = { scoid: scoid, - timemodified: 0 - } - }; - } - response[scoid].userdata[entry.element] = entry.value; - if (entry.timemodified > response[scoid].userdata.timemodified) { - response[scoid].userdata.timemodified = entry.timemodified; - } - }); + userdata: { + userid: userId, + scoid: scoid, + timemodified: 0 + } + }; + } + response[scoid].userdata[entry.element] = entry.value; + if (entry.timemodified > response[scoid].userdata.timemodified) { + response[scoid].userdata.timemodified = entry.timemodified; + } + }); - // Format each user data retrieved. - angular.forEach(response, function(sco) { - sco.userdata = formatInteractions(sco.userdata); - }); + // Format each user data retrieved. + angular.forEach(response, function(sco) { + sco.userdata = formatInteractions(sco.userdata); + }); - // Create empty entries for the SCOs without user data stored. - angular.forEach(scos, function(sco) { - if (!response[sco.id]) { - response[sco.id] = { - scoid: sco.id, - userdata: { - status: '', - score_raw: '' - } - }; - } - }); + // Create empty entries for the SCOs without user data stored. + angular.forEach(scos, function(sco) { + if (!response[sco.id]) { + response[sco.id] = { + scoid: sco.id, + userdata: { + status: '', + score_raw: '' + } + }; + } + }); - // Calculate defaultdata. - angular.forEach(response, function(sco) { - sco.defaultdata = {}; - sco.defaultdata['cmi.core.student_id'] = username; - sco.defaultdata['cmi.core.student_name'] = fullName; - sco.defaultdata['cmi.core.lesson_mode'] = 'normal'; // Overridden in player. - sco.defaultdata['cmi.core.credit'] = 'credit'; // Overridden in player. - if (sco.userdata.status === '') { - sco.defaultdata['cmi.core.entry'] = 'ab-initio'; - } else if (sco.userdata['cmi.core.exit'] === 'suspend') { - sco.defaultdata['cmi.core.entry'] = 'resume'; - } else { - sco.defaultdata['cmi.core.entry'] = ''; - } - sco.defaultdata['cmi.student_data.mastery_score'] = scormIsset(sco.userdata, 'masteryscore'); - sco.defaultdata['cmi.student_data.max_time_allowed'] = scormIsset(sco.userdata, 'max_time_allowed'); - sco.defaultdata['cmi.student_data.time_limit_action'] = scormIsset(sco.userdata, 'time_limit_action'); - sco.defaultdata['cmi.core.total_time'] = scormIsset(sco.userdata, 'cmi.core.total_time', '00:00:00'); - sco.defaultdata['cmi.launch_data'] = launchUrls[sco.scoid]; - - // Now handle standard userdata items. - sco.defaultdata['cmi.core.lesson_location'] = scormIsset(sco.userdata, 'cmi.core.lesson_location'); - sco.defaultdata['cmi.core.lesson_status'] = scormIsset(sco.userdata, 'cmi.core.lesson_status'); - sco.defaultdata['cmi.core.score.raw'] = scormIsset(sco.userdata, 'cmi.core.score.raw'); - sco.defaultdata['cmi.core.score.max'] = scormIsset(sco.userdata, 'cmi.core.score.max'); - sco.defaultdata['cmi.core.score.min'] = scormIsset(sco.userdata, 'cmi.core.score.min'); - sco.defaultdata['cmi.core.exit'] = scormIsset(sco.userdata, 'cmi.core.exit'); - sco.defaultdata['cmi.suspend_data'] = scormIsset(sco.userdata, 'cmi.suspend_data'); - sco.defaultdata['cmi.comments'] = scormIsset(sco.userdata, 'cmi.comments'); - sco.defaultdata['cmi.student_preference.language'] = scormIsset(sco.userdata, 'cmi.student_preference.language'); - sco.defaultdata['cmi.student_preference.audio'] = scormIsset(sco.userdata, 'cmi.student_preference.audio', '0'); - sco.defaultdata['cmi.student_preference.speed'] = scormIsset(sco.userdata, 'cmi.student_preference.speed', '0'); - sco.defaultdata['cmi.student_preference.text'] = scormIsset(sco.userdata, 'cmi.student_preference.text', '0'); - - // Some data needs to be both in default data and user data. - sco.userdata.student_id = username; - sco.userdata.student_name = fullName; - sco.userdata.mode = sco.defaultdata['cmi.core.lesson_mode']; - sco.userdata.credit = sco.defaultdata['cmi.core.credit']; - sco.userdata.entry = sco.defaultdata['cmi.core.entry']; - }); + // Calculate defaultdata. + angular.forEach(response, function(sco) { + sco.defaultdata = {}; + sco.defaultdata['cmi.core.student_id'] = username; + sco.defaultdata['cmi.core.student_name'] = fullName; + sco.defaultdata['cmi.core.lesson_mode'] = 'normal'; // Overridden in player. + sco.defaultdata['cmi.core.credit'] = 'credit'; // Overridden in player. + if (sco.userdata.status === '') { + sco.defaultdata['cmi.core.entry'] = 'ab-initio'; + } else if (sco.userdata['cmi.core.exit'] === 'suspend') { + sco.defaultdata['cmi.core.entry'] = 'resume'; + } else { + sco.defaultdata['cmi.core.entry'] = ''; + } + sco.defaultdata['cmi.student_data.mastery_score'] = scormIsset(sco.userdata, 'masteryscore'); + sco.defaultdata['cmi.student_data.max_time_allowed'] = scormIsset(sco.userdata, 'max_time_allowed'); + sco.defaultdata['cmi.student_data.time_limit_action'] = scormIsset(sco.userdata, 'time_limit_action'); + sco.defaultdata['cmi.core.total_time'] = scormIsset(sco.userdata, 'cmi.core.total_time', '00:00:00'); + sco.defaultdata['cmi.launch_data'] = launchUrls[sco.scoid]; + + // Now handle standard userdata items. + sco.defaultdata['cmi.core.lesson_location'] = scormIsset(sco.userdata, 'cmi.core.lesson_location'); + sco.defaultdata['cmi.core.lesson_status'] = scormIsset(sco.userdata, 'cmi.core.lesson_status'); + sco.defaultdata['cmi.core.score.raw'] = scormIsset(sco.userdata, 'cmi.core.score.raw'); + sco.defaultdata['cmi.core.score.max'] = scormIsset(sco.userdata, 'cmi.core.score.max'); + sco.defaultdata['cmi.core.score.min'] = scormIsset(sco.userdata, 'cmi.core.score.min'); + sco.defaultdata['cmi.core.exit'] = scormIsset(sco.userdata, 'cmi.core.exit'); + sco.defaultdata['cmi.suspend_data'] = scormIsset(sco.userdata, 'cmi.suspend_data'); + sco.defaultdata['cmi.comments'] = scormIsset(sco.userdata, 'cmi.comments'); + sco.defaultdata['cmi.student_preference.language'] = scormIsset(sco.userdata, 'cmi.student_preference.language'); + sco.defaultdata['cmi.student_preference.audio'] = scormIsset(sco.userdata, 'cmi.student_preference.audio', '0'); + sco.defaultdata['cmi.student_preference.speed'] = scormIsset(sco.userdata, 'cmi.student_preference.speed', '0'); + sco.defaultdata['cmi.student_preference.text'] = scormIsset(sco.userdata, 'cmi.student_preference.text', '0'); + + // Some data needs to be both in default data and user data. + sco.userdata.student_id = username; + sco.userdata.student_name = fullName; + sco.userdata.mode = sco.defaultdata['cmi.core.lesson_mode']; + sco.userdata.credit = sco.defaultdata['cmi.core.credit']; + sco.userdata.entry = sco.defaultdata['cmi.core.entry']; + }); - return response; + return response; + }); }); }; /** * Function to insert a track in the DB. Please do not use it directly, use insertTrack instead. * + * @param {Object} db Site's DB. * @param {Number} userId User ID. * @param {Number} scormId SCORM ID. * @param {Number} scoId SCO ID. @@ -552,7 +590,7 @@ angular.module('mm.addons.mod_scorm') * @param {Boolean} synchronous True if insert should NOT return a promise. Please use it only if synchronous is a must. * @return {Boolean|Promise} Returns a promise if synchronous=false, otherwise returns a boolean. */ - function insertTrackToDB(userId, scormId, scoId, attempt, element, value, synchronous) { + function insertTrackToDB(db, userId, scormId, scoId, attempt, element, value, synchronous) { var entry = { userid: userId, scormid: scormId, @@ -564,9 +602,9 @@ angular.module('mm.addons.mod_scorm') synced: 0 }; if (synchronous) { - return $mmSite.getDb().insertSync(mmaModScormOfflineTracksStore, entry); + return db.insertSync(mmaModScormOfflineTracksStore, entry); } else { - return $mmSite.getDb().insert(mmaModScormOfflineTracksStore, entry); + return db.insert(mmaModScormOfflineTracksStore, entry); } } @@ -574,7 +612,8 @@ angular.module('mm.addons.mod_scorm') * Insert a track in the offline tracks store. * This function is based on Moodle's scorm_insert_track. * - * @param {Number} [userId] User ID. + * @param {String} siteId Site ID. + * @param {Number} [userId] User ID. If not set use site's current user. * @param {Number} scormId SCORM ID. * @param {Number} scoId SCO ID. * @param {Number} attempt Attempt number. @@ -584,43 +623,46 @@ angular.module('mm.addons.mod_scorm') * @param {Object} [scoData] User data for the given SCO. * @return {Promise} Promise resolved when the insert is done. */ - function insertTrack(userId, scormId, scoId, attempt, element, value, forceCompleted, scoData) { - userId = userId || $mmSite.getUserId(); - scoData = scoData || {}; - - var promises = [], // List of promises for actions previous to the real insert. - lessonStatusInserted = false, - scoUserData = scoData.userdata || {}; - - if (forceCompleted) { - if (element == 'cmi.core.lesson_status' && value == 'incomplete') { - if (scoUserData['cmi.core.score.raw']) { - value = 'completed'; + function insertTrack(siteId, userId, scormId, scoId, attempt, element, value, forceCompleted, scoData) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + scoData = scoData || {}; + + var promises = [], // List of promises for actions previous to the real insert. + lessonStatusInserted = false, + scoUserData = scoData.userdata || {}, + db = site.getDb(); + + if (forceCompleted) { + if (element == 'cmi.core.lesson_status' && value == 'incomplete') { + if (scoUserData['cmi.core.score.raw']) { + value = 'completed'; + } } - } - if (element == 'cmi.core.score.raw') { - if (scoUserData['cmi.core.lesson_status'] == 'incomplete') { - lessonStatusInserted = true; - promises.push(insertTrackToDB(userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed')); + if (element == 'cmi.core.score.raw') { + if (scoUserData['cmi.core.lesson_status'] == 'incomplete') { + lessonStatusInserted = true; + promises.push(insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed')); + } } } - } - return $q.all(promises).then(function() { - // Don't update x.start.time, keep the original value. - if (!scoUserData[element] || element != 'x.start.time') { - - return insertTrackToDB(userId, scormId, scoId, attempt, element, value).catch(function() { - if (lessonStatusInserted) { - // Rollback previous insert. - return insertTrackToDB(userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete') - .then(function() { - return $q.reject(); - }); - } - return $q.reject(); - }); - } + return $q.all(promises).then(function() { + // Don't update x.start.time, keep the original value. + if (!scoUserData[element] || element != 'x.start.time') { + + return insertTrackToDB(db, userId, scormId, scoId, attempt, element, value).catch(function() { + if (lessonStatusInserted) { + // Rollback previous insert. + return insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete') + .then(function() { + return $q.reject(); + }); + } + return $q.reject(); + }); + } + }); }); } @@ -629,7 +671,7 @@ angular.module('mm.addons.mod_scorm') * Please use this function only if synchronous is a must. It's recommended to use insertTrack. * This function is based on Moodle's scorm_insert_track. * - * @param {Number} [userId] User ID. + * @param {Number} [userId] User ID. If not set use current user. * @param {Number} scormId SCORM ID. * @param {Number} scoId SCO ID. * @param {Number} attempt Attempt number. @@ -644,7 +686,8 @@ angular.module('mm.addons.mod_scorm') scoData = scoData || {}; var lessonStatusInserted = false, - scoUserData = scoData.userdata || {}; + scoUserData = scoData.userdata || {}, + db = $mmSite.getDb(); if (forceCompleted) { if (element == 'cmi.core.lesson_status' && value == 'incomplete') { @@ -655,7 +698,7 @@ angular.module('mm.addons.mod_scorm') if (element == 'cmi.core.score.raw') { if (scoUserData['cmi.core.lesson_status'] == 'incomplete') { lessonStatusInserted = true; - if (!insertTrackToDB(userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed', true)) { + if (!insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'completed', true)) { return false; } } @@ -664,11 +707,11 @@ angular.module('mm.addons.mod_scorm') // Don't update x.start.time, keep the original value. if (!scoUserData[element] || element != 'x.start.time') { - if (!insertTrackToDB(userId, scormId, scoId, attempt, element, value, true)) { + if (!insertTrackToDB(db, userId, scormId, scoId, attempt, element, value, true)) { // Insert failed. if (lessonStatusInserted) { // Rollback previous insert. - insertTrackToDB(userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete', true); + insertTrackToDB(db, userId, scormId, scoId, attempt, 'cmi.core.lesson_status', 'incomplete', true); } return false; } @@ -682,11 +725,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#isScormBlocked + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @return {Boolean} True if blocked, false otherwise. */ - self.isScormBlocked = function(scormId) { - return !!blockedScorms[scormId]; + self.isScormBlocked = function(siteId, scormId) { + if (!blockedScorms[siteId]) { + return false; + } + return !!blockedScorms[siteId][scormId]; }; /** @@ -695,28 +742,31 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#markAsSynced + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Number} scoId SCO ID. * @return {Promise} Promise resolved when marked. */ - self.markAsSynced = function(scormId, attempt, userId, scoId) { - $log.debug('Mark SCO ' + scoId + ' as synced for attempt ' + attempt + ' in SCORM ' + scormId); - userId = userId || $mmSite.getUserId(); - - return self.getScormStoredData(scormId, attempt, userId, true).then(function(entries) { - var promises = [], - db = $mmSite.getDb(); - - angular.forEach(entries, function(entry) { - if (entry.scoid == scoId) { - entry.synced = 1; - promises.push(db.insert(mmaModScormOfflineTracksStore, entry)); - } - }); + self.markAsSynced = function(siteId, scormId, attempt, userId, scoId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + $log.debug('Mark SCO ' + scoId + ' as synced for attempt ' + attempt + ' in SCORM ' + scormId); + userId = userId || site.getUserId(); + + return self.getScormStoredData(siteId, scormId, attempt, userId, true).then(function(entries) { + var promises = [], + db = site.getDb(); + + angular.forEach(entries, function(entry) { + if (entry.scoid == scoId) { + entry.synced = 1; + promises.push(db.insert(mmaModScormOfflineTracksStore, entry)); + } + }); - return $q.all(promises); + return $q.all(promises); + }); }); }; @@ -740,6 +790,7 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#saveTracks + * @param {String} siteId Site ID. * @param {Object} scorm SCORM. * @param {Number} scoId Sco ID. * @param {Number} attempt Attempt number. @@ -747,23 +798,28 @@ angular.module('mm.addons.mod_scorm') * @param {Object} userData User data for this attempt and SCO. * @return {Promise} Promise resolved when data is saved. */ - self.saveTracks = function(scorm, scoId, attempt, tracks, userData) { - var userId = $mmSite.getUserId(), - initialBlocked = !!blockedScorms[scorm.id]; // Save initial blocked state. + self.saveTracks = function(siteId, scorm, scoId, attempt, tracks, userData) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var userId = site.getUserId(), + initialBlocked; - blockedScorms[scorm.id] = true; // Block the SCORM so it can't be synced. - - // Insert all the tracks. - var promises = []; - angular.forEach(tracks, function(track) { - promises.push( - insertTrack(userId, scorm.id, scoId, attempt, track.element, track.value, scorm.forcecompleted, userData[scoId]) - ); - }); - return $q.all(promises).finally(function() { - if (!initialBlocked) { - blockedScorms[scorm.id] = false; // Unblock the SCORM only if it wasn't blocked by another function. + if (!blockedScorms[siteId]) { + blockedScorms[siteId] = {}; } + initialBlocked = !!blockedScorms[siteId][scorm.id]; // Save initial blocked state. + blockedScorms[siteId][scorm.id] = true; // Block the SCORM so it can't be synced. + + // Insert all the tracks. + var promises = []; + angular.forEach(tracks, function(track) { + promises.push(insertTrack(siteId, userId, scorm.id, scoId, attempt, + track.element, track.value, scorm.forcecompleted, userData[scoId])); + }); + return $q.all(promises).finally(function() { + if (!initialBlocked) { + blockedScorms[siteId][scorm.id] = false; // Unblock the SCORM only if it wasn't blocked by another function. + } + }); }); }; @@ -821,20 +877,23 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOffline#setAttemptSnapshot + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. * @param {Object} userData User data to store as snapshot. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when snapshot has been stored. */ - self.setAttemptSnapshot = function(scormId, attempt, userData, userId) { - $log.debug('Set snapshot for attempt ' + attempt + ' in SCORM ' + scormId); - userId = userId || $mmSite.getUserId(); - - return $mmSite.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).then(function(entry) { - entry.snapshot = removeDefaultData(userData); - entry.timemodified = $mmUtil.timestamp(); - return $mmSite.getDb().insert(mmaModScormOfflineAttemptsStore, entry); + self.setAttemptSnapshot = function(siteId, scormId, attempt, userData, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + $log.debug('Set snapshot for attempt ' + attempt + ' in SCORM ' + scormId); + userId = userId || site.getUserId(); + + return site.getDb().get(mmaModScormOfflineAttemptsStore, [scormId, userId, attempt]).then(function(entry) { + entry.snapshot = removeDefaultData(userData); + entry.timemodified = $mmUtil.timestamp(); + return site.getDb().insert(mmaModScormOfflineAttemptsStore, entry); + }); }); }; diff --git a/www/addons/mod_scorm/services/scorm_online.js b/www/addons/mod_scorm/services/scorm_online.js index 9d1ab52c869..bf5e6fff905 100644 --- a/www/addons/mod_scorm/services/scorm_online.js +++ b/www/addons/mod_scorm/services/scorm_online.js @@ -22,7 +22,7 @@ angular.module('mm.addons.mod_scorm') * @ngdoc service * @name $mmaModScormOnline */ -.factory('$mmaModScormOnline', function($mmSite, $q, $mmWS, $log, mmCoreWSPrefix) { +.factory('$mmaModScormOnline', function($mmSitesManager, $mmSite, $q, $mmWS, $log, mmCoreWSPrefix) { $log = $log.getInstance('$mmaModScormOnline'); var self = {}, @@ -34,10 +34,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#clearBlockedScorms + * @param {String} [siteId] If set, clear the blocked SCORMs only for this site. Otherwise clear all SCORMs. * @return {Void} */ - self.clearBlockedScorms = function() { - blockedScorms = {}; + self.clearBlockedScorms = function(siteId) { + if (siteId) { + delete blockedScorms[siteId]; + } else { + blockedScorms = {}; + } }; /** @@ -58,38 +63,37 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#getAttemptCount + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @param {Boolean} ignoreMissing True if it should ignore attempts that haven't reported a grade/completion. * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the attempt count is retrieved. */ - self.getAttemptCount = function(scormId, userId, ignoreMissing, ignoreCache) { - userId = userId || $mmSite.getUserId(); - - if (!$mmSite.isLoggedIn()) { - return $q.reject(); - } - - var params = { - scormid: scormId, - userid: userId, - ignoremissingcompletion: ignoreMissing ? 1 : 0 - }, - preSets = { - cacheKey: getAttemptCountCacheKey(scormId, userId) - }; - - if (ignoreCache) { - preSets.getFromCache = 0; - preSets.emergencyCache = 0; - } - - return $mmSite.read('mod_scorm_get_scorm_attempt_count', params, preSets).then(function(response) { - if (response && typeof response.attemptscount != 'undefined') { - return response.attemptscount; + self.getAttemptCount = function(siteId, scormId, userId, ignoreMissing, ignoreCache) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + + var params = { + scormid: scormId, + userid: userId, + ignoremissingcompletion: ignoreMissing ? 1 : 0 + }, + preSets = { + cacheKey: getAttemptCountCacheKey(scormId, userId) + }; + + if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; } - return $q.reject(); + + return site.read('mod_scorm_get_scorm_attempt_count', params, preSets).then(function(response) { + if (response && typeof response.attemptscount != 'undefined') { + return response.attemptscount; + } + return $q.reject(); + }); }); }; @@ -120,52 +124,51 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#getScormUserData + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. * @param {Number} attempt Attempt number. * @param {Boolean} ignoreCache True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when the user data is retrieved. */ - self.getScormUserData = function(scormId, attempt, ignoreCache) { - var params = { - scormid: scormId, - attempt: attempt - }, - preSets = { - cacheKey: getScormUserDataCacheKey(scormId, attempt) - }; - - if (ignoreCache) { - preSets.getFromCache = 0; - preSets.emergencyCache = 0; - } - - if (!$mmSite.isLoggedIn()) { - return $q.reject(); - } - - return $mmSite.read('mod_scorm_get_scorm_user_data', params, preSets).then(function(response) { - if (response && response.data) { - // Format the response. - var data = {}; - angular.forEach(response.data, function(sco) { - var formattedDefaultData = {}, - formattedUserData = {}; + self.getScormUserData = function(siteId, scormId, attempt, ignoreCache) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + scormid: scormId, + attempt: attempt + }, + preSets = { + cacheKey: getScormUserDataCacheKey(scormId, attempt) + }; + + if (ignoreCache) { + preSets.getFromCache = 0; + preSets.emergencyCache = 0; + } - angular.forEach(sco.defaultdata, function(entry) { - formattedDefaultData[entry.element] = entry.value; - }); - angular.forEach(sco.userdata, function(entry) { - formattedUserData[entry.element] = entry.value; + return site.read('mod_scorm_get_scorm_user_data', params, preSets).then(function(response) { + if (response && response.data) { + // Format the response. + var data = {}; + angular.forEach(response.data, function(sco) { + var formattedDefaultData = {}, + formattedUserData = {}; + + angular.forEach(sco.defaultdata, function(entry) { + formattedDefaultData[entry.element] = entry.value; + }); + angular.forEach(sco.userdata, function(entry) { + formattedUserData[entry.element] = entry.value; + }); + + sco.defaultdata = formattedDefaultData; + sco.userdata = formattedUserData; + + data[sco.scoid] = sco; }); - - sco.defaultdata = formattedDefaultData; - sco.userdata = formattedUserData; - - data[sco.scoid] = sco; - }); - return data; - } - return $q.reject(); + return data; + } + return $q.reject(); + }); }); }; @@ -175,12 +178,16 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#invalidateAttemptCount + * @param {String} siteId Site ID. * @param {Number} scormId SCORM ID. - * @param {Number} [userId] User ID. If not defined, current user. + * @param {Number} [userId] User ID. If not defined use site's current user. * @return {Promise} Promise resolved when the data is invalidated. */ - self.invalidateAttemptCount = function(scormId, userId) { - return $mmSite.invalidateWsCacheForKey(getAttemptCountCacheKey(scormId, userId)); + self.invalidateAttemptCount = function(siteId, scormId, userId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + userId = userId || site.getUserId(); + return site.invalidateWsCacheForKey(getAttemptCountCacheKey(scormId, userId)); + }); }; /** @@ -189,11 +196,14 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#invalidateScormUserData - * @param {Number} scormId SCORM ID. - * @return {Promise} Promise resolved when the data is invalidated. + * @param {String} siteId Site ID. + * @param {Number} scormId SCORM ID. + * @return {Promise} Promise resolved when the data is invalidated. */ - self.invalidateScormUserData = function(scormId) { - return $mmSite.invalidateWsCacheForKeyStartingWith(getScormUserDataCommonCacheKey(scormId)); + self.invalidateScormUserData = function(siteId, scormId) { + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.invalidateWsCacheForKeyStartingWith(getScormUserDataCommonCacheKey(scormId)); + }); }; /** @@ -202,11 +212,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#isScormBlocked + * @param {String} siteId Site ID. If not set, use current site. * @param {Number} scormId SCORM ID. * @return {Boolean} True if blocked, false otherwise. */ - self.isScormBlocked = function(scormId) { - return !!blockedScorms[scormId]; + self.isScormBlocked = function(siteId, scormId) { + if (!blockedScorms[siteId]) { + return false; + } + return !!blockedScorms[siteId][scormId]; }; /** @@ -215,36 +229,38 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormOnline#saveTracks + * @param {String} siteId Site ID. If not set, use current site. * @param {Number} scormId SCORM ID. * @param {Number} scoId Sco ID. * @param {Number} attempt Attempt number. * @param {Object[]} tracks Tracking data. * @return {Promise} Promise resolved when data is saved. */ - self.saveTracks = function(scormId, scoId, attempt, tracks) { - var params = { - scoid: scoId, - attempt: attempt, - tracks: tracks - }; - - if (!tracks || !tracks.length) { - return $q.when(); // Nothing to save. - } - - if (!$mmSite.isLoggedIn()) { - return $q.reject(); - } + self.saveTracks = function(siteId, scormId, scoId, attempt, tracks) { + return $mmSitesManager.getSite(siteId).then(function(site) { + var params = { + scoid: scoId, + attempt: attempt, + tracks: tracks + }; - blockedScorms[scormId] = true; + if (!tracks || !tracks.length) { + return $q.when(); // Nothing to save. + } - return $mmSite.write('mod_scorm_insert_scorm_tracks', params).then(function(response) { - if (response && response.trackids) { - return response.trackids; + if (!blockedScorms[siteId]) { + blockedScorms[siteId] = {}; } - return $q.reject(); - }).finally(function() { - blockedScorms[scormId] = false; + blockedScorms[siteId][scormId] = true; + + return site.write('mod_scorm_insert_scorm_tracks', params).then(function(response) { + if (response && response.trackids) { + return response.trackids; + } + return $q.reject(); + }).finally(function() { + blockedScorms[siteId][scormId] = false; + }); }); }; diff --git a/www/addons/mod_scorm/services/scorm_sync.js b/www/addons/mod_scorm/services/scorm_sync.js index f3fb830bd5e..4477aef4118 100644 --- a/www/addons/mod_scorm/services/scorm_sync.js +++ b/www/addons/mod_scorm/services/scorm_sync.js @@ -36,7 +36,7 @@ angular.module('mm.addons.mod_scorm') */ .factory('$mmaModScormSync', function($mmaModScorm, $mmSite, $q, $translate, $mmaModScormOnline, $mmaModScormOffline, $mmUtil, $log, mmaModScormSynchronizationStore, mmaModScormSyncTime, $mmConfig, mmCoreSettingsSyncOnlyOnWifi, $mmApp, - $mmEvents, mmaModScormEventAutomSynced) { + $mmEvents, mmaModScormEventAutomSynced, $mmSitesManager) { $log = $log.getInstance('$mmaModScormSync'); var self = {}, @@ -48,14 +48,18 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#getScormSyncTime - * @param {Number} scormId SCORM ID. - * @return {Promise} Promise resolved with the time. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with the time. */ - self.getScormSyncTime = function(scormId) { - return $mmSite.getDb().get(mmaModScormSynchronizationStore, scormId).then(function(entry) { - return entry.time; - }).catch(function() { - return 0; + self.getScormSyncTime = function(scormId, siteId) { + siteId = siteId || $mmSite.getId(); + return $mmSitesManager.getSiteDb(siteId).then(function(db) { + return db.get(mmaModScormSynchronizationStore, scormId).then(function(entry) { + return entry.time; + }).catch(function() { + return 0; + }); }); }; @@ -65,15 +69,19 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#setScormSyncTime - * @param {Number} scormId SCORM ID. - * @param {Number} [time] Time to set. If not defined, current time. - * @return {Promise} Promise resolved when the time is set. + * @param {Number} scormId SCORM ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Number} [time] Time to set. If not defined, current time. + * @return {Promise} Promise resolved when the time is set. */ - self.setScormSyncTime = function(scormId, time) { - if (typeof time == 'undefined') { - time = new Date().getTime(); - } - return $mmSite.getDb().insert(mmaModScormSynchronizationStore, {scormid: scormId, time: time}); + self.setScormSyncTime = function(scormId, siteId, time) { + siteId = siteId || $mmSite.getId(); + return $mmSitesManager.getSiteDb(siteId).then(function(db) { + if (typeof time == 'undefined') { + time = new Date().getTime(); + } + return db.insert(mmaModScormSynchronizationStore, {scormid: scormId, time: time}); + }); }; /** @@ -82,9 +90,10 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#syncAllScorms - * @return {Promise} Promise resolved when the sync is done. + * @param {String} [siteId] Site ID to sync. If not defined, sync all sites. + * @return {Promise} Promise resolved when the sync is done. */ - self.syncAllScorms = function() { + self.syncAllScorms = function(siteId) { if (!$mmApp.isOnline()) { $log.debug('Cannot sync all SCORMs because device is offline.'); return $q.reject(); @@ -98,39 +107,58 @@ angular.module('mm.addons.mod_scorm') return $q.reject(); } - return $mmaModScormOffline.getAllAttempts().then(function(attempts) { - var scorms = [], - ids = [], // To prevent duplicates. - promises = [], - siteId = $mmSite.getId(); - - // Get the IDs of all the SCORMs that have something to be synced. - angular.forEach(attempts, function(attempt) { - if (ids.indexOf(attempt.scormid) == -1) { - ids.push(attempt.scormid); - scorms.push({ - id: attempt.scormid, - courseid: attempt.courseid + var promise; + if (!siteId) { + // No site ID defined, sync all sites. + $log.debug('Try to sync SCORMs in all sites.'); + promise = $mmSitesManager.getSitesIds(); + } else { + $log.debug('Try to sync SCORMs in site ' + siteId); + promise = $q.when([siteId]); + } + + return promise.then(function(siteIds) { + var sitePromises = []; + + angular.forEach(siteIds, function(siteId) { + sitePromises.push($mmaModScormOffline.getAllAttempts(siteId).then(function(attempts) { + var scorms = [], + ids = [], // To prevent duplicates. + promises = []; + + // Get the IDs of all the SCORMs that have something to be synced. + angular.forEach(attempts, function(attempt) { + if (ids.indexOf(attempt.scormid) == -1) { + ids.push(attempt.scormid); + scorms.push({ + id: attempt.scormid, + courseid: attempt.courseid + }); + } }); - } - }); - // Sync all SCORMs that haven't been synced for a while and that aren't played right now. - angular.forEach(scorms, function(scorm) { - if (!$mmaModScorm.isScormBeingPlayed(scorm.id)) { - promises.push($mmaModScorm.getScormById(scorm.courseid, scorm.id).then(function(scorm) { - return self.syncScormIfNeeded(scorm).then(function(warnings) { - if (typeof warnings != 'undefined') { - // We tried to sync. Send event. - $mmEvents.trigger(mmaModScormEventAutomSynced, { - siteid: siteId, - scormid: scorm.id + // Sync all SCORMs that haven't been synced for a while and that aren't played right now. + angular.forEach(scorms, function(scorm) { + if (!$mmaModScorm.isScormBeingPlayed(scorm.id, siteId)) { + promises.push($mmaModScorm.getScormById(scorm.courseid, scorm.id, '', siteId).then(function(scorm) { + return self.syncScormIfNeeded(scorm, siteId).then(function(warnings) { + if (typeof warnings != 'undefined') { + // We tried to sync. Send event. + $mmEvents.trigger(mmaModScormEventAutomSynced, { + siteid: siteId, + scormid: scorm.id + }); + } }); - } - }); - })); - } + })); + } + }); + + return $q.all(promises); + })); }); + + return $q.all(sitePromises); }); }); }; @@ -142,15 +170,17 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#_syncAttempt - * @param {Number} scormId SCORM ID. - * @param {Number} attempt Attempt number. - * @return {Promise} Promise resolved when the attempt is successfully synced. + * @param {Number} scormId SCORM ID. + * @param {Number} attempt Attempt number. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the attempt is successfully synced. * @protected */ - self._syncAttempt = function(scormId, attempt) { - $log.debug('Try to sync attempt ' + attempt + ' in SCORM ' + scormId); + self._syncAttempt = function(scormId, attempt, siteId) { + siteId = siteId || $mmSite.getId(); + $log.debug('Try to sync attempt ' + attempt + ' in SCORM ' + scormId + ' and site ' + siteId); // Get only not synced entries. - return $mmaModScormOffline.getScormStoredData(scormId, attempt, undefined, true).then(function(entries) { + return $mmaModScormOffline.getScormStoredData(siteId, scormId, attempt, undefined, true).then(function(entries) { var scos = {}, promises = [], somethingSynced = false; @@ -169,9 +199,9 @@ angular.module('mm.addons.mod_scorm') }); angular.forEach(scos, function(tracks, scoId) { - promises.push($mmaModScormOnline.saveTracks(scormId, scoId, attempt, tracks).then(function() { + promises.push($mmaModScormOnline.saveTracks(siteId, scormId, scoId, attempt, tracks).then(function() { // Sco data successfully sent. Mark them as synced. This is needed because some SCOs sync might fail. - return $mmaModScormOffline.markAsSynced(scormId, attempt, undefined, scoId).catch(function() { + return $mmaModScormOffline.markAsSynced(siteId, scormId, attempt, undefined, scoId).catch(function() { // Ignore errors. }).then(function() { somethingSynced = true; @@ -181,9 +211,9 @@ angular.module('mm.addons.mod_scorm') return $mmUtil.allPromises(promises).then(function() { // Attempt has been sent. Let's delete it from local. - return $mmaModScormOffline.deleteAttempt(scormId, attempt).catch(function() { + return $mmaModScormOffline.deleteAttempt(siteId, scormId, attempt).catch(function() { // Failed to delete (shouldn't happen). Let's retry once. - return $mmaModScormOffline.deleteAttempt(scormId, attempt).catch(function() { + return $mmaModScormOffline.deleteAttempt(siteId, scormId, attempt).catch(function() { // Maybe there's something wrong with the data or the storage implementation. $log.error('After sync: error deleting attempt ' + attempt + ' in SCORM ' + scormId); }); @@ -193,7 +223,7 @@ angular.module('mm.addons.mod_scorm') // Some SCOs have been synced and some not. We'll try to store a snapshot of the current state // to be able to re-try the synchronization later. $log.error('Error synchronizing some SCOs for attempt ' + attempt + ' in SCORM ' + scormId + '. Saving snapshot.'); - return saveSyncSnapshot(scormId, attempt).then(function() { + return saveSyncSnapshot(scormId, attempt, siteId).then(function() { return $q.reject(); }); } else { @@ -209,21 +239,23 @@ angular.module('mm.addons.mod_scorm') * * @param {Number} scormId SCORM ID. * @param {Number} attempt Attemot number. + * @param {String} siteId Site ID. * @return {Promise} Promise resolved when the snapshot is stored. */ - function saveSyncSnapshot(scormId, attempt) { + function saveSyncSnapshot(scormId, attempt, siteId) { // Try to get current state from Moodle. - return $mmaModScorm.getScormUserData(scormId, attempt, false, undefined, true).then(function(data) { - return $mmaModScormOffline.setAttemptSnapshot(scormId, attempt, data); + return $mmaModScorm.getScormUserData(scormId, attempt, false, siteId, undefined, true).then(function(data) { + return $mmaModScormOffline.setAttemptSnapshot(siteId, scormId, attempt, data); }, function() { // Error getting user data from Moodle. We'll have to build it ourselves. // Let's try to get cached data about the attempt. - return $mmaModScorm.getScormUserData(scormId, attempt, false).catch(function() { + return $mmaModScorm.getScormUserData(scormId, attempt, false, siteId).catch(function() { // No cached data, Moodle has no data stored. return {}; }).then(function(data) { // We need to add the synced data to the snapshot. - return $mmaModScormOffline.getScormStoredData(scormId, attempt, undefined, false, true).then(function(synced) { + return $mmaModScormOffline.getScormStoredData(siteId, scormId, attempt, undefined, false, true) + .then(function(synced) { angular.forEach(synced, function(entry) { if (!data[entry.scoid]) { data[entry.scoid] = { @@ -233,7 +265,7 @@ angular.module('mm.addons.mod_scorm') } data[entry.scoid].userdata[entry.element] = entry.value; }); - return $mmaModScormOffline.setAttemptSnapshot(scormId, attempt, data); + return $mmaModScormOffline.setAttemptSnapshot(siteId, scormId, attempt, data); }); }); }); @@ -245,13 +277,15 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#syncScormIfNeeded - * @param {Object} scorm SCORM downloaded. - * @return {Promise} Promise resolved when the SCORM is synced or if it doesn't need to be synced. + * @param {Object} scorm SCORM downloaded. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the SCORM is synced or if it doesn't need to be synced. */ - self.syncScormIfNeeded = function(scorm) { - return self.getScormSyncTime(scorm.id).then(function(time) { + self.syncScormIfNeeded = function(scorm, siteId) { + siteId = siteId || $mmSite.getId(); + return self.getScormSyncTime(scorm.id, siteId).then(function(time) { if (new Date().getTime() - mmaModScormSyncTime >= time) { - return self.syncScorm(scorm); + return self.syncScorm(scorm, siteId); } }); }; @@ -264,12 +298,13 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#syncScorm - * @param {Object} scorm SCORM to sync. - * @return {Promise} Promise resolved with warnings in success, rejected if synchronization fails. + * @param {Object} scorm SCORM to sync. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with warnings in success, rejected if synchronization fails. */ - self.syncScorm = function(scorm) { + self.syncScorm = function(scorm, siteId) { + siteId = siteId || $mmSite.getId(); var warnings = [], - siteId = $mmSite.getId(), syncPromise, deleted = false; @@ -280,16 +315,18 @@ angular.module('mm.addons.mod_scorm') syncPromises[siteId] = {}; } - if ($mmaModScormOnline.isScormBlocked(scorm.id) || $mmaModScormOffline.isScormBlocked(scorm.id)) { - // The SCORM is blocked, cannot sync. + if ($mmaModScormOnline.isScormBlocked(siteId, scorm.id) || $mmaModScormOffline.isScormBlocked(siteId, scorm.id)) { + $log.debug('Cannot sync SCORM ' + scorm.id + ' because it is blocked.'); return $q.reject(); } + $log.debug('Try to sync SCORM ' + scorm.id + ' in site ' + siteId); + // Prefetches data , set sync time and return warnings. function finishSync() { - return $mmaModScorm.invalidateAllScormData(scorm.id).catch(function() {}).then(function() { - return $mmaModScorm.prefetchData(scorm).then(function() { - return self.setScormSyncTime(scorm.id).catch(function() { + return $mmaModScorm.invalidateAllScormData(scorm.id, siteId).catch(function() {}).then(function() { + return $mmaModScorm.prefetchData(scorm, siteId).then(function() { + return self.setScormSyncTime(scorm.id, siteId).catch(function() { // Ignore errors. }).then(function() { return warnings; // No offline attempts, nothing to sync. @@ -299,7 +336,7 @@ angular.module('mm.addons.mod_scorm') } // Get attempts data. We ignore cache for online attempts, so this call will fail if offline or server down. - syncPromise = $mmaModScorm.getAttemptCount(scorm.id, $mmSite.getUserId(), false, true).then(function(attemptsData) { + syncPromise = $mmaModScorm.getAttemptCount(scorm.id, siteId, undefined, false, true).then(function(attemptsData) { if (!attemptsData.offline || !attemptsData.offline.length) { return finishSync(); } @@ -317,7 +354,7 @@ angular.module('mm.addons.mod_scorm') }); // Check if last online attempt is finished. Ignore cache. - promise = lastOnline > 0 ? $mmaModScorm.isAttemptIncomplete(scorm.id, lastOnline, false, true) : $q.when(false); + promise = lastOnline > 0 ? $mmaModScorm.isAttemptIncomplete(scorm.id, lastOnline, false, true, siteId) : $q.when(false); return promise.then(function(incomplete) { if (!collisions.length && !incomplete) { @@ -325,7 +362,7 @@ angular.module('mm.addons.mod_scorm') var promises = []; angular.forEach(attemptsData.offline, function(attempt) { if (scorm.maxattempt == 0 || attempt <= scorm.maxattempt) { - promises.push(self._syncAttempt(scorm.id, attempt)); + promises.push(self._syncAttempt(scorm.id, attempt, siteId)); } }); return $q.all(promises).then(function() { @@ -334,11 +371,11 @@ angular.module('mm.addons.mod_scorm') } else if (collisions.length) { // We have collisions, treat them. - return treatCollisions(scorm.id, collisions, lastOnline, attemptsData.offline).then(function(warns) { + return treatCollisions(scorm.id, siteId, collisions, lastOnline, attemptsData.offline).then(function(warns) { warnings = warnings.concat(warns); // The offline attempts might have changed since some collisions can be converted to new attempts. - return $mmaModScormOffline.getAttempts(scorm.id, $mmSite.getUserId()).then(function(entries) { + return $mmaModScormOffline.getAttempts(siteId, scorm.id).then(function(entries) { var promises = [], cannotSyncSome = false; @@ -355,7 +392,7 @@ angular.module('mm.addons.mod_scorm') // We'll only sync new attemps if last online attempt is completed. if (!incomplete || attempt <= lastOnline) { if (scorm.maxattempt == 0 || attempt <= scorm.maxattempt) { - promises.push(self._syncAttempt(scorm.id, attempt)); + promises.push(self._syncAttempt(scorm.id, attempt, siteId)); } } else { cannotSyncSome = true; @@ -390,6 +427,7 @@ angular.module('mm.addons.mod_scorm') * Treat collisions found in a SCORM synchronization process. * * @param {Number} scormId SCORM ID. + * @param {String} siteId Site ID. * @param {Number[]} collisions Numbers of attempts that exist both in online and offline. * @param {Number} lastOnline Last online attempt. * @param {Number[]} offlineAttempts Numbers of offline attempts. @@ -413,12 +451,11 @@ angular.module('mm.addons.mod_scorm') * of the list if the last attempt is completed. If the last attempt is not completed then the offline data will de deleted * because we can't create a new attempt. */ - function treatCollisions(scormId, collisions, lastOnline, offlineAttempts) { + function treatCollisions(scormId, siteId, collisions, lastOnline, offlineAttempts) { var warnings = [], promises = [], newAttemptsSameOrder = [], // Attempts that will be created as new attempts but keeping the current order. newAttemptsAtEnd = {}, // Attempts that will be created at the end of the list of attempts (should be max 1 attempt). - userId = $mmSite.getUserId(), lastCollision = Math.max.apply(Math, collisions), lastOffline = Math.max.apply(Math, offlineAttempts), lastOfflineIncomplete, @@ -427,9 +464,9 @@ angular.module('mm.addons.mod_scorm') // Get the creation time and the status (complete/incomplete) of the last offline attempt. function getLastOfflineAttemptData() { // Check if last offline attempt is incomplete. - return $mmaModScorm.isAttemptIncomplete(scormId, lastOffline, true).then(function(incomplete) { + return $mmaModScorm.isAttemptIncomplete(scormId, lastOffline, true, false, siteId).then(function(incomplete) { lastOfflineIncomplete = incomplete; - return $mmaModScormOffline.getAttemptCreationTime(scormId, lastOffline).then(function(time) { + return $mmaModScormOffline.getAttemptCreationTime(siteId, scormId, lastOffline).then(function(time) { lastOfflineCreated = time; }); }); @@ -443,13 +480,13 @@ angular.module('mm.addons.mod_scorm') return $q.when(); } - return $mmaModScormOffline.getAttemptCreationTime(scormId, attempt).then(function(time) { + return $mmaModScormOffline.getAttemptCreationTime(siteId, scormId, attempt).then(function(time) { if (time > lastOfflineCreated) { // This attempt was created after the last offline attempt, we'll add it to the end of the list if possible. if (lastOfflineIncomplete) { // It can't be added because the last offline attempt is incomplete, delete it. $log.debug('Try to delete attempt ' + attempt + ' because it cannot be created as a new attempt.'); - return $mmaModScormOffline.deleteAttempt(scormId, attempt).then(function() { + return $mmaModScormOffline.deleteAttempt(siteId, scormId, attempt).then(function() { warnings.push($translate.instant('mma.mod_scorm.warningofflinedatadeleted', {number: attempt})); }).catch(function() { // Maybe there's something wrong with the data or the storage implementation. @@ -469,11 +506,12 @@ angular.module('mm.addons.mod_scorm') collisions.forEach(function(attempt) { // First get synced entries to detect if it was a failed synchronization. - var promise = $mmaModScormOffline.getScormStoredData(scormId, attempt, userId, false, true).then(function(synced) { + var getDataFn = $mmaModScormOffline.getScormStoredData, + promise = getDataFn(siteId, scormId, attempt, undefined, false, true).then(function(synced) { if (synced && synced.length) { // The attempt has synced entries, it seems to be a failed synchronization. // Let's get the entries that haven't been synced, maybe it just failed to delete the attempt. - return $mmaModScormOffline.getScormStoredData(scormId, attempt, userId, true).then(function(entries) { + return getDataFn(siteId, scormId, attempt, undefined, true).then(function(entries) { var hasDataToSend = false; angular.forEach(entries, function(entry) { if (entry.element.indexOf('.') > -1) { @@ -483,25 +521,26 @@ angular.module('mm.addons.mod_scorm') if (hasDataToSend) { // There are elements to sync. We need to check if it's possible to sync them or not. - return canRetrySync(scormId, attempt, lastOnline).catch(function() { + return canRetrySync(scormId, siteId, attempt, lastOnline).catch(function() { // Cannot retry sync, we'll create a new offline attempt if possible. return addToNewOrDelete(attempt); }); } else { // Nothing to sync, delete the attempt. - return $mmaModScormOffline.deleteAttempt(scormId, attempt).catch(function() { + return $mmaModScormOffline.deleteAttempt(siteId, scormId, attempt).catch(function() { // Maybe there's something wrong with the data or the storage implementation. }); } }); } else { // It's not a failed synchronization. Check if it's an attempt continued in offline. - return $mmaModScormOffline.getAttemptSnapshot(scormId, attempt).then(function(snapshot) { + return $mmaModScormOffline.getAttemptSnapshot(siteId, scormId, attempt).then(function(snapshot) { if (snapshot && Object.keys(snapshot).length) { // It has a snapshot, it means it continued an online attempt. We need to check if they've diverged. // If it's the last attempt we don't need to ignore cache because we already did it. var refresh = lastOnline != attempt; - return $mmaModScorm.getScormUserData(scormId, attempt, false, undefined, refresh).then(function(data) { + return $mmaModScorm.getScormUserData(scormId, attempt, false, siteId, undefined, refresh) + .then(function(data) { if (!snapshotEquals(snapshot, data)) { // Snapshot has diverged, it will be converted into a new attempt if possible. return addToNewOrDelete(attempt); @@ -518,11 +557,11 @@ angular.module('mm.addons.mod_scorm') }); return $q.all(promises).then(function() { - return moveNewAttempts(scormId, newAttemptsSameOrder, lastOnline, lastCollision, offlineAttempts).then(function() { + return moveNewAttempts(scormId, siteId, newAttemptsSameOrder, lastOnline, lastCollision, offlineAttempts).then(function() { // The new attempts that need to keep the order have been created. Now we'll create the new attempts // at the end of the list of offline attempts. It should only be 1 attempt max. lastOffline = lastOffline + newAttemptsSameOrder.length; - return createNewAttemptsAtEnd(scormId, newAttemptsAtEnd, lastOffline).then(function() { + return createNewAttemptsAtEnd(scormId, siteId, newAttemptsAtEnd, lastOffline).then(function() { return warnings; }); }); @@ -537,13 +576,14 @@ angular.module('mm.addons.mod_scorm') * to be a new attempt. #3 will now be #4, and #2 will now be #3. * * @param {Number} scormId SCORM ID. + * @param {String} siteId Site ID. * @param {Number[]} newAttempts Attempts that need to be converted into new attempts. * @param {Number} lastOnline Last online attempt. * @param {Number} lastCollision Last attempt with collision (exists in online and offline). * @param {Number[]} offlineAttempts Numbers of offline attempts. * @return {Promise} Promise resolved when attempts have been moved. */ - function moveNewAttempts(scormId, newAttempts, lastOnline, lastCollision, offlineAttempts) { + function moveNewAttempts(scormId, siteId, newAttempts, lastOnline, lastCollision, offlineAttempts) { if (!newAttempts.length) { return $q.when(); } @@ -561,7 +601,8 @@ angular.module('mm.addons.mod_scorm') if (attempt > lastCollision) { // We use a chain of promises because we need to move them in order. promise = promise.then(function() { - return $mmaModScormOffline.changeAttemptNumber(scormId, attempt, attempt + newAttempts.length).then(function() { + var newNumber = attempt + newAttempts.length; + return $mmaModScormOffline.changeAttemptNumber(siteId, scormId, attempt, newNumber).then(function() { lastSuccessful = attempt; }); }); @@ -579,8 +620,9 @@ angular.module('mm.addons.mod_scorm') // Now move the attempts in newAttempts. angular.forEach(newAttempts, function(attempt, index) { - // No need to use chain of promiss, - promises.push($mmaModScormOffline.changeAttemptNumber(scormId, attempt, lastOnline + index + 1).then(function() { + // No need to use chain of promises. + var newNumber = lastOnline + index + 1; + promises.push($mmaModScormOffline.changeAttemptNumber(siteId, scormId, attempt, newNumber).then(function() { successful.push(attempt); })); }); @@ -589,8 +631,8 @@ angular.module('mm.addons.mod_scorm') // Moving the new attempts failed (it shouldn't happen). Let's undo the new attempts move. promises = []; angular.forEach(successful, function(attempt) { - var newAttemptNumber = lastOnline + newAttempts.indexOf(attempt) + 1; - promises.push($mmaModScormOffline.changeAttemptNumber(scormId, newAttemptNumber, attempt)); + var newNumber = lastOnline + newAttempts.indexOf(attempt) + 1; + promises.push($mmaModScormOffline.changeAttemptNumber(siteId, scormId, newNumber, attempt)); }); return $mmUtil.allPromises(promises).then(function() { return $q.reject(); // It will now enter the .catch that moves offline attempts after collisions. @@ -612,7 +654,7 @@ angular.module('mm.addons.mod_scorm') attemptsToUndo.forEach(function(attempt) { promise = promise.then(function() { // Move it back. - return $mmaModScormOffline.changeAttemptNumber(scormId, attempt + newAttempts.length, attempt); + return $mmaModScormOffline.changeAttemptNumber(siteId, scormId, attempt + newAttempts.length, attempt); }); }); return promise.then(function() { @@ -625,11 +667,12 @@ angular.module('mm.addons.mod_scorm') * Create new attempts at the end of the offline attempts list. * * @param {Number} scormId SCORM ID. + * @param {String} siteId Site ID. * @param {Object} newAttempts Attempts to create. The keys are the timecreated, the values are the attempt number. * @param {Number} lastOffline Number of last offline attempt. * @return {Promise} Promise resolved when the creation is finished. */ - function createNewAttemptsAtEnd(scormId, newAttempts, lastOffline) { + function createNewAttemptsAtEnd(scormId, siteId, newAttempts, lastOffline) { var times = Object.keys(newAttempts).sort(), // Sort in ASC order. promises = []; @@ -639,7 +682,7 @@ angular.module('mm.addons.mod_scorm') angular.forEach(times, function(time, index) { var attempt = newAttempts[time]; - promises.push($mmaModScormOffline.changeAttemptNumber(scormId, attempt, lastOffline + index + 1)); + promises.push($mmaModScormOffline.changeAttemptNumber(siteId, scormId, attempt, lastOffline + index + 1)); }); return $mmUtil.allPromises(promises); } @@ -648,15 +691,17 @@ angular.module('mm.addons.mod_scorm') * Check if can retry an attempt synchronization. * * @param {Number} scormId SCORM ID. + * @param {String} siteId Site ID. * @param {Number} attempt Attempt number. * @param {Number} lastOnline Last online attempt number. * @return {Promise} Promise resolved if can retry the synchronization, false otherwise. */ - function canRetrySync(scormId, attempt, lastOnline) { + function canRetrySync(scormId, siteId, attempt, lastOnline) { // If it's the last attempt we don't need to ignore cache because we already did it. - return $mmaModScorm.getScormUserData(scormId, attempt, false, undefined, lastOnline != attempt).then(function(siteData) { + var refresh = lastOnline != attempt; + return $mmaModScorm.getScormUserData(scormId, attempt, false, siteId, undefined, refresh).then(function(siteData) { // Get synchronization snapshot (if sync fails it should store a snapshot). - return $mmaModScormOffline.getAttemptSnapshot(scormId, attempt).then(function(snapshot) { + return $mmaModScormOffline.getAttemptSnapshot(siteId, scormId, attempt).then(function(snapshot) { if (!snapshot || !Object.keys(snapshot).length || !snapshotEquals(snapshot, siteData)) { // No snapshot or it doesn't match, we can't retry the synchronization. return $q.reject(); @@ -718,11 +763,12 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormSync#waitForSync - * @param {Number} scormId SCORM to check. - * @return {Promise} Promise resolved when there's no sync going on for the SCORM. + * @param {Number} scormId SCORM to check. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when there's no sync going on for the SCORM. */ - self.waitForSync = function(scormId) { - var siteId = $mmSite.getId(); + self.waitForSync = function(scormId, siteId) { + siteId = siteId || $mmSite.getId(); if (syncPromises[siteId] && syncPromises[siteId][scormId]) { // There's a sync ongoing for this SCORM. return syncPromises[siteId][scormId].catch(function() {});