MOBILE-2330 question: Implement question provider
parent
1ebe8ba57b
commit
5fd33e4634
|
@ -67,6 +67,7 @@ import { CoreGradesModule } from '@core/grades/grades.module';
|
||||||
import { CoreSettingsModule } from '@core/settings/settings.module';
|
import { CoreSettingsModule } from '@core/settings/settings.module';
|
||||||
import { CoreSitePluginsModule } from '@core/siteplugins/siteplugins.module';
|
import { CoreSitePluginsModule } from '@core/siteplugins/siteplugins.module';
|
||||||
import { CoreCompileModule } from '@core/compile/compile.module';
|
import { CoreCompileModule } from '@core/compile/compile.module';
|
||||||
|
import { CoreQuestionModule } from '@core/question/question.module';
|
||||||
|
|
||||||
// Addon modules.
|
// Addon modules.
|
||||||
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
import { AddonBadgesModule } from '@addon/badges/badges.module';
|
||||||
|
@ -155,6 +156,7 @@ export const CORE_PROVIDERS: any[] = [
|
||||||
CoreSettingsModule,
|
CoreSettingsModule,
|
||||||
CoreSitePluginsModule,
|
CoreSitePluginsModule,
|
||||||
CoreCompileModule,
|
CoreCompileModule,
|
||||||
|
CoreQuestionModule,
|
||||||
AddonBadgesModule,
|
AddonBadgesModule,
|
||||||
AddonCalendarModule,
|
AddonCalendarModule,
|
||||||
AddonCompetencyModule,
|
AddonCompetencyModule,
|
||||||
|
|
|
@ -29,6 +29,7 @@ import { CORE_FILEUPLOADER_PROVIDERS } from '@core/fileuploader/fileuploader.mod
|
||||||
import { CORE_GRADES_PROVIDERS } from '@core/grades/grades.module';
|
import { CORE_GRADES_PROVIDERS } from '@core/grades/grades.module';
|
||||||
import { CORE_LOGIN_PROVIDERS } from '@core/login/login.module';
|
import { CORE_LOGIN_PROVIDERS } from '@core/login/login.module';
|
||||||
import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module';
|
import { CORE_MAINMENU_PROVIDERS } from '@core/mainmenu/mainmenu.module';
|
||||||
|
import { CORE_QUESTION_PROVIDERS } from '@core/question/question.module';
|
||||||
import { CORE_SHAREDFILES_PROVIDERS } from '@core/sharedfiles/sharedfiles.module';
|
import { CORE_SHAREDFILES_PROVIDERS } from '@core/sharedfiles/sharedfiles.module';
|
||||||
import { CORE_SITEHOME_PROVIDERS } from '@core/sitehome/sitehome.module';
|
import { CORE_SITEHOME_PROVIDERS } from '@core/sitehome/sitehome.module';
|
||||||
import { CORE_USER_PROVIDERS } from '@core/user/user.module';
|
import { CORE_USER_PROVIDERS } from '@core/user/user.module';
|
||||||
|
@ -164,7 +165,7 @@ export class CoreCompileProvider {
|
||||||
.concat(CORE_COURSES_PROVIDERS).concat(CORE_FILEUPLOADER_PROVIDERS).concat(CORE_GRADES_PROVIDERS)
|
.concat(CORE_COURSES_PROVIDERS).concat(CORE_FILEUPLOADER_PROVIDERS).concat(CORE_GRADES_PROVIDERS)
|
||||||
.concat(CORE_LOGIN_PROVIDERS).concat(CORE_MAINMENU_PROVIDERS).concat(CORE_SHAREDFILES_PROVIDERS)
|
.concat(CORE_LOGIN_PROVIDERS).concat(CORE_MAINMENU_PROVIDERS).concat(CORE_SHAREDFILES_PROVIDERS)
|
||||||
.concat(CORE_SITEHOME_PROVIDERS).concat([CoreSitePluginsProvider]).concat(CORE_USER_PROVIDERS)
|
.concat(CORE_SITEHOME_PROVIDERS).concat([CoreSitePluginsProvider]).concat(CORE_USER_PROVIDERS)
|
||||||
.concat(IONIC_NATIVE_PROVIDERS).concat(this.OTHER_PROVIDERS);
|
.concat(CORE_QUESTION_PROVIDERS).concat(IONIC_NATIVE_PROVIDERS).concat(this.OTHER_PROVIDERS);
|
||||||
|
|
||||||
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
// We cannot inject anything to this constructor. Use the Injector to inject all the providers into the instance.
|
||||||
for (const i in providers) {
|
for (const i in providers) {
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
{
|
||||||
|
"answer": "Answer",
|
||||||
|
"answersaved": "Answer saved",
|
||||||
|
"certainty": "Certainty",
|
||||||
|
"complete": "Complete",
|
||||||
|
"correct": "Correct",
|
||||||
|
"errorattachmentsnotsupported": "The application doesn't support attaching files to answers yet.",
|
||||||
|
"errorinlinefilesnotsupported": "The application doesn't support editing inline files yet.",
|
||||||
|
"errorquestionnotsupported": "This question type is not supported by the app: {{$a}}.",
|
||||||
|
"feedback": "Feedback",
|
||||||
|
"howtodraganddrop": "Tap to select then tap to drop.",
|
||||||
|
"incorrect": "Incorrect",
|
||||||
|
"information": "Information",
|
||||||
|
"invalidanswer": "Incomplete answer",
|
||||||
|
"notanswered": "Not answered",
|
||||||
|
"notyetanswered": "Not yet answered",
|
||||||
|
"partiallycorrect": "Partially correct",
|
||||||
|
"questionmessage": "Question {{$a}}: {{$b}}",
|
||||||
|
"questionno": "Question {{$a}}",
|
||||||
|
"requiresgrading": "Requires grading",
|
||||||
|
"unknown": "Cannot determine status"
|
||||||
|
}
|
|
@ -0,0 +1,613 @@
|
||||||
|
// (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 { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object to represent a question state.
|
||||||
|
*/
|
||||||
|
export interface CoreQuestionState {
|
||||||
|
/**
|
||||||
|
* Name of the state.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class of the state.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
class: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The string key to translate the status.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
status: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the question with this state is active.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
active: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the question with this state is finished.
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
finished: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle questions.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreQuestionProvider {
|
||||||
|
// Variables for database.
|
||||||
|
protected QUESTION_TABLE = 'questions';
|
||||||
|
protected QUESTION_ANSWERS_TABLE = 'question_answers';
|
||||||
|
protected tablesSchema = [
|
||||||
|
{
|
||||||
|
name: this.QUESTION_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'component',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attemptId',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'slot',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'componentId',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'number',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
type: 'TEXT'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['component', 'attemptId', 'slot']
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: this.QUESTION_ANSWERS_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'component',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'attemptId',
|
||||||
|
type: 'INTEGER',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'name',
|
||||||
|
type: 'TEXT',
|
||||||
|
notNull: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'componentId',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'userId',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'questionSlot',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'state',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'value',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timemodified',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
],
|
||||||
|
primaryKeys: ['component', 'attemptId', 'name']
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
protected QUESTION_PREFIX_REGEX = /q\d+:(\d+)_/;
|
||||||
|
protected STATES: {[name: string]: CoreQuestionState} = {
|
||||||
|
todo: {
|
||||||
|
name: 'todo',
|
||||||
|
class: 'core-question-notyetanswered',
|
||||||
|
status: 'notyetanswered',
|
||||||
|
active: true,
|
||||||
|
finished: false
|
||||||
|
},
|
||||||
|
invalid: {
|
||||||
|
name: 'invalid',
|
||||||
|
class: 'core-question-invalidanswer',
|
||||||
|
status: 'invalidanswer',
|
||||||
|
active: true,
|
||||||
|
finished: false
|
||||||
|
},
|
||||||
|
complete: {
|
||||||
|
name: 'complete',
|
||||||
|
class: 'core-question-answersaved',
|
||||||
|
status: 'answersaved',
|
||||||
|
active: true,
|
||||||
|
finished: false
|
||||||
|
},
|
||||||
|
needsgrading: {
|
||||||
|
name: 'needsgrading',
|
||||||
|
class: 'core-question-requiresgrading',
|
||||||
|
status: 'requiresgrading',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
finished: {
|
||||||
|
name: 'finished',
|
||||||
|
class: 'core-question-complete',
|
||||||
|
status: 'complete',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
gaveup: {
|
||||||
|
name: 'gaveup',
|
||||||
|
class: 'core-question-notanswered',
|
||||||
|
status: 'notanswered',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
gradedwrong: {
|
||||||
|
name: 'gradedwrong',
|
||||||
|
class: 'core-question-incorrect',
|
||||||
|
status: 'incorrect',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
gradedpartial: {
|
||||||
|
name: 'gradedpartial',
|
||||||
|
class: 'core-question-partiallycorrect',
|
||||||
|
status: 'partiallycorrect',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
gradedright: {
|
||||||
|
name: 'gradedright',
|
||||||
|
class: 'core-question-correct',
|
||||||
|
status: 'correct',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
mangrwrong: {
|
||||||
|
name: 'mangrwrong',
|
||||||
|
class: 'core-question-incorrect',
|
||||||
|
status: 'incorrect',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
mangrpartial: {
|
||||||
|
name: 'mangrpartial',
|
||||||
|
class: 'core-question-partiallycorrect',
|
||||||
|
status: 'partiallycorrect',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
mangrright: {
|
||||||
|
name: 'mangrright',
|
||||||
|
class: 'core-question-correct',
|
||||||
|
status: 'correct',
|
||||||
|
active: false,
|
||||||
|
finished: true
|
||||||
|
},
|
||||||
|
unknown: { // Special state for Mobile, sometimes we won't have enough data to detemrine the state.
|
||||||
|
name: 'unknown',
|
||||||
|
class: 'core-question-unknown',
|
||||||
|
status: 'unknown',
|
||||||
|
active: true,
|
||||||
|
finished: false
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
protected logger;
|
||||||
|
|
||||||
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider,
|
||||||
|
private utils: CoreUtilsProvider) {
|
||||||
|
this.logger = logger.getInstance('CoreQuestionProvider');
|
||||||
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compare that all the answers in two objects are equal, except some extra data like sequencecheck or certainty.
|
||||||
|
*
|
||||||
|
* @param {any} prevAnswers Object with previous answers.
|
||||||
|
* @param {any} newAnswers Object with new answers.
|
||||||
|
* @return {boolean} Whether all answers are equal.
|
||||||
|
*/
|
||||||
|
compareAllAnswers(prevAnswers: any, newAnswers: any): boolean {
|
||||||
|
// Get all the keys.
|
||||||
|
const keys = this.utils.mergeArraysWithoutDuplicates(Object.keys(prevAnswers), Object.keys(newAnswers));
|
||||||
|
|
||||||
|
// Check that all the keys have the same value on both objects.
|
||||||
|
for (const i in keys) {
|
||||||
|
const key = keys[i];
|
||||||
|
|
||||||
|
// Ignore extra answers like sequencecheck or certainty.
|
||||||
|
if (!this.isExtraAnswer(key[0])) {
|
||||||
|
if (!this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, key)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a list of answers retrieved from local DB to an object with name - value.
|
||||||
|
*
|
||||||
|
* @param {any[]} answers List of answers.
|
||||||
|
* @param {boolean} [removePrefix] Whether to remove the prefix in the answer's name.
|
||||||
|
* @return {any} Object with name -> value.
|
||||||
|
*/
|
||||||
|
convertAnswersArrayToObject(answers: any[], removePrefix?: boolean): any {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
answers.forEach((answer) => {
|
||||||
|
if (removePrefix) {
|
||||||
|
const nameWithoutPrefix = this.removeQuestionPrefix(answer.name);
|
||||||
|
result[nameWithoutPrefix] = answer.value;
|
||||||
|
} else {
|
||||||
|
result[answer.name] = answer.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an answer from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} name Answer's name.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the answer.
|
||||||
|
*/
|
||||||
|
getAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecord(this.QUESTION_ANSWERS_TABLE, {component, attemptId, name});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an attempt answers from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the answers.
|
||||||
|
*/
|
||||||
|
getAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an attempt questions from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the questions.
|
||||||
|
*/
|
||||||
|
getAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(this.QUESTION_TABLE, {component, attemptId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the answers that aren't "extra" (sequencecheck, certainty, ...).
|
||||||
|
*
|
||||||
|
* @param {any} answers Object with all the answers.
|
||||||
|
* @return {any} Object with the basic answers.
|
||||||
|
*/
|
||||||
|
getBasicAnswers(answers: any): any {
|
||||||
|
const result = {};
|
||||||
|
|
||||||
|
for (const name in answers) {
|
||||||
|
if (!this.isExtraAnswer(name)) {
|
||||||
|
result[name] = answers[name];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all the answers that aren't "extra" (sequencecheck, certainty, ...).
|
||||||
|
*
|
||||||
|
* @param {any[]} answers List of answers.
|
||||||
|
* @return {any[]} List with the basic answers.
|
||||||
|
*/
|
||||||
|
getBasicAnswersFromArray(answers: any[]): any[] {
|
||||||
|
const result = [];
|
||||||
|
|
||||||
|
answers.forEach((answer) => {
|
||||||
|
if (this.isExtraAnswer(answer.name)) {
|
||||||
|
result.push(answer);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a question from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} slot Question slot.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the question.
|
||||||
|
*/
|
||||||
|
getQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecord(this.QUESTION_TABLE, {component, attemptId, slot});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a question answers from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} slot Question slot.
|
||||||
|
* @param {boolean} [filter] Whether it should ignore "extra" answers like sequencecheck or certainty.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the answers.
|
||||||
|
*/
|
||||||
|
getQuestionAnswers(component: string, attemptId: number, slot: number, filter?: boolean, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().getRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, slot}).then((answers) => {
|
||||||
|
if (filter) {
|
||||||
|
// Get only answers that isn't "extra" data like sequencecheck or certainty.
|
||||||
|
return this.getBasicAnswersFromArray(answers);
|
||||||
|
} else {
|
||||||
|
return answers;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract the question slot from a question name.
|
||||||
|
*
|
||||||
|
* @param {string} name Question name.
|
||||||
|
* @return {number} Question slot.
|
||||||
|
*/
|
||||||
|
getQuestionSlotFromName(name: string): number {
|
||||||
|
if (name) {
|
||||||
|
const match = name.match(this.QUESTION_PREFIX_REGEX);
|
||||||
|
if (match && match[1]) {
|
||||||
|
return parseInt(match[1], 10);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get question state based on state name.
|
||||||
|
*
|
||||||
|
* @param {string} name State name.
|
||||||
|
* @return {CoreQuestionState} State.
|
||||||
|
*/
|
||||||
|
getState(name: string): CoreQuestionState {
|
||||||
|
return this.STATES[name || 'unknown'];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if an answer is extra data like sequencecheck or certainty.
|
||||||
|
*
|
||||||
|
* @param {string} name Answer name.
|
||||||
|
* @return {boolean} Whether it's extra data.
|
||||||
|
*/
|
||||||
|
isExtraAnswer(name: string): boolean {
|
||||||
|
// Maybe the name still has the prefix.
|
||||||
|
name = this.removeQuestionPrefix(name);
|
||||||
|
|
||||||
|
return name[0] == '-' || name[0] == ':';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an attempt answers from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
removeAttemptAnswers(component: string, attemptId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an attempt questions from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
removeAttemptQuestions(component: string, attemptId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(this.QUESTION_TABLE, {component, attemptId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an answer from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} name Answer's name.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
removeAnswer(component: string, attemptId: number, name: string, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, name});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a question from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} slot Question slot.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
removeQuestion(component: string, attemptId: number, slot: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(this.QUESTION_TABLE, {component, attemptId, slot});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove a question answers from site DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the attempt belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {string} slot Question slot.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
removeQuestionAnswers(component: string, attemptId: number, slot: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
return site.getDb().deleteRecords(this.QUESTION_ANSWERS_TABLE, {component, attemptId, slot});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the prefix from a question answer name.
|
||||||
|
*
|
||||||
|
* @param {string} name Question name.
|
||||||
|
* @return {string} Name without prefix.
|
||||||
|
*/
|
||||||
|
removeQuestionPrefix(name: string): string {
|
||||||
|
if (name) {
|
||||||
|
return name.replace(this.QUESTION_PREFIX_REGEX, '');
|
||||||
|
}
|
||||||
|
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save answers in local DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the answers belong to. E.g. 'mmaModQuiz'.
|
||||||
|
* @param {number} componentId ID of the component the answers belong to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {any} answers Object with the answers to save.
|
||||||
|
* @param {number} [timemodified] 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(component: string, componentId: number, attemptId: number, userId: number, answers: any, timemodified?: number,
|
||||||
|
siteId?: string): Promise<any> {
|
||||||
|
timemodified = timemodified || this.timeUtils.timestamp();
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const db = site.getDb(),
|
||||||
|
promises = [];
|
||||||
|
|
||||||
|
for (const name in answers) {
|
||||||
|
const value = answers[name],
|
||||||
|
entry = {
|
||||||
|
component: component,
|
||||||
|
componentId: componentId,
|
||||||
|
attemptId: attemptId,
|
||||||
|
userId: userId,
|
||||||
|
questionSlot: this.getQuestionSlotFromName(name),
|
||||||
|
name: name,
|
||||||
|
value: value,
|
||||||
|
timemodified: timemodified
|
||||||
|
};
|
||||||
|
|
||||||
|
promises.push(db.insertRecord(this.QUESTION_ANSWERS_TABLE, entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save a question in local DB.
|
||||||
|
*
|
||||||
|
* @param {string} component Component the question belongs to. E.g. 'mmaModQuiz'.
|
||||||
|
* @param {number} componentId ID of the component the question belongs to.
|
||||||
|
* @param {number} attemptId Attempt ID.
|
||||||
|
* @param {number} userId User ID.
|
||||||
|
* @param {any} question The question to save.
|
||||||
|
* @param {string} state Question's state.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
|
*/
|
||||||
|
saveQuestion(component: string, componentId: number, attemptId: number, userId: number, question: any, state: string,
|
||||||
|
siteId?: string): Promise<any> {
|
||||||
|
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const entry = {
|
||||||
|
component: component,
|
||||||
|
componentId: componentId,
|
||||||
|
attemptid: attemptId,
|
||||||
|
userid: userId,
|
||||||
|
number: question.number,
|
||||||
|
slot: question.slot,
|
||||||
|
state: state
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(this.QUESTION_TABLE, entry);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
// (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 { NgModule } from '@angular/core';
|
||||||
|
import { CoreQuestionProvider } from './providers/question';
|
||||||
|
|
||||||
|
// List of providers.
|
||||||
|
export const CORE_QUESTION_PROVIDERS = [
|
||||||
|
CoreQuestionProvider
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [],
|
||||||
|
imports: [
|
||||||
|
],
|
||||||
|
providers: CORE_QUESTION_PROVIDERS,
|
||||||
|
exports: []
|
||||||
|
})
|
||||||
|
export class CoreQuestionModule {}
|
Loading…
Reference in New Issue