MOBILE-2272 quiz: Support add new files in essay in online
parent
3f40127661
commit
46efcc441a
|
@ -569,7 +569,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
// Prepare the answers to be sent for the attempt.
|
// Prepare the answers to be sent for the attempt.
|
||||||
protected prepareAnswers(): Promise<any> {
|
protected prepareAnswers(): Promise<any> {
|
||||||
return this.questionHelper.prepareAnswers(this.questions, this.getAnswers(), this.offline);
|
return this.questionHelper.prepareAnswers(this.questions, this.getAnswers(), this.offline, this.component,
|
||||||
|
this.quiz.coursemodule);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -612,6 +613,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy {
|
||||||
if (this.formElement) {
|
if (this.formElement) {
|
||||||
this.domUtils.triggerFormSubmittedEvent(this.formElement, !this.offline, this.sitesProvider.getCurrentSiteId());
|
this.domUtils.triggerFormSubmittedEvent(this.formElement, !this.offline, this.sitesProvider.getCurrentSiteId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return this.questionHelper.clearTmpData(this.questions, this.component, this.quiz.coursemodule);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -342,8 +342,8 @@ export class AddonModQuizOfflineProvider {
|
||||||
for (const slot in questionsWithAnswers) {
|
for (const slot in questionsWithAnswers) {
|
||||||
const question = questionsWithAnswers[slot];
|
const question = questionsWithAnswers[slot];
|
||||||
|
|
||||||
promises.push(this.behaviourDelegate.determineNewState(
|
promises.push(this.behaviourDelegate.determineNewState(quiz.preferredbehaviour, AddonModQuizProvider.COMPONENT,
|
||||||
quiz.preferredbehaviour, AddonModQuizProvider.COMPONENT, attempt.id, question, siteId).then((state) => {
|
attempt.id, question, quiz.coursemodule, siteId).then((state) => {
|
||||||
// Check if state has changed.
|
// Check if state has changed.
|
||||||
if (state && state.name != question.state) {
|
if (state && state.name != question.state) {
|
||||||
newStates[question.slot] = state.name;
|
newStates[question.slot] = state.name;
|
||||||
|
|
|
@ -40,13 +40,14 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return New state (or promise resolved with state).
|
* @return New state (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState> {
|
: CoreQuestionState | Promise<CoreQuestionState> {
|
||||||
// Depends on deferredfeedback.
|
// Depends on deferredfeedback.
|
||||||
return this.deferredFeedbackHandler.determineNewStateDeferred(component, attemptId, question, siteId,
|
return this.deferredFeedbackHandler.determineNewStateDeferred(component, attemptId, question, componentId, siteId,
|
||||||
this.isCompleteResponse.bind(this), this.isSameResponse.bind(this));
|
this.isCompleteResponse.bind(this), this.isSameResponse.bind(this));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,11 +72,13 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
protected isCompleteResponse(question: any, answers: any): number {
|
protected isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// First check if the question answer is complete.
|
// First check if the question answer is complete.
|
||||||
const complete = this.questionDelegate.isCompleteResponse(question, answers);
|
const complete = this.questionDelegate.isCompleteResponse(question, answers, component, componentId);
|
||||||
if (complete > 0) {
|
if (complete > 0) {
|
||||||
// Answer is complete, check the user answered CBM too.
|
// Answer is complete, check the user answered CBM too.
|
||||||
return answers['-certainty'] ? 1 : 0;
|
return answers['-certainty'] ? 1 : 0;
|
||||||
|
@ -101,12 +104,14 @@ export class AddonQbehaviourDeferredCBMHandler implements CoreQuestionBehaviourH
|
||||||
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
protected isSameResponse(question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any)
|
protected isSameResponse(question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any, newBasicAnswers: any,
|
||||||
: boolean {
|
component: string, componentId: string | number): boolean {
|
||||||
// First check if the question answer is the same.
|
// First check if the question answer is the same.
|
||||||
const same = this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers);
|
const same = this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers, component, componentId);
|
||||||
if (same) {
|
if (same) {
|
||||||
// Same response, check the CBM is the same too.
|
// Same response, check the CBM is the same too.
|
||||||
return prevAnswers['-certainty'] == newAnswers['-certainty'];
|
return prevAnswers['-certainty'] == newAnswers['-certainty'];
|
||||||
|
|
|
@ -23,9 +23,11 @@ import { CoreQuestionProvider, CoreQuestionState } from '@core/question/provider
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
export type isCompleteResponseFunction = (question: any, answers: any) => number;
|
export type isCompleteResponseFunction = (question: any, answers: any, component: string, componentId: string | number) => number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if two responses are the same.
|
* Check if two responses are the same.
|
||||||
|
@ -35,10 +37,12 @@ export type isCompleteResponseFunction = (question: any, answers: any) => number
|
||||||
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any,
|
export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any,
|
||||||
newBasicAnswers: any) => boolean;
|
newBasicAnswers: any, component: string, componentId: string | number) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to support deferred feedback question behaviour.
|
* Handler to support deferred feedback question behaviour.
|
||||||
|
@ -58,12 +62,13 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return New state (or promise resolved with state).
|
* @return New state (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState> {
|
: CoreQuestionState | Promise<CoreQuestionState> {
|
||||||
return this.determineNewStateDeferred(component, attemptId, question, siteId);
|
return this.determineNewStateDeferred(component, attemptId, question, componentId, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,12 +77,13 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @param isCompleteFn Function to override the default isCompleteResponse check.
|
* @param isCompleteFn Function to override the default isCompleteResponse check.
|
||||||
* @param isSameFn Function to override the default isSameResponse check.
|
* @param isSameFn Function to override the default isSameResponse check.
|
||||||
* @return Promise resolved with state.
|
* @return Promise resolved with state.
|
||||||
*/
|
*/
|
||||||
determineNewStateDeferred(component: string, attemptId: number, question: any, siteId?: string,
|
determineNewStateDeferred(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string,
|
||||||
isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise<CoreQuestionState> {
|
isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise<CoreQuestionState> {
|
||||||
|
|
||||||
// Check if we have local data for the question.
|
// Check if we have local data for the question.
|
||||||
|
@ -103,11 +109,12 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav
|
||||||
|
|
||||||
// If answers haven't changed the state is the same.
|
// If answers haven't changed the state is the same.
|
||||||
if (isSameFn) {
|
if (isSameFn) {
|
||||||
if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers)) {
|
if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers,
|
||||||
|
component, componentId)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers)) {
|
if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers, component, componentId)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,10 +124,10 @@ export class AddonQbehaviourDeferredFeedbackHandler implements CoreQuestionBehav
|
||||||
newState: string;
|
newState: string;
|
||||||
if (isCompleteFn) {
|
if (isCompleteFn) {
|
||||||
// Pass all the answers since some behaviours might need the extra data.
|
// Pass all the answers since some behaviours might need the extra data.
|
||||||
complete = isCompleteFn(question, question.answers);
|
complete = isCompleteFn(question, question.answers, component, componentId);
|
||||||
} else {
|
} else {
|
||||||
// Only pass the basic answers since questions should be independent of extra data.
|
// Only pass the basic answers since questions should be independent of extra data.
|
||||||
complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers);
|
complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers, component, componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (complete < 0) {
|
if (complete < 0) {
|
||||||
|
|
|
@ -35,10 +35,11 @@ export class AddonQbehaviourInformationItemHandler implements CoreQuestionBehavi
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return New state (or promise resolved with state).
|
* @return New state (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState> {
|
: CoreQuestionState | Promise<CoreQuestionState> {
|
||||||
if (question.answers['-seen']) {
|
if (question.answers['-seen']) {
|
||||||
return this.questionProvider.getState('complete');
|
return this.questionProvider.getState('complete');
|
||||||
|
|
|
@ -23,9 +23,11 @@ import { CoreQuestionProvider, CoreQuestionState } from '@core/question/provider
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
export type isCompleteResponseFunction = (question: any, answers: any) => number;
|
export type isCompleteResponseFunction = (question: any, answers: any, component: string, componentId: string | number) => number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if two responses are the same.
|
* Check if two responses are the same.
|
||||||
|
@ -35,10 +37,12 @@ export type isCompleteResponseFunction = (question: any, answers: any) => number
|
||||||
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param prevBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
* @param newBasicAnswers Object with the previous basic" answers (without sequencecheck, certainty, ...).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any,
|
export type isSameResponseFunction = (question: any, prevAnswers: any, prevBasicAnswers: any, newAnswers: any,
|
||||||
newBasicAnswers: any) => boolean;
|
newBasicAnswers: any, component: string, componentId: string | number) => boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handler to support manual graded question behaviour.
|
* Handler to support manual graded question behaviour.
|
||||||
|
@ -58,12 +62,13 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return New state (or promise resolved with state).
|
* @return New state (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState> {
|
: CoreQuestionState | Promise<CoreQuestionState> {
|
||||||
return this.determineNewStateManualGraded(component, attemptId, question, siteId);
|
return this.determineNewStateManualGraded(component, attemptId, question, componentId, siteId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -72,13 +77,15 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @param isCompleteFn Function to override the default isCompleteResponse check.
|
* @param isCompleteFn Function to override the default isCompleteResponse check.
|
||||||
* @param isSameFn Function to override the default isSameResponse check.
|
* @param isSameFn Function to override the default isSameResponse check.
|
||||||
* @return Promise resolved with state.
|
* @return Promise resolved with state.
|
||||||
*/
|
*/
|
||||||
determineNewStateManualGraded(component: string, attemptId: number, question: any, siteId?: string,
|
determineNewStateManualGraded(component: string, attemptId: number, question: any, componentId: string | number,
|
||||||
isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction): Promise<CoreQuestionState> {
|
siteId?: string, isCompleteFn?: isCompleteResponseFunction, isSameFn?: isSameResponseFunction)
|
||||||
|
: Promise<CoreQuestionState> {
|
||||||
|
|
||||||
// Check if we have local data for the question.
|
// Check if we have local data for the question.
|
||||||
return this.questionProvider.getQuestion(component, attemptId, question.slot, siteId).catch(() => {
|
return this.questionProvider.getQuestion(component, attemptId, question.slot, siteId).catch(() => {
|
||||||
|
@ -103,11 +110,12 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour
|
||||||
|
|
||||||
// If answers haven't changed the state is the same.
|
// If answers haven't changed the state is the same.
|
||||||
if (isSameFn) {
|
if (isSameFn) {
|
||||||
if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers)) {
|
if (isSameFn(question, prevAnswers, prevBasicAnswers, question.answers, newBasicAnswers,
|
||||||
|
component, componentId)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers)) {
|
if (this.questionDelegate.isSameResponse(question, prevBasicAnswers, newBasicAnswers, component, componentId)) {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -117,10 +125,10 @@ export class AddonQbehaviourManualGradedHandler implements CoreQuestionBehaviour
|
||||||
newState: string;
|
newState: string;
|
||||||
if (isCompleteFn) {
|
if (isCompleteFn) {
|
||||||
// Pass all the answers since some behaviours might need the extra data.
|
// Pass all the answers since some behaviours might need the extra data.
|
||||||
complete = isCompleteFn(question, question.answers);
|
complete = isCompleteFn(question, question.answers, component, componentId);
|
||||||
} else {
|
} else {
|
||||||
// Only pass the basic answers since questions should be independent of extra data.
|
// Only pass the basic answers since questions should be independent of extra data.
|
||||||
complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers);
|
complete = this.questionDelegate.isCompleteResponse(question, newBasicAnswers, component, componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (complete < 0) {
|
if (complete < 0) {
|
||||||
|
|
|
@ -46,9 +46,11 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) {
|
if (this.isGradableResponse(question, answers) === 0 || !this.validateUnits(answers['answer'])) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
@ -93,9 +95,11 @@ export class AddonQtypeCalculatedHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') &&
|
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') &&
|
||||||
this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'unit');
|
this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'unit');
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,11 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// This question type depends on multichoice.
|
// This question type depends on multichoice.
|
||||||
return this.multichoiceHandler.isCompleteResponseSingle(answers);
|
return this.multichoiceHandler.isCompleteResponseSingle(answers);
|
||||||
}
|
}
|
||||||
|
@ -81,9 +83,11 @@ export class AddonQtypeCalculatedMultiHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
// This question type depends on multichoice.
|
// This question type depends on multichoice.
|
||||||
return this.multichoiceHandler.isSameResponseSingle(prevAnswers, newAnswers);
|
return this.multichoiceHandler.isSameResponseSingle(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,11 +46,13 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// This question type depends on calculated.
|
// This question type depends on calculated.
|
||||||
return this.calculatedHandler.isCompleteResponse(question, answers);
|
return this.calculatedHandler.isCompleteResponse(question, answers, component, componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,10 +83,12 @@ export class AddonQtypeCalculatedSimpleHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
// This question type depends on calculated.
|
// This question type depends on calculated.
|
||||||
return this.calculatedHandler.isSameResponse(question, prevAnswers, newAnswers);
|
return this.calculatedHandler.isSameResponse(question, prevAnswers, newAnswers, component, componentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,11 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// An answer is complete if all drop zones have an answer.
|
// An answer is complete if all drop zones have an answer.
|
||||||
// We should always receive all the drop zones with their value ('' if not answered).
|
// We should always receive all the drop zones with their value ('' if not answered).
|
||||||
for (const name in answers) {
|
for (const name in answers) {
|
||||||
|
@ -110,9 +112,11 @@ export class AddonQtypeDdImageOrTextHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,11 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// If 1 dragitem is set we assume the answer is complete (like Moodle does).
|
// If 1 dragitem is set we assume the answer is complete (like Moodle does).
|
||||||
for (const name in answers) {
|
for (const name in answers) {
|
||||||
if (answers[name]) {
|
if (answers[name]) {
|
||||||
|
@ -93,7 +95,7 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
|
||||||
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isGradableResponse(question: any, answers: any): number {
|
isGradableResponse(question: any, answers: any): number {
|
||||||
return this.isCompleteResponse(question, answers);
|
return this.isCompleteResponse(question, answers, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -102,9 +104,11 @@ export class AddonQtypeDdMarkerHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -61,9 +61,11 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
for (const name in answers) {
|
for (const name in answers) {
|
||||||
const value = answers[name];
|
const value = answers[name];
|
||||||
if (!value || value === '0') {
|
if (!value || value === '0') {
|
||||||
|
@ -108,9 +110,11 @@ export class AddonQtypeDdwtosHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,7 +27,7 @@
|
||||||
|
|
||||||
<!-- Attachments. -->
|
<!-- Attachments. -->
|
||||||
<ng-container *ngIf="question.allowsAttachments">
|
<ng-container *ngIf="question.allowsAttachments">
|
||||||
<core-attachments *ngIf="uploadFilesSupported" [files]="attachments" [component]="component" [componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles"></core-attachments>
|
<core-attachments *ngIf="uploadFilesSupported" [files]="attachments" [component]="component" [componentId]="componentId" [maxSize]="question.attachmentsMaxBytes" [maxSubmissions]="question.attachmentsMaxFiles" [allowOffline]="offline"></core-attachments>
|
||||||
|
|
||||||
<input item-content *ngIf="uploadFilesSupported" type="hidden" [name]="question.attachmentsDraftIdInput.name" [value]="question.attachmentsDraftIdInput.value" >
|
<input item-content *ngIf="uploadFilesSupported" type="hidden" [name]="question.attachmentsDraftIdInput.name" [value]="question.attachmentsDraftIdInput.value" >
|
||||||
|
|
||||||
|
|
|
@ -14,9 +14,9 @@
|
||||||
|
|
||||||
import { Component, OnInit, Injector } from '@angular/core';
|
import { Component, OnInit, Injector } from '@angular/core';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSites } from '@providers/sites';
|
|
||||||
import { CoreWSExternalFile } from '@providers/ws';
|
import { CoreWSExternalFile } from '@providers/ws';
|
||||||
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
import { CoreQuestionBaseComponent } from '@core/question/classes/base-question-component';
|
||||||
|
import { CoreQuestion } from '@core/question/providers/question';
|
||||||
import { FormControl, FormBuilder } from '@angular/forms';
|
import { FormControl, FormBuilder } from '@angular/forms';
|
||||||
import { CoreFileSession } from '@providers/file-session';
|
import { CoreFileSession } from '@providers/file-session';
|
||||||
|
|
||||||
|
@ -42,14 +42,15 @@ export class AddonQtypeEssayComponent extends CoreQuestionBaseComponent implemen
|
||||||
* Component being initialized.
|
* Component being initialized.
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.uploadFilesSupported = CoreSites.instance.getCurrentSite().isVersionGreaterEqualThan('3.10');
|
this.uploadFilesSupported = typeof this.question.responsefileareas != 'undefined';
|
||||||
this.initEssayComponent();
|
this.initEssayComponent();
|
||||||
|
|
||||||
this.formControl = this.fb.control(this.question.textarea && this.question.textarea.text);
|
this.formControl = this.fb.control(this.question.textarea && this.question.textarea.text);
|
||||||
|
|
||||||
if (this.question.allowsAttachments && this.uploadFilesSupported) {
|
if (this.question.allowsAttachments && this.uploadFilesSupported) {
|
||||||
this.attachments = Array.from(this.questionHelper.getResponseFileAreaFiles(this.question, 'attachments'));
|
this.attachments = Array.from(this.questionHelper.getResponseFileAreaFiles(this.question, 'attachments'));
|
||||||
CoreFileSession.instance.setFiles(this.component, this.componentId + '_' + this.question.id, this.attachments);
|
CoreFileSession.instance.setFiles(this.component,
|
||||||
|
CoreQuestion.instance.getQuestionComponentId(this.question, this.componentId), this.attachments);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,12 +14,15 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable, Injector } from '@angular/core';
|
import { Injectable, Injector } from '@angular/core';
|
||||||
|
import { CoreFileSession } from '@providers/file-session';
|
||||||
import { CoreSites } from '@providers/sites';
|
import { CoreSites } from '@providers/sites';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreFileUploader } from '@core/fileuploader/providers/fileuploader';
|
||||||
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
import { CoreQuestionHandler } from '@core/question/providers/delegate';
|
||||||
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
import { CoreQuestionHelperProvider } from '@core/question/providers/helper';
|
||||||
|
import { CoreQuestion } from '@core/question/providers/question';
|
||||||
import { AddonQtypeEssayComponent } from '../component/essay';
|
import { AddonQtypeEssayComponent } from '../component/essay';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -33,6 +36,24 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider,
|
constructor(private utils: CoreUtilsProvider, private questionHelper: CoreQuestionHelperProvider,
|
||||||
private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) { }
|
private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider) { }
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear temporary data after the data has been saved.
|
||||||
|
*
|
||||||
|
* @param question Question.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
|
*/
|
||||||
|
clearTmpData(question: any, component: string, componentId: string | number): void {
|
||||||
|
const questionComponentId = CoreQuestion.instance.getQuestionComponentId(question, componentId);
|
||||||
|
const files = CoreFileSession.instance.getFiles(component, questionComponentId);
|
||||||
|
|
||||||
|
// Clear the files in session for this question.
|
||||||
|
CoreFileSession.instance.clearFiles(component, questionComponentId);
|
||||||
|
|
||||||
|
// Now delete the local files from the tmp folder.
|
||||||
|
CoreFileUploader.instance.clearTmpFiles(files);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return the name of the behaviour to use for the question.
|
* Return the name of the behaviour to use for the question.
|
||||||
* If the question should use the default behaviour you shouldn't implement this function.
|
* If the question should use the default behaviour you shouldn't implement this function.
|
||||||
|
@ -66,13 +87,14 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
*/
|
*/
|
||||||
getPreventSubmitMessage(question: any): string {
|
getPreventSubmitMessage(question: any): string {
|
||||||
const element = this.domUtils.convertToElement(question.html);
|
const element = this.domUtils.convertToElement(question.html);
|
||||||
|
const uploadFilesSupported = typeof question.responsefileareas != 'undefined';
|
||||||
|
|
||||||
if (element.querySelector('div[id*=filemanager]')) {
|
if (!uploadFilesSupported && element.querySelector('div[id*=filemanager]')) {
|
||||||
// The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
|
// The question allows attachments. Since the app cannot attach files yet we will prevent submitting the question.
|
||||||
return 'core.question.errorattachmentsnotsupportedinsite';
|
return 'core.question.errorattachmentsnotsupportedinsite';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.questionHelper.hasDraftFileUrls(element.innerHTML)) {
|
if (!uploadFilesSupported && this.questionHelper.hasDraftFileUrls(element.innerHTML)) {
|
||||||
return 'core.question.errorinlinefilesnotsupportedinsite';
|
return 'core.question.errorinlinefilesnotsupportedinsite';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -82,22 +104,38 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
const element = this.domUtils.convertToElement(question.html);
|
const element = this.domUtils.convertToElement(question.html);
|
||||||
|
|
||||||
const hasInlineText = answers['answer'] && answers['answer'] !== '',
|
const hasInlineText = answers['answer'] && answers['answer'] !== '';
|
||||||
allowsAttachments = !!element.querySelector('div[id*=filemanager]');
|
const allowsInlineText = !!element.querySelector('textarea[name*=_answer]');
|
||||||
|
const allowsAttachments = !!element.querySelector('div[id*=filemanager]');
|
||||||
|
const uploadFilesSupported = typeof question.responsefileareas != 'undefined';
|
||||||
|
|
||||||
if (!allowsAttachments) {
|
if (!allowsAttachments) {
|
||||||
return hasInlineText ? 1 : 0;
|
return hasInlineText ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!uploadFilesSupported) {
|
||||||
// We can't know if the attachments are required or if the user added any in web.
|
// We can't know if the attachments are required or if the user added any in web.
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const questionComponentId = CoreQuestion.instance.getQuestionComponentId(question, componentId);
|
||||||
|
const attachments = CoreFileSession.instance.getFiles(component, questionComponentId);
|
||||||
|
|
||||||
|
if (!allowsInlineText) {
|
||||||
|
return attachments && attachments.length > 0 ? 1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If any of the fields is missing return -1 because we can't know if they're required or not.
|
||||||
|
return hasInlineText && attachments && attachments.length > 0 ? 1 : -1;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the handler is enabled on a site level.
|
* Whether or not the handler is enabled on a site level.
|
||||||
*
|
*
|
||||||
|
@ -125,10 +163,30 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
const element = this.domUtils.convertToElement(question.html);
|
||||||
|
const allowsInlineText = !!element.querySelector('textarea[name*=_answer]');
|
||||||
|
const allowsAttachments = !!element.querySelector('div[id*=filemanager]');
|
||||||
|
const uploadFilesSupported = typeof question.responsefileareas != 'undefined';
|
||||||
|
|
||||||
|
// First check the inline text.
|
||||||
|
const answerIsEqual = allowsInlineText ? this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer') : true;
|
||||||
|
|
||||||
|
if (!allowsAttachments || !uploadFilesSupported || !answerIsEqual) {
|
||||||
|
// No need to check attachments.
|
||||||
|
return answerIsEqual;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check attachments now.
|
||||||
|
const questionComponentId = CoreQuestion.instance.getQuestionComponentId(question, componentId);
|
||||||
|
const attachments = CoreFileSession.instance.getFiles(component, questionComponentId);
|
||||||
|
const originalAttachments = this.questionHelper.getResponseFileAreaFiles(question, 'attachments');
|
||||||
|
|
||||||
|
return CoreFileUploader.instance.areFileListDifferent(attachments, originalAttachments);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -137,11 +195,16 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Return a promise resolved when done if async, void if sync.
|
* @return Return a promise resolved when done if async, void if sync.
|
||||||
*/
|
*/
|
||||||
async prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): Promise<void> {
|
async prepareAnswers(question: any, answers: any, offline: boolean, component: string, componentId: string | number,
|
||||||
|
siteId?: string): Promise<void> {
|
||||||
|
|
||||||
const element = this.domUtils.convertToElement(question.html);
|
const element = this.domUtils.convertToElement(question.html);
|
||||||
|
const attachmentsInput = <HTMLInputElement> element.querySelector('.attachments input[name*=_attachments]');
|
||||||
|
|
||||||
// Search the textarea to get its name.
|
// Search the textarea to get its name.
|
||||||
const textarea = <HTMLTextAreaElement> element.querySelector('textarea[name*=_answer]');
|
const textarea = <HTMLTextAreaElement> element.querySelector('textarea[name*=_answer]');
|
||||||
|
@ -158,5 +221,18 @@ export class AddonQtypeEssayHandler implements CoreQuestionHandler {
|
||||||
// Add some HTML to the text if needed.
|
// Add some HTML to the text if needed.
|
||||||
answers[textarea.name] = this.textUtils.formatHtmlLines(answers[textarea.name]);
|
answers[textarea.name] = this.textUtils.formatHtmlLines(answers[textarea.name]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (attachmentsInput) {
|
||||||
|
// Treat attachments if any.
|
||||||
|
const questionComponentId = CoreQuestion.instance.getQuestionComponentId(question, componentId);
|
||||||
|
const attachments = CoreFileSession.instance.getFiles(component, questionComponentId);
|
||||||
|
const draftId = Number(attachmentsInput.value);
|
||||||
|
|
||||||
|
if (offline) {
|
||||||
|
// @TODO Support offline.
|
||||||
|
} else {
|
||||||
|
await CoreFileUploader.instance.uploadFiles(draftId, attachments, siteId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,11 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// We should always get a value for each select so we can assume we receive all the possible answers.
|
// We should always get a value for each select so we can assume we receive all the possible answers.
|
||||||
for (const name in answers) {
|
for (const name in answers) {
|
||||||
const value = answers[name];
|
const value = answers[name];
|
||||||
|
@ -110,9 +112,11 @@ export class AddonQtypeGapSelectHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,9 +61,11 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// We should always get a value for each select so we can assume we receive all the possible answers.
|
// We should always get a value for each select so we can assume we receive all the possible answers.
|
||||||
for (const name in answers) {
|
for (const name in answers) {
|
||||||
const value = answers[name];
|
const value = answers[name];
|
||||||
|
@ -110,9 +112,11 @@ export class AddonQtypeMatchHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,9 +62,11 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// Get all the inputs in the question to check if they've all been answered.
|
// Get all the inputs in the question to check if they've all been answered.
|
||||||
const names = this.questionProvider.getBasicAnswers(this.questionHelper.getAllInputNamesFromHtml(question.html));
|
const names = this.questionProvider.getBasicAnswers(this.questionHelper.getAllInputNamesFromHtml(question.html));
|
||||||
for (const name in names) {
|
for (const name in names) {
|
||||||
|
@ -112,9 +114,11 @@ export class AddonQtypeMultiAnswerHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
return this.questionProvider.compareAllAnswers(prevAnswers, newAnswers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -45,9 +45,11 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
let isSingle = true,
|
let isSingle = true,
|
||||||
isMultiComplete = false;
|
isMultiComplete = false;
|
||||||
|
|
||||||
|
@ -98,7 +100,7 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler {
|
||||||
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isGradableResponse(question: any, answers: any): number {
|
isGradableResponse(question: any, answers: any): number {
|
||||||
return this.isCompleteResponse(question, answers);
|
return this.isCompleteResponse(question, answers, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -118,9 +120,11 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
let isSingle = true,
|
let isSingle = true,
|
||||||
isMultiSame = true;
|
isMultiSame = true;
|
||||||
|
|
||||||
|
@ -158,10 +162,13 @@ export class AddonQtypeMultichoiceHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Return a promise resolved when done if async, void if sync.
|
* @return Return a promise resolved when done if async, void if sync.
|
||||||
*/
|
*/
|
||||||
prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> {
|
prepareAnswers(question: any, answers: any, offline: boolean, component: string, componentId: string | number, siteId?: string)
|
||||||
|
: void | Promise<any> {
|
||||||
if (question && !question.multi && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) {
|
if (question && !question.multi && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) {
|
||||||
/* It's a single choice and the user hasn't answered. Delete the answer because
|
/* It's a single choice and the user hasn't answered. Delete the answer because
|
||||||
sending an empty string (default value) will mark the first option as selected. */
|
sending an empty string (default value) will mark the first option as selected. */
|
||||||
|
|
|
@ -46,11 +46,13 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
// This question behaves like a match question.
|
// This question behaves like a match question.
|
||||||
return this.matchHandler.isCompleteResponse(question, answers);
|
return this.matchHandler.isCompleteResponse(question, answers, component, componentId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -81,10 +83,12 @@ export class AddonQtypeRandomSaMatchHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
// This question behaves like a match question.
|
// This question behaves like a match question.
|
||||||
return this.matchHandler.isSameResponse(question, prevAnswers, newAnswers);
|
return this.matchHandler.isSameResponse(question, prevAnswers, newAnswers, component, componentId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,9 +45,11 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
return (answers['answer'] || answers['answer'] === 0) ? 1 : 0;
|
return (answers['answer'] || answers['answer'] === 0) ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -69,7 +71,7 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler {
|
||||||
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isGradableResponse(question: any, answers: any): number {
|
isGradableResponse(question: any, answers: any): number {
|
||||||
return this.isCompleteResponse(question, answers);
|
return this.isCompleteResponse(question, answers, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -78,9 +80,11 @@ export class AddonQtypeShortAnswerHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,9 +46,11 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
return answers['answer'] ? 1 : 0;
|
return answers['answer'] ? 1 : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
|
||||||
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
* @return 1 if gradable, 0 if not gradable, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isGradableResponse(question: any, answers: any): number {
|
isGradableResponse(question: any, answers: any): number {
|
||||||
return this.isCompleteResponse(question, answers);
|
return this.isCompleteResponse(question, answers, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,9 +81,11 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,10 +95,13 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Return a promise resolved when done if async, void if sync.
|
* @return Return a promise resolved when done if async, void if sync.
|
||||||
*/
|
*/
|
||||||
prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> {
|
prepareAnswers(question: any, answers: any, offline: boolean, component: string, componentId: string | number, siteId?: string)
|
||||||
|
: void | Promise<any> {
|
||||||
if (question && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) {
|
if (question && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) {
|
||||||
// The user hasn't answered. Delete the answer to prevent marking one of the answers automatically.
|
// The user hasn't answered. Delete the answer to prevent marking one of the answers automatically.
|
||||||
delete answers[question.optionsName];
|
delete answers[question.optionsName];
|
||||||
|
|
|
@ -15,6 +15,7 @@
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { ModalController } from 'ionic-angular';
|
import { ModalController } from 'ionic-angular';
|
||||||
import { Camera, CameraOptions } from '@ionic-native/camera';
|
import { Camera, CameraOptions } from '@ionic-native/camera';
|
||||||
|
import { FileEntry } from '@ionic-native/file';
|
||||||
import { MediaCapture, MediaFile, CaptureError, CaptureAudioOptions, CaptureVideoOptions } from '@ionic-native/media-capture';
|
import { MediaCapture, MediaFile, CaptureError, CaptureAudioOptions, CaptureVideoOptions } from '@ionic-native/media-capture';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreFileProvider } from '@providers/file';
|
import { CoreFileProvider } from '@providers/file';
|
||||||
|
@ -25,9 +26,10 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreWSFileUploadOptions } from '@providers/ws';
|
import { CoreWSFileUploadOptions, CoreWSExternalFile } from '@providers/ws';
|
||||||
import { Subject } from 'rxjs';
|
import { Subject } from 'rxjs';
|
||||||
import { CoreApp } from '@providers/app';
|
import { CoreApp } from '@providers/app';
|
||||||
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* File upload options.
|
* File upload options.
|
||||||
|
@ -96,7 +98,7 @@ export class CoreFileUploaderProvider {
|
||||||
// Currently we are going to compare the order of the files as well.
|
// Currently we are going to compare the order of the files as well.
|
||||||
// This function can be improved comparing more fields or not comparing the order.
|
// This function can be improved comparing more fields or not comparing the order.
|
||||||
for (let i = 0; i < a.length; i++) {
|
for (let i = 0; i < a.length; i++) {
|
||||||
if ((a[i].name || a[i].filename) != (b[i].name || b[i].filename)) {
|
if (a[i].name != b[i].name || a[i].filename != b[i].filename) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -534,6 +536,37 @@ export class CoreFileUploaderProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a list of files (either online files or local files), upload the local files to the draft area.
|
||||||
|
* Local files are not deleted from the device after upload.
|
||||||
|
*
|
||||||
|
* @param itemId Draft ID.
|
||||||
|
* @param files List of files.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Promise resolved with the itemId.
|
||||||
|
*/
|
||||||
|
async uploadFiles(itemId: number, files: (CoreWSExternalFile | FileEntry)[], siteId?: string): Promise<void> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (!files || !files.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await Promise.all(files.map(async (file) => {
|
||||||
|
if ((<CoreWSExternalFile> file).filename && !(<FileEntry> file).name) {
|
||||||
|
// File already uploaded, ignore it.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
file = <FileEntry> file;
|
||||||
|
|
||||||
|
// Now upload the file.
|
||||||
|
const options = this.getFileUploadOptions(file.toURL(), file.name, undefined, false, 'draft', itemId);
|
||||||
|
|
||||||
|
await this.uploadFile(file.toURL(), options, undefined, siteId);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Upload a file to a draft area and return the draft ID.
|
* Upload a file to a draft area and return the draft ID.
|
||||||
*
|
*
|
||||||
|
@ -615,3 +648,5 @@ export class CoreFileUploaderProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CoreFileUploader extends makeSingleton(CoreFileUploaderProvider) {}
|
||||||
|
|
|
@ -34,10 +34,11 @@ export class CoreQuestionBehaviourBaseHandler implements CoreQuestionBehaviourHa
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return New state (or promise resolved with state).
|
* @return New state (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState> {
|
: CoreQuestionState | Promise<CoreQuestionState> {
|
||||||
// Return the current state.
|
// Return the current state.
|
||||||
return this.questionProvider.getState(question.state);
|
return this.questionProvider.getState(question.state);
|
||||||
|
|
|
@ -79,9 +79,11 @@ export class CoreQuestionBaseHandler implements CoreQuestionHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -103,9 +105,11 @@ export class CoreQuestionBaseHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param prevAnswers Object with the previous question answers.
|
* @param prevAnswers Object with the previous question answers.
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -115,10 +119,13 @@ export class CoreQuestionBaseHandler implements CoreQuestionHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Return a promise resolved when done if async, void if sync.
|
* @return Return a promise resolved when done if async, void if sync.
|
||||||
*/
|
*/
|
||||||
prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> {
|
prepareAnswers(question: any, answers: any, offline: boolean, component: string, componentId: string | number, siteId?: string)
|
||||||
|
: void | Promise<any> {
|
||||||
// Nothing to do.
|
// Nothing to do.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -36,10 +36,11 @@ export interface CoreQuestionBehaviourHandler extends CoreDelegateHandler {
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return State (or promise resolved with state).
|
* @return State (or promise resolved with state).
|
||||||
*/
|
*/
|
||||||
determineNewState?(component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState?(component: string, attemptId: number, question: any, componentId: string | number, siteId?: string)
|
||||||
: CoreQuestionState | Promise<CoreQuestionState>;
|
: CoreQuestionState | Promise<CoreQuestionState>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -74,15 +75,16 @@ export class CoreQuestionBehaviourDelegate extends CoreDelegate {
|
||||||
* @param component Component the question belongs to.
|
* @param component Component the question belongs to.
|
||||||
* @param attemptId Attempt ID the question belongs to.
|
* @param attemptId Attempt ID the question belongs to.
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved with state.
|
* @return Promise resolved with state.
|
||||||
*/
|
*/
|
||||||
determineNewState(behaviour: string, component: string, attemptId: number, question: any, siteId?: string)
|
determineNewState(behaviour: string, component: string, attemptId: number, question: any, componentId: string | number,
|
||||||
: Promise<CoreQuestionState> {
|
siteId?: string): Promise<CoreQuestionState> {
|
||||||
behaviour = this.questionDelegate.getBehaviourForQuestion(question, behaviour);
|
behaviour = this.questionDelegate.getBehaviourForQuestion(question, behaviour);
|
||||||
|
|
||||||
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'determineNewState',
|
return Promise.resolve(this.executeFunctionOnEnabled(behaviour, 'determineNewState',
|
||||||
[component, attemptId, question, siteId]));
|
[component, attemptId, question, componentId, siteId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -62,9 +62,11 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse?(question: any, answers: any): number;
|
isCompleteResponse?(question: any, answers: any, component: string, componentId: string | number): number;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
* Check if a student has provided enough of an answer for the question to be graded automatically,
|
||||||
|
@ -84,7 +86,7 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse?(question: any, prevAnswers: any, newAnswers: any): boolean;
|
isSameResponse?(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Prepare and add to answers the data to send to server based in the input. Return promise if async.
|
* Prepare and add to answers the data to send to server based in the input. Return promise if async.
|
||||||
|
@ -92,10 +94,13 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Return a promise resolved when done if async, void if sync.
|
* @return Return a promise resolved when done if async, void if sync.
|
||||||
*/
|
*/
|
||||||
prepareAnswers?(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any>;
|
prepareAnswers?(question: any, answers: any, offline: boolean, component: string, componentId: string | number,
|
||||||
|
siteId?: string): void | Promise<any>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validate if an offline sequencecheck is valid compared with the online one.
|
* Validate if an offline sequencecheck is valid compared with the online one.
|
||||||
|
@ -115,6 +120,15 @@ export interface CoreQuestionHandler extends CoreDelegateHandler {
|
||||||
* @return List of URLs.
|
* @return List of URLs.
|
||||||
*/
|
*/
|
||||||
getAdditionalDownloadableFiles?(question: any, usageId: number): string[];
|
getAdditionalDownloadableFiles?(question: any, usageId: number): string[];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear temporary data after the data has been saved.
|
||||||
|
*
|
||||||
|
* @param question Question.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
|
*/
|
||||||
|
clearTmpData?(question: any, component: string, componentId: string | number): void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -196,12 +210,14 @@ export class CoreQuestionDelegate extends CoreDelegate {
|
||||||
*
|
*
|
||||||
* @param question The question.
|
* @param question The question.
|
||||||
* @param answers Object with the question answers (without prefix).
|
* @param answers Object with the question answers (without prefix).
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
* @return 1 if complete, 0 if not complete, -1 if cannot determine.
|
||||||
*/
|
*/
|
||||||
isCompleteResponse(question: any, answers: any): number {
|
isCompleteResponse(question: any, answers: any, component: string, componentId: string | number): number {
|
||||||
const type = this.getTypeName(question);
|
const type = this.getTypeName(question);
|
||||||
|
|
||||||
return this.executeFunctionOnEnabled(type, 'isCompleteResponse', [question, answers]);
|
return this.executeFunctionOnEnabled(type, 'isCompleteResponse', [question, answers, component, componentId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -226,10 +242,10 @@ export class CoreQuestionDelegate extends CoreDelegate {
|
||||||
* @param newAnswers Object with the new question answers.
|
* @param newAnswers Object with the new question answers.
|
||||||
* @return Whether they're the same.
|
* @return Whether they're the same.
|
||||||
*/
|
*/
|
||||||
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
|
isSameResponse(question: any, prevAnswers: any, newAnswers: any, component: string, componentId: string | number): boolean {
|
||||||
const type = this.getTypeName(question);
|
const type = this.getTypeName(question);
|
||||||
|
|
||||||
return this.executeFunctionOnEnabled(type, 'isSameResponse', [question, prevAnswers, newAnswers]);
|
return this.executeFunctionOnEnabled(type, 'isSameResponse', [question, prevAnswers, newAnswers, component, componentId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -248,13 +264,17 @@ export class CoreQuestionDelegate extends CoreDelegate {
|
||||||
* @param question Question.
|
* @param question Question.
|
||||||
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
* @param answers The answers retrieved from the form. Prepared answers must be stored in this object.
|
||||||
* @param offline Whether the data should be saved in offline.
|
* @param offline Whether the data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved when data has been prepared.
|
* @return Promise resolved when data has been prepared.
|
||||||
*/
|
*/
|
||||||
prepareAnswersForQuestion(question: any, answers: any, offline: boolean, siteId?: string): Promise<any> {
|
prepareAnswersForQuestion(question: any, answers: any, offline: boolean, component: string, componentId: string | number,
|
||||||
|
siteId?: string): Promise<any> {
|
||||||
const type = this.getTypeName(question);
|
const type = this.getTypeName(question);
|
||||||
|
|
||||||
return Promise.resolve(this.executeFunctionOnEnabled(type, 'prepareAnswers', [question, answers, offline, siteId]));
|
return Promise.resolve(this.executeFunctionOnEnabled(type, 'prepareAnswers',
|
||||||
|
[question, answers, offline, component, componentId, siteId]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -282,4 +302,17 @@ export class CoreQuestionDelegate extends CoreDelegate {
|
||||||
|
|
||||||
return this.executeFunctionOnEnabled(type, 'getAdditionalDownloadableFiles', [question, usageId]) || [];
|
return this.executeFunctionOnEnabled(type, 'getAdditionalDownloadableFiles', [question, usageId]) || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear temporary data after the data has been saved.
|
||||||
|
*
|
||||||
|
* @param question Question.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
|
*/
|
||||||
|
clearTmpData(question: any, component: string, componentId: string | number): void | Promise<void> {
|
||||||
|
const type = this.getTypeName(question);
|
||||||
|
|
||||||
|
return this.executeFunctionOnEnabled(type, 'clearTmpData', [question, component, componentId]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,6 +60,22 @@ export class CoreQuestionHelperProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear questions temporary data after the data has been saved.
|
||||||
|
*
|
||||||
|
* @param questions The list of questions.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
async clearTmpData(questions: any[], component: string, componentId: string | number): Promise<void> {
|
||||||
|
questions = questions || [];
|
||||||
|
|
||||||
|
await Promise.all(questions.map(async (question) => {
|
||||||
|
await this.questionDelegate.clearTmpData(question, component, componentId);
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract question behaviour submit buttons from the question's HTML and add them to "behaviourButtons" property.
|
* Extract question behaviour submit buttons from the question's HTML and add them to "behaviourButtons" property.
|
||||||
* The buttons aren't deleted from the content because all the im-controls block will be removed afterwards.
|
* The buttons aren't deleted from the content because all the im-controls block will be removed afterwards.
|
||||||
|
@ -541,7 +557,7 @@ export class CoreQuestionHelperProvider {
|
||||||
|
|
||||||
if (!component) {
|
if (!component) {
|
||||||
component = CoreQuestionProvider.COMPONENT;
|
component = CoreQuestionProvider.COMPONENT;
|
||||||
componentId = question.id;
|
componentId = question.number;
|
||||||
}
|
}
|
||||||
|
|
||||||
urls.push(...this.questionDelegate.getAdditionalDownloadableFiles(question, usageId));
|
urls.push(...this.questionDelegate.getAdditionalDownloadableFiles(question, usageId));
|
||||||
|
@ -572,15 +588,19 @@ export class CoreQuestionHelperProvider {
|
||||||
* @param questions The list of questions.
|
* @param questions The list of questions.
|
||||||
* @param answers The input data.
|
* @param answers The input data.
|
||||||
* @param offline True if data should be saved in offline.
|
* @param offline True if data should be saved in offline.
|
||||||
|
* @param component The component the question is related to.
|
||||||
|
* @param componentId Component ID.
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved with answers to send to server.
|
* @return Promise resolved with answers to send to server.
|
||||||
*/
|
*/
|
||||||
prepareAnswers(questions: any[], answers: any, offline?: boolean, siteId?: string): Promise<any> {
|
prepareAnswers(questions: any[], answers: any, offline: boolean, component: string, componentId: string | number,
|
||||||
|
siteId?: string): Promise<any> {
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
questions = questions || [];
|
questions = questions || [];
|
||||||
questions.forEach((question) => {
|
questions.forEach((question) => {
|
||||||
promises.push(this.questionDelegate.prepareAnswersForQuestion(question, answers, offline, siteId));
|
promises.push(this.questionDelegate.prepareAnswersForQuestion(question, answers, offline, component, componentId,
|
||||||
|
siteId));
|
||||||
});
|
});
|
||||||
|
|
||||||
return this.utils.allPromises(promises).then(() => {
|
return this.utils.allPromises(promises).then(() => {
|
||||||
|
|
|
@ -13,10 +13,13 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreFile } from '@providers/file';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
|
||||||
|
import { CoreTextUtils } from '@providers/utils/text';
|
||||||
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { makeSingleton } from '@singletons/core.singletons';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An object to represent a question state.
|
* An object to represent a question state.
|
||||||
|
@ -413,6 +416,35 @@ export class CoreQuestionProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Given a question and a componentId, return a componentId that is unique for the question.
|
||||||
|
*
|
||||||
|
* @param question Question.
|
||||||
|
* @param componentId Component ID.
|
||||||
|
* @return Question component ID.
|
||||||
|
*/
|
||||||
|
getQuestionComponentId(question: any, componentId: string | number): string {
|
||||||
|
return componentId + '_' + question.number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the path to the folder where to store files for an offline question.
|
||||||
|
*
|
||||||
|
* @param type Question type.
|
||||||
|
* @param component Component the question is related to.
|
||||||
|
* @param componentId Question component ID, returned by getQuestionComponentId.
|
||||||
|
* @param siteId Site ID. If not defined, current site.
|
||||||
|
* @return Folder path.
|
||||||
|
*/
|
||||||
|
getQuestionFolder(type: string, component: string, componentId: string, siteId?: string): string {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
const siteFolderPath = CoreFile.instance.getSiteFolder(siteId);
|
||||||
|
const questionFolderPath = 'offlinequestion/' + type + '/' + component + '/' + componentId;
|
||||||
|
|
||||||
|
return CoreTextUtils.instance.concatenatePaths(siteFolderPath, questionFolderPath);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Extract the question slot from a question name.
|
* Extract the question slot from a question name.
|
||||||
*
|
*
|
||||||
|
@ -612,3 +644,5 @@ export class CoreQuestionProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class CoreQuestion extends makeSingleton(CoreQuestionProvider) {}
|
||||||
|
|
Loading…
Reference in New Issue