diff --git a/src/addons/mod/quiz/components/index/index.ts b/src/addons/mod/quiz/components/index/index.ts index c08c5e18f..a3fd16fa3 100644 --- a/src/addons/mod/quiz/components/index/index.ts +++ b/src/addons/mod/quiz/components/index/index.ts @@ -413,7 +413,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp try { await AddonModQuiz.instance.getAttemptReview(attemptId, { page: -1, cmId: this.module!.id }); - CoreNavigator.instance.navigate(`../../review/${this.courseId}/${this.quiz!.id}/${attemptId}`); + await CoreNavigator.instance.navigate(`review/${attemptId}`); } catch { // Ignore errors. } @@ -534,7 +534,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp protected openQuiz(): void { this.hasPlayed = true; - CoreNavigator.instance.navigate(`../../player/${this.courseId}/${this.quiz!.id}`, { + CoreNavigator.instance.navigate('player', { params: { moduleUrl: this.module?.url, }, @@ -639,10 +639,12 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp // Get gradebook grade. const data = await AddonModQuiz.instance.getGradeFromGradebook(this.courseId!, this.module!.id); - this.gradebookData = { - grade: data.graderaw, - feedback: data.feedback, - }; + if (data) { + this.gradebookData = { + grade: 'graderaw' in data ? data.graderaw : Number(data.grade), + feedback: data.feedback, + }; + } } catch { // Fallback to quiz best grade if failure or not found. this.gradebookData = { @@ -657,7 +659,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp * @return Promise resolved when done. */ async viewAttempt(attemptId: number): Promise { - CoreNavigator.instance.navigate(`../../attempt/${this.courseId}/${this.quiz!.id}/${attemptId}`); + CoreNavigator.instance.navigate(`attempt/${attemptId}`); } /** diff --git a/src/addons/mod/quiz/pages/attempt/attempt.html b/src/addons/mod/quiz/pages/attempt/attempt.html index 872803363..032b4b9b8 100644 --- a/src/addons/mod/quiz/pages/attempt/attempt.html +++ b/src/addons/mod/quiz/pages/attempt/attempt.html @@ -46,7 +46,7 @@

{{ 'addon.mod_quiz.feedback' | translate }}

+ contextLevel="module" [contextInstanceId]="cmId" [courseId]="courseId">

diff --git a/src/addons/mod/quiz/pages/attempt/attempt.ts b/src/addons/mod/quiz/pages/attempt/attempt.ts index ccf7ebd30..fe1ee3d27 100644 --- a/src/addons/mod/quiz/pages/attempt/attempt.ts +++ b/src/addons/mod/quiz/pages/attempt/attempt.ts @@ -44,15 +44,15 @@ export class AddonModQuizAttemptPage implements OnInit { loaded = false; // Whether data has been loaded. feedback?: string; // Attempt feedback. showReviewColumn = false; + cmId!: number; // Course module id the attempt belongs to. protected attemptId!: number; // Attempt to view. - protected quizId!: number; // ID of the quiz the attempt belongs to. /** * Component being initialized. */ ngOnInit(): void { - this.quizId = CoreNavigator.instance.getRouteNumberParam('quizId')!; + this.cmId = CoreNavigator.instance.getRouteNumberParam('cmId')!; this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!; this.attemptId = CoreNavigator.instance.getRouteNumberParam('attemptId')!; @@ -79,7 +79,7 @@ export class AddonModQuizAttemptPage implements OnInit { */ protected async fetchQuizData(): Promise { try { - this.quiz = await AddonModQuiz.instance.getQuizById(this.courseId, this.quizId); + this.quiz = await AddonModQuiz.instance.getQuiz(this.courseId, this.cmId); this.componentId = this.quiz.coursemodule; @@ -123,7 +123,7 @@ export class AddonModQuizAttemptPage implements OnInit { */ protected async fetchAttempt(): Promise { // Get all the attempts and search the one we want. - const attempts = await AddonModQuiz.instance.getUserAttempts(this.quizId, { cmId: this.quiz!.coursemodule }); + const attempts = await AddonModQuiz.instance.getUserAttempts(this.quiz!.id, { cmId: this.cmId }); const attempt = attempts.find(attempt => attempt.id == this.attemptId); @@ -143,7 +143,7 @@ export class AddonModQuizAttemptPage implements OnInit { * @return Promise resolved when done. */ protected async fetchAccessInfo(): Promise { - const accessInfo = await AddonModQuiz.instance.getQuizAccessInformation(this.quizId, { cmId: this.quiz!.coursemodule }); + const accessInfo = await AddonModQuiz.instance.getQuizAccessInformation(this.quiz!.id, { cmId: this.cmId }); if (!accessInfo.canreviewmyattempts) { return accessInfo; @@ -171,13 +171,16 @@ export class AddonModQuizAttemptPage implements OnInit { const promises: Promise[] = []; promises.push(AddonModQuiz.instance.invalidateQuizData(this.courseId)); - promises.push(AddonModQuiz.instance.invalidateUserAttemptsForUser(this.quizId)); - promises.push(AddonModQuiz.instance.invalidateQuizAccessInformation(this.quizId)); - promises.push(AddonModQuiz.instance.invalidateCombinedReviewOptionsForUser(this.quizId)); promises.push(AddonModQuiz.instance.invalidateAttemptReview(this.attemptId)); - if (this.attempt && typeof this.feedback != 'undefined') { - promises.push(AddonModQuiz.instance.invalidateFeedback(this.quizId)); + if (this.quiz) { + promises.push(AddonModQuiz.instance.invalidateUserAttemptsForUser(this.quiz.id)); + promises.push(AddonModQuiz.instance.invalidateQuizAccessInformation(this.quiz.id)); + promises.push(AddonModQuiz.instance.invalidateCombinedReviewOptionsForUser(this.quiz.id)); + + if (this.attempt && typeof this.feedback != 'undefined') { + promises.push(AddonModQuiz.instance.invalidateFeedback(this.quiz.id)); + } } await CoreUtils.instance.ignoreErrors(Promise.all(promises)); @@ -191,7 +194,7 @@ export class AddonModQuizAttemptPage implements OnInit { * @return Promise resolved when done. */ async reviewAttempt(): Promise { - CoreNavigator.instance.navigate(`../../../../review/${this.courseId}/${this.quiz!.id}/${this.attempt!.id}`); + CoreNavigator.instance.navigate(`../../review/${this.attempt!.id}`); } } diff --git a/src/addons/mod/quiz/pages/player/player.html b/src/addons/mod/quiz/pages/player/player.html index 8c8f39b12..bf984b8c6 100644 --- a/src/addons/mod/quiz/pages/player/player.html +++ b/src/addons/mod/quiz/pages/player/player.html @@ -78,8 +78,8 @@ diff --git a/src/addons/mod/quiz/pages/player/player.ts b/src/addons/mod/quiz/pages/player/player.ts index 046db21de..3eb0a3d82 100644 --- a/src/addons/mod/quiz/pages/player/player.ts +++ b/src/addons/mod/quiz/pages/player/player.ts @@ -80,8 +80,8 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { readableTimeLimit?: string; // Time limit in a readable format. dueDateWarning?: string; // Warning about due date. courseId!: number; // The course ID the quiz belongs to. + cmId!: number; // Course module ID. - protected quizId!: number; // Quiz ID to attempt. protected preflightData: Record = {}; // Preflight data to attempt the quiz. protected quizAccessInfo?: AddonModQuizGetQuizAccessInformationWSResponse; // Quiz access information. protected attemptAccessInfo?: AddonModQuizGetAttemptAccessInformationWSResponse; // Attempt access info. @@ -104,13 +104,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { * Component being initialized. */ ngOnInit(): void { - this.quizId = CoreNavigator.instance.getRouteNumberParam('quizId')!; + this.cmId = CoreNavigator.instance.getRouteNumberParam('cmId')!; this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!; this.moduleUrl = CoreNavigator.instance.getRouteParam('moduleUrl'); - // Block the quiz so it cannot be synced. - CoreSync.instance.blockOperation(AddonModQuizProvider.COMPONENT, this.quizId); - // Create the auto save instance. this.autoSave = new AddonModQuizAutoSave( 'addon-mod_quiz-player-form', @@ -136,8 +133,10 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { this.autoSave.stopCheckChangesProcess(); this.autoSaveErrorSubscription?.unsubscribe(); - // Unblock the quiz so it can be synced. - CoreSync.instance.unblockOperation(AddonModQuizProvider.COMPONENT, this.quizId); + if (this.quiz) { + // Unblock the quiz so it can be synced. + CoreSync.instance.unblockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id); + } } /** @@ -320,11 +319,13 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { */ protected async fetchData(): Promise { try { - // Wait for any ongoing sync to finish. We won't sync a quiz while it's being played. - await AddonModQuizSync.instance.waitForSync(this.quizId); + this.quiz = await AddonModQuiz.instance.getQuiz(this.courseId, this.cmId); - // Sync finished, now get the quiz. - this.quiz = await AddonModQuiz.instance.getQuizById(this.courseId, this.quizId); + // Block the quiz so it cannot be synced. + CoreSync.instance.blockOperation(AddonModQuizProvider.COMPONENT, this.quiz.id); + + // Wait for any ongoing sync to finish. We won't sync a quiz while it's being played. + await AddonModQuizSync.instance.waitForSync(this.quiz.id); this.isSequential = AddonModQuiz.instance.isNavigationSequential(this.quiz); @@ -397,7 +398,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Trigger an event to notify the attempt was finished. CoreEvents.trigger(AddonModQuizProvider.ATTEMPT_FINISHED_EVENT, { - quizId: this.quizId, + quizId: this.quiz!.id, attemptId: this.attempt!.id, synced: !this.offline, }, CoreSites.instance.getCurrentSiteId()); @@ -537,7 +538,7 @@ export class AddonModQuizPlayerPage implements OnInit, OnDestroy { // Log summary as viewed. CoreUtils.instance.ignoreErrors( - AddonModQuiz.instance.logViewAttemptSummary(this.attempt!.id, this.preflightData, this.quizId, this.quiz!.name), + AddonModQuiz.instance.logViewAttemptSummary(this.attempt!.id, this.preflightData, this.quiz!.id, this.quiz!.name), ); } diff --git a/src/addons/mod/quiz/pages/review/review.html b/src/addons/mod/quiz/pages/review/review.html index b740c07c9..7bb142d42 100644 --- a/src/addons/mod/quiz/pages/review/review.html +++ b/src/addons/mod/quiz/pages/review/review.html @@ -73,8 +73,8 @@

{{ data.title }}

- +
@@ -103,9 +103,9 @@ - diff --git a/src/addons/mod/quiz/pages/review/review.ts b/src/addons/mod/quiz/pages/review/review.ts index f02a67fcd..49ec63a6d 100644 --- a/src/addons/mod/quiz/pages/review/review.ts +++ b/src/addons/mod/quiz/pages/review/review.ts @@ -51,7 +51,6 @@ export class AddonModQuizReviewPage implements OnInit { attempt?: AddonModQuizAttemptWSData; // The attempt being reviewed. component = AddonModQuizProvider.COMPONENT; // Component to link the files to. - componentId?: number; // ID to use in conjunction with the component. showAll = false; // Whether to view all questions in the same page. numPages?: number; // Number of pages. showCompleted = false; // Whether to show completed time. @@ -68,8 +67,8 @@ export class AddonModQuizReviewPage implements OnInit { overTime?: string; quiz?: AddonModQuizQuizWSData; // The quiz the attempt belongs to. courseId!: number; // The course ID the quiz belongs to. + cmId!: number; // Course module id the attempt belongs to. - protected quizId!: number; // Quiz ID the attempt belongs to. protected attemptId!: number; // The attempt being reviewed. protected currentPage!: number; // The current page being reviewed. protected options?: AddonModQuizCombinedReviewOptions; // Review options. @@ -83,7 +82,7 @@ export class AddonModQuizReviewPage implements OnInit { * Component being initialized. */ async ngOnInit(): Promise { - this.quizId = CoreNavigator.instance.getRouteNumberParam('quizId')!; + this.cmId = CoreNavigator.instance.getRouteNumberParam('cmId')!; this.courseId = CoreNavigator.instance.getRouteNumberParam('courseId')!; this.attemptId = CoreNavigator.instance.getRouteNumberParam('attemptId')!; this.currentPage = CoreNavigator.instance.getRouteNumberParam('page') || -1; @@ -93,7 +92,7 @@ export class AddonModQuizReviewPage implements OnInit { await this.fetchData(); CoreUtils.instance.ignoreErrors( - AddonModQuiz.instance.logViewAttemptReview(this.attemptId, this.quizId, this.quiz!.name), + AddonModQuiz.instance.logViewAttemptReview(this.attemptId, this.quiz!.id, this.quiz!.name), ); } finally { this.loaded = true; @@ -144,11 +143,9 @@ export class AddonModQuizReviewPage implements OnInit { */ protected async fetchData(): Promise { try { - this.quiz = await AddonModQuiz.instance.getQuizById(this.courseId, this.quizId); + this.quiz = await AddonModQuiz.instance.getQuiz(this.courseId, this.cmId); - this.componentId = this.quiz.coursemodule; - - this.options = await AddonModQuiz.instance.getCombinedReviewOptions(this.quizId, { cmId: this.quiz.coursemodule }); + this.options = await AddonModQuiz.instance.getCombinedReviewOptions(this.quiz.id, { cmId: this.cmId }); // Load the navigation data. await this.loadNavigation(); @@ -214,11 +211,15 @@ export class AddonModQuizReviewPage implements OnInit { * @param refresher Refresher */ async refreshData(refresher: IonRefresher): Promise { - await CoreUtils.instance.ignoreErrors(Promise.all([ - AddonModQuiz.instance.invalidateQuizData(this.courseId), - AddonModQuiz.instance.invalidateCombinedReviewOptionsForUser(this.quizId), - AddonModQuiz.instance.invalidateAttemptReview(this.attemptId), - ])); + const promises: Promise[] = []; + + promises.push(AddonModQuiz.instance.invalidateQuizData(this.courseId)); + promises.push(AddonModQuiz.instance.invalidateAttemptReview(this.attemptId)); + if (this.quiz) { + promises.push(AddonModQuiz.instance.invalidateCombinedReviewOptionsForUser(this.quiz.id)); + } + + await CoreUtils.instance.ignoreErrors(Promise.all(promises)); try { await this.fetchData(); diff --git a/src/addons/mod/quiz/quiz-lazy.module.ts b/src/addons/mod/quiz/quiz-lazy.module.ts index 1cd5e8998..691bfff63 100644 --- a/src/addons/mod/quiz/quiz-lazy.module.ts +++ b/src/addons/mod/quiz/quiz-lazy.module.ts @@ -17,19 +17,19 @@ import { RouterModule, Routes } from '@angular/router'; const routes: Routes = [ { - path: ':courseId/:cmdId', + path: ':courseId/:cmId', loadChildren: () => import('./pages/index/index.module').then( m => m.AddonModQuizIndexPageModule), }, { - path: 'player/:courseId/:quizId', + path: ':courseId/:cmId/player', loadChildren: () => import('./pages/player/player.module').then( m => m.AddonModQuizPlayerPageModule), }, { - path: 'attempt/:courseId/:quizId/:attemptId', + path: ':courseId/:cmId/attempt/:attemptId', loadChildren: () => import('./pages/attempt/attempt.module').then( m => m.AddonModQuizAttemptPageModule), }, { - path: 'review/:courseId/:quizId/:attemptId', + path: ':courseId/:cmId/review/:attemptId', loadChildren: () => import('./pages/review/review.module').then( m => m.AddonModQuizReviewPageModule), }, ]; diff --git a/src/addons/mod/quiz/services/handlers/prefetch.ts b/src/addons/mod/quiz/services/handlers/prefetch.ts index 251a5e5ed..0cddf1b5a 100644 --- a/src/addons/mod/quiz/services/handlers/prefetch.ts +++ b/src/addons/mod/quiz/services/handlers/prefetch.ts @@ -524,7 +524,7 @@ export class AddonModQuizPrefetchHandlerService extends CoreCourseActivityPrefet try { const gradebookData = await AddonModQuiz.instance.getGradeFromGradebook(quiz.course, quiz.coursemodule, true, siteId); - if (typeof gradebookData.graderaw != 'undefined') { + if (gradebookData && 'graderaw' in gradebookData && gradebookData.graderaw !== undefined) { await AddonModQuiz.instance.getFeedbackForGrade(quiz.id, gradebookData.graderaw, modOptions); } } catch { diff --git a/src/addons/mod/quiz/services/quiz-helper.ts b/src/addons/mod/quiz/services/quiz-helper.ts index 6fb0851c3..8df6627a6 100644 --- a/src/addons/mod/quiz/services/quiz-helper.ts +++ b/src/addons/mod/quiz/services/quiz-helper.ts @@ -16,7 +16,7 @@ import { Injectable } from '@angular/core'; import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreError } from '@classes/errors/error'; -import { CoreCourseHelper } from '@features/course/services/course-helper'; +import { CoreCourse } from '@features/course/services/course'; import { CoreNavigator } from '@services/navigator'; import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; @@ -232,12 +232,13 @@ export class AddonModQuizHelperProvider { if (!quizId) { quizId = await this.getQuizIdByAttemptId(attemptId, { siteId }); } - if (!courseId) { - courseId = await CoreCourseHelper.instance.getModuleCourseIdByInstance(quizId, 'quiz', siteId); - } + + const module = await CoreCourse.instance.getModuleBasicInfoByInstance(quizId, 'quiz', siteId); + + courseId = courseId || module.course; // Go to the review page. - await CoreNavigator.instance.navigateToSitePath(`mod_quiz/review/${courseId}/${quizId}/${attemptId}`, { + await CoreNavigator.instance.navigateToSitePath(`mod_quiz/${courseId}/${module.id}/review/${attemptId}`, { params: { page: page == undefined || isNaN(page) ? -1 : page, }, diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts index 7b40b3252..92b25e9f3 100644 --- a/src/addons/mod/quiz/services/quiz.ts +++ b/src/addons/mod/quiz/services/quiz.ts @@ -19,7 +19,7 @@ import { CoreWSError } from '@classes/errors/wserror'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreCourseCommonModWSOptions } from '@features/course/services/course'; import { CoreCourseLogHelper } from '@features/course/services/log-helper'; -import { CoreGradesFormattedItem, CoreGradesHelper } from '@features/grades/services/grades-helper'; +import { CoreGradesFormattedItem, CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; import { CoreQuestion, @@ -634,7 +634,7 @@ export class AddonModQuizProvider { ignoreCache?: boolean, siteId?: string, userId?: number, - ): Promise { + ): Promise { const items = await CoreGradesHelper.instance.getGradeModuleItems( courseId,