MOBILE-2348 quiz: Implement offline provider
parent
2ae27ed66b
commit
fd41274d9e
|
@ -0,0 +1,381 @@
|
|||
// (C) Copyright 2015 Martin Dougiamas
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { TranslateService } from '@ngx-translate/core';
|
||||
import { CoreLoggerProvider } from '@providers/logger';
|
||||
import { CoreSitesProvider } from '@providers/sites';
|
||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||
import { CoreQuestionProvider } from '@core/question/providers/question';
|
||||
import { CoreQuestionBehaviourDelegate } from '@core/question/providers/behaviour-delegate';
|
||||
import { AddonModQuizProvider } from './quiz';
|
||||
import { SQLiteDB } from '@classes/sqlitedb';
|
||||
|
||||
/**
|
||||
* Service to handle offline quiz.
|
||||
*/
|
||||
@Injectable()
|
||||
export class AddonModQuizOfflineProvider {
|
||||
|
||||
protected logger;
|
||||
|
||||
// Variables for database.
|
||||
protected ATTEMPTS_TABLE = 'addon_mod_quiz_attempts';
|
||||
protected tablesSchema = [
|
||||
{
|
||||
name: this.ATTEMPTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'id', // Attempt ID.
|
||||
type: 'INTEGER',
|
||||
primaryKey: true
|
||||
},
|
||||
{
|
||||
name: 'attempt', // Attempt number.
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'userid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'quizid',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'currentpage',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'timecreated',
|
||||
type: 'INTEGER'
|
||||
},
|
||||
{
|
||||
name: 'finished',
|
||||
type: 'INTEGER'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private questionProvider: CoreQuestionProvider, private translate: TranslateService, private utils: CoreUtilsProvider,
|
||||
private behaviourDelegate: CoreQuestionBehaviourDelegate) {
|
||||
this.logger = logger.getInstance('AddonModQuizOfflineProvider');
|
||||
|
||||
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||
}
|
||||
|
||||
/**
|
||||
* Classify the answers in questions.
|
||||
*
|
||||
* @param {any} answers List of answers.
|
||||
* @return {any} Object with the questions, the keys are the slot. Each question contains its answers.
|
||||
*/
|
||||
classifyAnswersInQuestions(answers: any): any {
|
||||
const questionsWithAnswers = {};
|
||||
|
||||
// Classify the answers in each question.
|
||||
for (const name in answers) {
|
||||
const slot = this.questionProvider.getQuestionSlotFromName(name),
|
||||
nameWithoutPrefix = this.questionProvider.removeQuestionPrefix(name);
|
||||
|
||||
if (!questionsWithAnswers[slot]) {
|
||||
questionsWithAnswers[slot] = {
|
||||
answers: {},
|
||||
prefix: name.substr(0, name.indexOf(nameWithoutPrefix))
|
||||
};
|
||||
}
|
||||
questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name];
|
||||
}
|
||||
|
||||
return questionsWithAnswers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of questions with answers classified in it (@see AddonModQuizOfflineProvider.classifyAnswersInQuestions),
|
||||
* returns a list of answers (including prefix in the name).
|
||||
*
|
||||
* @param {any} questions Questions.
|
||||
* @return {any} Answers.
|
||||
*/
|
||||
extractAnswersFromQuestions(questions: any): any {
|
||||
const answers = {};
|
||||
|
||||
for (const slot in questions) {
|
||||
const question = questions[slot];
|
||||
|
||||
for (const name in question.answers) {
|
||||
answers[question.prefix + name] = question.answers[name];
|
||||
}
|
||||
}
|
||||
|
||||
return answers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all the offline attempts in a certain site.
|
||||
*
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with the offline attempts.
|
||||
*/
|
||||
getAllAttempts(siteId?: string): Promise<any[]> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getAllRecords(this.ATTEMPTS_TABLE);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an attempt answers from site DB.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any[]>} Promise resolved with the answers.
|
||||
*/
|
||||
getAttemptAnswers(attemptId: number, siteId?: string): Promise<any[]> {
|
||||
return this.questionProvider.getAttemptAnswers(AddonModQuizProvider.COMPONENT, attemptId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an attempt from site DB.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the attempt.
|
||||
*/
|
||||
getAttemptById(attemptId: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.getRecord(this.ATTEMPTS_TABLE, {id: attemptId});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve an attempt from site DB.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @param {number} [userId] User ID. If not defined, user current site's user.
|
||||
* @return {Promise<any[]>} Promise resolved with the attempts.
|
||||
*/
|
||||
getQuizAttempts(quizId: number, siteId?: string, userId?: number): Promise<any[]> {
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
|
||||
return site.getDb().getRecords(this.ATTEMPTS_TABLE, {quizid: quizId, userid: userId});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Load local state in the questions.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {any[]} questions List of questions.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
loadQuestionsLocalStates(attemptId: number, questions: any[], siteId?: string): Promise<any[]> {
|
||||
const promises = [];
|
||||
|
||||
questions.forEach((question) => {
|
||||
promises.push(this.questionProvider.getQuestion(AddonModQuizProvider.COMPONENT, attemptId, question.slot, siteId)
|
||||
.then((q) => {
|
||||
|
||||
const state = this.questionProvider.getState(q.state);
|
||||
question.state = q.state;
|
||||
question.status = this.translate.instant('core.question.' + state.status);
|
||||
}).catch(() => {
|
||||
// Question not found.
|
||||
}));
|
||||
});
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
return questions;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an attempt, saving its data.
|
||||
*
|
||||
* @param {any} quiz Quiz.
|
||||
* @param {any} attempt Attempt.
|
||||
* @param {any} questions Object with the questions of the quiz. The keys should be the question slot.
|
||||
* @param {any} data Data to save.
|
||||
* @param {boolean} [finish] Whether to finish the quiz.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
processAttempt(quiz: any, attempt: any, questions: any, data: any, finish?: boolean, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const now = this.timeUtils.timestamp();
|
||||
let db: SQLiteDB;
|
||||
|
||||
return this.sitesProvider.getSiteDb(siteId).then((siteDb) => {
|
||||
db = siteDb;
|
||||
|
||||
// Check if an attempt already exists.
|
||||
return this.getAttemptById(attempt.id, siteId).catch(() => {
|
||||
// Attempt doesn't exist, create a new entry.
|
||||
return {
|
||||
quizid: quiz.id,
|
||||
userid: attempt.userid,
|
||||
id: attempt.id,
|
||||
courseid: quiz.course,
|
||||
timecreated: now,
|
||||
attempt: attempt.attempt,
|
||||
currentpage: attempt.currentpage
|
||||
};
|
||||
});
|
||||
}).then((entry) => {
|
||||
// Save attempt in DB.
|
||||
entry.timemodified = now;
|
||||
entry.finished = !!finish;
|
||||
|
||||
return db.insertRecord(this.ATTEMPTS_TABLE, entry);
|
||||
}).then(() => {
|
||||
// Attempt has been saved, now we need to save the answers.
|
||||
return this.saveAnswers(quiz, attempt, questions, data, now, siteId);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove an attempt and its answers from local DB.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
removeAttemptAndAnswers(attemptId: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const promises = [];
|
||||
|
||||
// Remove stored answers and questions.
|
||||
promises.push(this.questionProvider.removeAttemptAnswers(AddonModQuizProvider.COMPONENT, attemptId, siteId));
|
||||
promises.push(this.questionProvider.removeAttemptQuestions(AddonModQuizProvider.COMPONENT, attemptId, siteId));
|
||||
|
||||
// Remove the attempt.
|
||||
promises.push(this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.deleteRecords(this.ATTEMPTS_TABLE, {id: attemptId});
|
||||
}));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a question and its answers from local DB.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {number} slot Question slot.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when finished.
|
||||
*/
|
||||
removeQuestionAndAnswers(attemptId: number, slot: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
const promises = [];
|
||||
|
||||
promises.push(this.questionProvider.removeQuestion(AddonModQuizProvider.COMPONENT, attemptId, slot, siteId));
|
||||
promises.push(this.questionProvider.removeQuestionAnswers(AddonModQuizProvider.COMPONENT, attemptId, slot, siteId));
|
||||
|
||||
return Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an attempt's answers and calculate state for questions modified.
|
||||
*
|
||||
* @param {any} quiz Quiz.
|
||||
* @param {any} attempt Attempt.
|
||||
* @param {any} questions Object with the questions of the quiz. The keys should be the question slot.
|
||||
* @param {any} answers Answers to save.
|
||||
* @param {number} [timeMod] Time modified to set in the answers. If not defined, current time.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved when done.
|
||||
*/
|
||||
saveAnswers(quiz: any, attempt: any, questions: any, answers: any, timeMod?: number, siteId?: string): Promise<any> {
|
||||
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||
timeMod = timeMod || this.timeUtils.timestamp();
|
||||
|
||||
const questionsWithAnswers = {},
|
||||
newStates = {};
|
||||
let promises = [];
|
||||
|
||||
// Classify the answers in each question.
|
||||
for (const name in answers) {
|
||||
const slot = this.questionProvider.getQuestionSlotFromName(name),
|
||||
nameWithoutPrefix = this.questionProvider.removeQuestionPrefix(name);
|
||||
|
||||
if (questions[slot]) {
|
||||
if (!questionsWithAnswers[slot]) {
|
||||
questionsWithAnswers[slot] = questions[slot];
|
||||
questionsWithAnswers[slot].answers = {};
|
||||
}
|
||||
questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name];
|
||||
}
|
||||
}
|
||||
|
||||
// First determine the new state of each question. We won't save the new state yet.
|
||||
for (const slot in questionsWithAnswers) {
|
||||
const question = questionsWithAnswers[slot];
|
||||
|
||||
promises.push(this.behaviourDelegate.determineNewState(
|
||||
quiz.preferredbehaviour, AddonModQuizProvider.COMPONENT, attempt.id, question, siteId).then((state) => {
|
||||
// Check if state has changed.
|
||||
if (state && state.name != question.state) {
|
||||
newStates[question.slot] = state.name;
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
return Promise.all(promises).then(() => {
|
||||
// Now save the answers.
|
||||
return this.questionProvider.saveAnswers(AddonModQuizProvider.COMPONENT, quiz.id, attempt.id, attempt.userid,
|
||||
answers, timeMod, siteId);
|
||||
}).then(() => {
|
||||
// Answers have been saved, now we can save the questions with the states.
|
||||
promises = [];
|
||||
|
||||
for (const slot in newStates) {
|
||||
const question = questionsWithAnswers[slot];
|
||||
|
||||
promises.push(this.questionProvider.saveQuestion(AddonModQuizProvider.COMPONENT, quiz.id, attempt.id,
|
||||
attempt.userid, question, newStates[slot], siteId));
|
||||
}
|
||||
|
||||
return this.utils.allPromises(promises).catch((err) => {
|
||||
// Ignore errors when saving question state.
|
||||
this.logger.error('Error saving question state', err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Set attempt's current page.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {number} page Page to set.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
setAttemptCurrentPage(attemptId: number, page: number, siteId?: string): Promise<any> {
|
||||
return this.sitesProvider.getSiteDb(siteId).then((db) => {
|
||||
return db.updateRecords(this.ATTEMPTS_TABLE, {currentpage: page}, {id: attemptId});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -24,6 +24,7 @@ import { CoreSiteWSPreSets } from '@classes/site';
|
|||
import { CoreGradesHelperProvider } from '@core/grades/providers/helper';
|
||||
import { CoreQuestionDelegate } from '@core/question/providers/delegate';
|
||||
import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate';
|
||||
import { AddonModQuizOfflineProvider } from './quiz-offline';
|
||||
import * as moment from 'moment';
|
||||
|
||||
/**
|
||||
|
@ -60,7 +61,7 @@ export class AddonModQuizProvider {
|
|||
private translate: TranslateService, private textUtils: CoreTextUtilsProvider,
|
||||
private gradesHelper: CoreGradesHelperProvider, private questionDelegate: CoreQuestionDelegate,
|
||||
private filepoolProvider: CoreFilepoolProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||
private accessRulesDelegate: AddonModQuizAccessRuleDelegate) {
|
||||
private accessRulesDelegate: AddonModQuizAccessRuleDelegate, private quizOfflineProvider: AddonModQuizOfflineProvider) {
|
||||
this.logger = logger.getInstance('AddonModQuizProvider');
|
||||
}
|
||||
|
||||
|
@ -431,10 +432,10 @@ export class AddonModQuizProvider {
|
|||
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
|
||||
* @param {boolean} [loadLocal] Whether it should load local state for each question. Only applicable if offline=true.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved with the attempt summary.
|
||||
* @return {Promise<any[]>} Promise resolved with the list of questions for the attempt summary.
|
||||
*/
|
||||
getAttemptSummary(attemptId: number, preflightData: any, offline?: boolean, ignoreCache?: boolean, loadLocal?: boolean,
|
||||
siteId?: string): Promise<any> {
|
||||
siteId?: string): Promise<any[]> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
|
@ -455,7 +456,7 @@ export class AddonModQuizProvider {
|
|||
return site.read('mod_quiz_get_attempt_summary', params, preSets).then((response) => {
|
||||
if (response && response.questions) {
|
||||
if (offline && loadLocal) {
|
||||
// @todo return $mmaModQuizOffline.loadQuestionsLocalStates(attemptId, response.questions, site.getId());
|
||||
return this.quizOfflineProvider.loadQuestionsLocalStates(attemptId, response.questions, site.getId());
|
||||
}
|
||||
|
||||
return response.questions;
|
||||
|
@ -1399,8 +1400,11 @@ export class AddonModQuizProvider {
|
|||
* @return {Promise<boolean>} Promise resolved with boolean: true if finished in offline but not synced, false otherwise.
|
||||
*/
|
||||
isAttemptFinishedOffline(attemptId: number, siteId?: string): Promise<boolean> {
|
||||
// @todo
|
||||
return Promise.resolve(false);
|
||||
return this.quizOfflineProvider.getAttemptById(attemptId, siteId).then((attempt) => {
|
||||
return !!attempt.finished;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1438,8 +1442,13 @@ export class AddonModQuizProvider {
|
|||
* @return {Promise<boolean>} Promise resolved with boolean: true if last offline attempt is unfinished, false otherwise.
|
||||
*/
|
||||
isLastAttemptOfflineUnfinished(quiz: any, siteId?: string, userId?: number): Promise<boolean> {
|
||||
// @todo
|
||||
return Promise.resolve(false);
|
||||
return this.quizOfflineProvider.getQuizAttempts(quiz.id, siteId, userId).then((attempts) => {
|
||||
const last = attempts.pop();
|
||||
|
||||
return last && !last.finished;
|
||||
}).catch(() => {
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1524,7 +1533,7 @@ export class AddonModQuizProvider {
|
|||
|
||||
promises.push(this.sitesProvider.getCurrentSite().write('mod_quiz_view_attempt', params));
|
||||
if (offline) {
|
||||
// @todo promises.push($mmaModQuizOffline.setAttemptCurrentPage(attemptId, page));
|
||||
promises.push(this.quizOfflineProvider.setAttemptCurrentPage(attemptId, page));
|
||||
}
|
||||
|
||||
return Promise.all(promises);
|
||||
|
@ -1582,15 +1591,54 @@ export class AddonModQuizProvider {
|
|||
* @param {any} data Data to save.
|
||||
* @param {any} preflightData Preflight required data (like password).
|
||||
* @param {boolean} [finish] Whether to finish the quiz.
|
||||
* @param {boolean} [timeup] Whether the quiz time is up, false otherwise.
|
||||
* @param {boolean} [timeUp] Whether the quiz time is up, false otherwise.
|
||||
* @param {boolean} [offline] Whether the attempt is offline.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
processAttempt(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, timeup?: boolean, offline?: boolean,
|
||||
processAttempt(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, timeUp?: boolean, offline?: boolean,
|
||||
siteId?: string): Promise<any> {
|
||||
// @todo
|
||||
return Promise.resolve();
|
||||
if (offline) {
|
||||
return this.processAttemptOffline(quiz, attempt, data, preflightData, finish, siteId);
|
||||
}
|
||||
|
||||
return this.processAttemptOnline(attempt.id, data, preflightData, finish, timeUp, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process an online attempt, saving its data.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {any} data Data to save.
|
||||
* @param {any} preflightData Preflight required data (like password).
|
||||
* @param {boolean} [finish] Whether to finish the quiz.
|
||||
* @param {boolean} [timeUp] Whether the quiz time is up, false otherwise.
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
protected processAttemptOnline(attemptId: number, data: any, preflightData: any, finish?: boolean, timeUp?: boolean,
|
||||
siteId?: string): Promise<any> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
attemptid: attemptId,
|
||||
data: this.utils.objectToArrayOfObjects(data, 'name', 'value'),
|
||||
finishattempt: finish ? 1 : 0,
|
||||
timeup: timeUp ? 1 : 0,
|
||||
preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value')
|
||||
};
|
||||
|
||||
return site.write('mod_quiz_process_attempt', params).then((response) => {
|
||||
if (response && response.warnings && response.warnings.length) {
|
||||
// Reject with the first warning.
|
||||
return Promise.reject(response.warnings[0]);
|
||||
} else if (response && response.state) {
|
||||
return response.state;
|
||||
}
|
||||
|
||||
return Promise.reject(null);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1604,7 +1652,7 @@ export class AddonModQuizProvider {
|
|||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<any>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
protected processOfflineAttempt(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, siteId?: string)
|
||||
protected processAttemptOffline(quiz: any, attempt: any, data: any, preflightData: any, finish?: boolean, siteId?: string)
|
||||
: Promise<any> {
|
||||
|
||||
// Get attempt summary to have the list of questions.
|
||||
|
@ -1612,8 +1660,7 @@ export class AddonModQuizProvider {
|
|||
// Convert the question array to an object.
|
||||
const questions = this.utils.arrayToObject(questionArray, 'slot');
|
||||
|
||||
return questions;
|
||||
// @todo return $mmaModQuizOffline.processAttempt(quiz, attempt, questions, data, finish, siteId);
|
||||
return this.quizOfflineProvider.processAttempt(quiz, attempt, questions, data, finish, siteId);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -1678,10 +1725,10 @@ export class AddonModQuizProvider {
|
|||
saveAttempt(quiz: any, attempt: any, data: any, preflightData: any, offline?: boolean, siteId?: string): Promise<any> {
|
||||
try {
|
||||
if (offline) {
|
||||
return this.processOfflineAttempt(quiz, attempt, data, preflightData, false, siteId);
|
||||
return this.processAttemptOffline(quiz, attempt, data, preflightData, false, siteId);
|
||||
}
|
||||
|
||||
// @todo return $mmaModQuizOnline.saveAttempt(attempt.id, data, preflightData, siteId);
|
||||
return this.saveAttemptOnline(attempt.id, data, preflightData, siteId);
|
||||
} catch (ex) {
|
||||
this.logger.error(ex);
|
||||
|
||||
|
@ -1689,6 +1736,35 @@ export class AddonModQuizProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Save an attempt data.
|
||||
*
|
||||
* @param {number} attemptId Attempt ID.
|
||||
* @param {any} data Data to save.
|
||||
* @param {any} preflightData Preflight required data (like password).
|
||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||
* @return {Promise<void>} Promise resolved in success, rejected otherwise.
|
||||
*/
|
||||
protected saveAttemptOnline(attemptId: number, data: any, preflightData: any, siteId?: string): Promise<void> {
|
||||
|
||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||
const params = {
|
||||
attemptid: attemptId,
|
||||
data: this.utils.objectToArrayOfObjects(data, 'name', 'value'),
|
||||
preflightdata: this.utils.objectToArrayOfObjects(preflightData, 'name', 'value')
|
||||
};
|
||||
|
||||
return site.write('mod_quiz_save_attempt', params).then((response) => {
|
||||
if (response && response.warnings && response.warnings.length) {
|
||||
// Reject with the first warning.
|
||||
return Promise.reject(response.warnings[0]);
|
||||
} else if (!response || !response.status) {
|
||||
return Promise.reject(null);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if time left should be shown.
|
||||
*
|
||||
|
@ -1727,7 +1803,7 @@ export class AddonModQuizProvider {
|
|||
return site.write('mod_quiz_start_attempt', params).then((response) => {
|
||||
if (response && response.warnings && response.warnings.length) {
|
||||
// Reject with the first warning.
|
||||
return Promise.reject(response.warnings[0].message);
|
||||
return Promise.reject(response.warnings[0]);
|
||||
} else if (response && response.attempt) {
|
||||
return response.attempt;
|
||||
}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
import { AddonModQuizAccessRuleDelegate } from './providers/access-rules-delegate';
|
||||
import { AddonModQuizProvider } from './providers/quiz';
|
||||
import { AddonModQuizOfflineProvider } from './providers/quiz-offline';
|
||||
|
||||
@NgModule({
|
||||
declarations: [
|
||||
|
@ -23,7 +24,8 @@ import { AddonModQuizProvider } from './providers/quiz';
|
|||
],
|
||||
providers: [
|
||||
AddonModQuizAccessRuleDelegate,
|
||||
AddonModQuizProvider
|
||||
AddonModQuizProvider,
|
||||
AddonModQuizOfflineProvider
|
||||
]
|
||||
})
|
||||
export class AddonModQuizModule { }
|
||||
|
|
Loading…
Reference in New Issue