MOBILE-4263 quiz: Remove non-null assertions
This commit is contained in:
		
							parent
							
								
									4792e47737
								
							
						
					
					
						commit
						9744eb5137
					
				| @ -333,15 +333,16 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const bestGrade = this.bestGrade.grade; | ||||
|         const formattedGradebookGrade = AddonModQuiz.formatGrade(this.gradebookData.grade, quiz.decimalpoints); | ||||
|         const formattedBestGrade = AddonModQuiz.formatGrade(this.bestGrade.grade, quiz.decimalpoints); | ||||
|         const formattedBestGrade = AddonModQuiz.formatGrade(bestGrade, quiz.decimalpoints); | ||||
|         let gradeToShow = formattedGradebookGrade; // By default we show the grade in the gradebook.
 | ||||
| 
 | ||||
|         this.showResults = true; | ||||
|         this.gradeOverridden = formattedGradebookGrade != formattedBestGrade; | ||||
|         this.gradebookFeedback = this.gradebookData.feedback; | ||||
| 
 | ||||
|         if (this.bestGrade.grade! > this.gradebookData.grade && this.gradebookData.grade == quiz.grade) { | ||||
|         if (bestGrade && bestGrade > this.gradebookData.grade && this.gradebookData.grade == quiz.grade) { | ||||
|             // The best grade is higher than the max grade for the quiz.
 | ||||
|             // We'll do like Moodle web and show the best grade instead of the gradebook grade.
 | ||||
|             this.gradeOverridden = false; | ||||
| @ -556,8 +557,16 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|      * | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected sync(): Promise<AddonModQuizSyncResult> { | ||||
|         return AddonModQuizSync.syncQuiz(this.candidateQuiz!, true); | ||||
|     protected async sync(): Promise<AddonModQuizSyncResult> { | ||||
|         if (!this.candidateQuiz) { | ||||
|             return { | ||||
|                 warnings: [], | ||||
|                 attemptFinished: false, | ||||
|                 updated: false, | ||||
|             }; | ||||
|         } | ||||
| 
 | ||||
|         return AddonModQuizSync.syncQuiz(this.candidateQuiz, true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -579,36 +588,31 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp | ||||
|         } | ||||
| 
 | ||||
|         const lastFinished = AddonModQuiz.getLastFinishedAttemptFromList(attempts); | ||||
|         const promises: Promise<unknown>[] = []; | ||||
|         let openReview = false; | ||||
| 
 | ||||
|         if (this.autoReview && lastFinished && lastFinished.id >= this.autoReview.attemptId) { | ||||
|             // User just finished an attempt in offline and it seems it's been synced, since it's finished in online.
 | ||||
|             // Go to the review of this attempt if the user hasn't left this view.
 | ||||
|             if (!this.isDestroyed && this.isCurrentView) { | ||||
|                 promises.push(this.goToAutoReview()); | ||||
|                 openReview = true; | ||||
|             } | ||||
|             this.autoReview = undefined; | ||||
|         } | ||||
| 
 | ||||
|         // Get combined review options.
 | ||||
|         promises.push(AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }).then((options) => { | ||||
|             this.options = options; | ||||
| 
 | ||||
|             return; | ||||
|         })); | ||||
| 
 | ||||
|         // Get best grade.
 | ||||
|         promises.push(this.getQuizGrade()); | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|         const [options] = await Promise.all([ | ||||
|             AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }), | ||||
|             this.getQuizGrade(), | ||||
|             openReview ? this.goToAutoReview() : undefined, | ||||
|         ]); | ||||
| 
 | ||||
|         this.options = options; | ||||
|         const grade = this.gradebookData?.grade !== undefined ? this.gradebookData.grade : this.bestGrade?.grade; | ||||
|         const quizGrade = AddonModQuiz.formatGrade(grade, quiz.decimalpoints); | ||||
| 
 | ||||
|         // Calculate data to construct the header of the attempts table.
 | ||||
|         AddonModQuizHelper.setQuizCalculatedData(quiz, this.options!); | ||||
|         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 >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX; | ||||
| 
 | ||||
|         // Calculate data to show for each attempt.
 | ||||
|         const formattedAttempts = await Promise.all(attempts.map((attempt, index) => { | ||||
|  | ||||
| @ -61,6 +61,6 @@ export type AddonModQuizNavigationQuestion = CoreQuestionQuestionParsed & { | ||||
| }; | ||||
| 
 | ||||
| export type AddonModQuizNavigationModalReturn = { | ||||
|     page?: number; | ||||
|     page: number; | ||||
|     slot?: number; | ||||
| }; | ||||
|  | ||||
| @ -66,11 +66,13 @@ export class AddonModQuizPreflightModalComponent implements OnInit { | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             const quiz = this.quiz; | ||||
| 
 | ||||
|             await Promise.all(this.rules.map(async (rule) => { | ||||
|                 // Check if preflight is required for rule and, if so, get the component to render it.
 | ||||
|                 const required = await AddonModQuizAccessRuleDelegate.isPreflightCheckRequiredForRule( | ||||
|                     rule, | ||||
|                     this.quiz!, | ||||
|                     quiz, | ||||
|                     this.attempt, | ||||
|                     this.prefetch, | ||||
|                     this.siteId, | ||||
|  | ||||
| @ -94,20 +94,20 @@ export class AddonModQuizAttemptPage implements OnInit { | ||||
|             // Load attempt data.
 | ||||
|             const [options, accessInfo, attempt] = await Promise.all([ | ||||
|                 AddonModQuiz.getCombinedReviewOptions(this.quiz.id, { cmId: this.quiz.coursemodule }), | ||||
|                 this.fetchAccessInfo(), | ||||
|                 this.fetchAttempt(), | ||||
|                 this.fetchAccessInfo(this.quiz), | ||||
|                 this.fetchAttempt(this.quiz.id), | ||||
|             ]); | ||||
| 
 | ||||
|             // Set calculated data.
 | ||||
|             this.showReviewColumn = accessInfo.canreviewmyattempts; | ||||
|             AddonModQuizHelper.setQuizCalculatedData(this.quiz, options); | ||||
| 
 | ||||
|             this.attempt = await AddonModQuizHelper.setAttemptCalculatedData(this.quiz!, attempt, false, undefined, true); | ||||
|             this.attempt = await AddonModQuizHelper.setAttemptCalculatedData(this.quiz, attempt, false, undefined, true); | ||||
| 
 | ||||
|             // Check if the feedback should be displayed.
 | ||||
|             const grade = Number(this.attempt!.rescaledGrade); | ||||
|             const grade = Number(this.attempt.rescaledGrade); | ||||
| 
 | ||||
|             if (this.quiz.showFeedbackColumn && AddonModQuiz.isAttemptFinished(this.attempt!.state) && | ||||
|             if (this.quiz.showFeedbackColumn && AddonModQuiz.isAttemptFinished(this.attempt.state) && | ||||
|                     options.someoptions.overallfeedback && !isNaN(grade)) { | ||||
| 
 | ||||
|                 // Feedback should be displayed, get the feedback for the grade.
 | ||||
| @ -127,11 +127,12 @@ export class AddonModQuizAttemptPage implements OnInit { | ||||
|     /** | ||||
|      * Get the attempt. | ||||
|      * | ||||
|      * @param quizId Quiz ID. | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchAttempt(): Promise<AddonModQuizAttemptWSData> { | ||||
|     protected async fetchAttempt(quizId: number): Promise<AddonModQuizAttemptWSData> { | ||||
|         // Get all the attempts and search the one we want.
 | ||||
|         const attempts = await AddonModQuiz.getUserAttempts(this.quiz!.id, { cmId: this.cmId }); | ||||
|         const attempts = await AddonModQuiz.getUserAttempts(quizId, { cmId: this.cmId }); | ||||
| 
 | ||||
|         const attempt = attempts.find(attempt => attempt.id == this.attemptId); | ||||
| 
 | ||||
| @ -148,10 +149,11 @@ export class AddonModQuizAttemptPage implements OnInit { | ||||
|     /** | ||||
|      * Get the access info. | ||||
|      * | ||||
|      * @param quiz Quiz instance. | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async fetchAccessInfo(): Promise<AddonModQuizGetQuizAccessInformationWSResponse> { | ||||
|         const accessInfo = await AddonModQuiz.getQuizAccessInformation(this.quiz!.id, { cmId: this.cmId }); | ||||
|     protected async fetchAccessInfo(quiz: AddonModQuizQuizData): Promise<AddonModQuizGetQuizAccessInformationWSResponse> { | ||||
|         const accessInfo = await AddonModQuiz.getQuizAccessInformation(quiz.id, { cmId: this.cmId }); | ||||
| 
 | ||||
|         if (!accessInfo.canreviewmyattempts) { | ||||
|             return accessInfo; | ||||
| @ -161,7 +163,7 @@ export class AddonModQuizAttemptPage implements OnInit { | ||||
|         await CoreUtils.ignoreErrors(AddonModQuiz.invalidateAttemptReviewForPage(this.attemptId, -1)); | ||||
| 
 | ||||
|         try { | ||||
|             await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz!.coursemodule }); | ||||
|             await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: quiz.coursemodule }); | ||||
|         } catch { | ||||
|             // Error getting the review, assume the user cannot review the attempt.
 | ||||
|             accessInfo.canreviewmyattempts = false; | ||||
| @ -202,7 +204,11 @@ export class AddonModQuizAttemptPage implements OnInit { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     async reviewAttempt(): Promise<void> { | ||||
|         CoreNavigator.navigate(`../../review/${this.attempt!.id}`); | ||||
|         if (!this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         CoreNavigator.navigate(`../../review/${this.attempt.id}`); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -203,6 +203,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @param button Clicked button. | ||||
|      */ | ||||
|     async behaviourButtonClicked(button: CoreQuestionBehaviourButton): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let modal: CoreIonLoadingElement | undefined; | ||||
| 
 | ||||
|         try { | ||||
| @ -212,13 +216,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             modal = await CoreDomUtils.showModalLoading('core.sending', true); | ||||
| 
 | ||||
|             // Get the answers.
 | ||||
|             const answers = await this.prepareAnswers(); | ||||
|             const answers = await this.prepareAnswers(this.quiz.coursemodule); | ||||
| 
 | ||||
|             // Add the clicked button data.
 | ||||
|             answers[button.name] = button.value; | ||||
| 
 | ||||
|             // Behaviour checks are always in online.
 | ||||
|             await AddonModQuiz.processAttempt(this.quiz!, this.attempt!, answers, this.preflightData); | ||||
|             await AddonModQuiz.processAttempt(this.quiz, this.attempt, answers, this.preflightData); | ||||
| 
 | ||||
|             this.reloadNavigation = true; // Data sent to server, navigation should be reloaded.
 | ||||
| 
 | ||||
| @ -230,7 +234,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             this.content?.scrollToTop(); // Scroll top so the spinner is seen.
 | ||||
| 
 | ||||
|             try { | ||||
|                 await this.loadPage(this.attempt!.currentpage!); | ||||
|                 await this.loadPage(this.attempt.currentpage ?? 0); | ||||
|             } finally { | ||||
|                 this.loaded = true; | ||||
|                 if (scrollTop != -1) { | ||||
| @ -311,8 +315,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             } | ||||
|         } catch (error) { | ||||
|             // If the user isn't seeing the summary, start the check again.
 | ||||
|             if (!this.showSummary) { | ||||
|                 this.autoSave.startCheckChangesProcess(this.quiz!, this.attempt, this.preflightData, this.offline); | ||||
|             if (!this.showSummary && this.quiz) { | ||||
|                 this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline); | ||||
|             } | ||||
| 
 | ||||
|             CoreDomUtils.showErrorModalDefault(error, 'addon.mod_quiz.errorgetquestions', true); | ||||
| @ -351,7 +355,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             this.offline = await AddonModQuiz.isLastAttemptOfflineUnfinished(this.quiz); | ||||
|         } | ||||
| 
 | ||||
|         if (this.quiz!.timelimit && this.quiz!.timelimit > 0) { | ||||
|         if (this.quiz.timelimit && this.quiz.timelimit > 0) { | ||||
|             this.readableTimeLimit = CoreTime.formatTime(this.quiz.timelimit); | ||||
|         } | ||||
| 
 | ||||
| @ -394,11 +398,15 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     async finishAttempt(userFinish?: boolean, timeUp?: boolean): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let modal: CoreIonLoadingElement | undefined; | ||||
| 
 | ||||
|         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 == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { | ||||
|                 await CoreDomUtils.showConfirm(Translate.instant('addon.mod_quiz.confirmclose')); | ||||
|             } | ||||
| 
 | ||||
| @ -408,8 +416,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|             // Trigger an event to notify the attempt was finished.
 | ||||
|             CoreEvents.trigger(AddonModQuizProvider.ATTEMPT_FINISHED_EVENT, { | ||||
|                 quizId: this.quiz!.id, | ||||
|                 attemptId: this.attempt!.id, | ||||
|                 quizId: this.quiz.id, | ||||
|                 attemptId: this.attempt.id, | ||||
|                 synced: !this.offline, | ||||
|             }, CoreSites.getCurrentSiteId()); | ||||
| 
 | ||||
| @ -431,9 +439,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async fixSequenceChecks(): Promise<void> { | ||||
|         if (!this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Get current page data again to get the latest sequencechecks.
 | ||||
|         const data = await AddonModQuiz.getAttemptData(this.attempt!.id, this.attempt!.currentpage!, this.preflightData, { | ||||
|             cmId: this.quiz!.coursemodule, | ||||
|         const data = await AddonModQuiz.getAttemptData(this.attempt.id, this.attempt.currentpage ?? 0, this.preflightData, { | ||||
|             cmId: this.quiz?.coursemodule, | ||||
|             readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK, | ||||
|         }); | ||||
| 
 | ||||
| @ -465,14 +477,14 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * Initializes the timer if enabled. | ||||
|      */ | ||||
|     protected initTimer(): void { | ||||
|         if (!this.attemptAccessInfo?.endtime || this.attemptAccessInfo.endtime < 0) { | ||||
|         if (!this.quizAccessInfo || !this.attempt || !this.attemptAccessInfo?.endtime || this.attemptAccessInfo.endtime < 0) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Quiz has an end time. Check if time left should be shown.
 | ||||
|         const shouldShowTime = AddonModQuiz.shouldShowTimeLeft( | ||||
|             this.quizAccessInfo!.activerulenames, | ||||
|             this.attempt!, | ||||
|             this.quizAccessInfo.activerulenames, | ||||
|             this.attempt, | ||||
|             this.attemptAccessInfo.endtime, | ||||
|         ); | ||||
| 
 | ||||
| @ -490,8 +502,12 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadPage(page: number): Promise<void> { | ||||
|         const data = await AddonModQuiz.getAttemptData(this.attempt!.id, page, this.preflightData, { | ||||
|             cmId: this.quiz!.coursemodule, | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const data = await AddonModQuiz.getAttemptData(this.attempt.id, page, this.preflightData, { | ||||
|             cmId: this.quiz.coursemodule, | ||||
|             readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK, | ||||
|         }); | ||||
| 
 | ||||
| @ -523,7 +539,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|         ); | ||||
| 
 | ||||
|         // Start looking for changes.
 | ||||
|         this.autoSave.startCheckChangesProcess(this.quiz!, this.attempt, this.preflightData, this.offline); | ||||
|         this.autoSave.startCheckChangesProcess(this.quiz, this.attempt, this.preflightData, this.offline); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -532,23 +548,27 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadSummary(): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.summaryQuestions = []; | ||||
| 
 | ||||
|         this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt!.id, this.preflightData, { | ||||
|             cmId: this.quiz!.coursemodule, | ||||
|         this.summaryQuestions = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, { | ||||
|             cmId: this.quiz.coursemodule, | ||||
|             loadLocal: this.offline, | ||||
|             readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK, | ||||
|         }); | ||||
| 
 | ||||
|         this.showSummary = true; | ||||
|         this.canReturn = this.attempt!.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS && !this.attempt!.finishedOffline; | ||||
|         this.canReturn = this.attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS && !this.attempt.finishedOffline; | ||||
|         this.preventSubmitMessages = AddonModQuiz.getPreventSubmitMessages(this.summaryQuestions); | ||||
| 
 | ||||
|         this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz!, this.attempt!); | ||||
|         this.dueDateWarning = AddonModQuiz.getAttemptDueDateWarning(this.quiz, this.attempt); | ||||
| 
 | ||||
|         // Log summary as viewed.
 | ||||
|         CoreUtils.ignoreErrors( | ||||
|             AddonModQuiz.logViewAttemptSummary(this.attempt!.id, this.preflightData, this.quiz!.id, this.quiz!.name), | ||||
|             AddonModQuiz.logViewAttemptSummary(this.attempt.id, this.preflightData, this.quiz.id, this.quiz.name), | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -558,9 +578,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadNavigation(): Promise<void> { | ||||
|         if (!this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // We use the attempt summary to build the navigation because it contains all the questions.
 | ||||
|         this.navigation = await AddonModQuiz.getAttemptSummary(this.attempt!.id, this.preflightData, { | ||||
|             cmId: this.quiz!.coursemodule, | ||||
|         this.navigation = await AddonModQuiz.getAttemptSummary(this.attempt.id, this.preflightData, { | ||||
|             cmId: this.quiz?.coursemodule, | ||||
|             loadLocal: this.offline, | ||||
|             readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK, | ||||
|         }); | ||||
| @ -602,21 +626,22 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.changePage(modalData.page!, true, modalData.slot); | ||||
|         this.changePage(modalData.page, true, modalData.slot); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prepare the answers to be sent for the attempt. | ||||
|      * | ||||
|      * @param componentId Component ID. | ||||
|      * @returns Promise resolved with the answers. | ||||
|      */ | ||||
|     protected prepareAnswers(): Promise<CoreQuestionsAnswers> { | ||||
|     protected prepareAnswers(componentId: number): Promise<CoreQuestionsAnswers> { | ||||
|         return CoreQuestionHelper.prepareAnswers( | ||||
|             this.questions, | ||||
|             this.getAnswers(), | ||||
|             this.offline, | ||||
|             this.component, | ||||
|             this.quiz!.coursemodule, | ||||
|             componentId, | ||||
|         ); | ||||
|     } | ||||
| 
 | ||||
| @ -629,18 +654,22 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async processAttempt(userFinish?: boolean, timeUp?: boolean, retrying?: boolean): Promise<void> { | ||||
|         if (!this.quiz || !this.attempt) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Get the answers to send.
 | ||||
|         let answers: CoreQuestionsAnswers = {}; | ||||
| 
 | ||||
|         if (!this.showSummary) { | ||||
|             answers = await this.prepareAnswers(); | ||||
|             answers = await this.prepareAnswers(this.quiz.coursemodule); | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             // Send the answers.
 | ||||
|             await AddonModQuiz.processAttempt( | ||||
|                 this.quiz!, | ||||
|                 this.attempt!, | ||||
|                 this.quiz, | ||||
|                 this.attempt, | ||||
|                 answers, | ||||
|                 this.preflightData, | ||||
|                 userFinish, | ||||
| @ -676,7 +705,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|             CoreForms.triggerFormSubmittedEvent(this.formElement, !this.offline, CoreSites.getCurrentSiteId()); | ||||
|         } | ||||
| 
 | ||||
|         return CoreQuestionHelper.clearTmpData(this.questions, this.component, this.quiz!.coursemodule); | ||||
|         return CoreQuestionHelper.clearTmpData(this.questions, this.component, this.quiz.coursemodule); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -731,12 +760,16 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async startOrContinueAttempt(): Promise<void> { | ||||
|         if (!this.quiz || !this.quizAccessInfo) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let attempt = this.newAttempt ? undefined : this.lastAttempt; | ||||
| 
 | ||||
|         // Get the preflight data and start attempt if needed.
 | ||||
|         attempt = await AddonModQuizHelper.getAndCheckPreflightData( | ||||
|             this.quiz!, | ||||
|             this.quizAccessInfo!, | ||||
|             this.quiz, | ||||
|             this.quizAccessInfo, | ||||
|             this.preflightData, | ||||
|             attempt, | ||||
|             this.offline, | ||||
| @ -745,8 +778,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
|         ); | ||||
| 
 | ||||
|         // Re-fetch attempt access information with the right attempt (might have changed because a new attempt was created).
 | ||||
|         this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(this.quiz!.id, attempt.id, { | ||||
|             cmId: this.quiz!.coursemodule, | ||||
|         this.attemptAccessInfo = await AddonModQuiz.getAttemptAccessInformation(this.quiz.id, attempt.id, { | ||||
|             cmId: this.quiz.coursemodule, | ||||
|             readingStrategy: this.offline ? CoreSitesReadingStrategy.PREFER_CACHE : CoreSitesReadingStrategy.ONLY_NETWORK, | ||||
|         }); | ||||
| 
 | ||||
| @ -756,7 +789,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy, CanLeave { | ||||
| 
 | ||||
|         if (this.attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !this.attempt.finishedOffline) { | ||||
|             // Attempt not overdue and not finished in offline, load page.
 | ||||
|             await this.loadPage(this.attempt.currentpage!); | ||||
|             await this.loadPage(this.attempt.currentpage ?? 0); | ||||
| 
 | ||||
|             this.initTimer(); | ||||
|         } else { | ||||
|  | ||||
| @ -112,7 +112,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|      * @param slot Slot of the question to scroll to. | ||||
|      */ | ||||
|     async changePage(page: number, slot?: number): Promise<void> { | ||||
|         if (slot !== undefined && (this.attempt!.currentpage == -1 || page == this.currentPage)) { | ||||
|         if (slot !== undefined && (this.attempt?.currentpage == -1 || page == this.currentPage)) { | ||||
|             // Scrol to a certain question in the current page.
 | ||||
|             this.scrollToQuestion(slot); | ||||
| 
 | ||||
| @ -174,7 +174,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|      * @returns Promise resolved when done. | ||||
|      */ | ||||
|     protected async loadPage(page: number): Promise<void> { | ||||
|         const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page, cmId: this.quiz!.coursemodule }); | ||||
|         const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page, cmId: this.quiz?.coursemodule }); | ||||
| 
 | ||||
|         this.attempt = data.attempt; | ||||
|         this.attempt.currentpage = page; | ||||
| @ -203,7 +203,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|      */ | ||||
|     protected async loadNavigation(): Promise<void> { | ||||
|         // Get all questions in single page to retrieve all the questions.
 | ||||
|         const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz!.coursemodule }); | ||||
|         const data = await AddonModQuiz.getAttemptReview(this.attemptId, { page: -1, cmId: this.quiz?.coursemodule }); | ||||
| 
 | ||||
|         this.navigation = data.questions; | ||||
| 
 | ||||
| @ -260,7 +260,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.readableState = AddonModQuiz.getAttemptReadableStateName(this.attempt!.state || ''); | ||||
|         this.readableState = AddonModQuiz.getAttemptReadableStateName(this.attempt.state ?? ''); | ||||
| 
 | ||||
|         if (this.attempt.state != AddonModQuizProvider.ATTEMPT_FINISHED) { | ||||
|             return; | ||||
| @ -283,7 +283,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|         } | ||||
| 
 | ||||
|         // Treat grade.
 | ||||
|         if (this.options!.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX && | ||||
|         if (this.options && this.options.someoptions.marks >= AddonModQuizProvider.QUESTION_OPTIONS_MARK_AND_MAX && | ||||
|                 AddonModQuiz.quizHasGrades(this.quiz)) { | ||||
| 
 | ||||
|             if (data.grade === null || data.grade === undefined) { | ||||
| @ -348,7 +348,7 @@ export class AddonModQuizReviewPage implements OnInit { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         this.changePage(modalData.page!, modalData.slot); | ||||
|         this.changePage(modalData.page, modalData.slot); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -241,7 +241,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet | ||||
| 
 | ||||
|         const isLastFinished = !attempts.length || AddonModQuiz.isAttemptFinished(attempts[attempts.length - 1].state); | ||||
| 
 | ||||
|         return quiz.attempts === 0 || quiz.attempts! > attempts.length || !isLastFinished; | ||||
|         return quiz.attempts === 0 || (quiz.attempts ?? 0) > attempts.length || !isLastFinished; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -444,7 +444,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet | ||||
|             if (attempt.state == AddonModQuizProvider.ATTEMPT_IN_PROGRESS) { | ||||
|                 // Get data for each page.
 | ||||
|                 promises = promises.concat(pages.map(async (page) => { | ||||
|                     if (isSequential && page < attempt.currentpage!) { | ||||
|                     if (isSequential && attempt.currentpage && page < attempt.currentpage) { | ||||
|                         // Sequential quiz, cannot get pages before the current one.
 | ||||
|                         return; | ||||
|                     } | ||||
|  | ||||
| @ -43,7 +43,7 @@ export class AddonModQuizPushClickHandlerService implements CorePushNotification | ||||
|      */ | ||||
|     async handles(notification: AddonModQuizPushNotificationData): Promise<boolean> { | ||||
|         return CoreUtils.isTrueOrOne(notification.notif) && notification.moodlecomponent == 'mod_quiz' && | ||||
|                 this.SUPPORTED_NAMES.indexOf(notification.name!) != -1; | ||||
|                 this.SUPPORTED_NAMES.indexOf(notification.name ?? '') != -1; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -358,7 +358,7 @@ export class AddonModQuizHelperProvider { | ||||
|             if (attempt) { | ||||
|                 if (attempt.state != AddonModQuizProvider.ATTEMPT_OVERDUE && !attempt.finishedOffline) { | ||||
|                     // We're continuing an attempt. Call getAttemptData to validate the preflight data.
 | ||||
|                     await AddonModQuiz.getAttemptData(attempt.id, attempt.currentpage!, preflightData, modOptions); | ||||
|                     await AddonModQuiz.getAttemptData(attempt.id, attempt.currentpage ?? 0, preflightData, modOptions); | ||||
| 
 | ||||
|                     if (offline) { | ||||
|                         // Get current page stored in local.
 | ||||
|  | ||||
| @ -198,11 +198,11 @@ export class AddonModQuizOfflineProvider { | ||||
|         } else { | ||||
|             entry = { | ||||
|                 quizid: quiz.id, | ||||
|                 userid: attempt.userid!, | ||||
|                 userid: attempt.userid ?? CoreSites.getCurrentSiteUserId(), | ||||
|                 id: attempt.id, | ||||
|                 courseid: quiz.course, | ||||
|                 timecreated: now, | ||||
|                 attempt: attempt.attempt!, | ||||
|                 attempt: attempt.attempt ?? 0, | ||||
|                 currentpage: attempt.currentpage, | ||||
|                 timemodified: now, | ||||
|                 finished: finish ? 1 : 0, | ||||
| @ -284,10 +284,12 @@ export class AddonModQuizOfflineProvider { | ||||
| 
 | ||||
|             if (questions[slot]) { | ||||
|                 if (!questionsWithAnswers[slot]) { | ||||
|                     questionsWithAnswers[slot] = questions[slot]; | ||||
|                     questionsWithAnswers[slot].answers = {}; | ||||
|                     questionsWithAnswers[slot] = { | ||||
|                         ...questions[slot], | ||||
|                         answers: {}, | ||||
|                     }; | ||||
|                 } | ||||
|                 questionsWithAnswers[slot].answers![nameWithoutPrefix] = answers[name]; | ||||
|                 questionsWithAnswers[slot].answers[nameWithoutPrefix] = answers[name]; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
| @ -295,7 +297,7 @@ export class AddonModQuizOfflineProvider { | ||||
|         await Promise.all(Object.values(questionsWithAnswers).map(async (question) => { | ||||
| 
 | ||||
|             const state = await CoreQuestionBehaviourDelegate.determineNewState( | ||||
|                 quiz.preferredbehaviour!, | ||||
|                 quiz.preferredbehaviour ?? '', | ||||
|                 AddonModQuizProvider.COMPONENT, | ||||
|                 attempt.id, | ||||
|                 question, | ||||
| @ -317,7 +319,7 @@ export class AddonModQuizOfflineProvider { | ||||
|             AddonModQuizProvider.COMPONENT, | ||||
|             quiz.id, | ||||
|             attempt.id, | ||||
|             attempt.userid!, | ||||
|             attempt.userid ?? CoreSites.getCurrentSiteUserId(), | ||||
|             answers, | ||||
|             timeMod, | ||||
|             siteId, | ||||
| @ -332,7 +334,7 @@ export class AddonModQuizOfflineProvider { | ||||
|                     AddonModQuizProvider.COMPONENT, | ||||
|                     quiz.id, | ||||
|                     attempt.id, | ||||
|                     attempt.userid!, | ||||
|                     attempt.userid ?? CoreSites.getCurrentSiteUserId(), | ||||
|                     question, | ||||
|                     newStates[slot], | ||||
|                     siteId, | ||||
|  | ||||
| @ -305,8 +305,9 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider | ||||
| 
 | ||||
|         // Get all the offline attempts for the quiz. It should always be 0 or 1 attempt
 | ||||
|         const offlineAttempts = await AddonModQuizOffline.getQuizAttempts(quiz.id, siteId); | ||||
|         const offlineAttempt = offlineAttempts.pop(); | ||||
| 
 | ||||
|         if (!offlineAttempts.length) { | ||||
|         if (!offlineAttempt) { | ||||
|             // Nothing to sync, finish.
 | ||||
|             return this.finishSync(siteId, quiz, courseId, warnings); | ||||
|         } | ||||
| @ -316,8 +317,6 @@ export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider | ||||
|             throw new CoreError(Translate.instant('core.cannotconnect')); | ||||
|         } | ||||
| 
 | ||||
|         const offlineAttempt = offlineAttempts.pop()!; | ||||
| 
 | ||||
|         // Now get the list of online attempts to make sure this attempt exists and isn't finished.
 | ||||
|         const onlineAttempts = await AddonModQuiz.getUserAttempts(quiz.id, modOptions); | ||||
| 
 | ||||
|  | ||||
| @ -290,7 +290,7 @@ export class AddonModQuizProvider { | ||||
|                 return dueDate * 1000; | ||||
| 
 | ||||
|             case AddonModQuizProvider.ATTEMPT_OVERDUE: | ||||
|                 return (dueDate + quiz.graceperiod!) * 1000; | ||||
|                 return (dueDate + (quiz.graceperiod ?? 0)) * 1000; | ||||
| 
 | ||||
|             default: | ||||
|                 this.logger.warn('Unexpected state when getting due date: ' + attempt.state); | ||||
| @ -356,7 +356,7 @@ export class AddonModQuizProvider { | ||||
|                     Translate.instant('addon.mod_quiz.statefinished'), | ||||
|                     Translate.instant( | ||||
|                         'addon.mod_quiz.statefinisheddetails', | ||||
|                         { $a: CoreTimeUtils.userDate(attempt.timefinish! * 1000) }, | ||||
|                         { $a: CoreTimeUtils.userDate((attempt.timefinish ?? 0) * 1000) }, | ||||
|                     ), | ||||
|                 ]; | ||||
| 
 | ||||
| @ -625,7 +625,7 @@ export class AddonModQuizProvider { | ||||
|         } | ||||
| 
 | ||||
|         if (quiz.questiondecimalpoints == -1) { | ||||
|             return quiz.decimalpoints!; | ||||
|             return quiz.decimalpoints ?? 1; | ||||
|         } | ||||
| 
 | ||||
|         return quiz.questiondecimalpoints; | ||||
| @ -1780,7 +1780,7 @@ export class AddonModQuizProvider { | ||||
|      * @returns Whether quiz is graded. | ||||
|      */ | ||||
|     quizHasGrades(quiz: AddonModQuizQuizWSData): boolean { | ||||
|         return quiz.grade! >= 0.000005 && quiz.sumgrades! >= 0.000005; | ||||
|         return (quiz.grade ?? 0) >= 0.000005 && (quiz.sumgrades ?? 0) >= 0.000005; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -1802,8 +1802,8 @@ export class AddonModQuizProvider { | ||||
| 
 | ||||
|         const rawGradeNum = typeof rawGrade == 'string' ? parseFloat(rawGrade) : rawGrade; | ||||
|         if (rawGradeNum !== undefined && rawGradeNum !== null && !isNaN(rawGradeNum)) { | ||||
|             if (quiz.sumgrades! >= 0.000005) { | ||||
|                 grade = rawGradeNum * quiz.grade! / quiz.sumgrades!; | ||||
|             if (quiz.sumgrades && quiz.sumgrades >= 0.000005) { | ||||
|                 grade = rawGradeNum * (quiz.grade ?? 0) / quiz.sumgrades; | ||||
|             } else { | ||||
|                 grade = 0; | ||||
|             } | ||||
| @ -1816,7 +1816,7 @@ export class AddonModQuizProvider { | ||||
|         if (format === 'question') { | ||||
|             return this.formatGrade(grade, this.getGradeDecimals(quiz)); | ||||
|         } else if (format) { | ||||
|             return this.formatGrade(grade, quiz.decimalpoints!); | ||||
|             return this.formatGrade(grade, quiz.decimalpoints ?? 1); | ||||
|         } | ||||
| 
 | ||||
|         return String(grade); | ||||
|  | ||||
| @ -132,5 +132,5 @@ export const CoreQuestionBehaviourDelegate = makeSingleton(CoreQuestionBehaviour | ||||
|  * Answers classified by question slot. | ||||
|  */ | ||||
| export type CoreQuestionQuestionWithAnswers = CoreQuestionQuestionParsed & { | ||||
|     answers?: CoreQuestionsAnswers; | ||||
|     answers: CoreQuestionsAnswers; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user