diff --git a/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts b/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts index 27488cdde..919fabc41 100644 --- a/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts +++ b/src/addons/mod/quiz/accessrules/openclosedate/services/handlers/openclosedate.ts @@ -15,8 +15,9 @@ import { Injectable } from '@angular/core'; import { AddonModQuizAccessRuleHandler } from '@addons/mod/quiz/services/access-rules-delegate'; -import { AddonModQuizAttemptWSData, AddonModQuizProvider } from '@addons/mod/quiz/services/quiz'; +import { AddonModQuizAttemptWSData } from '@addons/mod/quiz/services/quiz'; import { makeSingleton } from '@singletons'; +import { ADDON_MOD_QUIZ_SHOW_TIME_BEFORE_DEADLINE } from '@addons/mod/quiz/constants'; /** * Handler to support open/close date access rule. @@ -50,8 +51,8 @@ export class AddonModQuizAccessOpenCloseDateHandlerService implements AddonModQu return false; } - // Show the time left only if it's less than QUIZ_SHOW_TIME_BEFORE_DEADLINE. - if (timeNow > endTime - AddonModQuizProvider.QUIZ_SHOW_TIME_BEFORE_DEADLINE) { + // Show the time left only if it's less than ADDON_MOD_QUIZ_SHOW_TIME_BEFORE_DEADLINE. + if (timeNow > endTime - ADDON_MOD_QUIZ_SHOW_TIME_BEFORE_DEADLINE) { return true; } diff --git a/src/addons/mod/quiz/components/index/index.ts b/src/addons/mod/quiz/components/index/index.ts index cef7514ad..e06ae0c69 100644 --- a/src/addons/mod/quiz/components/index/index.ts +++ b/src/addons/mod/quiz/components/index/index.ts @@ -36,7 +36,6 @@ import { AddonModQuizGetAttemptAccessInformationWSResponse, AddonModQuizGetQuizAccessInformationWSResponse, AddonModQuizGetUserBestGradeWSResponse, - AddonModQuizProvider, } from '../../services/quiz'; import { AddonModQuizAttempt, AddonModQuizHelper, AddonModQuizQuizData } from '../../services/quiz-helper'; import { @@ -45,6 +44,8 @@ import { AddonModQuizSyncProvider, AddonModQuizSyncResult, } from '../../services/quiz-sync'; +import { ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, ADDON_MOD_QUIZ_COMPONENT, AddonModQuizGradeMethods } from '../../constants'; +import { QuestionDisplayOptionsMarks } from '@features/question/constants'; /** * Component that displays a quiz entry page. @@ -56,7 +57,7 @@ import { }) export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComponent implements OnInit, OnDestroy { - component = AddonModQuizProvider.COMPONENT; + component = ADDON_MOD_QUIZ_COMPONENT; pluginName = 'quiz'; quiz?: AddonModQuizQuizData; // The quiz. now?: number; // Current time. @@ -110,7 +111,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp // Listen for attempt finished events. this.finishedObserver = CoreEvents.on( - AddonModQuizProvider.ATTEMPT_FINISHED_EVENT, + ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, (data) => { // Go to review attempt if an attempt in this quiz was finished and synced. if (this.quiz && data.quizId == this.quiz.id) { @@ -609,12 +610,12 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp // Calculate data to construct the header of the attempts table. AddonModQuizHelper.setQuizCalculatedData(quiz, this.options); - this.overallStats = !!lastFinished && this.options.alloptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX; + this.overallStats = !!lastFinished && this.options.alloptions.marks >= QuestionDisplayOptionsMarks.MARK_AND_MAX; // Calculate data to show for each attempt. const formattedAttempts = await Promise.all(attempts.map((attempt, index) => { // Highlight the highest grade if appropriate. - const shouldHighlight = this.overallStats && quiz.grademethod == AddonModQuizProvider.GRADEHIGHEST && + const shouldHighlight = this.overallStats && quiz.grademethod === AddonModQuizGradeMethods.HIGHEST_GRADE && attempts.length > 1; const isLast = index == attempts.length - 1; diff --git a/src/addons/mod/quiz/constants.ts b/src/addons/mod/quiz/constants.ts index dc11c9d2e..2f7e47674 100644 --- a/src/addons/mod/quiz/constants.ts +++ b/src/addons/mod/quiz/constants.ts @@ -12,4 +12,30 @@ // See the License for the specific language governing permissions and // limitations under the License. +export const ADDON_MOD_QUIZ_COMPONENT = 'mmaModQuiz'; + export const ADDON_MOD_QUIZ_FEATURE_NAME = 'CoreCourseModuleDelegate_AddonModQuiz'; + +export const ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT = 'addon_mod_quiz_attempt_finished'; + +export const ADDON_MOD_QUIZ_SHOW_TIME_BEFORE_DEADLINE = 3600; + +/** + * Possible grade methods for a quiz. + */ +export const enum AddonModQuizGradeMethods { + HIGHEST_GRADE = 1, + AVERAGE_GRADE = 2, + FIRST_ATTEMPT = 3, + LAST_ATTEMPT = 4, +} + +/** + * Possible states for an attempt. + */ +export const enum AddonModQuizAttemptStates { + IN_PROGRESS = 'inprogress', + OVERDUE = 'overdue', + FINISHED = 'finished', + ABANDONED = 'abandoned', +} diff --git a/src/addons/mod/quiz/pages/attempt/attempt.ts b/src/addons/mod/quiz/pages/attempt/attempt.ts index 71f6c87b5..3151a69ea 100644 --- a/src/addons/mod/quiz/pages/attempt/attempt.ts +++ b/src/addons/mod/quiz/pages/attempt/attempt.ts @@ -23,9 +23,9 @@ import { AddonModQuiz, AddonModQuizAttemptWSData, AddonModQuizGetQuizAccessInformationWSResponse, - AddonModQuizProvider, } from '../../services/quiz'; import { AddonModQuizAttempt, AddonModQuizHelper, AddonModQuizQuizData } from '../../services/quiz-helper'; +import { ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; /** * Page that displays some summary data about an attempt. @@ -39,7 +39,7 @@ export class AddonModQuizAttemptPage implements OnInit { courseId!: number; // The course ID the quiz belongs to. quiz?: AddonModQuizQuizData; // The quiz the attempt belongs to. attempt?: AddonModQuizAttempt; // The attempt to view. - component = AddonModQuizProvider.COMPONENT; // Component to link the files to. + component = ADDON_MOD_QUIZ_COMPONENT; // Component to link the files to. componentId?: number; // Component ID to use in conjunction with the component. loaded = false; // Whether data has been loaded. feedback?: string; // Attempt feedback. diff --git a/src/addons/mod/quiz/pages/player/player.ts b/src/addons/mod/quiz/pages/player/player.ts index 8cb628905..0e528f676 100644 --- a/src/addons/mod/quiz/pages/player/player.ts +++ b/src/addons/mod/quiz/pages/player/player.ts @@ -38,7 +38,6 @@ import { AddonModQuizAttemptWSData, AddonModQuizGetAttemptAccessInformationWSResponse, AddonModQuizGetQuizAccessInformationWSResponse, - AddonModQuizProvider, AddonModQuizQuizWSData, } from '../../services/quiz'; import { AddonModQuizAttempt, AddonModQuizHelper } from '../../services/quiz-helper'; @@ -50,6 +49,7 @@ import { CoreTime } from '@singletons/time'; import { CoreDirectivesRegistry } from '@singletons/directives-registry'; import { CoreWSError } from '@classes/errors/wserror'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, AddonModQuizAttemptStates, ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; /** * Page that allows attempting a quiz. @@ -68,7 +68,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { quiz?: AddonModQuizQuizWSData; // The quiz the attempt belongs to. attempt?: AddonModQuizAttempt; // The attempt being attempted. moduleUrl?: string; // URL to the module in the site. - component = AddonModQuizProvider.COMPONENT; // Component to link the files to. + component = ADDON_MOD_QUIZ_COMPONENT; // Component to link the files to. loaded = false; // Whether data has been loaded. quizAborted = false; // Whether the quiz was aborted due to an error. offline = false; // Whether the quiz is being attempted in offline mode. @@ -146,7 +146,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { if (this.quiz) { // Unblock the quiz so it can be synced. - CoreSync.unblockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id); + CoreSync.unblockOperation(ADDON_MOD_QUIZ_COMPONENT, this.quiz.id); } } @@ -263,7 +263,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { return; } - if (page != -1 && (this.attempt.state == AddonModQuizProvider.ATTEMPT_OVERDUE || this.attempt.finishedOffline)) { + if (page != -1 && (this.attempt.state === AddonModQuizAttemptStates.OVERDUE || this.attempt.finishedOffline)) { // We can't load a page if overdue or the local attempt is finished. return; } else if (page == this.attempt.currentpage && !this.showSummary && slot !== undefined) { @@ -341,7 +341,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { this.quiz = await AddonModQuiz.getQuiz(this.courseId, this.cmId); // Block the quiz so it cannot be synced. - CoreSync.blockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id); + CoreSync.blockOperation(ADDON_MOD_QUIZ_COMPONENT, this.quiz.id); // Wait for any ongoing sync to finish. We won't sync a quiz while it's being played. await AddonModQuizSync.waitForSync(this.quiz.id); @@ -408,7 +408,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { try { // Show confirm if the user clicked the finish button and the quiz is in progress. - if (!timeUp && this.attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { + if (!timeUp && this.attempt.state === AddonModQuizAttemptStates.IN_PROGRESS) { let message = Translate.instant('addon.mod_quiz.confirmclose'); const unansweredCount = this.summaryQuestions @@ -444,7 +444,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { await this.processAttempt(userFinish, timeUp); // Trigger an event to notify the attempt was finished. - CoreEvents.trigger(AddonModQuizProvider.ATTEMPT_FINISHED_EVENT, { + CoreEvents.trigger(ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, { quizId: this.quiz.id, attemptId: this.attempt.id, synced: !this.offline, @@ -679,7 +679,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { }); this.showSummary = true; - this.canReturn = this.attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS && !this.attempt.finishedOffline; + this.canReturn = this.attempt.state === AddonModQuizAttemptStates.IN_PROGRESS && !this.attempt.finishedOffline; this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions); this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt); @@ -904,7 +904,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { await this.loadNavigation(); - if (this.attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !this.attempt.finishedOffline) { + if (this.attempt.state !== AddonModQuizAttemptStates.OVERDUE && !this.attempt.finishedOffline) { // Attempt not overdue and not finished in offline, load page. await this.loadPage(this.attempt.currentpage ?? 0); diff --git a/src/addons/mod/quiz/pages/review/review.ts b/src/addons/mod/quiz/pages/review/review.ts index 188a756e6..a8b6ee23e 100644 --- a/src/addons/mod/quiz/pages/review/review.ts +++ b/src/addons/mod/quiz/pages/review/review.ts @@ -32,12 +32,13 @@ import { AddonModQuizAttemptWSData, AddonModQuizCombinedReviewOptions, AddonModQuizGetAttemptReviewResponse, - AddonModQuizProvider, AddonModQuizQuizWSData, AddonModQuizWSAdditionalData, } from '../../services/quiz'; import { AddonModQuizHelper } from '../../services/quiz-helper'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; +import { AddonModQuizAttemptStates, ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; +import { QuestionDisplayOptionsMarks } from '@features/question/constants'; /** * Page that allows reviewing a quiz attempt. @@ -52,7 +53,7 @@ export class AddonModQuizReviewPage implements OnInit { @ViewChild(IonContent) content?: IonContent; attempt?: AddonModQuizAttemptWSData; // The attempt being reviewed. - component = AddonModQuizProvider.COMPONENT; // Component to link the files to. + component = ADDON_MOD_QUIZ_COMPONENT; // Component to link the files to. showAll = false; // Whether to view all questions in the same page. numPages = 1; // Number of pages. showCompleted = false; // Whether to show completed time. @@ -265,7 +266,7 @@ export class AddonModQuizReviewPage implements OnInit { this.readableState = AddonModQuiz.getAttemptReadableStateName(this.attempt.state ?? ''); - if (this.attempt.state != AddonModQuizProvider.ATTEMPT_FINISHED) { + if (this.attempt.state !== AddonModQuizAttemptStates.FINISHED) { return; } @@ -299,7 +300,7 @@ export class AddonModQuizReviewPage implements OnInit { } // Treat grade. - if (this.options && this.options.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX && + if (this.options && this.options.someoptions.marks >= QuestionDisplayOptionsMarks.MARK_AND_MAX && AddonModQuiz.quizHasGrades(this.quiz)) { if (data.grade === null || data.grade === undefined) { diff --git a/src/addons/mod/quiz/quiz.module.ts b/src/addons/mod/quiz/quiz.module.ts index 3dbf6688e..e51dc8888 100644 --- a/src/addons/mod/quiz/quiz.module.ts +++ b/src/addons/mod/quiz/quiz.module.ts @@ -33,7 +33,7 @@ import { AddonModQuizPrefetchHandler } from './services/handlers/prefetch'; import { AddonModQuizPushClickHandler } from './services/handlers/push-click'; import { AddonModQuizReviewLinkHandler } from './services/handlers/review-link'; import { AddonModQuizSyncCronHandler } from './services/handlers/sync-cron'; -import { AddonModQuizProvider } from './services/quiz'; +import { ADDON_MOD_QUIZ_COMPONENT } from './constants'; /** * Get mod Quiz services. @@ -98,7 +98,7 @@ const routes: Routes = [ CorePushNotificationsDelegate.registerClickHandler(AddonModQuizPushClickHandler.instance); CoreCronDelegate.register(AddonModQuizSyncCronHandler.instance); - CoreCourseHelper.registerModuleReminderClick(AddonModQuizProvider.COMPONENT); + CoreCourseHelper.registerModuleReminderClick(ADDON_MOD_QUIZ_COMPONENT); }, }, ], diff --git a/src/addons/mod/quiz/services/handlers/prefetch.ts b/src/addons/mod/quiz/services/handlers/prefetch.ts index 05c470835..49f6a0100 100644 --- a/src/addons/mod/quiz/services/handlers/prefetch.ts +++ b/src/addons/mod/quiz/services/handlers/prefetch.ts @@ -32,11 +32,11 @@ import { AddonModQuiz, AddonModQuizAttemptWSData, AddonModQuizGetQuizAccessInformationWSResponse, - AddonModQuizProvider, AddonModQuizQuizWSData, } from '../quiz'; import { AddonModQuizHelper } from '../quiz-helper'; import { AddonModQuizSync, AddonModQuizSyncResult } from '../quiz-sync'; +import { AddonModQuizAttemptStates, ADDON_MOD_QUIZ_COMPONENT } from '../../constants'; /** * Handler to prefetch quizzes. @@ -46,7 +46,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet name = 'AddonModQuiz'; modName = 'quiz'; - component = AddonModQuizProvider.COMPONENT; + component = ADDON_MOD_QUIZ_COMPONENT; updatesNames = /^configuration$|^.*files$|^grades$|^gradeitems$|^questions$|^attempts$/; /** @@ -321,7 +321,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet AddonModQuiz.getUserAttempts(quiz.id, modOptions), AddonModQuiz.getAttemptAccessInformation(quiz.id, 0, modOptions), AddonModQuiz.getQuizRequiredQtypes(quiz.id, modOptions), - CoreFilepool.addFilesToQueue(siteId, introFiles, AddonModQuizProvider.COMPONENT, module.id), + CoreFilepool.addFilesToQueue(siteId, introFiles, ADDON_MOD_QUIZ_COMPONENT, module.id), ]); // Check if we need to start a new attempt. @@ -353,17 +353,17 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet const attemptFiles = await this.getAttemptsFeedbackFiles(quiz, attempts, siteId); - return CoreFilepool.addFilesToQueue(siteId, attemptFiles, AddonModQuizProvider.COMPONENT, module.id); + return CoreFilepool.addFilesToQueue(siteId, attemptFiles, ADDON_MOD_QUIZ_COMPONENT, module.id); })); // Update the download time to prevent detecting the new attempt as an update. promises.push(CoreUtils.ignoreErrors( - CoreFilepool.updatePackageDownloadTime(siteId, AddonModQuizProvider.COMPONENT, module.id), + CoreFilepool.updatePackageDownloadTime(siteId, ADDON_MOD_QUIZ_COMPONENT, module.id), )); } else { // Use the already fetched attempts. promises.push(this.getAttemptsFeedbackFiles(quiz, attempts, siteId).then((attemptFiles) => - CoreFilepool.addFilesToQueue(siteId, attemptFiles, AddonModQuizProvider.COMPONENT, module.id))); + CoreFilepool.addFilesToQueue(siteId, attemptFiles, ADDON_MOD_QUIZ_COMPONENT, module.id))); } // Fetch attempt related data. @@ -444,7 +444,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet promises.push(AddonModQuiz.getAttemptAccessInformation(quiz.id, attempt.id, modOptions)); promises.push(AddonModQuiz.getAttemptSummary(attempt.id, preflightData, modOptions)); - if (attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { + if (attempt.state === AddonModQuizAttemptStates.IN_PROGRESS) { // Get data for each page. promises = promises.concat(pages.map(async (page) => { if (isSequential && typeof attempt.currentpage === 'number' && page < attempt.currentpage) { diff --git a/src/addons/mod/quiz/services/quiz-helper.ts b/src/addons/mod/quiz/services/quiz-helper.ts index cba2dcf1f..588c29d68 100644 --- a/src/addons/mod/quiz/services/quiz-helper.ts +++ b/src/addons/mod/quiz/services/quiz-helper.ts @@ -30,10 +30,11 @@ import { AddonModQuizAttemptWSData, AddonModQuizCombinedReviewOptions, AddonModQuizGetQuizAccessInformationWSResponse, - AddonModQuizProvider, AddonModQuizQuizWSData, } from './quiz'; import { AddonModQuizOffline } from './quiz-offline'; +import { AddonModQuizAttemptStates } from '../constants'; +import { QuestionDisplayOptionsMarks } from '@features/question/constants'; /** * Helper service that provides some features for quiz. @@ -291,7 +292,7 @@ export class AddonModQuizHelperProvider { // Highlight the highest grade if appropriate. formattedAttempt.highlightGrade = !!(highlight && !attempt.preview && - attempt.state == AddonModQuizProvider.ATTEMPT_FINISHED && formattedAttempt.readableGrade == bestGrade); + attempt.state === AddonModQuizAttemptStates.FINISHED && formattedAttempt.readableGrade == bestGrade); } else { formattedAttempt.readableGrade = ''; } @@ -317,7 +318,7 @@ export class AddonModQuizHelperProvider { formattedQuiz.gradeFormatted = AddonModQuiz.formatGrade(quiz.grade, quiz.decimalpoints); formattedQuiz.showAttemptColumn = quiz.attempts != 1; - formattedQuiz.showGradeColumn = options.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX && + formattedQuiz.showGradeColumn = options.someoptions.marks >= QuestionDisplayOptionsMarks.MARK_AND_MAX && AddonModQuiz.quizHasGrades(quiz); formattedQuiz.showMarkColumn = formattedQuiz.showGradeColumn && quiz.grade != quiz.sumgrades; formattedQuiz.showFeedbackColumn = !!quiz.hasfeedback && !!options.alloptions.overallfeedback; @@ -356,7 +357,7 @@ export class AddonModQuizHelperProvider { try { if (attempt) { - if (attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !attempt.finishedOffline) { + if (attempt.state !== AddonModQuizAttemptStates.OVERDUE && !attempt.finishedOffline) { // We're continuing an attempt. Call getAttemptData to validate the preflight data. await AddonModQuiz.getAttemptData(attempt.id, attempt.currentpage ?? 0, preflightData, modOptions); diff --git a/src/addons/mod/quiz/services/quiz-offline.ts b/src/addons/mod/quiz/services/quiz-offline.ts index 15663f3d0..94354859b 100644 --- a/src/addons/mod/quiz/services/quiz-offline.ts +++ b/src/addons/mod/quiz/services/quiz-offline.ts @@ -23,7 +23,8 @@ import { CoreUtils } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { AddonModQuizAttemptDBRecord, ATTEMPTS_TABLE_NAME } from './database/quiz'; -import { AddonModQuizAttemptWSData, AddonModQuizProvider, AddonModQuizQuizWSData } from './quiz'; +import { AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from './quiz'; +import { ADDON_MOD_QUIZ_COMPONENT } from '../constants'; /** * Service to handle offline quiz. @@ -103,7 +104,7 @@ export class AddonModQuizOfflineProvider { * @returns Promise resolved with the answers. */ getAttemptAnswers(attemptId: number, siteId?: string): Promise { - return CoreQuestion.getAttemptAnswers(AddonModQuizProvider.COMPONENT, attemptId, siteId); + return CoreQuestion.getAttemptAnswers(ADDON_MOD_QUIZ_COMPONENT, attemptId, siteId); } /** @@ -149,7 +150,7 @@ export class AddonModQuizOfflineProvider { await Promise.all(questions.map(async (question) => { const dbQuestion = await CoreUtils.ignoreErrors( - CoreQuestion.getQuestion(AddonModQuizProvider.COMPONENT, attemptId, question.slot, siteId), + CoreQuestion.getQuestion(ADDON_MOD_QUIZ_COMPONENT, attemptId, question.slot, siteId), ); if (!dbQuestion) { @@ -230,8 +231,8 @@ export class AddonModQuizOfflineProvider { const db = await CoreSites.getSiteDb(siteId); await Promise.all([ - CoreQuestion.removeAttemptAnswers(AddonModQuizProvider.COMPONENT, attemptId, siteId), - CoreQuestion.removeAttemptQuestions(AddonModQuizProvider.COMPONENT, attemptId, siteId), + CoreQuestion.removeAttemptAnswers(ADDON_MOD_QUIZ_COMPONENT, attemptId, siteId), + CoreQuestion.removeAttemptQuestions(ADDON_MOD_QUIZ_COMPONENT, attemptId, siteId), db.deleteRecords(ATTEMPTS_TABLE_NAME, { id: attemptId }), ]); } @@ -248,8 +249,8 @@ export class AddonModQuizOfflineProvider { siteId = siteId || CoreSites.getCurrentSiteId(); await Promise.all([ - CoreQuestion.removeQuestion(AddonModQuizProvider.COMPONENT, attemptId, slot, siteId), - CoreQuestion.removeQuestionAnswers(AddonModQuizProvider.COMPONENT, attemptId, slot, siteId), + CoreQuestion.removeQuestion(ADDON_MOD_QUIZ_COMPONENT, attemptId, slot, siteId), + CoreQuestion.removeQuestionAnswers(ADDON_MOD_QUIZ_COMPONENT, attemptId, slot, siteId), ]); } @@ -299,7 +300,7 @@ export class AddonModQuizOfflineProvider { const state = await CoreQuestionBehaviourDelegate.determineNewState( quiz.preferredbehaviour ?? '', - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, attempt.id, question, quiz.coursemodule, @@ -312,12 +313,12 @@ export class AddonModQuizOfflineProvider { } // Delete previously stored answers for this question. - await CoreQuestion.removeQuestionAnswers(AddonModQuizProvider.COMPONENT, attempt.id, question.slot, siteId); + await CoreQuestion.removeQuestionAnswers(ADDON_MOD_QUIZ_COMPONENT, attempt.id, question.slot, siteId); })); // Now save the answers. await CoreQuestion.saveAnswers( - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quiz.id, attempt.id, attempt.userid ?? CoreSites.getCurrentSiteUserId(), @@ -332,7 +333,7 @@ export class AddonModQuizOfflineProvider { const question = questionsWithAnswers[Number(slot)]; await CoreQuestion.saveQuestion( - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quiz.id, attempt.id, attempt.userid ?? CoreSites.getCurrentSiteUserId(), diff --git a/src/addons/mod/quiz/services/quiz-sync.ts b/src/addons/mod/quiz/services/quiz-sync.ts index 1dd59d96c..66fc6da13 100644 --- a/src/addons/mod/quiz/services/quiz-sync.ts +++ b/src/addons/mod/quiz/services/quiz-sync.ts @@ -29,8 +29,9 @@ import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; import { AddonModQuizAttemptDBRecord } from './database/quiz'; import { AddonModQuizPrefetchHandler } from './handlers/prefetch'; -import { AddonModQuiz, AddonModQuizAttemptWSData, AddonModQuizProvider, AddonModQuizQuizWSData } from './quiz'; +import { AddonModQuiz, AddonModQuizAttemptWSData, AddonModQuizQuizWSData } from './quiz'; import { AddonModQuizOffline, AddonModQuizQuestionsWithAnswers } from './quiz-offline'; +import { ADDON_MOD_QUIZ_COMPONENT } from '../constants'; /** * Service to sync quizzes. @@ -79,7 +80,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider for (const slot in options.onlineQuestions) { promises.push(CoreQuestionDelegate.deleteOfflineData( options.onlineQuestions[slot], - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quiz.coursemodule, siteId, )); @@ -204,7 +205,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider } quizIds[attempt.quizid] = true; - if (CoreSync.isBlocked(AddonModQuizProvider.COMPONENT, attempt.quizid, siteId)) { + if (CoreSync.isBlocked(ADDON_MOD_QUIZ_COMPONENT, attempt.quizid, siteId)) { return; } @@ -268,7 +269,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider } // Verify that quiz isn't blocked. - if (CoreSync.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) { + if (CoreSync.isBlocked(ADDON_MOD_QUIZ_COMPONENT, quiz.id, siteId)) { this.logger.debug('Cannot sync quiz ' + quiz.id + ' because it is blocked.'); throw new CoreError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate })); @@ -300,7 +301,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider // Sync offline logs. await CoreUtils.ignoreErrors( - CoreCourseLogHelper.syncActivity(AddonModQuizProvider.COMPONENT, quiz.id, siteId), + CoreCourseLogHelper.syncActivity(ADDON_MOD_QUIZ_COMPONENT, quiz.id, siteId), ); // Get all the offline attempts for the quiz. It should always be 0 or 1 attempt @@ -381,7 +382,7 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider await CoreQuestionDelegate.prepareSyncData( onlineQuestion, offlineQuestions[slot].answers, - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quiz.coursemodule, siteId, ); diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts index d8edc9157..10404764e 100644 --- a/src/addons/mod/quiz/services/quiz.ts +++ b/src/addons/mod/quiz/services/quiz.ts @@ -42,8 +42,12 @@ import { AddonModQuizOffline, AddonModQuizQuestionsWithAnswers } from './quiz-of import { AddonModQuizAutoSyncData, AddonModQuizSyncProvider } from './quiz-sync'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; import { QUESTION_INVALID_STATE_CLASSES, QUESTION_TODO_STATE_CLASSES } from '@features/question/constants'; - -const ROOT_CACHE_KEY = 'mmaModQuiz:'; +import { + ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT, + AddonModQuizAttemptStates, + ADDON_MOD_QUIZ_COMPONENT, + AddonModQuizGradeMethods, +} from '../constants'; declare module '@singletons/events' { @@ -53,7 +57,7 @@ declare module '@singletons/events' { * @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation */ export interface CoreEventsData { - [AddonModQuizProvider.ATTEMPT_FINISHED_EVENT]: AddonModQuizAttemptFinishedData; + [ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT]: AddonModQuizAttemptFinishedData; [AddonModQuizSyncProvider.AUTO_SYNCED]: AddonModQuizAutoSyncData; } @@ -65,27 +69,7 @@ declare module '@singletons/events' { @Injectable({ providedIn: 'root' }) export class AddonModQuizProvider { - static readonly COMPONENT = 'mmaModQuiz'; - static readonly ATTEMPT_FINISHED_EVENT = 'addon_mod_quiz_attempt_finished'; - - // Grade methods. - static readonly GRADEHIGHEST = 1; - static readonly GRADEAVERAGE = 2; - static readonly ATTEMPTFIRST = 3; - static readonly ATTEMPTLAST = 4; - - // Question options. - static readonly QUESTION_OPTIONS_MAX_ONLY = 1; - static readonly QUESTION_OPTIONS_MARK_AND_MAX = 2; - - // Attempt state. - static readonly ATTEMPT_IN_PROGRESS = 'inprogress'; - static readonly ATTEMPT_OVERDUE = 'overdue'; - static readonly ATTEMPT_FINISHED = 'finished'; - static readonly ATTEMPT_ABANDONED = 'abandoned'; - - // Show the countdown timer if there is less than this amount of time left before the the quiz close date. - static readonly QUIZ_SHOW_TIME_BEFORE_DEADLINE = 3600; + protected static readonly ROOT_CACHE_KEY = 'mmaModQuiz:'; protected logger: CoreLogger; @@ -164,7 +148,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getAttemptAccessInformationCommonCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'attemptAccessInformation:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'attemptAccessInformation:' + quizId; } /** @@ -189,7 +173,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getAttemptAccessInformationCacheKey(quizId, attemptId), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -215,7 +199,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getAttemptDataCommonCacheKey(attemptId: number): string { - return ROOT_CACHE_KEY + 'attemptData:' + attemptId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'attemptData:' + attemptId; } /** @@ -248,7 +232,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getAttemptDataCacheKey(attemptId, page), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -288,10 +272,10 @@ export class AddonModQuizProvider { } switch (attempt.state) { - case AddonModQuizProvider.ATTEMPT_IN_PROGRESS: + case AddonModQuizAttemptStates.IN_PROGRESS: return dueDate * 1000; - case AddonModQuizProvider.ATTEMPT_OVERDUE: + case AddonModQuizAttemptStates.OVERDUE: return (dueDate + (quiz.graceperiod ?? 0)) * 1000; default: @@ -311,7 +295,7 @@ export class AddonModQuizProvider { getAttemptDueDateWarning(quiz: AddonModQuizQuizWSData, attempt: AddonModQuizAttemptWSData): string | undefined { const dueDate = this.getAttemptDueDate(quiz, attempt); - if (attempt.state === AddonModQuizProvider.ATTEMPT_OVERDUE) { + if (attempt.state === AddonModQuizAttemptStates.OVERDUE) { return Translate.instant( 'addon.mod_quiz.overduemustbesubmittedby', { $a: CoreTimeUtils.userDate(dueDate) }, @@ -334,10 +318,10 @@ export class AddonModQuizProvider { } switch (attempt.state) { - case AddonModQuizProvider.ATTEMPT_IN_PROGRESS: + case AddonModQuizAttemptStates.IN_PROGRESS: return [Translate.instant('addon.mod_quiz.stateinprogress')]; - case AddonModQuizProvider.ATTEMPT_OVERDUE: { + case AddonModQuizAttemptStates.OVERDUE: { const sentences: string[] = []; const dueDate = this.getAttemptDueDate(quiz, attempt); @@ -353,7 +337,7 @@ export class AddonModQuizProvider { return sentences; } - case AddonModQuizProvider.ATTEMPT_FINISHED: + case AddonModQuizAttemptStates.FINISHED: return [ Translate.instant('addon.mod_quiz.statefinished'), Translate.instant( @@ -362,7 +346,7 @@ export class AddonModQuizProvider { ), ]; - case AddonModQuizProvider.ATTEMPT_ABANDONED: + case AddonModQuizAttemptStates.ABANDONED: return [Translate.instant('addon.mod_quiz.stateabandoned')]; default: @@ -378,16 +362,16 @@ export class AddonModQuizProvider { */ getAttemptReadableStateName(state: string): string { switch (state) { - case AddonModQuizProvider.ATTEMPT_IN_PROGRESS: + case AddonModQuizAttemptStates.IN_PROGRESS: return Translate.instant('addon.mod_quiz.stateinprogress'); - case AddonModQuizProvider.ATTEMPT_OVERDUE: + case AddonModQuizAttemptStates.OVERDUE: return Translate.instant('addon.mod_quiz.stateoverdue'); - case AddonModQuizProvider.ATTEMPT_FINISHED: + case AddonModQuizAttemptStates.FINISHED: return Translate.instant('addon.mod_quiz.statefinished'); - case AddonModQuizProvider.ATTEMPT_ABANDONED: + case AddonModQuizAttemptStates.ABANDONED: return Translate.instant('addon.mod_quiz.stateabandoned'); default: @@ -413,7 +397,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getAttemptReviewCommonCacheKey(attemptId: number): string { - return ROOT_CACHE_KEY + 'attemptReview:' + attemptId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'attemptReview:' + attemptId; } /** @@ -438,7 +422,7 @@ export class AddonModQuizProvider { const preSets = { cacheKey: this.getAttemptReviewCacheKey(attemptId, page), cacheErrors: ['noreview'], - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -457,7 +441,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getAttemptSummaryCacheKey(attemptId: number): string { - return ROOT_CACHE_KEY + 'attemptSummary:' + attemptId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'attemptSummary:' + attemptId; } /** @@ -487,7 +471,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getAttemptSummaryCacheKey(attemptId), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -521,7 +505,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getCombinedReviewOptionsCommonCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'combinedReviewOptions:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'combinedReviewOptions:' + quizId; } /** @@ -544,7 +528,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getCombinedReviewOptionsCacheKey(quizId, userId), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -581,7 +565,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getFeedbackForGradeCommonCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'feedbackForGrade:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'feedbackForGrade:' + quizId; } /** @@ -606,7 +590,7 @@ export class AddonModQuizProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getFeedbackForGradeCacheKey(quizId, grade), updateFrequency: CoreSite.FREQUENCY_RARELY, - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -719,7 +703,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getQuizDataCacheKey(courseId: number): string { - return ROOT_CACHE_KEY + 'quiz:' + courseId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'quiz:' + courseId; } /** @@ -746,7 +730,7 @@ export class AddonModQuizProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getQuizDataCacheKey(courseId), updateFrequency: CoreSite.FREQUENCY_RARELY, - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -797,7 +781,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getQuizAccessInformationCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'quizAccessInformation:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'quizAccessInformation:' + quizId; } /** @@ -818,7 +802,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getQuizAccessInformationCacheKey(quizId), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -842,13 +826,13 @@ export class AddonModQuizProvider { } switch (method) { - case AddonModQuizProvider.GRADEHIGHEST: + case AddonModQuizGradeMethods.HIGHEST_GRADE: return Translate.instant('addon.mod_quiz.gradehighest'); - case AddonModQuizProvider.GRADEAVERAGE: + case AddonModQuizGradeMethods.AVERAGE_GRADE: return Translate.instant('addon.mod_quiz.gradeaverage'); - case AddonModQuizProvider.ATTEMPTFIRST: + case AddonModQuizGradeMethods.FIRST_ATTEMPT: return Translate.instant('addon.mod_quiz.attemptfirst'); - case AddonModQuizProvider.ATTEMPTLAST: + case AddonModQuizGradeMethods.LAST_ATTEMPT: return Translate.instant('addon.mod_quiz.attemptlast'); default: return ''; @@ -862,7 +846,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getQuizRequiredQtypesCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'quizRequiredQtypes:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'quizRequiredQtypes:' + quizId; } /** @@ -881,7 +865,7 @@ export class AddonModQuizProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getQuizRequiredQtypesCacheKey(quizId), updateFrequency: CoreSite.FREQUENCY_SOMETIMES, - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1015,7 +999,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getUserAttemptsCommonCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'userAttempts:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'userAttempts:' + quizId; } /** @@ -1045,7 +1029,7 @@ export class AddonModQuizProvider { const preSets: CoreSiteWSPreSets = { cacheKey: this.getUserAttemptsCacheKey(quizId, userId), updateFrequency: CoreSite.FREQUENCY_SOMETIMES, - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1073,7 +1057,7 @@ export class AddonModQuizProvider { * @returns Cache key. */ protected getUserBestGradeCommonCacheKey(quizId: number): string { - return ROOT_CACHE_KEY + 'userBestGrade:' + quizId; + return AddonModQuizProvider.ROOT_CACHE_KEY + 'userBestGrade:' + quizId; } /** @@ -1093,7 +1077,7 @@ export class AddonModQuizProvider { }; const preSets: CoreSiteWSPreSets = { cacheKey: this.getUserBestGradeCacheKey(quizId, userId), - component: AddonModQuizProvider.COMPONENT, + component: ADDON_MOD_QUIZ_COMPONENT, componentId: options.cmId, ...CoreSites.getReadingStrategyPreSets(options.readingStrategy), // Include reading strategy preSets. }; @@ -1430,7 +1414,7 @@ export class AddonModQuizProvider { * @returns Whether it's finished. */ isAttemptFinished(state?: string): boolean { - return state == AddonModQuizProvider.ATTEMPT_FINISHED || state == AddonModQuizProvider.ATTEMPT_ABANDONED; + return state === AddonModQuizAttemptStates.FINISHED || state === AddonModQuizAttemptStates.ABANDONED; } /** @@ -1461,7 +1445,7 @@ export class AddonModQuizProvider { * @returns Whether it's nearly over or over. */ isAttemptTimeNearlyOver(quiz: AddonModQuizQuizWSData, attempt: AddonModQuizAttemptWSData): boolean { - if (attempt.state != AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { + if (attempt.state !== AddonModQuizAttemptStates.IN_PROGRESS) { // Attempt not in progress, return true. return true; } @@ -1600,7 +1584,7 @@ export class AddonModQuizProvider { return CoreCourseLogHelper.log( 'mod_quiz_view_attempt_review', params, - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quizId, siteId, ); @@ -1633,7 +1617,7 @@ export class AddonModQuizProvider { return CoreCourseLogHelper.log( 'mod_quiz_view_attempt_summary', params, - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, quizId, siteId, ); @@ -1654,7 +1638,7 @@ export class AddonModQuizProvider { return CoreCourseLogHelper.log( 'mod_quiz_view_quiz', params, - AddonModQuizProvider.COMPONENT, + ADDON_MOD_QUIZ_COMPONENT, id, siteId, ); @@ -1897,7 +1881,7 @@ export class AddonModQuizProvider { shouldShowTimeLeft(rules: string[], attempt: AddonModQuizAttemptWSData, endTime: number): boolean { const timeNow = CoreTimeUtils.timestamp(); - if (attempt.state != AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { + if (attempt.state !== AddonModQuizAttemptStates.IN_PROGRESS) { return false; } @@ -2392,7 +2376,7 @@ export type AddonModQuizViewQuizWSParams = { }; /** - * Data passed to ATTEMPT_FINISHED_EVENT event. + * Data passed to ADDON_MOD_QUIZ_ATTEMPT_FINISHED_EVENT event. */ export type AddonModQuizAttemptFinishedData = { quizId: number; diff --git a/src/core/features/question/constants.ts b/src/core/features/question/constants.ts index 21b7e6768..961fb13d1 100644 --- a/src/core/features/question/constants.ts +++ b/src/core/features/question/constants.ts @@ -19,3 +19,8 @@ export const QUESTION_NEEDS_GRADING_STATE_CLASSES = ['requiresgrading', 'complet export const QUESTION_FINISHED_STATE_CLASSES = ['complete'] as const; export const QUESTION_GAVE_UP_STATE_CLASSES = ['notanswered'] as const; export const QUESTION_GRADED_STATE_CLASSES = ['complete', 'incorrect', 'partiallycorrect', 'correct'] as const; + +export const enum QuestionDisplayOptionsMarks { + MAX_ONLY = 1, + MARK_AND_MAX = 2, +}