forked from CIT/Vmeda.Online
		
	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) => {
 | 
			
		||||
        const [options] = await Promise.all([
 | 
			
		||||
            AddonModQuiz.getCombinedReviewOptions(quiz.id, { cmId: this.module.id }),
 | 
			
		||||
            this.getQuizGrade(),
 | 
			
		||||
            openReview ? this.goToAutoReview() : undefined,
 | 
			
		||||
        ]);
 | 
			
		||||
 | 
			
		||||
        this.options = options;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        // Get best grade.
 | 
			
		||||
        promises.push(this.getQuizGrade());
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        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