From 91a3aeff783ce73366e196a82543736724ecaf71 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 17 May 2016 16:44:29 +0200 Subject: [PATCH 1/5] MOBILE-1543 quiz: Listen for status changes in quiz entry page --- www/addons/mod_quiz/controllers/index.js | 84 +++++++++++++++++------- 1 file changed, 62 insertions(+), 22 deletions(-) diff --git a/www/addons/mod_quiz/controllers/index.js b/www/addons/mod_quiz/controllers/index.js index d9cfbd40203..f3a5570c801 100644 --- a/www/addons/mod_quiz/controllers/index.js +++ b/www/addons/mod_quiz/controllers/index.js @@ -23,8 +23,9 @@ angular.module('mm.addons.mod_quiz') */ .controller('mmaModQuizIndexCtrl', function($scope, $stateParams, $mmaModQuiz, $mmCourse, $ionicPlatform, $q, $translate, $mmaModQuizHelper, $ionicHistory, $ionicScrollDelegate, $mmEvents, mmaModQuizEventAttemptFinished, $state, - $mmQuestionBehaviourDelegate, $mmaModQuizSync, $mmText, $mmUtil, mmaModQuizEventAutomSynced, - $mmCoursePrefetchDelegate, mmCoreDownloaded) { + $mmQuestionBehaviourDelegate, $mmaModQuizSync, $mmText, $mmUtil, mmaModQuizEventAutomSynced, $mmSite, + $mmCoursePrefetchDelegate, mmCoreDownloaded, mmCoreDownloading, mmCoreEventPackageStatusChanged, + mmaModQuizComponent) { var module = $stateParams.module || {}, courseId = $stateParams.courseid, quiz, @@ -37,7 +38,8 @@ angular.module('mm.addons.mod_quiz') attemptAccessInfo, moreAttempts, scrollView = $ionicScrollDelegate.$getByHandle('mmaModQuizIndexScroll'), - autoReview; + autoReview, + currentStatus; $scope.title = module.name; $scope.description = module.description; @@ -126,6 +128,12 @@ angular.module('mm.addons.mod_quiz') return $mmaModQuiz.getUserAttempts(quiz.id).then(function(atts) { attempts = atts; + if ($mmaModQuiz.isQuizOffline(quiz)) { + // Handle status. We don't add getStatus to promises because it should be fast. + setStatusListener(); + getStatus().then(showStatus); + } + return treatAttempts().then(function() { // Check if user can create/continue attempts. if (attempts.length) { @@ -336,6 +344,38 @@ angular.module('mm.addons.mod_quiz') }); } + // Get status of the quiz. + function getStatus() { + var revision = $mmaModQuiz.getQuizRevisionFromAttempts(attempts), + timemodified = $mmaModQuiz.getQuizTimemodifiedFromAttempts(attempts); + + return $mmCoursePrefetchDelegate.getModuleStatus(module, courseId, revision, timemodified); + } + + // Set a listener to monitor changes on this SCORM status to show a message to the user. + function setStatusListener() { + if (typeof statusObserver !== 'undefined') { + return; // Already set. + } + + // Listen for changes on this module status to show a message to the user. + statusObserver = $mmEvents.on(mmCoreEventPackageStatusChanged, function(data) { + if (data.siteid === $mmSite.getId() && data.componentId === module.id && + data.component === mmaModQuizComponent) { + showStatus(data.status); + } + }); + } + + // Showing or hide a status message depending on the SCORM status. + function showStatus(status) { + currentStatus = status; + + if (status == mmCoreDownloading) { + $scope.downloading = true; + } + } + // Fetch the Quiz data. fetchQuizData().then(function() { $mmaModQuiz.logViewQuiz(quiz.id).then(function() { @@ -369,28 +409,28 @@ angular.module('mm.addons.mod_quiz') // Attempt the quiz. $scope.attemptQuiz = function() { + if ($scope.downloading) { + // Scope is being downloaded, abort. + return; + } + if ($mmaModQuiz.isQuizOffline(quiz)) { // Quiz supports offline, check if it needs to be downloaded. - var revision = $mmaModQuiz.getQuizRevisionFromAttempts(attempts), - timemodified = $mmaModQuiz.getQuizTimemodifiedFromAttempts(attempts), - modal = $mmUtil.showModalLoading(); - - $mmCoursePrefetchDelegate.getModuleStatus(module, courseId, revision, timemodified).then(function(status) { - if (status != mmCoreDownloaded) { - // Prefetch the quiz. - return $mmaModQuiz.prefetch(module, courseId).then(function() { - // Success downloading, open quiz. - openQuiz(); - }).catch(function() { - $mmUtil.showErrorModal('mma.mod_quiz.errordownloading', true); - }); - } else { - // Already downloaded, open it. + if (currentStatus != mmCoreDownloaded) { + // Prefetch the quiz. + $scope.downloading = true; + return $mmaModQuiz.prefetch(module, courseId, true).then(function() { + // Success downloading, open quiz. openQuiz(); - } - }).finally(function() { - modal.dismiss(); - }); + }).catch(function(error) { + $mmaModQuizHelper.showError(error, 'mma.mod_quiz.errordownloading'); + }).finally(function() { + $scope.downloading = false; + }); + } else { + // Already downloaded, open it. + openQuiz(); + } } else { openQuiz(); } From 803fb4f0d29bc48c848fe80e750e113840a43dc7 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 18 May 2016 09:15:45 +0200 Subject: [PATCH 2/5] MOBILE-1543 prefetch: Add 'single' param to prefetch handlers --- www/addons/mod_book/services/prefetch_handler.js | 8 +++++--- www/addons/mod_folder/services/prefetch_handler.js | 8 +++++--- www/addons/mod_imscp/services/prefetch_handler.js | 8 +++++--- www/addons/mod_page/services/prefetch_handler.js | 8 +++++--- www/addons/mod_quiz/services/prefetch_handler.js | 11 ++++++----- www/addons/mod_resource/services/prefetch_handler.js | 8 +++++--- www/addons/mod_scorm/services/prefetch_handler.js | 11 ++++++----- www/addons/mod_wiki/services/prefetch_handler.js | 9 +++++---- .../components/course/services/prefetchdelegate.js | 2 +- 9 files changed, 43 insertions(+), 30 deletions(-) diff --git a/www/addons/mod_book/services/prefetch_handler.js b/www/addons/mod_book/services/prefetch_handler.js index d5ee5b976d5..bbe9ab384e2 100644 --- a/www/addons/mod_book/services/prefetch_handler.js +++ b/www/addons/mod_book/services/prefetch_handler.js @@ -82,10 +82,12 @@ angular.module('mm.addons.mod_book') * @module mm.addons.mod_book * @ngdoc method * @name $mmaModBookPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module) { + self.prefetch = function(module, courseId, single) { return $mmaModBook.prefetchContent(module); }; diff --git a/www/addons/mod_folder/services/prefetch_handler.js b/www/addons/mod_folder/services/prefetch_handler.js index 6f77751afdc..0d0a8cbfd56 100644 --- a/www/addons/mod_folder/services/prefetch_handler.js +++ b/www/addons/mod_folder/services/prefetch_handler.js @@ -64,10 +64,12 @@ angular.module('mm.addons.mod_folder') * @module mm.addons.mod_folder * @ngdoc method * @name $mmaModFolderPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module) { + self.prefetch = function(module, courseId, single) { return $mmaModFolder.prefetchContent(module); }; diff --git a/www/addons/mod_imscp/services/prefetch_handler.js b/www/addons/mod_imscp/services/prefetch_handler.js index bc701334efd..f95a2008740 100644 --- a/www/addons/mod_imscp/services/prefetch_handler.js +++ b/www/addons/mod_imscp/services/prefetch_handler.js @@ -64,10 +64,12 @@ angular.module('mm.addons.mod_imscp') * @module mm.addons.mod_imscp * @ngdoc method * @name $mmaModImscpPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module) { + self.prefetch = function(module, courseId, single) { return $mmaModImscp.prefetchContent(module); }; diff --git a/www/addons/mod_page/services/prefetch_handler.js b/www/addons/mod_page/services/prefetch_handler.js index a62831c1362..7149f70dca8 100644 --- a/www/addons/mod_page/services/prefetch_handler.js +++ b/www/addons/mod_page/services/prefetch_handler.js @@ -64,10 +64,12 @@ angular.module('mm.addons.mod_page') * @module mm.addons.mod_page * @ngdoc method * @name $mmaModPagePrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module) { + self.prefetch = function(module, courseId, single) { return $mmaModPage.prefetchContent(module); }; diff --git a/www/addons/mod_quiz/services/prefetch_handler.js b/www/addons/mod_quiz/services/prefetch_handler.js index 4d41fe50639..52b2af9ce31 100644 --- a/www/addons/mod_quiz/services/prefetch_handler.js +++ b/www/addons/mod_quiz/services/prefetch_handler.js @@ -136,12 +136,13 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuizPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @param {Number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. + * @param { Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. */ - self.prefetch = function(module, courseId) { - return $mmaModQuiz.prefetch(module, courseId); + self.prefetch = function(module, courseId, single) { + return $mmaModQuiz.prefetch(module, courseId, single); }; return self; diff --git a/www/addons/mod_resource/services/prefetch_handler.js b/www/addons/mod_resource/services/prefetch_handler.js index 63921932551..48934ad7bb4 100644 --- a/www/addons/mod_resource/services/prefetch_handler.js +++ b/www/addons/mod_resource/services/prefetch_handler.js @@ -64,10 +64,12 @@ angular.module('mm.addons.mod_resource') * @module mm.addons.mod_resource * @ngdoc method * @name $mmaModResourcePrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module) { + self.prefetch = function(module, courseId, single) { return $mmaModResource.prefetchContent(module); }; diff --git a/www/addons/mod_scorm/services/prefetch_handler.js b/www/addons/mod_scorm/services/prefetch_handler.js index aaca408e776..ea8ab938a6d 100644 --- a/www/addons/mod_scorm/services/prefetch_handler.js +++ b/www/addons/mod_scorm/services/prefetch_handler.js @@ -117,12 +117,13 @@ angular.module('mm.addons.mod_scorm') * @module mm.addons.mod_scorm * @ngdoc method * @name $mmaModScormPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @param {Number} courseid Course ID the module belongs to. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module, courseid) { - return $mmaModScorm.getScorm(courseid, module.id, module.url).then(function(scorm) { + self.prefetch = function(module, courseId, single) { + return $mmaModScorm.getScorm(courseId, module.id, module.url).then(function(scorm) { return $mmaModScorm.prefetch(scorm); }); }; diff --git a/www/addons/mod_wiki/services/prefetch_handler.js b/www/addons/mod_wiki/services/prefetch_handler.js index 4d1e92f4f25..3421a0cd22f 100644 --- a/www/addons/mod_wiki/services/prefetch_handler.js +++ b/www/addons/mod_wiki/services/prefetch_handler.js @@ -176,11 +176,12 @@ angular.module('mm.addons.mod_wiki') * @module mm.addons.mod_wiki * @ngdoc method * @name $mmaModWikiPrefetchHandler#prefetch - * @param {Object} module The module object returned by WS. - * @param {Number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} single True if we're downloading a single module, false if we're downloading a whole section. + * @return {Promise} Promise resolved when all files have been downloaded. Data returned is not reliable. */ - self.prefetch = function(module, courseId) { + self.prefetch = function(module, courseId, single) { var siteId = $mmSite.getId(), userid = userid || $mmSite.getUserId(); diff --git a/www/core/components/course/services/prefetchdelegate.js b/www/core/components/course/services/prefetchdelegate.js index 65ae03e04f3..26ab65ee1b4 100644 --- a/www/core/components/course/services/prefetchdelegate.js +++ b/www/core/components/course/services/prefetchdelegate.js @@ -47,7 +47,7 @@ angular.module('mm.core') * - component (String) Handler's component. * - getDownloadSize(module, courseid) (Number|Promise) Get the download size of a module. * - isEnabled() (Boolean|Promise) Whether or not the handler is enabled on a site level. - * - prefetch(module, courseid) (Promise) Prefetches a module. + * - prefetch(module, courseid, single) (Promise) Prefetches a module. * - (Optional) getFiles(module, courseid) (Object[]|Promise) Get list of files. If not defined, * we'll assume they're in module.contents. * - (Optional) determineStatus(status) (String) Returns status to show based on current. E.g. for From b0b4c2fbcef66a7a1e61d89b19ee0b4d385ec772 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 18 May 2016 09:35:50 +0200 Subject: [PATCH 3/5] MOBILE-1543 quiz: Ask preflight in prefetch --- .../delaybetweenattempts/handlers.js | 3 +- .../accessrules/ipaddress/handlers.js | 3 +- .../accessrules/numattempts/handlers.js | 3 +- .../accessrules/offlineattempts/handlers.js | 10 +- .../accessrules/openclosedate/handlers.js | 3 +- .../mod_quiz/accessrules/password/handlers.js | 3 +- .../accessrules/safebrowser/handlers.js | 3 +- .../accessrules/securewindow/handlers.js | 3 +- .../accessrules/timelimit/handlers.js | 4 +- www/addons/mod_quiz/controllers/player.js | 6 +- .../mod_quiz/services/accessrulesdelegate.js | 24 +- www/addons/mod_quiz/services/handlers.js | 6 +- www/addons/mod_quiz/services/helper.js | 139 +-------- www/addons/mod_quiz/services/quiz.js | 291 ++++++++++++++++-- www/addons/mod_quiz/templates/index.html | 5 +- 15 files changed, 314 insertions(+), 192 deletions(-) diff --git a/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js b/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js index 75f3928180e..07c7a8fb1f6 100644 --- a/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/ipaddress/handlers.js b/www/addons/mod_quiz/accessrules/ipaddress/handlers.js index 2e0a2e97096..85faa2cf50f 100644 --- a/www/addons/mod_quiz/accessrules/ipaddress/handlers.js +++ b/www/addons/mod_quiz/accessrules/ipaddress/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/numattempts/handlers.js b/www/addons/mod_quiz/accessrules/numattempts/handlers.js index 9348b8ac55e..5b66f4e80ad 100644 --- a/www/addons/mod_quiz/accessrules/numattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/numattempts/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js b/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js index ceec300c8d4..e13745f339c 100644 --- a/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js @@ -38,9 +38,14 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { + if (prefetch) { + return false; + } + if (!attempt) { return true; } @@ -57,9 +62,10 @@ angular.module('mm.addons.mod_quiz') * @name $mmaModQuizAccessRulesDelegate#getFixedPreflightData * @param {Object} attempt Attempt. * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Void} */ - self.getFixedPreflightData = function(attempt, preflightData) { + self.getFixedPreflightData = function(attempt, preflightData, prefetch) { preflightData.confirmdatasaved = 1; }; diff --git a/www/addons/mod_quiz/accessrules/openclosedate/handlers.js b/www/addons/mod_quiz/accessrules/openclosedate/handlers.js index 070c0cbe4d6..58527d8dd28 100644 --- a/www/addons/mod_quiz/accessrules/openclosedate/handlers.js +++ b/www/addons/mod_quiz/accessrules/openclosedate/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/password/handlers.js b/www/addons/mod_quiz/accessrules/password/handlers.js index 803107fb81e..be3b883e705 100644 --- a/www/addons/mod_quiz/accessrules/password/handlers.js +++ b/www/addons/mod_quiz/accessrules/password/handlers.js @@ -47,9 +47,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { // If the password rule is active in a quiz we always require to input the password. return true; }; diff --git a/www/addons/mod_quiz/accessrules/safebrowser/handlers.js b/www/addons/mod_quiz/accessrules/safebrowser/handlers.js index 683b8fd4f4d..61da23c0084 100644 --- a/www/addons/mod_quiz/accessrules/safebrowser/handlers.js +++ b/www/addons/mod_quiz/accessrules/safebrowser/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/securewindow/handlers.js b/www/addons/mod_quiz/accessrules/securewindow/handlers.js index d50e91dd3f5..cec13eb9ba8 100644 --- a/www/addons/mod_quiz/accessrules/securewindow/handlers.js +++ b/www/addons/mod_quiz/accessrules/securewindow/handlers.js @@ -38,9 +38,10 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/timelimit/handlers.js b/www/addons/mod_quiz/accessrules/timelimit/handlers.js index 5ef796b7dab..bef8de8eb9d 100644 --- a/www/addons/mod_quiz/accessrules/timelimit/handlers.js +++ b/www/addons/mod_quiz/accessrules/timelimit/handlers.js @@ -38,10 +38,12 @@ angular.module('mm.addons.mod_quiz') * Check if a preflight check is required. * * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt) { + self.isPreflightCheckRequired = function(attempt, prefetch) { // Warning only required if the attempt is not already started. + // prefetch should always be false since offline isn't compatible with timed quizzes. return !attempt; }; diff --git a/www/addons/mod_quiz/controllers/player.js b/www/addons/mod_quiz/controllers/player.js index 5dd7c294011..81d2266c26e 100644 --- a/www/addons/mod_quiz/controllers/player.js +++ b/www/addons/mod_quiz/controllers/player.js @@ -132,7 +132,7 @@ angular.module('mm.addons.mod_quiz') function startOrContinueAttempt(fromModal) { // Check preflight data and start attempt if needed. var att = newAttempt ? undefined : attempt; - return $mmaModQuizHelper.checkPreflightData($scope, quiz, quizAccessInfo, att, offline, fromModal).then(function(att) { + return $mmaModQuiz.checkPreflightData($scope, quiz, quizAccessInfo, att, offline, fromModal).then(function(att) { // Re-fetch attempt access information with the right attempt (might have changed because a new attempt was created). return $mmaModQuiz.getAttemptAccessInformation(quiz.id, att.id, offline, true).then(function(info) { @@ -156,6 +156,10 @@ angular.module('mm.addons.mod_quiz') return loadSummary(); } }); + }).catch(function(error) { + if (error) { + return $mmaModQuizHelper.showError(error, 'mm.core.error'); + } }); } diff --git a/www/addons/mod_quiz/services/accessrulesdelegate.js b/www/addons/mod_quiz/services/accessrulesdelegate.js index bd952aa6dfa..861c39224f0 100644 --- a/www/addons/mod_quiz/services/accessrulesdelegate.js +++ b/www/addons/mod_quiz/services/accessrulesdelegate.js @@ -77,13 +77,14 @@ angular.module('mm.addons.mod_quiz') * @param {String[]} rules Name of the rules. * @param {Object} attempt Attempt. * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. * @return {Void} */ - self.getFixedPreflightData = function(rules, attempt, preflightData) { + self.getFixedPreflightData = function(rules, attempt, preflightData, prefetch) { angular.forEach(rules, function(rule) { var handler = self.getAccessRuleHandler(rule); if (handler && handler.getFixedPreflightData) { - handler.getFixedPreflightData(attempt, preflightData); + handler.getFixedPreflightData(attempt, preflightData, prefetch); } }); }; @@ -107,16 +108,17 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuizAccessRulesDelegate#isPreflightCheckRequired - * @param {String[]} rules Name of the rules. - * @param {Object} attempt Attempt. - * @return {Boolean} True if required, false otherwise. + * @param {String[]} rules Name of the rules. + * @param {Object} attempt Attempt. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @return {Boolean} True if required, false otherwise. */ - self.isPreflightCheckRequired = function(rules, attempt) { + self.isPreflightCheckRequired = function(rules, attempt, prefetch) { var isRequired = false; angular.forEach(rules, function(rule) { var handler = self.getAccessRuleHandler(rule); if (handler) { - if (handler.isPreflightCheckRequired(attempt)) { + if (handler.isPreflightCheckRequired(attempt, prefetch)) { isRequired = true; } } @@ -136,10 +138,10 @@ angular.module('mm.addons.mod_quiz') * returning an object defining these properties. See {@link $mmUtil#resolveObject}. * - isEnabled (Boolean|Promise) Whether or not the handler is enabled on a site level. * When using a promise, it should return a boolean. - * - isPreflightCheckRequired(attempt) (Boolean|Promise) Whether or not the rule requires a - * preflight check when starting/continuing an attempt. - * - getFixedPreflightData(attempt, preflightData) Optional. Should add preflight data that doesn't - * require user interaction. + * - isPreflightCheckRequired(attempt, prefetch) (Boolean|Promise) Whether or not the rule requires + * a preflight check when prefetch or start/continue an attempt. + * - getFixedPreflightData(attempt, preflightData, prefetch) Optional. Should add preflight data + * that doesn't require user interaction. * - getPreflightDirectiveName() (String) Optional. Returns the name of the directive to render * the access rule preflight. Required if the handler needs a * preflight check in some cases. diff --git a/www/addons/mod_quiz/services/handlers.js b/www/addons/mod_quiz/services/handlers.js index 9daa7d653ac..207ce02a5b9 100644 --- a/www/addons/mod_quiz/services/handlers.js +++ b/www/addons/mod_quiz/services/handlers.js @@ -23,7 +23,7 @@ angular.module('mm.addons.mod_quiz') */ .factory('$mmaModQuizHandlers', function($mmCourse, $mmaModQuiz, $state, $q, $mmContentLinksHelper, $mmUtil, $mmCourseHelper, $mmSite, $mmCoursePrefetchDelegate, $mmaModQuizPrefetchHandler, $mmEvents, mmCoreEventPackageStatusChanged, - mmaModQuizComponent, mmCoreDownloading, mmCoreNotDownloaded, mmCoreOutdated) { + mmaModQuizComponent, mmCoreDownloading, mmCoreNotDownloaded, mmCoreOutdated, $mmaModQuizHelper) { var self = {}; @@ -92,10 +92,10 @@ angular.module('mm.addons.mod_quiz') } $scope.spinner = true; // Show spinner since this operation might take a while. - $mmaModQuizPrefetchHandler.prefetch(module, courseId).catch(function() { + $mmaModQuizPrefetchHandler.prefetch(module, courseId, true).catch(function(error) { $scope.spinner = false; if (!$scope.$$destroyed) { - $mmUtil.showErrorModal('mm.core.errordownloading', true); + $mmaModQuizHelper.showError(error, 'mm.core.errordownloading'); } }); } diff --git a/www/addons/mod_quiz/services/helper.js b/www/addons/mod_quiz/services/helper.js index 848783cd7e7..8bd81058392 100644 --- a/www/addons/mod_quiz/services/helper.js +++ b/www/addons/mod_quiz/services/helper.js @@ -21,91 +21,10 @@ angular.module('mm.addons.mod_quiz') * @ngdoc service * @name $mmaModQuizHelper */ -.factory('$mmaModQuizHelper', function($mmaModQuiz, $mmUtil, $q, $ionicModal, $mmaModQuizAccessRulesDelegate, $translate, - $mmaModQuizOffline, $mmaModQuizSync, $timeout) { +.factory('$mmaModQuizHelper', function($mmaModQuiz, $mmUtil, $q, $translate, $mmaModQuizSync) { var self = {}; - /** - * Validate a preflight data or show a modal to input the preflight data if required. - * It calls $mmaModQuiz#startAttempt if a new attempt is needed. - * - * @module mm.addons.mod_quiz - * @ngdoc method - * @name $mmaModQuizHelper#checkPreflightData - * @param {Object} scope Scope. - * @param {Object} quiz Quiz. - * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. - * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. - * @param {Boolean} offline True if attempt is offline. - * @param {Boolean} fromModal True if sending data using preflight modal, false otherwise. - * @return {Promise} Promise resolved when the preflight data is validated. - */ - self.checkPreflightData = function(scope, quiz, quizAccessInfo, attempt, offline, fromModal) { - var promise, - preflightRequired = $mmaModQuizAccessRulesDelegate.isPreflightCheckRequired(quizAccessInfo.activerulenames, attempt); - - if (preflightRequired && !fromModal) { - // Preflight check is required but no preflightData has been sent. Show a modal with the preflight form. - if (!scope.modal) { - // Modal hasn't been created yet. Create it and show it. - return self.initPreflightModal(scope, quizAccessInfo, attempt).catch(function(error) { - return self.showError(error, 'Error initializing preflight modal.'); - }).then(function() { - scope.modal.show(); - return $q.reject(); - }); - } else if (!scope.modal.isShown()) { - // Modal is created but not shown. Show it. - scope.modal.show(); - } - return $q.reject(); - } - - // Hide modal if needed. - scope.modal && scope.modal.hide(); - - // Get some fixed preflight data from access rules (data that doesn't require user interaction). - $mmaModQuizAccessRulesDelegate.getFixedPreflightData(quizAccessInfo.activerulenames, attempt, scope.preflightData); - - if (attempt) { - if (attempt.state != $mmaModQuiz.ATTEMPT_OVERDUE && !attempt.finishedOffline) { - // We're continuing an attempt. Call getAttemptData to validate the preflight data. - var page = attempt.currentpage; - promise = $mmaModQuiz.getAttemptData(attempt.id, page, scope.preflightData, offline, true).then(function() { - if (offline) { - // Get current page stored in local. - return $mmaModQuizOffline.getAttemptById(attempt.id).then(function(localAttempt) { - attempt.currentpage = localAttempt.currentpage; - }).catch(function() { - // No local data. - }); - } - }); - } else { - // Attempt is overdue or finished in offline, we can only see the summary. - // Call getAttemptSummary to validate the preflight data. - promise = $mmaModQuiz.getAttemptSummary(attempt.id, scope.preflightData, offline, true); - } - } else { - // We're starting a new attempt, call startAttempt. - promise = $mmaModQuiz.startAttempt(quiz.id, scope.preflightData).then(function(att) { - attempt = att; - }); - } - - return promise.then(function() { - // Preflight data validated. Close modal if needed. - return attempt; - }).catch(function(error) { - // Show modal again. We need to wait a bit because if it's called too close to .hide then it won't be shown. - $timeout(function() { - scope.modal && scope.modal.show(); - }, 500); - return self.showError(error, 'mm.core.error'); - }); - }; - /** * Gets the mark string from a question HTML. * Example result: "Marked out of 1.00". @@ -153,62 +72,6 @@ angular.module('mm.addons.mod_quiz') } }; - /** - * Init a preflight modal, adding it to the scope. - * - * @module mm.addons.mod_quiz - * @ngdoc method - * @name $mmaModQuizHelper#initPreflightModal - * @param {Object} scope Scope. - * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. - * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. - * @return {Promise} Promise resolved when the modal is initialized. - */ - self.initPreflightModal = function(scope, quizAccessInfo, attempt) { - var notSupported = [], - directives = [], - handlers = []; - - angular.forEach(quizAccessInfo.activerulenames, function(rule) { - var handler = $mmaModQuizAccessRulesDelegate.getAccessRuleHandler(rule); - if (handler) { - if (handler.isPreflightCheckRequired(attempt)) { - handlers.push(handler); - directives.push(handler.getPreflightDirectiveName()); - } - } else { - notSupported.push(rule); - } - }); - - if (notSupported.length) { - var error = $translate.instant('mma.mod_quiz.errorrulesnotsupported') + ' ' + JSON.stringify(notSupported); - return $q.reject(error); - } - - scope.accessRulesDirectives = directives; - - return $ionicModal.fromTemplateUrl('addons/mod_quiz/templates/preflight-modal.html', { - scope: scope, - animation: 'slide-in-up' - }).then(function(modal) { - scope.modal = modal; - - scope.closeModal = function() { - modal.hide(); - // Clean the preflight data. - handlers.forEach(function(handler) { - if (typeof handler.cleanPreflight == 'function') { - handler.cleanPreflight(scope.preflightData); - } - }); - }; - scope.$on('$destroy', function() { - modal.remove(); - }); - }); - }; - /** * Add some calculated data to the attempt. * diff --git a/www/addons/mod_quiz/services/quiz.js b/www/addons/mod_quiz/services/quiz.js index 625e0de3404..38f53c742c2 100644 --- a/www/addons/mod_quiz/services/quiz.js +++ b/www/addons/mod_quiz/services/quiz.js @@ -23,7 +23,8 @@ angular.module('mm.addons.mod_quiz') */ .factory('$mmaModQuiz', function($log, $mmSite, $mmSitesManager, $q, $translate, $mmUtil, $mmText, $mmQuestionDelegate, $mmaModQuizAccessRulesDelegate, $mmQuestionHelper, $mmFilepool, $mmaModQuizOnline, $mmaModQuizOffline, $state, - mmaModQuizComponent, mmCoreDownloaded, mmCoreDownloading, mmCoreNotDownloaded) { + mmaModQuizComponent, mmCoreDownloaded, mmCoreDownloading, mmCoreNotDownloaded, + $ionicModal, $timeout, $rootScope) { $log = $log.getInstance('$mmaModQuiz'); @@ -68,6 +69,98 @@ angular.module('mm.addons.mod_quiz') blockedQuizzes[siteId][quizId] = true; }; + /** + * Validate a preflight data or show a modal to input the preflight data if required. + * It calls $mmaModQuiz#startAttempt if a new attempt is needed. + * + * @module mm.addons.mod_quiz + * @ngdoc method + * @name $mmaModQuiz#checkPreflightData + * @param {Object} scope Scope. + * @param {Object} quiz Quiz. + * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. + * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param {Boolean} offline True if attempt is offline. + * @param {Boolean} fromModal True if sending data using preflight modal, false otherwise. + * @param {Boolean} prefetch True if prefetching. + * @return {Promise} Promise resolved when the preflight data is validated. + */ + self.checkPreflightData = function(scope, quiz, quizAccessInfo, attempt, offline, fromModal, prefetch) { + var promise, + rules = quizAccessInfo.activerulenames, + preflightRequired = $mmaModQuizAccessRulesDelegate.isPreflightCheckRequired(rules, attempt, prefetch); + + if (preflightRequired && !fromModal) { + // Preflight check is required but no preflightData has been sent. Show a modal with the preflight form. + if (!scope.modal) { + // Modal hasn't been created yet. Create it and show it. + return self.initPreflightModal(scope, quizAccessInfo, attempt, prefetch).catch(function(error) { + return $q.reject(error || 'Error initializing preflight modal.'); + }).then(function() { + scope.modal.show(); + return $q.reject(); + }); + } else if (!scope.modal.isShown()) { + // Modal is created but not shown. Show it. + scope.modal.show(); + } + return $q.reject(); + } + + // Hide modal if needed. + scope.modal && scope.modal.hide(); + + // Get some fixed preflight data from access rules (data that doesn't require user interaction). + $mmaModQuizAccessRulesDelegate.getFixedPreflightData(rules, attempt, scope.preflightData, prefetch); + + if (attempt) { + if (attempt.state != self.ATTEMPT_OVERDUE && !attempt.finishedOffline) { + // We're continuing an attempt. Call getAttemptData to validate the preflight data. + var page = attempt.currentpage; + promise = self.getAttemptData(attempt.id, page, scope.preflightData, offline, true).then(function() { + if (offline) { + // Get current page stored in local. + return $mmaModQuizOffline.getAttemptById(attempt.id).then(function(localAttempt) { + attempt.currentpage = localAttempt.currentpage; + }).catch(function() { + // No local data. + }); + } + }); + } else { + // Attempt is overdue or finished in offline, we can only see the summary. + // Call getAttemptSummary to validate the preflight data. + promise = self.getAttemptSummary(attempt.id, scope.preflightData, offline, true); + } + } else { + // We're starting a new attempt, call startAttempt. + promise = self.startAttempt(quiz.id, scope.preflightData).then(function(att) { + attempt = att; + }); + } + + return promise.then(function() { + // Preflight data validated. Close modal if needed. + return attempt; + }).catch(function(error) { + if (prefetch) { + return $q.reject(error); + } else { + // Show modal again. We need to wait a bit because if it's called too close to .hide then it won't be shown. + $timeout(function() { + scope.modal && scope.modal.show(); + }, 500); + + if (error) { + $mmUtil.showErrorModal(error); + } else { + $mmUtil.showErrorModal('mm.core.error', true); + } + return $q.reject(); + } + }); + }; + /** * Clear blocked quizzes. * @@ -1251,6 +1344,63 @@ angular.module('mm.addons.mod_quiz') }); }; + /** + * Init a preflight modal, adding it to the scope. + * + * @module mm.addons.mod_quiz + * @ngdoc method + * @name $mmaModQuiz#initPreflightModal + * @param {Object} scope Scope. + * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. + * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param {Boolean} prefetch True if prefetching. + * @return {Promise} Promise resolved when the modal is initialized. + */ + self.initPreflightModal = function(scope, quizAccessInfo, attempt, prefetch) { + var notSupported = [], + directives = [], + handlers = []; + + angular.forEach(quizAccessInfo.activerulenames, function(rule) { + var handler = $mmaModQuizAccessRulesDelegate.getAccessRuleHandler(rule); + if (handler) { + if (handler.isPreflightCheckRequired(attempt, prefetch)) { + handlers.push(handler); + directives.push(handler.getPreflightDirectiveName()); + } + } else { + notSupported.push(rule); + } + }); + + if (notSupported.length) { + var error = $translate.instant('mma.mod_quiz.errorrulesnotsupported') + ' ' + JSON.stringify(notSupported); + return $q.reject(error); + } + + scope.accessRulesDirectives = directives; + + return $ionicModal.fromTemplateUrl('addons/mod_quiz/templates/preflight-modal.html', { + scope: scope, + animation: 'slide-in-up' + }).then(function(modal) { + scope.modal = modal; + + scope.closeModal = function() { + modal.hide(); + // Clean the preflight data. + handlers.forEach(function(handler) { + if (typeof handler.cleanPreflight == 'function') { + handler.cleanPreflight(scope.preflightData); + } + }); + }; + scope.$on('$destroy', function() { + modal.remove(); + }); + }); + }; + /** * Invalidates all the data related to a certain quiz. * @@ -1888,15 +2038,19 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuiz#prefetch - * @param {Object} module The module object returned by WS. - * @param {Number} courseId Course ID the module belongs to. - * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. + * @param {Object} module The module object returned by WS. + * @param {Number} courseId Course ID the module belongs to. + * @param {Boolean} askPreflight True if we should ask for preflight data if needed, false otherwise. + * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. */ - self.prefetch = function(module, courseId) { + self.prefetch = function(module, courseId, askPreflight) { var siteId = $mmSite.getId(), attempts, startAttempt, - quiz; + quiz, + quizAccessInfo, + preflightData = {}, + scope; // Mark package as downloading. return $mmFilepool.storePackageStatus(siteId, mmaModQuizComponent, module.id, mmCoreDownloading).then(function() { @@ -1908,7 +2062,9 @@ angular.module('mm.addons.mod_quiz') var promises = []; // Get user attempts and data not related with attempts. - promises.push(self.getQuizAccessInformation(quiz.id, false, true, siteId)); + promises.push(self.getQuizAccessInformation(quiz.id, false, true, siteId).then(function(info) { + quizAccessInfo = info; + })); promises.push(self.getQuizRequiredQtypes(quiz.id, true, siteId)); promises.push(self.getUserAttempts(quiz.id, 'all', true, false, true, siteId).then(function(atts) { attempts = atts; @@ -1916,11 +2072,22 @@ angular.module('mm.addons.mod_quiz') return $q.all(promises); }).then(function() { - // Start a new attempt if needed. - var lastAttempt = attempts[attempts.length - 1]; - startAttempt = !attempts.length || self.isAttemptFinished(lastAttempt.state); + var attempt = attempts[attempts.length - 1]; + if (!attempt || self.isAttemptFinished(attempt.state)) { + startAttempt = true; + attempt = undefined; + } + + if (askPreflight) { + // Check if the quiz requires preflight data. + scope = $rootScope.$new(); + scope.preflightData = preflightData; - return startAttempt ? self.startAttempt(quiz.id, {}, false, siteId) : $q.when(); + return getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, false); + } else { + // Get some fixed preflight data from access rules (data that doesn't require user interaction). + $mmaModQuizAccessRulesDelegate.getFixedPreflightData(quizAccessInfo.activerulenames, attempt, preflightData, true); + } }).then(function() { promises = []; @@ -1946,7 +2113,7 @@ angular.module('mm.addons.mod_quiz') // We have quiz data, now we'll get specific data for each attempt. promises = []; angular.forEach(attempts, function(attempt) { - promises.push(self.prefetchAttempt(quiz, attempt, siteId)); + promises.push(self.prefetchAttempt(quiz, attempt, preflightData, siteId)); }); return $q.all(promises); @@ -1960,27 +2127,70 @@ angular.module('mm.addons.mod_quiz') return $mmFilepool.setPackagePreviousStatus(siteId, mmaModQuizComponent, module.id).then(function() { return $q.reject(error); }); + }).finally(function() { + if (scope) { + scope.$destroy(); + } }); }; + /** + * Convenience function to get preflight data for prefetch. + * + * @param {Object} scope Scope. + * @param {Object} quiz Quiz. + * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. + * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param {Boolean} fromModal True if sending data using preflight modal, false otherwise. + * @return {Promise} Promise resolved when the preflight data is validated. + */ + function getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, fromModal) { + // Check if preflight data is valid or not required. + return self.checkPreflightData(scope, quiz, quizAccessInfo, attempt, false, fromModal, true).catch(function(error) { + if (error) { + // Something went wrong, reject. + return $q.reject(error); + } else { + // No preflight data provided and it's required. We need to wait for user input. + var deferred = $q.defer(), + resolved = false; + + scope.start = function() { + resolved = true; + // Try to validate new preflightData (chain promises). + deferred.resolve(getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, true)); + }; + scope.$on('modal.hidden', function() { + if (!resolved) { + deferred.reject(); + } + }); + scope.$on('modal.removed', function() { + if (!resolved) { + deferred.reject(); + } + }); + return deferred.promise; + } + }); + } + /** * Prefetch all WS data for an attempt. * * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuiz#prefetchAttempt - * @param {Object} quiz Quiz. - * @param {Object} attempt Attempt. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Preflight required data (like password). + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the prefetch is finished. Data returned is not reliable. */ - self.prefetchAttempt = function(quiz, attempt, siteId) { + self.prefetchAttempt = function(quiz, attempt, preflightData, siteId) { var pages = self.getPagesFromLayout(attempt.layout), promises = [], - attemptGrade, - preflightData = { - confirmdatasaved: 1 - }; + attemptGrade; if (self.isAttemptFinished(attempt.state)) { // Attempt is finished, get feedback and review data. @@ -2002,6 +2212,7 @@ angular.module('mm.addons.mod_quiz') return $q.all(questionPromises); })); } else { + // Attempt not finished, get data needed to continue the attempt. promises.push(self.getAttemptAccessInformation(quiz.id, attempt.id, false, true, siteId)); promises.push(self.getAttemptSummary(attempt.id, preflightData, false, true, false, siteId)); @@ -2031,21 +2242,27 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuiz#prefetchQuizAndLastAttempt - * @param {Object} quiz Quiz. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when done. + * @param {Object} quiz Quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Boolean} askPreflight True if we should ask for preflight data if needed, false otherwise. + * @return {Promise} Promise resolved when done. */ - self.prefetchQuizAndLastAttempt = function(quiz, siteId) { + self.prefetchQuizAndLastAttempt = function(quiz, siteId, askPreflight) { siteId = siteId || $mmSite.getId(); var attempts, promises = [], component = mmaModQuizComponent, revision, - timemod; + timemod, + quizAccessInfo, + preflightData = {}, + scope; // Get quiz data. - promises.push(self.getQuizAccessInformation(quiz.id, false, true, siteId)); + promises.push(self.getQuizAccessInformation(quiz.id, false, true, siteId).then(function(info) { + quizAccessInfo = info; + })); promises.push(self.getQuizRequiredQtypes(quiz.id, true, siteId)); promises.push(self.getCombinedReviewOptions(quiz.id, true, siteId)); promises.push(self.getUserBestGrade(quiz.id, true, siteId)); @@ -2060,9 +2277,27 @@ angular.module('mm.addons.mod_quiz') promises.push(self.getAttemptAccessInformation(quiz.id, 0, false, true, siteId)); // Last attempt. return $q.all(promises).then(function() { + var attempt = attempts[attempts.length - 1]; + if (!attempt) { + // No need to get attempt data, we don't need preflight data. + return; + } + + if (askPreflight) { + // Check if the quiz requires preflight data. + scope = $rootScope.$new(); + scope.preflightData = preflightData; + + return getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, false); + } else { + // Get some fixed preflight data from access rules (data that doesn't require user interaction). + $mmaModQuizAccessRulesDelegate.getFixedPreflightData(quizAccessInfo.activerulenames, attempt, preflightData, true); + } + + }).then(function() { if (attempts && attempts.length) { // Get data for last attempt. - return self.prefetchAttempt(quiz, attempts[attempts.length - 1], siteId); + return self.prefetchAttempt(quiz, attempts[attempts.length - 1], preflightData, siteId); } }).then(function() { // Prefetch finished, get current status to determine if we need to change it. diff --git a/www/addons/mod_quiz/templates/index.html b/www/addons/mod_quiz/templates/index.html index 319dfee1544..fcc4203ea54 100644 --- a/www/addons/mod_quiz/templates/index.html +++ b/www/addons/mod_quiz/templates/index.html @@ -91,12 +91,15 @@

{{ 'mma.mod_quiz.summaryofattempts' | translate }}

{{ 'mma.mod_quiz.hasdatatosync' | translate }}

- From f4abe8cf4abc52431f88aca32c7e5a6d183f0d0b Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 18 May 2016 16:46:10 +0200 Subject: [PATCH 4/5] MOBILE-1543 quiz: Gather preflight in sync and store password in DB --- .../delaybetweenattempts/handlers.js | 4 +- .../accessrules/ipaddress/handlers.js | 4 +- .../accessrules/numattempts/handlers.js | 4 +- .../accessrules/offlineattempts/handlers.js | 11 +- .../accessrules/openclosedate/handlers.js | 4 +- .../mod_quiz/accessrules/password/handlers.js | 139 ++++++++- .../accessrules/safebrowser/handlers.js | 4 +- .../accessrules/securewindow/handlers.js | 4 +- .../accessrules/timelimit/handlers.js | 4 +- www/addons/mod_quiz/controllers/index.js | 21 +- www/addons/mod_quiz/controllers/player.js | 1 + .../mod_quiz/services/accessrulesdelegate.js | 109 ++++++- www/addons/mod_quiz/services/helper.js | 2 +- www/addons/mod_quiz/services/quiz.js | 282 +++++++++++------- www/addons/mod_quiz/services/quiz_sync.js | 40 +-- www/addons/mod_quiz/templates/index.html | 6 +- .../mod_quiz/templates/preflight-modal.html | 4 +- www/core/lang/en.json | 2 + www/core/lib/util.js | 39 ++- 19 files changed, 506 insertions(+), 178 deletions(-) diff --git a/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js b/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js index 07c7a8fb1f6..3a357f2c334 100644 --- a/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/delaybetweenattempts/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/ipaddress/handlers.js b/www/addons/mod_quiz/accessrules/ipaddress/handlers.js index 85faa2cf50f..bfabf547332 100644 --- a/www/addons/mod_quiz/accessrules/ipaddress/handlers.js +++ b/www/addons/mod_quiz/accessrules/ipaddress/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/numattempts/handlers.js b/www/addons/mod_quiz/accessrules/numattempts/handlers.js index 5b66f4e80ad..394334de7cc 100644 --- a/www/addons/mod_quiz/accessrules/numattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/numattempts/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js b/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js index e13745f339c..917f35f8c62 100644 --- a/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js +++ b/www/addons/mod_quiz/accessrules/offlineattempts/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { if (prefetch) { return false; } @@ -57,15 +59,14 @@ angular.module('mm.addons.mod_quiz') /** * Get fixed preflight data (data that doesn't require user interaction). * - * @module mm.addons.mod_quiz - * @ngdoc method - * @name $mmaModQuizAccessRulesDelegate#getFixedPreflightData + * @param {Object} quiz Quiz. * @param {Object} attempt Attempt. * @param {Object} preflightData Object where to store the preflight data. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Void} */ - self.getFixedPreflightData = function(attempt, preflightData, prefetch) { + self.getFixedPreflightData = function(quiz, attempt, preflightData, prefetch, siteId) { preflightData.confirmdatasaved = 1; }; diff --git a/www/addons/mod_quiz/accessrules/openclosedate/handlers.js b/www/addons/mod_quiz/accessrules/openclosedate/handlers.js index 58527d8dd28..4a144ca9efc 100644 --- a/www/addons/mod_quiz/accessrules/openclosedate/handlers.js +++ b/www/addons/mod_quiz/accessrules/openclosedate/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/password/handlers.js b/www/addons/mod_quiz/accessrules/password/handlers.js index be3b883e705..99c15718163 100644 --- a/www/addons/mod_quiz/accessrules/password/handlers.js +++ b/www/addons/mod_quiz/accessrules/password/handlers.js @@ -13,6 +13,18 @@ // limitations under the License. angular.module('mm.addons.mod_quiz') +.constant('mmaModQuizAccessPasswordStore', 'mod_quiz_access_password') + +.config(function($mmSitesFactoryProvider, mmaModQuizAccessPasswordStore) { + var stores = [ + { + name: mmaModQuizAccessPasswordStore, + keyPath: 'id', + indexes: [] + } + ]; + $mmSitesFactoryProvider.registerStores(stores); +}) /** * Handler for password quiz access rule. @@ -21,7 +33,7 @@ angular.module('mm.addons.mod_quiz') * @ngdoc service * @name $mmaQuizAccessPasswordHandler */ -.factory('$mmaQuizAccessPasswordHandler', function() { +.factory('$mmaQuizAccessPasswordHandler', function($mmSitesManager, $mmSite, $q, mmaModQuizAccessPasswordStore) { var self = {}; @@ -34,6 +46,80 @@ angular.module('mm.addons.mod_quiz') delete data.quizpassword; }; + /** + * Get a password stored in DB. + * + * @param {Number} quizId Quiz ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with password on success, rejected otherwise. + */ + function getPasswordEntry(quizId, siteId) { + siteId = siteId || $mmSite.getId(); + + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.getDb().get(mmaModQuizAccessPasswordStore, quizId); + }); + } + + /** + * Remove a password from DB. + * + * @param {Number} quizId Quiz ID. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved on success, rejected otherwise. + */ + function removePassword(quizId, siteId) { + siteId = siteId || $mmSite.getId(); + + return $mmSitesManager.getSite(siteId).then(function(site) { + return site.getDb().remove(mmaModQuizAccessPasswordStore, quizId); + }); + } + + /** + * Store a password in DB. + * + * @param {Number} quizId Quiz ID. + * @param {String} password Password. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved on success, rejected otherwise. + */ + function storePassword(quizId, password, siteId) { + siteId = siteId || $mmSite.getId(); + + return $mmSitesManager.getSite(siteId).then(function(site) { + var entry = { + id: quizId, + password: password, + timemodified: new Date().getTime() + }; + + return site.getDb().insert(mmaModQuizAccessPasswordStore, entry); + }); + } + + /** + * Get fixed preflight data (data that doesn't require user interaction). + * + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when preflight data has been added. + */ + self.getFixedPreflightData = function(quiz, attempt, preflightData, prefetch, siteId) { + if (quiz && quiz.id && typeof preflightData.quizpassword == 'undefined') { + return getPasswordEntry(quiz.id, siteId).then(function(entry) { + preflightData.quizpassword = entry.password; + }).catch(function() { + // Don't reject. + }); + } + + return $q.when(); + }; + /** * Whether or not the rule is enabled for the site. * @@ -46,13 +132,20 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. - * @return {Boolean} True if preflight check required. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with a boolean: true if preflight check required, false otherwise. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { - // If the password rule is active in a quiz we always require to input the password. - return true; + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { + // Check if we have a password stored. + return getPasswordEntry(quiz.id, siteId).then(function() { + return false; + }).catch(function() { + // Not stored. + return true; + }); }; /** @@ -64,6 +157,42 @@ angular.module('mm.addons.mod_quiz') return 'mma-quiz-access-password-preflight'; }; + /** + * The preflight check has passed. This is a chance to record that fact in some way. + * + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + self.notifyPreflightCheckPassed = function(quiz, attempt, preflightData, prefetch, siteId) { + if (quiz && quiz.id && typeof preflightData.quizpassword != 'undefined') { + return storePassword(quiz.id, preflightData.quizpassword, siteId); + } + + return $q.when(); + }; + + /** + * The preflight check has failed. + * + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + self.notifyPreflightCheckFailed = function(quiz, attempt, preflightData, prefetch, siteId) { + if (quiz && quiz.id) { + return removePassword(quiz.id, siteId); + } + + return $q.when(); + }; + return self; }) diff --git a/www/addons/mod_quiz/accessrules/safebrowser/handlers.js b/www/addons/mod_quiz/accessrules/safebrowser/handlers.js index 61da23c0084..485e61069d9 100644 --- a/www/addons/mod_quiz/accessrules/safebrowser/handlers.js +++ b/www/addons/mod_quiz/accessrules/safebrowser/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/securewindow/handlers.js b/www/addons/mod_quiz/accessrules/securewindow/handlers.js index cec13eb9ba8..d09178b3c87 100644 --- a/www/addons/mod_quiz/accessrules/securewindow/handlers.js +++ b/www/addons/mod_quiz/accessrules/securewindow/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { return false; }; diff --git a/www/addons/mod_quiz/accessrules/timelimit/handlers.js b/www/addons/mod_quiz/accessrules/timelimit/handlers.js index bef8de8eb9d..2acd61bd57d 100644 --- a/www/addons/mod_quiz/accessrules/timelimit/handlers.js +++ b/www/addons/mod_quiz/accessrules/timelimit/handlers.js @@ -37,11 +37,13 @@ angular.module('mm.addons.mod_quiz') /** * Check if a preflight check is required. * + * @param {Object} quiz Quiz. * @param {Object} [attempt] Attempt to continue. Not defined if starting a new attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Boolean} True if preflight check required. */ - self.isPreflightCheckRequired = function(attempt, prefetch) { + self.isPreflightCheckRequired = function(quiz, attempt, prefetch, siteId) { // Warning only required if the attempt is not already started. // prefetch should always be false since offline isn't compatible with timed quizzes. return !attempt; diff --git a/www/addons/mod_quiz/controllers/index.js b/www/addons/mod_quiz/controllers/index.js index f3a5570c801..c8801e2f2d6 100644 --- a/www/addons/mod_quiz/controllers/index.js +++ b/www/addons/mod_quiz/controllers/index.js @@ -316,7 +316,7 @@ angular.module('mm.addons.mod_quiz') // Tries to synchronize the current quiz. function syncQuiz(checkTime, showErrors) { - var promise = checkTime ? $mmaModQuizSync.syncQuizIfNeeded(quiz) : $mmaModQuizSync.syncQuiz(quiz); + var promise = checkTime ? $mmaModQuizSync.syncQuizIfNeeded(quiz, true) : $mmaModQuizSync.syncQuiz(quiz, true); return promise.then(function(warnings) { var message = $mmText.buildMessage(warnings); if (message) { @@ -372,7 +372,7 @@ angular.module('mm.addons.mod_quiz') currentStatus = status; if (status == mmCoreDownloading) { - $scope.downloading = true; + $scope.showSpinner = true; } } @@ -394,7 +394,12 @@ angular.module('mm.addons.mod_quiz') // Synchronize the quiz. $scope.sync = function() { - var modal = $mmUtil.showModalLoading('mm.settings.synchronizing', true); + if ($scope.showSpinner) { + // Scope is being or synchronized, abort. + return; + } + + $scope.showSpinner = true; syncQuiz(false, true).then(function() { // Refresh the data. $scope.quizLoaded = false; @@ -403,14 +408,14 @@ angular.module('mm.addons.mod_quiz') $scope.quizLoaded = true; }); }).finally(function() { - modal.dismiss(); + $scope.showSpinner = false; }); }; // Attempt the quiz. $scope.attemptQuiz = function() { - if ($scope.downloading) { - // Scope is being downloaded, abort. + if ($scope.showSpinner) { + // Scope is being or synchronized, abort. return; } @@ -418,14 +423,14 @@ angular.module('mm.addons.mod_quiz') // Quiz supports offline, check if it needs to be downloaded. if (currentStatus != mmCoreDownloaded) { // Prefetch the quiz. - $scope.downloading = true; + $scope.showSpinner = true; return $mmaModQuiz.prefetch(module, courseId, true).then(function() { // Success downloading, open quiz. openQuiz(); }).catch(function(error) { $mmaModQuizHelper.showError(error, 'mma.mod_quiz.errordownloading'); }).finally(function() { - $scope.downloading = false; + $scope.showSpinner = false; }); } else { // Already downloaded, open it. diff --git a/www/addons/mod_quiz/controllers/player.js b/www/addons/mod_quiz/controllers/player.js index 81d2266c26e..a70911919c5 100644 --- a/www/addons/mod_quiz/controllers/player.js +++ b/www/addons/mod_quiz/controllers/player.js @@ -46,6 +46,7 @@ angular.module('mm.addons.mod_quiz') $scope.component = mmaModQuizComponent; $scope.quizAborted = false; $scope.preflightData = {}; + $scope.preflightModalTitle = 'mma.mod_quiz.startattempt'; // Convenience function to start the player. function start(fromModal) { diff --git a/www/addons/mod_quiz/services/accessrulesdelegate.js b/www/addons/mod_quiz/services/accessrulesdelegate.js index 861c39224f0..b97501c2c55 100644 --- a/www/addons/mod_quiz/services/accessrulesdelegate.js +++ b/www/addons/mod_quiz/services/accessrulesdelegate.js @@ -75,18 +75,24 @@ angular.module('mm.addons.mod_quiz') * @ngdoc method * @name $mmaModQuizAccessRulesDelegate#getFixedPreflightData * @param {String[]} rules Name of the rules. + * @param {Object} quiz Quiz. * @param {Object} attempt Attempt. * @param {Object} preflightData Object where to store the preflight data. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. - * @return {Void} + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when all the data has been gathered. */ - self.getFixedPreflightData = function(rules, attempt, preflightData, prefetch) { + self.getFixedPreflightData = function(rules, quiz, attempt, preflightData, prefetch, siteId) { + var promises = []; angular.forEach(rules, function(rule) { var handler = self.getAccessRuleHandler(rule); if (handler && handler.getFixedPreflightData) { - handler.getFixedPreflightData(attempt, preflightData, prefetch); + promises.push($q.when(handler.getFixedPreflightData(quiz, attempt, preflightData, prefetch, siteId))); } }); + return $mmUtil.allPromises(promises).catch(function() { + // Never reject. + }); }; /** @@ -109,21 +115,87 @@ angular.module('mm.addons.mod_quiz') * @ngdoc method * @name $mmaModQuizAccessRulesDelegate#isPreflightCheckRequired * @param {String[]} rules Name of the rules. + * @param {Object} quiz Quiz. * @param {Object} attempt Attempt. * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. - * @return {Boolean} True if required, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved with boolean: true if required, false otherwise. */ - self.isPreflightCheckRequired = function(rules, attempt, prefetch) { - var isRequired = false; + self.isPreflightCheckRequired = function(rules, quiz, attempt, prefetch, siteId) { + var isRequired = false, + promises = []; + angular.forEach(rules, function(rule) { var handler = self.getAccessRuleHandler(rule); if (handler) { - if (handler.isPreflightCheckRequired(attempt, prefetch)) { - isRequired = true; - } + promises.push($q.when(handler.isPreflightCheckRequired(quiz, attempt, prefetch, siteId)).then(function(required) { + if (required) { + isRequired = true; + } + })); + } + }); + + return $mmUtil.allPromises(promises).then(function() { + return isRequired; + }).catch(function() { + // Never reject. + return isRequired; + }); + }; + + /** + * The preflight check has passed. This is a chance to record that fact in some way. + * + * @module mm.addons.mod_quiz + * @ngdoc method + * @name $mmaModQuizAccessRulesDelegate#notifyPreflightCheckPassed + * @param {String[]} rules Name of the rules. + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + self.notifyPreflightCheckPassed = function(rules, quiz, attempt, preflightData, prefetch, siteId) { + var promises = []; + angular.forEach(rules, function(rule) { + var handler = self.getAccessRuleHandler(rule); + if (handler && handler.notifyPreflightCheckPassed) { + promises.push($q.when(handler.notifyPreflightCheckPassed(quiz, attempt, preflightData, prefetch, siteId))); } }); - return isRequired; + return $mmUtil.allPromises(promises).catch(function() { + // Never reject. + }); + }; + + /** + * The preflight check has failed. + * + * @module mm.addons.mod_quiz + * @ngdoc method + * @name $mmaModQuizAccessRulesDelegate#notifyPreflightCheckFailed + * @param {String[]} rules Name of the rules. + * @param {Object} quiz Quiz. + * @param {Object} attempt Attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {Boolean} prefetch True if prefetching, false if attempting the quiz. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. + */ + self.notifyPreflightCheckFailed = function(rules, quiz, attempt, preflightData, prefetch, siteId) { + var promises = []; + angular.forEach(rules, function(rule) { + var handler = self.getAccessRuleHandler(rule); + if (handler && handler.notifyPreflightCheckFailed) { + promises.push($q.when(handler.notifyPreflightCheckFailed(quiz, attempt, preflightData, prefetch, siteId))); + } + }); + return $mmUtil.allPromises(promises).catch(function() { + // Never reject. + }); }; /** @@ -138,17 +210,24 @@ angular.module('mm.addons.mod_quiz') * returning an object defining these properties. See {@link $mmUtil#resolveObject}. * - isEnabled (Boolean|Promise) Whether or not the handler is enabled on a site level. * When using a promise, it should return a boolean. - * - isPreflightCheckRequired(attempt, prefetch) (Boolean|Promise) Whether or not the rule requires - * a preflight check when prefetch or start/continue an attempt. - * - getFixedPreflightData(attempt, preflightData, prefetch) Optional. Should add preflight data - * that doesn't require user interaction. + * - isPreflightCheckRequired(quiz, attempt, prefetch, siteId) (Boolean|Promise) Whether the rule + * requires a preflight check when prefetch/start/continue an attempt. + * It should return a boolean or a promise resolved with a boolean. + * - getFixedPreflightData(quiz, attempt, preflightData, prefetch, siteId) (Promise) Optional. + * Should add preflight data that doesn't require user interaction. + * Return a promise or nothing if synchronous. * - getPreflightDirectiveName() (String) Optional. Returns the name of the directive to render * the access rule preflight. Required if the handler needs a * preflight check in some cases. + * - notifyPreflightCheckPassed(quiz, attempt, preflightData, prefetch, siteId) (Promise) Optional. + * Called when the preflight check has passed. This is a chance to + * record that fact in some way. + * - notifyPreflightCheckFailed(quiz, attempt, preflightData, prefetch, siteId) (Promise) Optional. + * Called when the preflight check fails. * - shouldShowTimeLeft(attempt, endTime, timeNow) (Boolean) Optional. Whether or not the time * left of an attempt should be displayed. * - cleanPreflight(data) Function called when preflight form is closed. Should delete all the - * data that should be resetted for the next form show. + * data that should be resetted for the next form show. */ self.registerHandler = function(addon, ruleName, handler) { if (typeof handlers[ruleName] !== 'undefined') { diff --git a/www/addons/mod_quiz/services/helper.js b/www/addons/mod_quiz/services/helper.js index 8bd81058392..c5ad76b20b8 100644 --- a/www/addons/mod_quiz/services/helper.js +++ b/www/addons/mod_quiz/services/helper.js @@ -66,7 +66,7 @@ angular.module('mm.addons.mod_quiz') */ self.getReadableTimeFromTimestamp = function(timestamp) { if (!timestamp) { - return $translate('mm.core.none'); + return $translate.instant('mm.core.never'); } else { return moment(timestamp).format('LLL'); } diff --git a/www/addons/mod_quiz/services/quiz.js b/www/addons/mod_quiz/services/quiz.js index 38f53c742c2..ac84bf4df97 100644 --- a/www/addons/mod_quiz/services/quiz.js +++ b/www/addons/mod_quiz/services/quiz.js @@ -23,13 +23,14 @@ angular.module('mm.addons.mod_quiz') */ .factory('$mmaModQuiz', function($log, $mmSite, $mmSitesManager, $q, $translate, $mmUtil, $mmText, $mmQuestionDelegate, $mmaModQuizAccessRulesDelegate, $mmQuestionHelper, $mmFilepool, $mmaModQuizOnline, $mmaModQuizOffline, $state, - mmaModQuizComponent, mmCoreDownloaded, mmCoreDownloading, mmCoreNotDownloaded, - $ionicModal, $timeout, $rootScope) { + mmaModQuizComponent, mmCoreDownloaded, mmCoreDownloading, mmCoreNotDownloaded, $injector, $ionicModal, + $timeout, $rootScope) { $log = $log.getInstance('$mmaModQuiz'); var self = {}, - blockedQuizzes = {}; + blockedQuizzes = {}, + $mmaModQuizSync; // We'll inject it using $injector to prevent circular dependencies. // Constants. @@ -83,81 +84,86 @@ angular.module('mm.addons.mod_quiz') * @param {Boolean} offline True if attempt is offline. * @param {Boolean} fromModal True if sending data using preflight modal, false otherwise. * @param {Boolean} prefetch True if prefetching. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the preflight data is validated. */ - self.checkPreflightData = function(scope, quiz, quizAccessInfo, attempt, offline, fromModal, prefetch) { + self.checkPreflightData = function(scope, quiz, quizAccessInfo, attempt, offline, fromModal, prefetch, siteId) { var promise, - rules = quizAccessInfo.activerulenames, - preflightRequired = $mmaModQuizAccessRulesDelegate.isPreflightCheckRequired(rules, attempt, prefetch); - - if (preflightRequired && !fromModal) { - // Preflight check is required but no preflightData has been sent. Show a modal with the preflight form. - if (!scope.modal) { - // Modal hasn't been created yet. Create it and show it. - return self.initPreflightModal(scope, quizAccessInfo, attempt, prefetch).catch(function(error) { + rules = quizAccessInfo.activerulenames; + + return $mmaModQuizAccessRulesDelegate.isPreflightCheckRequired(rules, quiz, attempt, prefetch, siteId) + .then(function(preflightRequired) { + + if (preflightRequired && !fromModal) { + // Preflight check is required but no preflightData has been sent. Show a modal with the preflight form. + return self.initPreflightModal(scope, quiz, quizAccessInfo, attempt, prefetch, siteId).catch(function(error) { return $q.reject(error || 'Error initializing preflight modal.'); }).then(function() { scope.modal.show(); return $q.reject(); }); - } else if (!scope.modal.isShown()) { - // Modal is created but not shown. Show it. - scope.modal.show(); } - return $q.reject(); - } - // Hide modal if needed. - scope.modal && scope.modal.hide(); - - // Get some fixed preflight data from access rules (data that doesn't require user interaction). - $mmaModQuizAccessRulesDelegate.getFixedPreflightData(rules, attempt, scope.preflightData, prefetch); - - if (attempt) { - if (attempt.state != self.ATTEMPT_OVERDUE && !attempt.finishedOffline) { - // We're continuing an attempt. Call getAttemptData to validate the preflight data. - var page = attempt.currentpage; - promise = self.getAttemptData(attempt.id, page, scope.preflightData, offline, true).then(function() { - if (offline) { - // Get current page stored in local. - return $mmaModQuizOffline.getAttemptById(attempt.id).then(function(localAttempt) { - attempt.currentpage = localAttempt.currentpage; - }).catch(function() { - // No local data. - }); - } - }); + // Hide modal if needed. + scope.modal && scope.modal.hide(); + + // Get some fixed preflight data from access rules (data that doesn't require user interaction). + return $mmaModQuizAccessRulesDelegate.getFixedPreflightData(rules, quiz, attempt, scope.preflightData, prefetch, siteId); + }).then(function() { + if (attempt) { + if (attempt.state != self.ATTEMPT_OVERDUE && !attempt.finishedOffline) { + // We're continuing an attempt. Call getAttemptData to validate the preflight data. + var page = attempt.currentpage; + promise = self.getAttemptData(attempt.id, page, scope.preflightData, offline, true).then(function() { + if (offline) { + // Get current page stored in local. + return $mmaModQuizOffline.getAttemptById(attempt.id).then(function(localAttempt) { + attempt.currentpage = localAttempt.currentpage; + }).catch(function() { + // No local data. + }); + } + }); + } else { + // Attempt is overdue or finished in offline, we can only see the summary. + // Call getAttemptSummary to validate the preflight data. + promise = self.getAttemptSummary(attempt.id, scope.preflightData, offline, true); + } } else { - // Attempt is overdue or finished in offline, we can only see the summary. - // Call getAttemptSummary to validate the preflight data. - promise = self.getAttemptSummary(attempt.id, scope.preflightData, offline, true); + // We're starting a new attempt, call startAttempt. + promise = self.startAttempt(quiz.id, scope.preflightData).then(function(att) { + attempt = att; + }); } - } else { - // We're starting a new attempt, call startAttempt. - promise = self.startAttempt(quiz.id, scope.preflightData).then(function(att) { - attempt = att; - }); - } - return promise.then(function() { - // Preflight data validated. Close modal if needed. - return attempt; - }).catch(function(error) { - if (prefetch) { - return $q.reject(error); - } else { - // Show modal again. We need to wait a bit because if it's called too close to .hide then it won't be shown. - $timeout(function() { - scope.modal && scope.modal.show(); - }, 500); + return promise.then(function() { + // Preflight data validated. + $mmaModQuizAccessRulesDelegate.notifyPreflightCheckPassed(rules, quiz, attempt, + scope.preflightData, prefetch, siteId); + return attempt; + }).catch(function(error) { + if ($mmUtil.isWebServiceError(error)) { + // The WebService returned an error, assume the preflight failed. + $mmaModQuizAccessRulesDelegate.notifyPreflightCheckFailed(rules, quiz, attempt, + scope.preflightData, prefetch, siteId); + } - if (error) { - $mmUtil.showErrorModal(error); + if (prefetch) { + return $q.reject(error); } else { - $mmUtil.showErrorModal('mm.core.error', true); + // Show modal again. We need to wait a bit because if it's called too close to .hide then it won't be shown. + $timeout(function() { + scope.modal && scope.modal.show(); + }, 500); + + if (error) { + $mmUtil.showErrorModal(error); + } else { + $mmUtil.showErrorModal('mm.core.error', true); + } + return $q.reject(); } - return $q.reject(); - } + }); }); }; @@ -329,7 +335,7 @@ angular.module('mm.addons.mod_quiz') var params = { attemptid: attemptId, page: page, - preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value') + preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value', true) }, preSets = { cacheKey: getAttemptDataCacheKey(attemptId, page) @@ -563,7 +569,7 @@ angular.module('mm.addons.mod_quiz') return $mmSitesManager.getSite(siteId).then(function(site) { var params = { attemptid: attemptId, - preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value') + preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value', true) }, preSets = { cacheKey: getAttemptSummaryCacheKey(attemptId) @@ -1351,23 +1357,28 @@ angular.module('mm.addons.mod_quiz') * @ngdoc method * @name $mmaModQuiz#initPreflightModal * @param {Object} scope Scope. + * @param {Object} quiz Quiz. * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. * @param {Boolean} prefetch True if prefetching. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the modal is initialized. */ - self.initPreflightModal = function(scope, quizAccessInfo, attempt, prefetch) { - var notSupported = [], + self.initPreflightModal = function(scope, quiz, quizAccessInfo, attempt, prefetch, siteId) { + var promises = [], + notSupported = [], directives = [], handlers = []; angular.forEach(quizAccessInfo.activerulenames, function(rule) { var handler = $mmaModQuizAccessRulesDelegate.getAccessRuleHandler(rule); if (handler) { - if (handler.isPreflightCheckRequired(attempt, prefetch)) { - handlers.push(handler); - directives.push(handler.getPreflightDirectiveName()); - } + promises.push($q.when(handler.isPreflightCheckRequired(quiz, attempt, prefetch, siteId)).then(function(required) { + if (required) { + handlers.push(handler); + directives.push(handler.getPreflightDirectiveName()); + } + })); } else { notSupported.push(rule); } @@ -1378,25 +1389,38 @@ angular.module('mm.addons.mod_quiz') return $q.reject(error); } - scope.accessRulesDirectives = directives; + return $mmUtil.allPromises(promises).catch(function() { + // Ignore errors. + }).then(function() { + var promise; - return $ionicModal.fromTemplateUrl('addons/mod_quiz/templates/preflight-modal.html', { - scope: scope, - animation: 'slide-in-up' - }).then(function(modal) { - scope.modal = modal; + scope.accessRulesDirectives = directives; - scope.closeModal = function() { - modal.hide(); - // Clean the preflight data. - handlers.forEach(function(handler) { - if (typeof handler.cleanPreflight == 'function') { - handler.cleanPreflight(scope.preflightData); - } + if (scope.modal) { + // The modal is already created. + promise = $q.when(scope.modal); + } else { + promise = $ionicModal.fromTemplateUrl('addons/mod_quiz/templates/preflight-modal.html', { + scope: scope, + animation: 'slide-in-up' + }); + } + + return promise.then(function(modal) { + scope.modal = modal; + + scope.closeModal = function() { + modal.hide(); + // Clean the preflight data. + handlers.forEach(function(handler) { + if (typeof handler.cleanPreflight == 'function') { + handler.cleanPreflight(scope.preflightData); + } + }); + }; + scope.$on('$destroy', function() { + modal.remove(); }); - }; - scope.$on('$destroy', function() { - modal.remove(); }); }); }; @@ -2078,17 +2102,12 @@ angular.module('mm.addons.mod_quiz') attempt = undefined; } - if (askPreflight) { - // Check if the quiz requires preflight data. - scope = $rootScope.$new(); - scope.preflightData = preflightData; + // Get the preflight data. + return self.gatherPreflightData(quiz, quizAccessInfo, attempt, preflightData, siteId, askPreflight, 'mm.core.download'); + + }).then(function(scp) { + scope = scp; - return getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, false); - } else { - // Get some fixed preflight data from access rules (data that doesn't require user interaction). - $mmaModQuizAccessRulesDelegate.getFixedPreflightData(quizAccessInfo.activerulenames, attempt, preflightData, true); - } - }).then(function() { promises = []; if (startAttempt) { @@ -2122,6 +2141,17 @@ angular.module('mm.addons.mod_quiz') var revision = self.getQuizRevisionFromAttempts(attempts), timemod = self.getQuizTimemodifiedFromAttempts(attempts); return $mmFilepool.storePackageStatus(siteId, mmaModQuizComponent, module.id, mmCoreDownloaded, revision, timemod); + }).then(function() { + // If there's nothing to send, mark the quiz as synchronized. + // We don't return the promises because it should be fast and we don't want to block the user for this. + if (!$mmaModQuizSync) { + $mmaModQuizSync = $injector.get('$mmaModQuizSync'); + } + $mmaModQuizSync.hasDataToSync(quiz.id, siteId).then(function(hasData) { + if (!hasData) { + $mmaModQuizSync.setQuizSyncTime(quiz.id, siteId); + } + }); }).catch(function(error) { // Error prefetching, go back to previous status and reject the promise. return $mmFilepool.setPackagePreviousStatus(siteId, mmaModQuizComponent, module.id).then(function() { @@ -2134,6 +2164,39 @@ angular.module('mm.addons.mod_quiz') }); }; + /** + * Gather some preflight data for an attempt. + * + * @param {Object} quiz Quiz. + * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. + * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. + * @param {Object} preflightData Object where to store the preflight data. + * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Boolean} askPreflight True if we should ask for preflight data if needed, false otherwise. + * @param {String} [modalTitle] Lang key of the title to set to preflight modal (e.g. 'mma.mod_quiz.startattempt'). + * @return {Promise} Promise resolved when gathered. Resolve param is a scope if it was needed to create one. + * Please make sure to destroy the scope once you're done. + */ + self.gatherPreflightData = function(quiz, quizAccessInfo, attempt, preflightData, siteId, askPreflight, modalTitle) { + if (askPreflight) { + // Check if the quiz requires preflight data. + scope = $rootScope.$new(); + scope.preflightData = preflightData; + scope.preflightModalTitle = modalTitle; + + return getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, false, siteId).then(function() { + return scope; + }); + } else { + // Get some fixed preflight data from access rules (data that doesn't require user interaction). + var rules = quizAccessInfo.activerulenames; + return $mmaModQuizAccessRulesDelegate.getFixedPreflightData(rules, quiz, attempt, preflightData, true, siteId) + .then(function() { + // Don't return anything. + }); + } + }; + /** * Convenience function to get preflight data for prefetch. * @@ -2142,11 +2205,12 @@ angular.module('mm.addons.mod_quiz') * @param {Object} quizAccessInfo Quiz access info returned by $mmaModQuiz#getQuizAccessInformation. * @param {Object} [attempt] Attempt to continue. Don't pass any value if the user needs to start a new attempt. * @param {Boolean} fromModal True if sending data using preflight modal, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the preflight data is validated. */ - function getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, fromModal) { + function getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, fromModal, siteId) { // Check if preflight data is valid or not required. - return self.checkPreflightData(scope, quiz, quizAccessInfo, attempt, false, fromModal, true).catch(function(error) { + return self.checkPreflightData(scope, quiz, quizAccessInfo, attempt, false, fromModal, true, siteId).catch(function(error) { if (error) { // Something went wrong, reject. return $q.reject(error); @@ -2158,7 +2222,7 @@ angular.module('mm.addons.mod_quiz') scope.start = function() { resolved = true; // Try to validate new preflightData (chain promises). - deferred.resolve(getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, true)); + deferred.resolve(getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, true, siteId)); }; scope.$on('modal.hidden', function() { if (!resolved) { @@ -2283,18 +2347,12 @@ angular.module('mm.addons.mod_quiz') return; } - if (askPreflight) { - // Check if the quiz requires preflight data. - scope = $rootScope.$new(); - scope.preflightData = preflightData; + // Get the preflight data. + return self.gatherPreflightData(quiz, quizAccessInfo, attempt, preflightData, siteId, askPreflight, 'mm.core.download'); - return getPreflightDataForPrefetch(scope, quiz, quizAccessInfo, attempt, false); - } else { - // Get some fixed preflight data from access rules (data that doesn't require user interaction). - $mmaModQuizAccessRulesDelegate.getFixedPreflightData(quizAccessInfo.activerulenames, attempt, preflightData, true); - } + }).then(function(scp) { + scope = scp; - }).then(function() { if (attempts && attempts.length) { // Get data for last attempt. return self.prefetchAttempt(quiz, attempts[attempts.length - 1], preflightData, siteId); @@ -2313,6 +2371,10 @@ angular.module('mm.addons.mod_quiz') newStatus = isLastFinished ? mmCoreNotDownloaded : mmCoreDownloaded; return $mmFilepool.storePackageStatus(siteId, component, quiz.coursemodule, newStatus, revision, timemod); } + }).finally(function() { + if (scope) { + scope.$destroy(); + } }); }; @@ -2498,7 +2560,7 @@ angular.module('mm.addons.mod_quiz') return $mmSitesManager.getSite(siteId).then(function(site) { var params = { quizid: quizId, - preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value'), + preflightdata: $mmUtil.objectToArrayOfObjects(preflightData, 'name', 'value', true), forcenew: forceNew ? 1 : 0 }; diff --git a/www/addons/mod_quiz/services/quiz_sync.js b/www/addons/mod_quiz/services/quiz_sync.js index 306a3d8cd0a..decbf967a00 100644 --- a/www/addons/mod_quiz/services/quiz_sync.js +++ b/www/addons/mod_quiz/services/quiz_sync.js @@ -208,7 +208,7 @@ angular.module('mm.addons.mod_quiz') angular.forEach(quizzes, function(quiz) { if (!$mmaModQuiz.isQuizBeingPlayed(quiz.id, siteId)) { promises.push($mmaModQuiz.getQuizById(quiz.courseid, quiz.id, siteId).then(function(quiz) { - return self.syncQuizIfNeeded(quiz, siteId).then(function(warnings) { + return self.syncQuizIfNeeded(quiz, false, siteId).then(function(warnings) { if (warnings && warnings.length) { // Store the warnings to show them when the user opens the quiz. return self.setQuizSyncWarnings(quiz.id, warnings, siteId).then(function() { @@ -243,15 +243,16 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuizSync#syncQuizIfNeeded - * @param {Object} quiz Quiz downloaded. - * @param {String} [siteId] Site ID. If not defined, current site. - * @return {Promise} Promise resolved when the quiz is synced or if it doesn't need to be synced. + * @param {Object} quiz Quiz. + * @param {Boolean} askPreflight True if we should ask for preflight data if needed, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the quiz is synced or if it doesn't need to be synced. */ - self.syncQuizIfNeeded = function(quiz, siteId) { + self.syncQuizIfNeeded = function(quiz, askPreflight, siteId) { siteId = siteId || $mmSite.getId(); return self.getQuizSyncTime(quiz.id, siteId).then(function(time) { if (new Date().getTime() - mmaModQuizSyncTime >= time) { - return self.syncQuiz(quiz, siteId); + return self.syncQuiz(quiz, askPreflight, siteId); } }); }; @@ -263,11 +264,12 @@ angular.module('mm.addons.mod_quiz') * @module mm.addons.mod_quiz * @ngdoc method * @name $mmaModQuizSync#syncQuiz - * @param {Object} quiz Quiz. - * @param {String} [siteId] Site ID. If not defined, current site. + * @param {Object} quiz Quiz. + * @param {Boolean} askPreflight True if we should ask for preflight data if needed, false otherwise. + * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} [description] */ - self.syncQuiz = function(quiz, siteId) { + self.syncQuiz = function(quiz, askPreflight, siteId) { siteId = siteId || $mmSite.getId(); var warnings = [], @@ -276,9 +278,7 @@ angular.module('mm.addons.mod_quiz') deleted = false, offlineAttempt, onlineAttempt, - preflightData = { - confirmdatasaved: 1 - }; + preflightData = {}; if (syncPromises[siteId] && syncPromises[siteId][quiz.id]) { // There's already a sync ongoing for this quiz, return the promise. @@ -351,11 +351,16 @@ angular.module('mm.addons.mod_quiz') answers = $mmQuestion.convertAnswersArrayToObject(answers); offlineQuestions = $mmaModQuizOffline.classifyAnswersInQuestions(answers); - // Now get the online questions data. - pages = $mmaModQuiz.getPagesFromLayoutAndQuestions(onlineAttempt.layout, offlineQuestions); + // We're going to need preflightData, get it. + return $mmaModQuiz.getQuizAccessInformation(quiz.id, false, true, siteId).then(function(info) { + return $mmaModQuiz.gatherPreflightData(quiz, info, onlineAttempt, + preflightData, siteId, askPreflight, 'mm.settings.synchronization'); + }).then(function() { + // Now get the online questions data. + pages = $mmaModQuiz.getPagesFromLayoutAndQuestions(onlineAttempt.layout, offlineQuestions); - return $mmaModQuiz.getAllQuestionsData(onlineAttempt, preflightData, pages, false, true, siteId) - .then(function(onlineQuestions) { + return $mmaModQuiz.getAllQuestionsData(onlineAttempt, preflightData, pages, false, true, siteId); + }).then(function(onlineQuestions) { // Validate questions, discarding the offline answers that can't be synchronized. return self.validateQuestions(onlineAttempt.id, onlineQuestions, offlineQuestions, siteId); }).then(function(discardedData) { @@ -371,8 +376,7 @@ angular.module('mm.addons.mod_quiz') } } - return $mmaModQuiz.processAttempt(quiz, onlineAttempt, answers, - preflightData, finish, false, false, siteId); + return $mmaModQuiz.processAttempt(quiz, onlineAttempt, answers, preflightData, finish, false, false, siteId); }).then(function() { // Data sent. Finish the sync. return finishSync(lastAttemptId, true); diff --git a/www/addons/mod_quiz/templates/index.html b/www/addons/mod_quiz/templates/index.html index fcc4203ea54..5d547c1fa29 100644 --- a/www/addons/mod_quiz/templates/index.html +++ b/www/addons/mod_quiz/templates/index.html @@ -87,17 +87,17 @@

{{ 'mma.mod_quiz.summaryofattempts' | translate }}

{{ quiz.preferredbehaviour }}

-
+

{{ 'mma.mod_quiz.hasdatatosync' | translate }}

-
+ -
+
diff --git a/www/addons/mod_quiz/templates/preflight-modal.html b/www/addons/mod_quiz/templates/preflight-modal.html index 913c18495a2..c85f85902bf 100644 --- a/www/addons/mod_quiz/templates/preflight-modal.html +++ b/www/addons/mod_quiz/templates/preflight-modal.html @@ -1,13 +1,13 @@ -

{{ 'mma.mod_quiz.startattempt' | translate}}

+

{{ preflightModalTitle | translate}}

- +
diff --git a/www/core/lang/en.json b/www/core/lang/en.json index 2107e86b4d3..5fc17c0497c 100644 --- a/www/core/lang/en.json +++ b/www/core/lang/en.json @@ -79,6 +79,7 @@ "mod_workshop": "Workshop", "mygroups": "My groups", "networkerrormsg": "Network not enabled or not working.", + "never": "Never", "next": "Next", "no": "No", "none": "None", @@ -102,6 +103,7 @@ "sec" : "sec", "secs" : "secs", "seemoredetail": "Click here to see more detail", + "send": "Send", "sending": "Sending", "serverconnection": "Error connecting to the server", "sizeb": "bytes", diff --git a/www/core/lib/util.js b/www/core/lib/util.js index 0deb0ad6021..d4c4f0613c1 100644 --- a/www/core/lib/util.js +++ b/www/core/lib/util.js @@ -1392,14 +1392,21 @@ angular.module('mm.core') * @param {Object} obj Object to convert. * @param {String} keyName Name of the properties where to store the keys. * @param {String} valueName Name of the properties where to store the values. + * @param {Boolean} sort True to sort keys alphabetically, false otherwise. * @return {Object[]} Array of objects with the name & value of each property. */ - self.objectToArrayOfObjects = function(obj, keyName, valueName) { - var result = []; - angular.forEach(obj, function(value, key) { + self.objectToArrayOfObjects = function(obj, keyName, valueName, sort) { + var result = [], + keys = Object.keys(obj); + + if (sort) { + keys = keys.sort(); + } + + angular.forEach(keys, function(key) { var entry = {}; entry[keyName] = key; - entry[valueName] = value; + entry[valueName] = obj[key]; result.push(entry); }); return result; @@ -1466,6 +1473,30 @@ angular.module('mm.core') return unique; }; + /** + * Given an error returned by a WS call (site.read, site.write), + * check if the error is generated by the app or it has been returned by the WebSwervice. + * + * @module mm.core + * @ngdoc method + * @name $mmUtil#isWebServiceError + * @param {String} error Error returned. + * @return {Boolean} True if the error was returned by the WebService, false otherwise. + */ + self.isWebServiceError = function(error) { + var localErrors = [ + $translate.instant('mm.core.wsfunctionnotavailable'), + $translate.instant('mm.core.lostconnection'), + $translate.instant('mm.core.userdeleted'), + $translate.instant('mm.core.unexpectederror'), + $translate.instant('mm.core.networkerrormsg'), + $translate.instant('mm.core.serverconnection'), + $translate.instant('mm.core.errorinvalidresponse') + + ]; + return error && localErrors.indexOf(error) == -1; + }; + return self; }; }); From 944afe6ea50e4f3be51311a15d564f7086c76018 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 19 May 2016 13:12:21 +0200 Subject: [PATCH 5/5] MOBILE-1543 prefetch: Document new 'single' param in prefetch --- upgrade.txt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/upgrade.txt b/upgrade.txt index 6d5e84da37d..7c83f8e57af 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. +=== 3.1.0 === + + * A new param "single" is passed to the prefetch functions of handlers registered in $mmCoursePrefetchDelegateProvider. Please notice that the download of sections will always send false/undefined, if you use this function somewhere else to download a single module then you should pass single=true. + === 3.0 === * The function $mmaModBook#getChapterContent now requires to receive the result of $mmaModBook#getContentsMap instead of module.contents.