From c22424c93e6157be48171eb9cf7aa03aed5b4ab3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pau=20Ferrer=20Oca=C3=B1a?= Date: Wed, 30 Nov 2022 11:36:56 +0100 Subject: [PATCH] MOBILE-4270 chore: Solve lots of linter warnings --- .../pages/discussion/discussion.page.ts | 9 +- .../pages/discussions-35/discussions.page.ts | 22 ++-- src/addons/mod/choice/services/choice.ts | 5 +- src/addons/mod/forum/services/forum-helper.ts | 14 +-- src/addons/mod/quiz/services/quiz-offline.ts | 1 + src/addons/mod/quiz/services/quiz.ts | 4 +- .../mod/resource/components/index/index.ts | 2 +- .../resource/services/handlers/prefetch.ts | 4 +- .../mod/resource/services/resource-helper.ts | 4 +- .../mod/scorm/components/index/index.ts | 84 +++++++------- .../mod/survey/components/index/index.ts | 21 +++- src/addons/mod/url/components/index/index.ts | 6 +- src/addons/mod/wiki/pages/edit/edit.ts | 48 ++++---- .../mod/wiki/services/handlers/create-link.ts | 6 +- .../assessment-strategy.ts | 18 ++- .../mod/workshop/components/index/index.ts | 104 ++++++++++-------- .../pages/edit-submission/edit-submission.ts | 5 +- .../workshop/pages/submission/submission.ts | 94 ++++++++-------- .../workshop/services/handlers/prefetch.ts | 37 +++++-- .../mod/workshop/services/workshop-helper.ts | 63 +++++------ src/addons/mod/workshop/services/workshop.ts | 13 ++- src/app/app-routing.module.ts | 2 +- src/core/classes/queue-runner.ts | 7 +- src/core/classes/sqlitedb.ts | 10 +- src/core/classes/tests/error.test.ts | 20 ++-- .../components/attachments/attachments.ts | 4 +- src/core/components/chart/chart.ts | 22 ++-- src/core/components/file/file.ts | 24 ++-- src/core/components/files/files.ts | 4 +- src/core/components/recaptcha/recaptcha.ts | 12 +- src/core/directives/fa-icon.ts | 8 +- .../only-title-block/only-title-block.ts | 8 +- .../comments/pages/viewer/viewer.page.ts | 12 +- .../features/comments/services/comments.ts | 12 +- .../classes/module-grade-handler.ts | 6 +- .../components/course-format/course-format.ts | 1 + .../directives/download-module-main-file.ts | 13 ++- .../services/course-options-delegate.ts | 8 +- src/core/features/course/services/course.ts | 4 +- .../emulator/services/capture-helper.ts | 14 +-- .../emulator/services/file-transfer.ts | 4 +- .../services/fileuploader-delegate.ts | 2 +- .../services/fileuploader-helper.ts | 8 +- .../fileuploader/services/fileuploader.ts | 14 +-- .../grades/services/handlers/report-link.ts | 4 +- .../grades/services/handlers/user-link.ts | 4 +- src/core/features/h5p/classes/player.ts | 8 +- .../login/pages/email-signup/email-signup.ts | 5 +- .../forgotten-password/forgotten-password.ts | 26 ++--- .../features/login/services/login-helper.ts | 46 ++++---- .../classes/base-question-component.ts | 101 +++++++++-------- .../question/services/question-helper.ts | 14 ++- .../rating/components/aggregate/aggregate.ts | 16 +-- .../features/rating/components/rate/rate.ts | 8 +- .../search/services/search-history.service.ts | 7 +- .../sharedfiles/components/list/list.ts | 10 +- .../pages/choose-site/choose-site.ts | 30 +++-- .../handlers/user-profile-field-handler.ts | 2 +- .../services/siteplugins-helper.ts | 24 ++-- src/core/features/styles/services/styles.ts | 16 +-- .../tag/pages/index-area/index-area.page.ts | 6 +- src/core/features/user/services/user-sync.ts | 5 +- src/core/initializers/initialize-urlscheme.ts | 1 + src/core/services/cron.ts | 59 ++++------ src/core/services/local-notifications.ts | 13 ++- src/core/services/plugin-file-delegate.ts | 14 +-- src/core/services/utils/dom.ts | 2 +- src/core/services/utils/iframe.ts | 11 +- src/core/services/utils/text.ts | 11 +- src/theme/globals.variables.scss | 2 +- 70 files changed, 689 insertions(+), 539 deletions(-) diff --git a/src/addons/messages/pages/discussion/discussion.page.ts b/src/addons/messages/pages/discussion/discussion.page.ts index 9dbb10719..acb9aa30d 100644 --- a/src/addons/messages/pages/discussion/discussion.page.ts +++ b/src/addons/messages/pages/discussion/discussion.page.ts @@ -270,9 +270,10 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView } if (this.userId) { + const userId = this.userId; // Get the member info. Invalidate first to make sure we get the latest status. promises.push(AddonMessages.invalidateMemberInfo(this.userId).then(async () => { - this.otherMember = await AddonMessages.getMemberInfo(this.userId!); + this.otherMember = await AddonMessages.getMemberInfo(userId); if (!exists && this.otherMember) { this.conversationImage = this.otherMember.profileimageurl; @@ -288,6 +289,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView } else { if (this.userId) { + const userId = this.userId; + // Fake the user member info. promises.push(CoreUser.getProfile(this.userId).then(async (user) => { this.otherMember = { @@ -305,8 +308,8 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView canmessage: true, requirescontact: false, }; - this.otherMember.isblocked = await AddonMessages.isBlocked(this.userId!); - this.otherMember.iscontact = await AddonMessages.isContact(this.userId!); + this.otherMember.isblocked = await AddonMessages.isBlocked(userId); + this.otherMember.iscontact = await AddonMessages.isContact(userId); this.blockIcon = this.otherMember.isblocked ? 'fas-user-check' : 'fas-user-lock'; return; diff --git a/src/addons/messages/pages/discussions-35/discussions.page.ts b/src/addons/messages/pages/discussions-35/discussions.page.ts index 0706b9b9d..3b597d0c7 100644 --- a/src/addons/messages/pages/discussions-35/discussions.page.ts +++ b/src/addons/messages/pages/discussions-35/discussions.page.ts @@ -80,17 +80,17 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { if (data.userId && this.discussions) { - const discussion = this.discussions.find((disc) => disc.message!.user == data.userId); + const discussion = this.discussions.find((disc) => disc.message?.user === data.userId); if (discussion === undefined) { this.loaded = false; this.refreshData().finally(() => { this.loaded = true; }); - } else { - // An existing discussion has a new message, update the last message. - discussion.message!.message = data.message; - discussion.message!.timecreated = data.timecreated; + } else if (discussion.message) { + // An existing discussion has a new message, update the last message. + discussion.message.message = data.message; + discussion.message.timecreated = data.timecreated; } } }, @@ -102,10 +102,10 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { AddonMessagesProvider.READ_CHANGED_EVENT, (data) => { if (data.userId && this.discussions) { - const discussion = this.discussions.find((disc) => disc.message!.user == data.userId); + const discussion = this.discussions.find((disc) => disc.message?.user === data.userId); if (discussion !== undefined) { - // A discussion has been read reset counter. + // A discussion has been read reset counter. discussion.unread = false; // Conversations changed, invalidate them and refresh unread counts. @@ -138,7 +138,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { } /** - * Component loaded. + * @inheritdoc */ async ngOnInit(): Promise { this.route.queryParams.subscribe(async (params) => { @@ -150,9 +150,9 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { await this.fetchData(); - if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet) { + if (!this.discussionUserId && this.discussions.length > 0 && CoreScreen.isTablet && this.discussions[0].message) { // Take first and load it. - await this.gotoDiscussion(this.discussions[0].message!.user); + await this.gotoDiscussion(this.discussions[0].message.user); } // Treat deep link now that the conversation route has been loaded if needed. @@ -287,7 +287,7 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy { } /** - * Component destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.newMessagesObserver?.off(); diff --git a/src/addons/mod/choice/services/choice.ts b/src/addons/mod/choice/services/choice.ts index 459bbf3b2..57e002fea 100644 --- a/src/addons/mod/choice/services/choice.ts +++ b/src/addons/mod/choice/services/choice.ts @@ -77,15 +77,14 @@ export class AddonModChoiceProvider { choiceId: number, name: string, courseId: number, - responses?: number[], + responses: number[] = [], siteId?: string, ): Promise { siteId = siteId || CoreSites.getCurrentSiteId(); - responses = responses || []; // Convenience function to store a message to be synchronized later. const storeOffline = async (): Promise => { - await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses!, true, siteId); + await AddonModChoiceOffline.saveResponse(choiceId, name, courseId, responses, true, siteId); return false; }; diff --git a/src/addons/mod/forum/services/forum-helper.ts b/src/addons/mod/forum/services/forum-helper.ts index 16f9f1cc8..68c86e7aa 100644 --- a/src/addons/mod/forum/services/forum-helper.ts +++ b/src/addons/mod/forum/services/forum-helper.ts @@ -62,7 +62,7 @@ export class AddonModForumHelperProvider { message: string, attachments?: CoreFileEntry[], options?: AddonModForumDiscussionOptions, - groupIds?: number[], + groupIds: number[] = [], timeCreated?: number, siteId?: string, ): Promise { @@ -76,7 +76,7 @@ export class AddonModForumHelperProvider { // Convenience function to store a message to be synchronized later. const storeOffline = async (): Promise => { // Multiple groups, the discussion is being posted to all groups. - const groupId = groupIds!.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds![0]; + const groupId = groupIds.length > 1 ? AddonModForumProvider.ALL_GROUPS : groupIds[0]; if (offlineAttachments && options) { options.attachmentsid = offlineAttachments; @@ -182,7 +182,7 @@ export class AddonModForumHelperProvider { * @param siteId Site ID. If not defined, current site. * @returns Promise resolved with the object converted to Online. */ - convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise { + async convertOfflineReplyToOnline(offlineReply: AddonModForumOfflineReply, siteId?: string): Promise { const reply: AddonModForumPost = { id: -offlineReply.timecreated, discussionid: offlineReply.discussionid, @@ -236,11 +236,11 @@ export class AddonModForumHelperProvider { ), ); - return Promise.all(promises).then(() => { - reply.attachment = reply.attachments!.length > 0 ? 1 : 0; + await Promise.all(promises); - return reply; - }); + reply.attachment = reply.attachments?.length ? 1 : 0; + + return reply; } /** diff --git a/src/addons/mod/quiz/services/quiz-offline.ts b/src/addons/mod/quiz/services/quiz-offline.ts index ed477be09..5009a68d7 100644 --- a/src/addons/mod/quiz/services/quiz-offline.ts +++ b/src/addons/mod/quiz/services/quiz-offline.ts @@ -13,6 +13,7 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreError } from '@classes/errors/error'; import { CoreQuestionBehaviourDelegate, CoreQuestionQuestionWithAnswers } from '@features/question/services/behaviour-delegate'; import { CoreQuestionAnswerDBRecord } from '@features/question/services/database/question'; diff --git a/src/addons/mod/quiz/services/quiz.ts b/src/addons/mod/quiz/services/quiz.ts index 4ee1a7711..c9693a201 100644 --- a/src/addons/mod/quiz/services/quiz.ts +++ b/src/addons/mod/quiz/services/quiz.ts @@ -99,7 +99,7 @@ export class AddonModQuizProvider { * @returns Grade to display. */ formatGrade(grade?: number | null, decimals?: number): string { - if (grade === undefined || grade == -1 || grade === null || isNaN(grade)) { + if (grade === undefined || grade === -1 || grade === null || isNaN(grade)) { return Translate.instant('addon.mod_quiz.notyetgraded'); } @@ -1800,7 +1800,7 @@ export class AddonModQuizProvider { ): string | undefined { let grade: number | undefined; - const rawGradeNum = typeof rawGrade == 'string' ? parseFloat(rawGrade) : rawGrade; + const rawGradeNum = typeof rawGrade === 'string' ? parseFloat(rawGrade) : rawGrade; if (rawGradeNum !== undefined && rawGradeNum !== null && !isNaN(rawGradeNum)) { if (quiz.sumgrades && quiz.sumgrades >= 0.000005) { grade = rawGradeNum * (quiz.grade ?? 0) / quiz.sumgrades; diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts index 1fb0ecfeb..3871a2798 100644 --- a/src/addons/mod/resource/components/index/index.ts +++ b/src/addons/mod/resource/components/index/index.ts @@ -143,7 +143,7 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource this.displayDescription = false; this.warning = downloadResult.failed - ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!) + ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error ?? '') : ''; return; diff --git a/src/addons/mod/resource/services/handlers/prefetch.ts b/src/addons/mod/resource/services/handlers/prefetch.ts index 3342cc474..93ab036a9 100644 --- a/src/addons/mod/resource/services/handlers/prefetch.ts +++ b/src/addons/mod/resource/services/handlers/prefetch.ts @@ -61,8 +61,8 @@ export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePr async downloadOrPrefetch(module: CoreCourseModuleData, courseId: number, prefetch?: boolean): Promise { let dirPath: string | undefined; - if (AddonModResourceHelper.isDisplayedInIframe(module)) { - dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!); + if (AddonModResourceHelper.isDisplayedInIframe(module) && module.url !== undefined) { + dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url); } const promises: Promise[] = []; diff --git a/src/addons/mod/resource/services/resource-helper.ts b/src/addons/mod/resource/services/resource-helper.ts index f1ee994ef..11e75b041 100644 --- a/src/addons/mod/resource/services/resource-helper.ts +++ b/src/addons/mod/resource/services/resource-helper.ts @@ -62,7 +62,7 @@ export class AddonModResourceHelperProvider { * @returns Promise resolved with the iframe src. */ async getIframeSrc(module: CoreCourseModuleData): Promise { - if (!module.contents?.length) { + if (!module.contents?.length || module.url === undefined) { throw new CoreError('No contents available in module'); } @@ -74,7 +74,7 @@ export class AddonModResourceHelperProvider { } try { - const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!); + const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url); // This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser. return CorePath.concatenatePaths(dirPath, mainFilePath); diff --git a/src/addons/mod/scorm/components/index/index.ts b/src/addons/mod/scorm/components/index/index.ts index 66ac6508c..f3c0a7307 100644 --- a/src/addons/mod/scorm/components/index/index.ts +++ b/src/addons/mod/scorm/components/index/index.ts @@ -59,7 +59,10 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom moduleName = 'scorm'; scorm?: AddonModScormScorm; // The SCORM object. - currentOrganization: Partial = {}; // Selected organization. + currentOrganization: Partial & { identifier: string} = { + identifier: '', + }; // Selected organization. + startNewAttempt = false; errorMessage?: string; // Error message. syncTime?: string; // Last sync time. @@ -70,7 +73,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom percentage?: string; // Download/unzip percentage. showPercentage = false; // Whether to show the percentage. progressMessage?: string; // Message about download/unzip. - organizations?: AddonModScormOrganization[]; // List of organizations. + organizations: AddonModScormOrganization[] = []; // List of organizations. loadingToc = false; // Whether the TOC is being loaded. toc?: AddonModScormTOCScoWithIcon[]; // Table of contents (structure). accessInfo?: AddonModScormGetScormAccessInformationWSResponse; // Access information. @@ -128,7 +131,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom try { await AddonModScormPrefetchHandler.download(this.module, this.courseId, undefined, (data) => { - if (!data) { + if (!data || !this.scorm) { return; } @@ -137,8 +140,8 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom if (data.downloading) { // Downloading package. - if (this.scorm!.packagesize && data.progress) { - const percentageNumber = Number(data.progress.loaded / this.scorm!.packagesize) * 100; + if (this.scorm.packagesize && data.progress) { + const percentageNumber = Number(data.progress.loaded / this.scorm.packagesize) * 100; this.percentage = percentageNumber.toFixed(1); this.showPercentage = percentageNumber >= 0 && percentageNumber <= 100; } @@ -198,7 +201,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom ( this.accessInfo.canskipview && !this.accessInfo.canviewreport && (this.scorm.skipview ?? 0) >= AddonModScormProvider.SKIPVIEW_FIRST && - (this.scorm.skipview == AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt == 0) + (this.scorm.skipview === AddonModScormProvider.SKIPVIEW_ALWAYS || this.lastAttempt === 0) ) ); } @@ -221,7 +224,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.lastAttempt = attempt.num; this.lastIsOffline = attempt.offline; - if (this.lastAttempt != this.attempts.lastAttempt.num) { + if (this.lastAttempt !== this.attempts.lastAttempt.num) { this.attemptToContinue = this.lastAttempt; } else { this.attemptToContinue = undefined; @@ -237,7 +240,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.gradeMethodReadable = AddonModScorm.getScormGradeMethod(scorm); this.attemptsLeft = AddonModScorm.countAttemptsLeft(scorm, this.attempts.lastAttempt.num); - if (scorm.forcenewattempt == AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS || + if (scorm.forcenewattempt === AddonModScormProvider.SCORM_FORCEATTEMPT_ALWAYS || (scorm.forcenewattempt && !this.incomplete)) { this.startNewAttempt = true; } @@ -272,13 +275,9 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom protected async fetchStructure(scorm: AddonModScormScorm): Promise { this.organizations = await AddonModScorm.getOrganizations(scorm.id, { cmId: this.module.id }); - if (!this.currentOrganization.identifier) { + if (this.currentOrganization.identifier === '' && this.organizations[0]?.identifier) { // Load first organization (if any). - if (this.organizations.length) { - this.currentOrganization.identifier = this.organizations[0].identifier; - } else { - this.currentOrganization.identifier = ''; - } + this.currentOrganization.identifier = this.organizations[0].identifier; } return this.loadOrganizationToc(scorm, this.currentOrganization.identifier); @@ -297,7 +296,11 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom offline: boolean, attempts: Record, ): Promise { - const grade = await AddonModScorm.getAttemptGrade(this.scorm!, attempt, offline); + if (!this.scorm) { + return; + } + + const grade = await AddonModScorm.getAttemptGrade(this.scorm, attempt, offline); attempts[attempt] = { num: attempt, @@ -438,8 +441,12 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * Load a organization's TOC. */ async loadOrganization(): Promise { + if (!this.scorm) { + return; + } + try { - await this.loadOrganizationToc(this.scorm!, this.currentOrganization.identifier!); + await this.loadOrganizationToc(this.scorm, this.currentOrganization.identifier); } catch (error) { CoreDomUtils.showErrorModalDefault(error, this.fetchContentDefaultError, true); } @@ -453,7 +460,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @returns Promise resolved when done. */ protected async loadOrganizationToc(scorm: AddonModScormScorm, organizationId: string): Promise { - if (!scorm.displaycoursestructure) { + if (!scorm.displaycoursestructure || this.lastAttempt === undefined) { // TOC is not displayed, no need to load it. return; } @@ -461,18 +468,17 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.loadingToc = true; try { - this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt!, this.incomplete, { + this.toc = await AddonModScormHelper.getToc(scorm.id, this.lastAttempt, this.incomplete, { organization: organizationId, offline: this.lastIsOffline, cmId: this.module.id, }); // Search organization title. - this.organizations!.forEach((org) => { - if (org.identifier == organizationId) { - this.currentOrganization.title = org.title; - } - }); + const organization = this.organizations.find((org) => org.identifier === organizationId); + if (organization) { + this.currentOrganization.title = organization.title; + } } finally { this.loadingToc = false; } @@ -486,20 +492,18 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @param scoId SCO that needs to be loaded when the SCORM is opened. If not defined, load first SCO. */ async open(event?: Event, preview: boolean = false, scoId?: number): Promise { - if (event) { - event.preventDefault(); - event.stopPropagation(); - } + event?.preventDefault(); + event?.stopPropagation(); - if (this.downloading) { + if (this.downloading || !this.scorm) { // Scope is being downloaded, abort. return; } - const isOutdated = this.currentStatus == CoreConstants.OUTDATED; - const scorm = this.scorm!; + const isOutdated = this.currentStatus === CoreConstants.OUTDATED; + const scorm = this.scorm; - if (!isOutdated && this.currentStatus != CoreConstants.NOT_DOWNLOADED) { + if (!isOutdated && this.currentStatus !== CoreConstants.NOT_DOWNLOADED) { // Already downloaded, open it. this.openScorm(scoId, preview); @@ -552,7 +556,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom this.dataSentObserver?.off(); this.dataSentObserver = CoreEvents.on(AddonModScormProvider.DATA_SENT_EVENT, (data) => { - if (data.scormId === this.scorm!.id) { + if (data.scormId === this.scorm?.id) { this.dataSent = true; if (this.module.completiondata && CoreCourse.isIncompleteAutomaticCompletion(this.module.completiondata)) { @@ -581,14 +585,14 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom */ protected async showStatus(status: string): Promise { - if (status == CoreConstants.OUTDATED && this.scorm) { + if (status === CoreConstants.OUTDATED && this.scorm) { // Only show the outdated message if the file should be downloaded. const download = await AddonModScorm.shouldDownloadMainFile(this.scorm, true); this.statusMessage = download ? 'addon.mod_scorm.scormstatusoutdated' : ''; - } else if (status == CoreConstants.NOT_DOWNLOADED) { + } else if (status === CoreConstants.NOT_DOWNLOADED) { this.statusMessage = 'addon.mod_scorm.scormstatusnotdownloaded'; - } else if (status == CoreConstants.DOWNLOADING) { + } else if (status === CoreConstants.DOWNLOADING) { if (!this.downloading) { // It's being downloaded right now but the view isn't tracking it. "Restore" the download. this.downloadScormPackage(); @@ -604,15 +608,19 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom * @param retries Number of retries done. * @returns Promise resolved when done. */ - protected async sync(retries = 0): Promise { - if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm!.id) && retries < 5) { + protected async sync(retries = 0): Promise { + if (!this.scorm) { + return; + } + + if (CoreSync.isBlocked(AddonModScormProvider.COMPONENT, this.scorm.id) && retries < 5) { // Sync is currently blocked, this can happen when SCORM player is left. Retry in a bit. await CoreUtils.wait(400); return this.sync(retries + 1); } - const result = await AddonModScormSync.syncScorm(this.scorm!); + const result = await AddonModScormSync.syncScorm(this.scorm); if (!result.updated && this.dataSent) { // The user sent data to server, but not in the sync process. Check if we need to fetch data. diff --git a/src/addons/mod/survey/components/index/index.ts b/src/addons/mod/survey/components/index/index.ts index 7fa09627b..788ed1a0d 100644 --- a/src/addons/mod/survey/components/index/index.ts +++ b/src/addons/mod/survey/components/index/index.ts @@ -130,17 +130,18 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo : await AddonModSurveyOffline.hasAnswers(this.survey.id); if (!this.survey.surveydone && !this.hasOffline) { - await this.fetchQuestions(); + await this.fetchQuestions(this.survey.id); } } /** * Convenience function to get survey questions. * + * @param surveyId Survey Id. * @returns Promise resolved when done. */ - protected async fetchQuestions(): Promise { - const questions = await AddonModSurvey.getQuestions(this.survey!.id, { cmId: this.module.id }); + protected async fetchQuestions(surveyId: number): Promise { + const questions = await AddonModSurvey.getQuestions(surveyId, { cmId: this.module.id }); this.questions = AddonModSurveyHelper.formatQuestions(questions); @@ -183,6 +184,10 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo * Save options selected. */ async submit(): Promise { + if (!this.survey) { + return; + } + let modal: CoreIonLoadingElement | undefined; try { @@ -198,7 +203,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo }); } - const online = await AddonModSurvey.submitAnswers(this.survey!.id, this.survey!.name, this.courseId, answers); + const online = await AddonModSurvey.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers); CoreEvents.trigger(CoreEvents.ACTIVITY_DATA_SENT, { module: this.moduleName }); @@ -235,8 +240,12 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo * * @returns Promise resolved when done. */ - protected sync(): Promise { - return AddonModSurveySync.syncSurvey(this.survey!.id, this.currentUserId); + protected async sync(): Promise { + if (!this.survey) { + return; + } + + return AddonModSurveySync.syncSurvey(this.survey.id, this.currentUserId); } /** diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index 0d69f7f19..0739552df 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -189,7 +189,11 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo */ go(): void { this.logView(); - AddonModUrlHelper.open(this.url!); + if (!this.url) { + return; + } + + AddonModUrlHelper.open(this.url); } } diff --git a/src/addons/mod/wiki/pages/edit/edit.ts b/src/addons/mod/wiki/pages/edit/edit.ts index 7e9500256..6c2c1b96a 100644 --- a/src/addons/mod/wiki/pages/edit/edit.ts +++ b/src/addons/mod/wiki/pages/edit/edit.ts @@ -45,8 +45,8 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { cmId?: number; // Course module ID. courseId?: number; // Course the wiki belongs to. title?: string; // Title to display. - pageForm?: FormGroup; // The form group. - contentControl?: FormControl; // The FormControl for the page content. + pageForm: FormGroup; // The form group. + contentControl: FormControl; // The FormControl for the page content. canEditTitle = false; // Whether title can be edited. loaded = false; // Whether the data has been loaded. component = AddonModWikiProvider.COMPONENT; // Component to link the files to. @@ -71,7 +71,10 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { constructor( protected formBuilder: FormBuilder, - ) { } + ) { + this.contentControl = this.formBuilder.control(''); + this.pageForm = this.formBuilder.group({}); + } /** * @inheritdoc @@ -96,10 +99,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { this.blockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); // Create the form group and its controls. - this.contentControl = this.formBuilder.control(''); - this.pageForm = this.formBuilder.group({ - title: pageTitle, - }); + this.pageForm.addControl('title', this.formBuilder.control(pageTitle)); this.pageForm.addControl('text', this.contentControl); // Block the wiki so it cannot be synced. @@ -121,7 +121,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { if (success && !this.isDestroyed) { // Block the subwiki now that we have blockId for sure. const newBlockId = AddonModWikiSync.getSubwikiBlockId(this.subwikiId, this.wikiId, this.userId, this.groupId); - if (newBlockId != this.blockId) { + if (newBlockId !== this.blockId) { CoreSync.unblockOperation(this.component, this.blockId); this.blockId = newBlockId; CoreSync.blockOperation(this.component, this.blockId); @@ -143,7 +143,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { try { // Wait for sync to be over (if any). - const syncResult = await AddonModWikiSync.waitForSync(this.blockId!); + const syncResult = this.blockId ? await AddonModWikiSync.waitForSync(this.blockId) : undefined; if (this.pageId) { // Editing a page that already exists. @@ -154,7 +154,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { // Get page contents to obtain title and editing permission const pageContents = await AddonModWiki.getPageContents(this.pageId, { cmId: this.cmId }); - this.pageForm!.controls.title.setValue(pageContents.title); // Set the title in the form group. + this.pageForm.controls.title.setValue(pageContents.title); // Set the title in the form group. this.wikiId = pageContents.wikiid; this.subwikiId = pageContents.subwikiid; this.title = Translate.instant('addon.mod_wiki.editingpage', { $a: pageContents.title }); @@ -177,7 +177,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { // Get the original page contents, treating file URLs if needed. const content = CoreTextUtils.replacePluginfileUrls(editContents.content || '', this.subwikiFiles); - this.contentControl!.setValue(content); + this.contentControl.setValue(content); this.originalContent = content; this.version = editContents.version; @@ -188,7 +188,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { }, AddonModWikiProvider.RENEW_LOCK_TIME); } } else { - const pageTitle = this.pageForm!.controls.title.value; + const pageTitle = this.pageForm.controls.title.value; this.editing = false; canEdit = !!this.blockId; // If no blockId, the user cannot edit the page. @@ -222,7 +222,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { if (page) { // Load offline content. - this.contentControl!.setValue(page.cachedcontent); + this.contentControl.setValue(page.cachedcontent); this.originalContent = page.cachedcontent; this.editOffline = true; } else { @@ -286,13 +286,17 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @param title Page title. */ protected goToPage(title: string): void { + if (!this.wikiId) { + return; + } + // Not the firstpage. AddonModWiki.setEditedPageData({ cmId: this.cmId, courseId: this.courseId, pageId: this.pageId, pageTitle: title, - wikiId: this.wikiId!, + wikiId: this.wikiId, subwikiId: this.subwikiId, userId: this.userId, groupId: this.groupId, @@ -307,7 +311,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @returns Whether data has changed. */ protected hasDataChanged(): boolean { - const values = this.pageForm!.value; + const values = this.pageForm.value; return !(this.originalContent == values.text || (!this.editing && !values.text && !values.title)); } @@ -348,7 +352,7 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * @returns Promise resolved when done. */ async save(): Promise { - const values = this.pageForm!.value; + const values = this.pageForm.value; const title = values.title; let text = values.text; @@ -358,14 +362,14 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { text = CoreTextUtils.formatHtmlLines(text); try { - if (this.editing) { + if (this.editing && this.pageId) { // Edit existing page. - await AddonModWiki.editPage(this.pageId!, text, this.section); + await AddonModWiki.editPage(this.pageId, text, this.section); CoreForms.triggerFormSubmittedEvent(this.formElement, true, CoreSites.getCurrentSiteId()); // Invalidate page since it changed. - await AddonModWiki.invalidatePage(this.pageId!); + await AddonModWiki.invalidatePage(this.pageId); return this.goToPage(title); } @@ -451,7 +455,11 @@ export class AddonModWikiEditPage implements OnInit, OnDestroy, CanLeave { * Renew lock and control versions. */ protected async renewLock(): Promise { - const response = await AddonModWiki.getPageForEditing(this.pageId!, this.section, true); + if (!this.pageId) { + return; + } + + const response = await AddonModWiki.getPageForEditing(this.pageId, this.section, true); if (response.version && this.version != response.version) { this.wrongVersionLock = true; diff --git a/src/addons/mod/wiki/services/handlers/create-link.ts b/src/addons/mod/wiki/services/handlers/create-link.ts index 544808831..c25c37723 100644 --- a/src/addons/mod/wiki/services/handlers/create-link.ts +++ b/src/addons/mod/wiki/services/handlers/create-link.ts @@ -103,6 +103,10 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle try { const route = CoreNavigator.getCurrentRoute({ pageComponent: AddonModWikiIndexPage }); + if (!route) { + // Current view isn't wiki index. + return; + } const subwikiId = parseInt(params.swid, 10); const wikiId = parseInt(params.wid, 10); let path = AddonModWikiModuleHandlerService.PAGE_NAME; @@ -112,7 +116,7 @@ export class AddonModWikiCreateLinkHandlerService extends CoreContentLinksHandle if (isSameWiki) { // User is seeing the wiki, we can get the module from the wiki params. - path = path + `/${route!.snapshot.params.courseId}/${route!.snapshot.params.cmId}/edit`; + path = path + `/${route.snapshot.params.courseId}/${route.snapshot.params.cmId}/edit`; } else if (wikiId) { // The URL specifies which wiki it belongs to. Get the module. const module = await CoreCourse.getModuleBasicInfoByInstance( diff --git a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts index e749b2da6..e2ea393c4 100644 --- a/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts +++ b/src/addons/mod/workshop/components/assessment-strategy/assessment-strategy.ts @@ -162,6 +162,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe cmId: this.workshop.coursemodule, }); + if (!this.data.assessment.form) { + return; + } + if (this.edit) { try { const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop.id, this.assessmentId); @@ -176,7 +180,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe } // Override assessment plugins values. - this.data.assessment.form!.current = AddonModWorkshop.parseFields( + this.data.assessment.form.current = AddonModWorkshop.parseFields( CoreUtils.objectToArrayOfObjects(offlineData, 'name', 'value'), ); @@ -221,7 +225,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe try { this.data.selectedValues = await AddonWorkshopAssessmentStrategyDelegate.getOriginalValues( this.strategy, - this.data.assessment.form!, + this.data.assessment.form, this.workshop.id, ); } finally { @@ -245,7 +249,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe * @returns True if data has changed. */ hasDataChanged(): boolean { - if (!this.assessmentStrategyLoaded) { + if (!this.assessmentStrategyLoaded || !this.workshop.strategy) { return false; } @@ -269,7 +273,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe } return AddonWorkshopAssessmentStrategyDelegate.hasDataChanged( - this.workshop.strategy!, + this.workshop.strategy, this.originalData.selectedValues, this.data.selectedValues, ); @@ -281,6 +285,10 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe * @returns Promise resolved when done, rejected if assessment could not be saved. */ async saveAssessment(): Promise { + if (!this.data.assessment?.form) { + return; + } + const files = CoreFileSession.getFiles( AddonModWorkshopProvider.COMPONENT, this.workshop.id + '_' + this.assessmentId, @@ -328,7 +336,7 @@ export class AddonModWorkshopAssessmentStrategyComponent implements OnInit, OnDe this.workshop, this.data.selectedValues, text, - this.data.assessment!.form!, + this.data.assessment.form, attachmentsId, ); } catch (errors) { diff --git a/src/addons/mod/workshop/components/index/index.ts b/src/addons/mod/workshop/components/index/index.ts index 035c4ebfe..70933bd6f 100644 --- a/src/addons/mod/workshop/components/index/index.ts +++ b/src/addons/mod/workshop/components/index/index.ts @@ -14,6 +14,7 @@ import { Component, Input, OnDestroy, OnInit, Optional } from '@angular/core'; import { Params } from '@angular/router'; +import { CoreError } from '@classes/errors/error'; import { CoreCourseModuleMainActivityComponent } from '@features/course/classes/main-activity-component'; import { CoreCourseContentsPage } from '@features/course/pages/contents/contents'; import { IonContent } from '@ionic/angular'; @@ -264,7 +265,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @returns Resolved when done. */ async gotoSubmissionsPage(page: number): Promise { - const report = await AddonModWorkshop.getGradesReport(this.workshop!.id, { + if (!this.workshop) { + return; + } + const workshop = this.workshop; + + const report = await AddonModWorkshop.getGradesReport(workshop.id, { groupId: this.group, page, cmId: this.module.id, @@ -284,7 +290,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity await Promise.all(grades.map(async (grade) => { const submission: AddonModWorkshopSubmissionDataWithOfflineData = { id: grade.submissionid, - workshopid: this.workshop!.id, + workshopid: workshop.id, example: false, authorid: grade.userid, timecreated: grade.submissionmodified, @@ -303,7 +309,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity reviewerof: this.parseReviewer(grade.reviewerof), }; - if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) { + if (workshop.phase == AddonModWorkshopPhase.PHASE_ASSESSMENT) { submission.reviewedbydone = grade.reviewedby?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0; submission.reviewerofdone = grade.reviewerof?.reduce((a, b) => a + (b.grade ? 1 : 0), 0) || 0; submission.reviewedbycount = grade.reviewedby?.length || 0; @@ -313,7 +319,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity const offlineData = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions); if (offlineData !== undefined) { - this.grades!.push(offlineData); + this.grades.push(offlineData); } })); } @@ -358,8 +364,12 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * Go to submit page. */ gotoSubmit(): void { - if (this.canSubmit && ((this.access!.creatingsubmissionallowed && !this.submission) || - (this.access!.modifyingsubmissionallowed && this.submission))) { + if (!this.canSubmit || !this.access) { + return; + } + + if ((this.access.creatingsubmissionallowed && !this.submission) || + (this.access.modifyingsubmissionallowed && this.submission)) { const params: Params = { module: this.module, access: this.access, @@ -378,20 +388,22 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * View Phase info. */ async viewPhaseInfo(): Promise { - if (this.phases) { - const modalData = await CoreDomUtils.openModal({ - component: AddonModWorkshopPhaseInfoComponent, - componentProps: { - phases: CoreUtils.objectToArray(this.phases), - workshopPhase: this.workshop!.phase, - externalUrl: this.module.url, - showSubmit: this.showSubmit, - }, - }); + if (!this.phases || !this.workshop) { + return; + } - if (modalData === true) { - this.gotoSubmit(); - } + const modalData = await CoreDomUtils.openModal({ + component: AddonModWorkshopPhaseInfoComponent, + componentProps: { + phases: CoreUtils.objectToArray(this.phases), + workshopPhase: this.workshop.phase, + externalUrl: this.module.url, + showSubmit: this.showSubmit, + }, + }); + + if (modalData === true) { + this.gotoSubmit(); } } @@ -413,26 +425,32 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @returns Promise resolved when done. */ protected async setPhaseInfo(): Promise { + if (!this.phases || !this.workshop || !this.access) { + return; + } + this.submission = undefined; this.canAssess = false; this.assessments = []; this.userGrades = undefined; this.publishedSubmissions = []; + const workshop = this.workshop; + this.canSubmit = AddonModWorkshopHelper.canSubmit( - this.workshop!, - this.access!, - this.phases![AddonModWorkshopPhase.PHASE_SUBMISSION].tasks, + this.workshop, + this.access, + this.phases[AddonModWorkshopPhase.PHASE_SUBMISSION].tasks, ); this.showSubmit = this.canSubmit && - ((this.access!.creatingsubmissionallowed && !this.submission) || - (this.access!.modifyingsubmissionallowed && !!this.submission)); + ((this.access.creatingsubmissionallowed && !this.submission) || + (this.access.modifyingsubmissionallowed && !!this.submission)); const promises: Promise[] = []; if (this.canSubmit) { - promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop!.id, { cmId: this.module.id }) + promises.push(AddonModWorkshopHelper.getUserSubmission(this.workshop.id, { cmId: this.module.id }) .then(async (submission) => { this.submission = await AddonModWorkshopHelper.applyOfflineData(submission, this.offlineSubmissions); @@ -440,27 +458,27 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity })); } - if (this.access!.canviewallsubmissions && this.workshop!.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { + if (this.access.canviewallsubmissions && this.workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) { promises.push(this.gotoSubmissionsPage(this.page)); } let assessPromise = Promise.resolve(); - if (this.workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) { - this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop!, this.access!); + if (this.workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT) { + this.canAssess = AddonModWorkshopHelper.canAssess(this.workshop, this.access); if (this.canAssess) { - assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop!.id, { + assessPromise = AddonModWorkshopHelper.getReviewerAssessments(this.workshop.id, { cmId: this.module.id, }).then(async (assessments) => { await Promise.all(assessments.map(async (assessment) => { - assessment.strategy = this.workshop!.strategy; + assessment.strategy = workshop.strategy; if (!this.hasOffline) { return; } try { - const offlineAssessment = await AddonModWorkshopOffline.getAssessment(this.workshop!.id, assessment.id); + const offlineAssessment = await AddonModWorkshopOffline.getAssessment(workshop.id, assessment.id); assessment.offline = true; assessment.timemodified = Math.floor(offlineAssessment.timemodified / 1000); @@ -477,27 +495,23 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity } } - if (this.workshop!.phase == AddonModWorkshopPhase.PHASE_CLOSED) { - promises.push(AddonModWorkshop.getGrades(this.workshop!.id, { cmId: this.module.id }).then((grades) => { + if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) { + promises.push(AddonModWorkshop.getGrades(this.workshop.id, { cmId: this.module.id }).then((grades) => { this.userGrades = grades.submissionlongstrgrade || grades.assessmentlongstrgrade ? grades : undefined; return; })); - if (this.access!.canviewpublishedsubmissions) { + if (this.access.canviewpublishedsubmissions) { promises.push(assessPromise.then(async () => { const submissions: AddonModWorkshopSubmissionDataWithOfflineData[] = - await AddonModWorkshop.getSubmissions(this.workshop!.id, { cmId: this.module.id }); + await AddonModWorkshop.getSubmissions(workshop.id, { cmId: this.module.id }); this.publishedSubmissions = submissions.filter((submission) => { if (submission.published) { - submission.reviewedby = []; - - this.assessments.forEach((assessment) => { - if (assessment.submissionid == submission.id) { - submission.reviewedby!.push(AddonModWorkshopHelper.realGradeValue(this.workshop!, assessment)); - } - }); + submission.reviewedby = + this.assessments.filter((assessment) => assessment.submissionid === submission.id) + .map((assessment => AddonModWorkshopHelper.realGradeValue(workshop, assessment))); return true; } @@ -519,7 +533,11 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity * @returns Promise resolved when done. */ protected sync(): Promise { - return AddonModWorkshopSync.syncWorkshop(this.workshop!.id); + if (!this.workshop) { + throw new CoreError('Cannot sync without a workshop.'); + } + + return AddonModWorkshopSync.syncWorkshop(this.workshop.id); } /** diff --git a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts index 0878cdbce..d83fd9d4c 100644 --- a/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts +++ b/src/addons/mod/workshop/pages/edit-submission/edit-submission.ts @@ -385,11 +385,14 @@ export class AddonModWorkshopEditSubmissionPage implements OnInit, OnDestroy, Ca ); newSubmissionId = false; } else { + if (!submissionId) { + throw new CoreError('Submission cannot be updated without a submissionId'); + } // Try to send it to server. // Don't allow offline if there are attachments since they were uploaded fine. newSubmissionId = await AddonModWorkshop.updateSubmission( this.workshopId, - submissionId!, + submissionId, this.courseId, inputData.title, inputData.content, diff --git a/src/addons/mod/workshop/pages/submission/submission.ts b/src/addons/mod/workshop/pages/submission/submission.ts index 6534c0cbd..711c20fb5 100644 --- a/src/addons/mod/workshop/pages/submission/submission.ts +++ b/src/addons/mod/workshop/pages/submission/submission.ts @@ -254,7 +254,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea return; })); - } else if (this.currentUserId == this.userId && this.assessmentId) { + } else if (this.currentUserId === this.userId && this.assessmentId) { // Get new data, different that came from stateParams. promises.push(AddonModWorkshop.getAssessment(this.workshopId, this.assessmentId, { cmId: this.module.id, @@ -268,7 +268,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea return; })); - } else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.userId == this.currentUserId) { + } else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.userId === this.currentUserId) { const assessments = await AddonModWorkshop.getSubmissionAssessments(this.workshopId, this.submissionId, { cmId: this.module.id, }); @@ -276,7 +276,7 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea this.submissionInfo.reviewedby = assessments.map((assessment) => this.parseAssessment(assessment)); } - if (this.canAddFeedback || this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) { + if (this.canAddFeedback || this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED) { this.evaluate = { published: this.submission.published, text: this.submission.feedbackauthor || '', @@ -284,54 +284,13 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea } if (this.canAddFeedback) { - if (!this.isDestroyed) { // Block the workshop. CoreSync.blockOperation(this.component, this.workshopId); } - const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden'); - - promises.push(CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1) - .then(async (grades) => { - this.evaluationGrades = grades; - - this.evaluate!.grade = { - label: CoreGradesHelper.getGradeLabelFromValue(grades, this.submissionInfo.gradeover) || - defaultGrade, - value: this.submissionInfo.gradeover || -1, - }; - - try { - const offlineSubmission = - await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId); - - this.hasOffline = true; - this.evaluate!.published = offlineSubmission.published; - this.evaluate!.text = offlineSubmission.feedbacktext; - this.evaluate!.grade = { - label: CoreGradesHelper.getGradeLabelFromValue( - grades, - parseInt(offlineSubmission.gradeover, 10), - ) || defaultGrade, - value: offlineSubmission.gradeover || -1, - }; - } catch { - // Ignore errors. - this.hasOffline = false; - } finally { - this.originalEvaluation.published = this.evaluate!.published; - this.originalEvaluation.text = this.evaluate!.text; - this.originalEvaluation.grade = this.evaluate!.grade.value; - - this.feedbackForm.controls['published'].setValue(this.evaluate!.published); - this.feedbackForm.controls['grade'].setValue(this.evaluate!.grade.value); - this.feedbackForm.controls['text'].setValue(this.evaluate!.text); - } - - return; - })); - } else if (this.workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby && + promises.push(this.fillEvaluationsGrades()); + } else if (this.workshop.phase === AddonModWorkshopPhase.PHASE_CLOSED && this.submission.gradeoverby && this.evaluate && this.evaluate.text) { promises.push(CoreUser.getProfile(this.submission.gradeoverby, this.courseId, true).then((profile) => { this.evaluateByProfile = profile; @@ -362,6 +321,49 @@ export class AddonModWorkshopSubmissionPage implements OnInit, OnDestroy, CanLea } } + /** + * Fill evaluation grade info. + */ + protected async fillEvaluationsGrades(): Promise { + const defaultGrade = Translate.instant('addon.mod_workshop.notoverridden'); + + this.evaluationGrades = await CoreGradesHelper.makeGradesMenu(this.workshop.grade || 0, undefined, defaultGrade, -1); + + if (!this.evaluate) { + // Should not happen. + return; + } + + this.evaluate.grade = { + label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, this.submissionInfo.gradeover) || defaultGrade, + value: this.submissionInfo.gradeover || -1, + }; + + try { + const offlineSubmission = await AddonModWorkshopOffline.getEvaluateSubmission(this.workshopId, this.submissionId); + + this.hasOffline = true; + this.evaluate.published = offlineSubmission.published; + this.evaluate.text = offlineSubmission.feedbacktext; + this.evaluate.grade = { + label: CoreGradesHelper.getGradeLabelFromValue(this.evaluationGrades, parseInt(offlineSubmission.gradeover, 10)) || + defaultGrade, + value: offlineSubmission.gradeover || -1, + }; + } catch { + // Ignore errors. + this.hasOffline = false; + } finally { + this.originalEvaluation.published = this.evaluate.published; + this.originalEvaluation.text = this.evaluate.text; + this.originalEvaluation.grade = this.evaluate.grade.value; + + this.feedbackForm.controls['published'].setValue(this.evaluate.published); + this.feedbackForm.controls['grade'].setValue(this.evaluate.grade.value); + this.feedbackForm.controls['text'].setValue(this.evaluate.text); + } + } + /** * Parse assessment to be shown. * diff --git a/src/addons/mod/workshop/services/handlers/prefetch.ts b/src/addons/mod/workshop/services/handlers/prefetch.ts index 1b62c5f55..2da580e75 100644 --- a/src/addons/mod/workshop/services/handlers/prefetch.ts +++ b/src/addons/mod/workshop/services/handlers/prefetch.ts @@ -71,7 +71,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr ): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> { let groups: CoreGroup[] = []; let files: CoreWSFile[] = []; - let workshop: AddonModWorkshopData | undefined; + let workshop: AddonModWorkshopData; let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined; const modOptions = { @@ -79,11 +79,25 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr ...options, // Include all options. }; - try { - const site = await CoreSites.getSite(options.siteId); - const userId = site.getUserId(); - const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); + const site = await CoreSites.getSite(options.siteId); + options.siteId = options.siteId ?? site.getId(); + const userId = site.getUserId(); + try { + workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options); + } catch (error) { + if (options.omitFail) { + // Any error, return the info we have. + return { + groups: [], + files: [], + }; + } + + throw error; + } + + try { files = this.getIntroFilesFromInstance(module, workshop); files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []); @@ -124,7 +138,7 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr await Promise.all(submissions.map(async (submission) => { files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []); - const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop!.id, submission.id, { + const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop.id, submission.id, { cmId: module.id, }); @@ -133,9 +147,9 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr .concat(assessment.feedbackcontentfiles); }); - if (workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { + if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) { await Promise.all(assessments.map((assessment) => - AddonModWorkshopHelper.getReviewerAssessmentById(workshop!.id, assessment.id))); + AddonModWorkshopHelper.getReviewerAssessmentById(workshop.id, assessment.id))); } })); @@ -267,7 +281,12 @@ export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPr // Prefetch the workshop data. const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions); - const workshop = info.workshop!; + if (!info.workshop) { + // It would throw an exception so it would not happen. + return; + } + + const workshop = info.workshop; const promises: Promise[] = []; const assessmentIds: number[] = []; diff --git a/src/addons/mod/workshop/services/workshop-helper.ts b/src/addons/mod/workshop/services/workshop-helper.ts index aefb60346..f73414544 100644 --- a/src/addons/mod/workshop/services/workshop-helper.ts +++ b/src/addons/mod/workshop/services/workshop-helper.ts @@ -181,7 +181,7 @@ export class AddonModWorkshopHelperProvider { assessment = await AddonModWorkshop.getAssessment(workshopId, assessmentId, options); } catch (error) { const assessments = await AddonModWorkshop.getReviewerAssessments(workshopId, options); - assessment = assessments.find((assessment_1) => assessment_1.id == assessmentId); + assessment = assessments.find((ass) => ass.id === assessmentId); if (!assessment) { throw error; @@ -266,9 +266,7 @@ export class AddonModWorkshopHelperProvider { * Upload or store some files for a submission, depending if the user is offline or not. * * @param workshopId Workshop ID. - * @param submissionId If not editing, it will refer to timecreated. * @param files List of files. - * @param editing If the submission is being edited or added otherwise. * @param offline True if files sould be stored for offline, false to upload them. * @param siteId Site ID. If not defined, current site. * @returns Promise resolved if success. @@ -451,30 +449,26 @@ export class AddonModWorkshopHelperProvider { * @returns Promise resolved with the files. */ async applyOfflineData( - submission?: AddonModWorkshopSubmissionDataWithOfflineData, + submission: AddonModWorkshopSubmissionDataWithOfflineData = { + id: 0, + workshopid: 0, + title: '', + content: '', + timemodified: 0, + example: false, + authorid: 0, + timecreated: 0, + contenttrust: 0, + attachment: 0, + published: false, + late: 0, + }, actions: AddonModWorkshopOfflineSubmission[] = [], ): Promise { - if (actions.length == 0) { + if (actions.length === 0) { return submission; } - if (submission === undefined) { - submission = { - id: 0, - workshopid: 0, - title: '', - content: '', - timemodified: 0, - example: false, - authorid: 0, - timecreated: 0, - contenttrust: 0, - attachment: 0, - published: false, - late: 0, - }; - } - let attachmentsId: CoreFileUploaderStoreFilesResult | undefined; const workshopId = actions[0].workshopid; @@ -482,17 +476,17 @@ export class AddonModWorkshopHelperProvider { switch (action.action) { case AddonModWorkshopAction.ADD: case AddonModWorkshopAction.UPDATE: - submission!.title = action.title; - submission!.content = action.content; - submission!.title = action.title; - submission!.courseid = action.courseid; - submission!.submissionmodified = action.timemodified / 1000; - submission!.offline = true; + submission.title = action.title; + submission.content = action.content; + submission.title = action.title; + submission.courseid = action.courseid; + submission.submissionmodified = action.timemodified / 1000; + submission.offline = true; attachmentsId = action.attachmentsid as CoreFileUploaderStoreFilesResult; break; case AddonModWorkshopAction.DELETE: - submission!.deleted = true; - submission!.submissionmodified = action.timemodified / 1000; + submission.deleted = true; + submission.submissionmodified = action.timemodified / 1000; break; default: } @@ -534,7 +528,8 @@ export class AddonModWorkshopHelperProvider { } const data = - (await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy!, selectedValues, form)) || {}; + (await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy ?? '', selectedValues, form)) || + {}; data.feedbackauthor = feedbackText; data.feedbackauthorattachmentsid = attachmentsId; data.nodims = form.dimenssionscount; @@ -551,16 +546,16 @@ export class AddonModWorkshopHelperProvider { * @returns Real grade formatted. */ protected realGradeValueHelper(value?: number | string, max = 0, decimals = 0): string | undefined { - if (typeof value == 'string') { + if (typeof value === 'string') { // Already treated. return value; } - if (value == null || value === undefined) { + if (value === null || value === undefined) { return undefined; } - if (max == 0) { + if (max === 0) { return '0'; } diff --git a/src/addons/mod/workshop/services/workshop.ts b/src/addons/mod/workshop/services/workshop.ts index 1db0888c5..26c14809d 100644 --- a/src/addons/mod/workshop/services/workshop.ts +++ b/src/addons/mod/workshop/services/workshop.ts @@ -610,13 +610,16 @@ export class AddonModWorkshopProvider { grades: AddonModWorkshopGradesData[], options: AddonModWorkshopGetGradesReportOptions = {}, ): Promise { + options.page = options.page ?? 0; + options.perPage = options.perPage ?? AddonModWorkshopProvider.PER_PAGE; + const report = await this.getGradesReport(workshopId, options); Array.prototype.push.apply(grades, report.grades); - const canLoadMore = ((options.page! + 1) * options.perPage!) < report.totalcount; + const canLoadMore = ((options.page + 1) * options.perPage) < report.totalcount; if (canLoadMore) { - options.page!++; + options.page++; return this.fetchGradeReportsRecursive(workshopId, grades, options); } @@ -778,7 +781,11 @@ export class AddonModWorkshopProvider { // Other errors ocurring. CoreWS.throwOnFailedStatus(response, 'Add submission failed'); - return response.submissionid!; + if (!response.submissionid) { + throw new CoreError('Add submission failed, no submission id was returned'); + } + + return response.submissionid; } /** diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 2218e4001..b3887aa41 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -145,7 +145,7 @@ export function conditionalRoutes(routes: Routes, condition: () => boolean): Rou return { ...newRoute, - matcher: buildConditionalUrlMatcher(matcher || path!, condition), + matcher: buildConditionalUrlMatcher(matcher || path || '', condition), }; }); } diff --git a/src/core/classes/queue-runner.ts b/src/core/classes/queue-runner.ts index 7c09686e6..cb3536054 100644 --- a/src/core/classes/queue-runner.ts +++ b/src/core/classes/queue-runner.ts @@ -91,7 +91,12 @@ export class CoreQueueRunner { return; } - const item = this.orderedQueue.shift()!; + const item = this.orderedQueue.shift(); + if (!item) { + // No item found. + return; + } + this.numberRunning++; try { diff --git a/src/core/classes/sqlitedb.ts b/src/core/classes/sqlitedb.ts index ba21b4733..e25b6a85c 100644 --- a/src/core/classes/sqlitedb.ts +++ b/src/core/classes/sqlitedb.ts @@ -323,7 +323,7 @@ export class SQLiteDB { async close(): Promise { await this.ready(); - await this.db!.close(); + await this.db?.close(); } /** @@ -501,7 +501,7 @@ export class SQLiteDB { async execute(sql: string, params?: SQLiteDBRecordValue[]): Promise { await this.ready(); - return this.db!.executeSql(sql, params); + return this.db?.executeSql(sql, params); } /** @@ -516,7 +516,7 @@ export class SQLiteDB { async executeBatch(sqlStatements: (string | string[] | any)[]): Promise { await this.ready(); - await this.db!.sqlBatch(sqlStatements); + await this.db?.sqlBatch(sqlStatements); } /** @@ -544,7 +544,7 @@ export class SQLiteDB { * @returns List of params. */ protected formatDataToSQLParams(data: SQLiteDBRecordValues): SQLiteDBRecordValue[] { - return Object.keys(data).map((key) => data[key]!); + return Object.values(data).map((value) => value as SQLiteDBRecordValue); } /** @@ -958,7 +958,7 @@ export class SQLiteDB { async open(): Promise { await this.ready(); - await this.db!.open(); + await this.db?.open(); } /** diff --git a/src/core/classes/tests/error.test.ts b/src/core/classes/tests/error.test.ts index f3c4865fc..8932a6f30 100644 --- a/src/core/classes/tests/error.test.ts +++ b/src/core/classes/tests/error.test.ts @@ -24,7 +24,7 @@ describe('CoreError', () => { // Arrange const message = Faker.lorem.sentence(); - let error: CoreError | null = null; + let error: CoreError; // Act try { @@ -37,10 +37,10 @@ describe('CoreError', () => { expect(error).not.toBeNull(); expect(error).toBeInstanceOf(Error); expect(error).toBeInstanceOf(CoreError); - expect(error!.name).toEqual('CoreError'); - expect(error!.message).toEqual(message); - expect(error!.stack).not.toBeNull(); - expect(error!.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts')); + expect(error.name).toEqual('CoreError'); + expect(error.message).toEqual(message); + expect(error.stack).not.toBeNull(); + expect(error.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts')); }); it('can be subclassed', () => { @@ -55,7 +55,7 @@ describe('CoreError', () => { const message = Faker.lorem.sentence(); - let error: CustomCoreError | null = null; + let error: CustomCoreError; // Act try { @@ -69,10 +69,10 @@ describe('CoreError', () => { expect(error).toBeInstanceOf(Error); expect(error).toBeInstanceOf(CoreError); expect(error).toBeInstanceOf(CustomCoreError); - expect(error!.name).toEqual('CustomCoreError'); - expect(error!.message).toEqual(`Custom message: ${message}`); - expect(error!.stack).not.toBeNull(); - expect(error!.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts')); + expect(error.name).toEqual('CustomCoreError'); + expect(error.message).toEqual(`Custom message: ${message}`); + expect(error.stack).not.toBeNull(); + expect(error.stack).toContain(agnosticPath('src/core/classes/tests/error.test.ts')); }); }); diff --git a/src/core/components/attachments/attachments.ts b/src/core/components/attachments/attachments.ts index 1b19d8e31..97999dd16 100644 --- a/src/core/components/attachments/attachments.ts +++ b/src/core/components/attachments/attachments.ts @@ -46,7 +46,7 @@ import { CoreUtils } from '@services/utils/utils'; }) export class CoreAttachmentsComponent implements OnInit { - @Input() files?: CoreFileEntry[]; // List of attachments. New attachments will be added to this array. + @Input() files: CoreFileEntry[] = []; // List of attachments. New attachments will be added to this array. @Input() maxSize?: number; // Max size. -1 means unlimited, 0 means course/user max size, not defined means unknown. @Input() maxSubmissions?: number; // Max number of attachments. -1 means unlimited, not defined means unknown limit. @Input() component?: string; // Component the downloaded files will be linked to. @@ -177,7 +177,7 @@ export class CoreAttachmentsComponent implements OnInit { * @param data The data received. */ renamed(index: number, data: { file: FileEntry }): void { - this.files![index] = data.file; + this.files[index] = data.file; } } diff --git a/src/core/components/chart/chart.ts b/src/core/components/chart/chart.ts index 0b3c86000..8f181e239 100644 --- a/src/core/components/chart/chart.ts +++ b/src/core/components/chart/chart.ts @@ -71,11 +71,11 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { generateLabels: (chart: Chart): ChartLegendLabelItem[] => { const data = chart.data; if (data.labels?.length) { - const datasets = data.datasets![0]; + const datasets = data.datasets?.[0]; - return data.labels.map((label, i) => ({ - text: label + ': ' + datasets.data![i], - fillStyle: datasets.backgroundColor![i], + return data.labels.map((label, i) => ({ + text: label + ': ' + datasets?.data?.[i], + fillStyle: datasets?.backgroundColor?.[i], })); } @@ -87,14 +87,18 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { legend = Object.assign({}, this.legend); } - if (this.type == 'bar' && this.data.length >= 5) { + if (this.type === 'bar' && this.data.length >= 5) { this.type = 'horizontalBar'; } // Format labels if needed. await this.formatLabels(); - const context = this.canvas!.nativeElement.getContext('2d')!; + const context = this.canvas?.nativeElement.getContext('2d'); + if (!context) { + return; + } + this.chart = new Chart(context, { type: this.type, data: { @@ -123,7 +127,11 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { await this.formatLabels(); } - this.chart.data.datasets![0] = { + if (!this.chart.data.datasets) { + this.chart.data.datasets = []; + } + + this.chart.data.datasets[0] = { data: this.data, backgroundColor: this.getRandomColors(this.data.length), }; diff --git a/src/core/components/file/file.ts b/src/core/components/file/file.ts index d15e5e9e4..a9c331708 100644 --- a/src/core/components/file/file.ts +++ b/src/core/components/file/file.ts @@ -149,24 +149,30 @@ export class CoreFileComponent implements OnInit, OnDestroy { * @param isOpenButton Whether the open button was clicked. * @returns Promise resolved when file is opened. */ - openFile(ev?: Event, isOpenButton = false): Promise { + async openFile(ev?: Event, isOpenButton = false): Promise { ev?.preventDefault(); ev?.stopPropagation(); + if (!this.file) { + return; + } + const options: CoreUtilsOpenFileOptions = {}; if (isOpenButton) { // Use the non-default method. options.iOSOpenFileAction = this.defaultIsOpenWithPicker ? OpenFileAction.OPEN : OpenFileAction.OPEN_WITH; } - return CoreFileHelper.downloadAndOpenFile(this.file!, this.component, this.componentId, this.state, (event) => { - if (event && 'calculating' in event && event.calculating) { - // The process is calculating some data required for the download, show the spinner. - this.isDownloading = true; - } - }, undefined, options).catch((error) => { + try { + return await CoreFileHelper.downloadAndOpenFile(this.file, this.component, this.componentId, this.state, (event) => { + if (event && 'calculating' in event && event.calculating) { + // The process is calculating some data required for the download, show the spinner. + this.isDownloading = true; + } + }, undefined, options); + } catch (error) { CoreDomUtils.showErrorModalDefault(error, 'core.errordownloading', true); - }); + } } /** @@ -264,7 +270,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { } /** - * Component destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.observer?.off(); diff --git a/src/core/components/files/files.ts b/src/core/components/files/files.ts index 3194a45f7..7aefe9640 100644 --- a/src/core/components/files/files.ts +++ b/src/core/components/files/files.ts @@ -30,7 +30,7 @@ import { CoreUtils } from '@services/utils/utils'; }) export class CoreFilesComponent implements OnInit, DoCheck { - @Input() files?: CoreFileEntry[]; // List of files. + @Input() files: CoreFileEntry[] = []; // List of files. @Input() component?: string; // Component the downloaded files will be linked to. @Input() componentId?: string | number; // Component ID. @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded. @@ -75,7 +75,7 @@ export class CoreFilesComponent implements OnInit, DoCheck { * Calculate contentText based on fils that can be rendered inline. */ protected renderInlineFiles(): void { - this.contentText = this.files!.reduce((previous, file) => { + this.contentText = this.files.reduce((previous, file) => { const text = CoreMimetypeUtils.getEmbeddedHtml(file); return text ? previous + '
' + text : previous; diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index abca069ea..85d454de1 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -28,10 +28,10 @@ import { CorePath } from '@singletons/path'; }) export class CoreRecaptchaComponent implements OnInit { - @Input() model?: Record; // The model where to store the recaptcha response. + @Input() model: Record = {}; // The model where to store the recaptcha response. @Input() publicKey?: string; // The site public key. @Input() modelValueName = 'recaptcharesponse'; // Name of the model property where to store the response. - @Input() siteUrl?: string; // The site URL. If not defined, current site. + @Input() siteUrl = ''; // The site URL. If not defined, current site. expired = false; @@ -45,7 +45,7 @@ export class CoreRecaptchaComponent implements OnInit { * @inheritdoc */ ngOnInit(): void { - this.siteUrl = this.siteUrl || CoreSites.getCurrentSite()?.getURL(); + this.siteUrl = this.siteUrl || CoreSites.getRequiredCurrentSite().getURL(); } /** @@ -62,7 +62,7 @@ export class CoreRecaptchaComponent implements OnInit { // Open the recaptcha challenge in an InAppBrowser. // The app used to use an iframe for this, but the app can no longer access the iframe to create the required callbacks. // The app cannot render the recaptcha directly because it has problems with the local protocols and domains. - const src = CorePath.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang); + const src = CorePath.concatenatePaths(this.siteUrl, 'webservice/recaptcha.php?lang=' + this.lang); const inAppBrowserWindow = CoreUtils.openInApp(src); if (!inAppBrowserWindow) { @@ -90,7 +90,7 @@ export class CoreRecaptchaComponent implements OnInit { this.expireRecaptchaAnswer(); } else if (event.data.action == 'callback') { this.expired = false; - this.model![this.modelValueName] = event.data.value; + this.model[this.modelValueName] = event.data.value; // Close the InAppBrowser now. inAppBrowserWindow.close(); @@ -105,7 +105,7 @@ export class CoreRecaptchaComponent implements OnInit { */ expireRecaptchaAnswer(): void { this.expired = true; - this.model![this.modelValueName] = ''; + this.model[this.modelValueName] = ''; } } diff --git a/src/core/directives/fa-icon.ts b/src/core/directives/fa-icon.ts index 52e43b217..112a267df 100644 --- a/src/core/directives/fa-icon.ts +++ b/src/core/directives/fa-icon.ts @@ -54,7 +54,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { let iconName = this.name; let font = 'ionicons'; const parts = iconName.split('-', 2); - if (parts.length == 2) { + if (parts.length === 2) { switch (parts[0]) { case 'far': library = 'regular'; @@ -82,7 +82,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { } } - if (font == 'ionicons') { + if (font === 'ionicons') { this.element.removeAttribute('src'); this.logger.warn(`Ionic icon ${this.name} detected`); @@ -103,7 +103,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { ngAfterViewInit(): void { if (!this.element.getAttribute('aria-label') && !this.element.getAttribute('aria-labelledby') && - this.element.getAttribute('aria-hidden') != 'true') { + this.element.getAttribute('aria-hidden') !== 'true') { this.logger.warn('Aria label not set on icon ' + this.name, this.element); this.element.setAttribute('aria-hidden', 'true'); @@ -111,7 +111,7 @@ export class CoreFaIconDirective implements AfterViewInit, OnChanges { } /** - * Detect changes on input properties. + * @inheritdoc */ ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (!changes.name || !this.name) { diff --git a/src/core/features/block/components/only-title-block/only-title-block.ts b/src/core/features/block/components/only-title-block/only-title-block.ts index 4c6f4b8d5..cac191d0c 100644 --- a/src/core/features/block/components/only-title-block/only-title-block.ts +++ b/src/core/features/block/components/only-title-block/only-title-block.ts @@ -36,19 +36,23 @@ export class CoreBlockOnlyTitleComponent extends CoreBlockBaseComponent implemen async ngOnInit(): Promise { await super.ngOnInit(); - this.fetchContentDefaultError = 'Error getting ' + this.block.contents?.title + ' data.'; + this.fetchContentDefaultError = `Error getting ${this.block.contents?.title} data.`; } /** * Go to the block page. */ gotoBlock(): void { + if (!this.link) { + return; + } + const navOptions = this.navOptions || {}; if (this.linkParams) { navOptions.params = this.linkParams; } - CoreNavigator.navigateToSitePath(this.link!, navOptions); + CoreNavigator.navigateToSitePath(this.link, navOptions); } } diff --git a/src/core/features/comments/pages/viewer/viewer.page.ts b/src/core/features/comments/pages/viewer/viewer.page.ts index 47f585985..72ef4d97a 100644 --- a/src/core/features/comments/pages/viewer/viewer.page.ts +++ b/src/core/features/comments/pages/viewer/viewer.page.ts @@ -119,7 +119,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { } /** - * View loaded. + * @inheritdoc */ async ngOnInit(): Promise { try { @@ -445,9 +445,13 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { * @returns Promise resolved with modified comment when done. */ protected async loadCommentProfile(comment: CoreCommentsDataToDisplay): Promise { - // Get the user profile image. + if (!comment.userid) { + return comment; + } + try { - const user = await CoreUser.getProfile(comment.userid!, undefined, true); + // Get the user profile image. + const user = await CoreUser.getProfile(comment.userid, undefined, true); comment.profileimageurl = user.profileimageurl; comment.fullname = user.fullname; } catch { @@ -599,7 +603,7 @@ export class CoreCommentsViewerPage implements OnInit, OnDestroy { } /** - * Page destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.syncObserver?.off(); diff --git a/src/core/features/comments/services/comments.ts b/src/core/features/comments/services/comments.ts index a44eeee80..a183e9ed0 100644 --- a/src/core/features/comments/services/comments.ts +++ b/src/core/features/comments/services/comments.ts @@ -152,7 +152,7 @@ export class CoreCommentsProvider { this.invalidateCommentsData(contextLevel, instanceId, component, itemId, area, siteId), ); - return commentsResponse![0]; + return commentsResponse[0]; } /** @@ -166,9 +166,9 @@ export class CoreCommentsProvider { async addCommentsOnline( comments: CoreCommentsCommentBasicData[], siteId?: string, - ): Promise { + ): Promise { if (!comments || !comments.length) { - return; + return []; } const site = await CoreSites.getSite(siteId); @@ -231,8 +231,12 @@ export class CoreCommentsProvider { // Convenience function to store the action to be synchronized later. const storeOffline = async (): Promise => { + if (!comment.id) { + return false; + } + await CoreCommentsOffline.deleteComment( - comment.id!, + comment.id, comment.contextlevel, comment.instanceid, comment.component, diff --git a/src/core/features/contentlinks/classes/module-grade-handler.ts b/src/core/features/contentlinks/classes/module-grade-handler.ts index 69a498545..424acd2ee 100644 --- a/src/core/features/contentlinks/classes/module-grade-handler.ts +++ b/src/core/features/contentlinks/classes/module-grade-handler.ts @@ -67,7 +67,7 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB courseId?: number, ): CoreContentLinksAction[] | Promise { - courseId = Number(courseId || params.courseid || params.cid); + const courseIdentifier = Number(courseId || params.courseid || params.cid); return [{ action: async (siteId): Promise => { @@ -79,14 +79,14 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB CoreCourseHelper.navigateToModule( Number(params.id), { - courseId, + courseId: courseIdentifier, modName: this.useModNameToGetModule ? this.modName : undefined, siteId, }, ); } else if (this.canReview) { // Use the goToReview function. - this.goToReview(url, params, courseId!, siteId); + this.goToReview(url, params, courseIdentifier, siteId); } else { // Not current user and cannot review it in the app, open it in browser. site.openInBrowserWithAutoLogin(url); diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index c97c3b427..3d6e4ce36 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -76,6 +76,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { @Input() initialSectionNumber?: number; // The section to load first (by number). @Input() moduleId?: number; // The module ID to scroll to. Must be inside the initial selected section. + // eslint-disable-next-line @typescript-eslint/no-explicit-any @ViewChildren(CoreDynamicComponent) dynamicComponents?: QueryList>; // All the possible component classes. diff --git a/src/core/features/course/directives/download-module-main-file.ts b/src/core/features/course/directives/download-module-main-file.ts index ef673546d..5532dd69a 100644 --- a/src/core/features/course/directives/download-module-main-file.ts +++ b/src/core/features/course/directives/download-module-main-file.ts @@ -58,16 +58,19 @@ export class CoreCourseDownloadModuleMainFileDirective implements OnInit { ev.stopPropagation(); const modal = await CoreDomUtils.showModalLoading(); - const courseId = typeof this.courseId == 'string' ? parseInt(this.courseId, 10) : this.courseId; + const courseId = this.courseId ? Number(this.courseId) : undefined; try { if (!this.module) { - // Try to get the module from cache. - this.moduleId = typeof this.moduleId == 'string' ? parseInt(this.moduleId, 10) : this.moduleId; - this.module = await CoreCourse.getModule(this.moduleId!, courseId); + const moduleId = Number(this.moduleId); + if (!moduleId) { + return; + } + + this.module = await CoreCourse.getModule(moduleId, courseId); } - const componentId = this.componentId || module.id; + const componentId = this.componentId ? Number(this.componentId) : this.module.id; await CoreCourseHelper.downloadModuleAndOpenFile( this.module, diff --git a/src/core/features/course/services/course-options-delegate.ts b/src/core/features/course/services/course-options-delegate.ts index 5a0bc3e25..24b5ab1cd 100644 --- a/src/core/features/course/services/course-options-delegate.ts +++ b/src/core/features/course/services/course-options-delegate.ts @@ -448,7 +448,11 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate { + if (!getFunction) { + return; + } + + promises.push(Promise.resolve(getFunction.call(handler, courseWithOptions)).then((data) => { handlersToDisplay.push({ data: data, priority: handler.priority || 0, @@ -468,7 +472,7 @@ export class CoreCourseOptionsDelegateService extends CoreDelegate b.priority! - a.priority!); + ) => (b.priority || 0) - (a.priority || 0)); return handlersToDisplay; } diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index b12c571e9..b4c21263f 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -1274,9 +1274,9 @@ export class CoreCourseProvider { if (!result.status) { if (result.warnings && result.warnings.length) { throw new CoreWSError(result.warnings[0]); - } else { - throw new CoreError('Cannot change completion.'); } + + throw new CoreError('Cannot change completion.'); } return result; diff --git a/src/core/features/emulator/services/capture-helper.ts b/src/core/features/emulator/services/capture-helper.ts index 54885b622..42c0288a5 100644 --- a/src/core/features/emulator/services/capture-helper.ts +++ b/src/core/features/emulator/services/capture-helper.ts @@ -119,24 +119,20 @@ export class CoreEmulatorCaptureHelperProvider { if (mimetypes?.length) { // Search for a supported mimetype. - for (let i = 0; i < mimetypes.length; i++) { - const mimetype = mimetypes[i]; + result.mimetype = mimetypes.find((mimetype) => { const matches = mimetype.match(new RegExp('^' + type + '/')); - if (matches?.length && window.MediaRecorder.isTypeSupported(mimetype)) { - result.mimetype = mimetype; - break; - } - } + return matches?.length && window.MediaRecorder.isTypeSupported(mimetype); + }); } if (result.mimetype) { // Found a supported mimetype in the mimetypes array, get the extension. result.extension = CoreMimetypeUtils.getExtension(result.mimetype); - } else if (type == 'video') { + } else if (type === 'video' && this.videoMimeType) { // No mimetype found, use default extension. result.mimetype = this.videoMimeType; - result.extension = this.possibleVideoMimeTypes[result.mimetype!]; + result.extension = this.possibleVideoMimeTypes[result.mimetype]; } return result; diff --git a/src/core/features/emulator/services/file-transfer.ts b/src/core/features/emulator/services/file-transfer.ts index d3440304a..174ad8a42 100644 --- a/src/core/features/emulator/services/file-transfer.ts +++ b/src/core/features/emulator/services/file-transfer.ts @@ -76,7 +76,9 @@ export class FileTransferObjectMock extends FileTransferObject { abort(): void { if (this.xhr) { this.xhr.abort(); - this.reject!(new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source!, this.target!, 0, '', '')); + this.reject?.( + new FileTransferErrorMock(FileTransferErrorMock.ABORT_ERR, this.source || '', this.target || '', 0, '', ''), + ); } } diff --git a/src/core/features/fileuploader/services/fileuploader-delegate.ts b/src/core/features/fileuploader/services/fileuploader-delegate.ts index 787559424..1b2f2eb34 100644 --- a/src/core/features/fileuploader/services/fileuploader-delegate.ts +++ b/src/core/features/fileuploader/services/fileuploader-delegate.ts @@ -189,7 +189,7 @@ export class CoreFileUploaderDelegateService extends CoreDelegate a.priority! <= b.priority! ? 1 : -1); + handlers.sort((a, b) => (a.priority || 0) <= (b.priority || 0) ? 1 : -1); return handlers; } diff --git a/src/core/features/fileuploader/services/fileuploader-helper.ts b/src/core/features/fileuploader/services/fileuploader-helper.ts index cba30e2a7..6db1e65d3 100644 --- a/src/core/features/fileuploader/services/fileuploader-helper.ts +++ b/src/core/features/fileuploader/services/fileuploader-helper.ts @@ -806,8 +806,8 @@ export class CoreFileUploaderHelperProvider { } } - if (maxSize != -1 && size > maxSize) { - throw this.createMaxBytesError(maxSize, file!.name); + if (maxSize != -1 && size > maxSize && file) { + throw this.createMaxBytesError(maxSize, file.name); } if (size > 0) { @@ -849,12 +849,12 @@ export class CoreFileUploaderHelperProvider { stringKey: string, progress: ProgressEvent | CoreFileProgressEvent, ): void { - if (!progress || !progress.lengthComputable) { + if (!progress || !progress.lengthComputable || progress.loaded === undefined || !progress.total) { return; } // Calculate the progress percentage. - const perc = Math.min((progress.loaded! / progress.total!) * 100, 100); + const perc = Math.min((progress.loaded / progress.total) * 100, 100); if (isNaN(perc) || perc < 0) { return; diff --git a/src/core/features/fileuploader/services/fileuploader.ts b/src/core/features/fileuploader/services/fileuploader.ts index 9926844da..35351fd8b 100644 --- a/src/core/features/fileuploader/services/fileuploader.ts +++ b/src/core/features/fileuploader/services/fileuploader.ts @@ -286,8 +286,8 @@ export class CoreFileUploaderProvider { if (!stillInList) { filesToDelete.push({ - filepath: file.filepath!, - filename: file.filename!, + filepath: file.filepath || '', + filename: file.filename || '', }); } }); @@ -643,7 +643,7 @@ export class CoreFileUploaderProvider { filesToUpload.push( file); } else { // It's an online file. - usedNames[file.filename!.toLowerCase()] = file; + usedNames[(file.filename || '').toLowerCase()] = file; } }); @@ -681,7 +681,7 @@ export class CoreFileUploaderProvider { ): Promise { siteId = siteId || CoreSites.getCurrentSiteId(); - let fileName: string | undefined; + let fileName = ''; let fileEntry: FileEntry | undefined; const isOnline = !CoreUtils.isFileEntry(file); @@ -692,7 +692,7 @@ export class CoreFileUploaderProvider { fileEntry = file; } else { // It's an online file. We need to download it and re-upload it. - fileName = file.filename; + fileName = file.filename || ''; const path = await CoreFilepool.downloadUrl( siteId, @@ -710,9 +710,9 @@ export class CoreFileUploaderProvider { } // Now upload the file. - const extension = CoreMimetypeUtils.getFileExtension(fileName!); + const extension = CoreMimetypeUtils.getFileExtension(fileName); const mimetype = extension ? CoreMimetypeUtils.getMimeType(extension) : undefined; - const options = this.getFileUploadOptions(fileEntry.toURL(), fileName!, mimetype, isOnline, 'draft', itemId); + const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, mimetype, isOnline, 'draft', itemId); const result = await this.uploadFile(fileEntry.toURL(), options, undefined, siteId); diff --git a/src/core/features/grades/services/handlers/report-link.ts b/src/core/features/grades/services/handlers/report-link.ts index a6457e3a9..bd9c75f2e 100644 --- a/src/core/features/grades/services/handlers/report-link.ts +++ b/src/core/features/grades/services/handlers/report-link.ts @@ -39,7 +39,7 @@ export class CoreGradesReportLinkHandlerService extends CoreContentLinksHandlerB courseId?: number, data?: { cmid?: string }, ): CoreContentLinksAction[] | Promise { - courseId = courseId || Number(params.id); + const courseIdentifier = courseId || Number(params.id); data = data || {}; return [{ @@ -47,7 +47,7 @@ export class CoreGradesReportLinkHandlerService extends CoreContentLinksHandlerB const userId = params.userid ? parseInt(params.userid, 10) : undefined; const moduleId = data?.cmid && parseInt(data.cmid, 10) || undefined; - CoreGradesHelper.goToGrades(courseId!, userId, moduleId, siteId); + CoreGradesHelper.goToGrades(courseIdentifier, userId, moduleId, siteId); }, }]; } diff --git a/src/core/features/grades/services/handlers/user-link.ts b/src/core/features/grades/services/handlers/user-link.ts index b3fd56690..4741fccd3 100644 --- a/src/core/features/grades/services/handlers/user-link.ts +++ b/src/core/features/grades/services/handlers/user-link.ts @@ -39,7 +39,7 @@ export class CoreGradesUserLinkHandlerService extends CoreContentLinksHandlerBas courseId?: number, data?: { cmid?: string }, ): CoreContentLinksAction[] | Promise { - courseId = courseId || Number(params.id); + const courseIdentifier = courseId || Number(params.id); data = data || {}; return [{ @@ -47,7 +47,7 @@ export class CoreGradesUserLinkHandlerService extends CoreContentLinksHandlerBas const userId = params.user ? parseInt(params.user, 10) : undefined; const moduleId = data?.cmid && parseInt(data.cmid, 10) || undefined; - CoreGradesHelper.goToGrades(courseId!, userId, moduleId, siteId); + CoreGradesHelper.goToGrades(courseIdentifier, userId, moduleId, siteId); }, }]; } diff --git a/src/core/features/h5p/classes/player.ts b/src/core/features/h5p/classes/player.ts index 6973f1f52..7644f420b 100644 --- a/src/core/features/h5p/classes/player.ts +++ b/src/core/features/h5p/classes/player.ts @@ -166,16 +166,16 @@ export class CoreH5PPlayer { * @returns Promise resolved when done. */ async deleteAllContentIndexesForSite(siteId?: string): Promise { - siteId = siteId || CoreSites.getCurrentSiteId(); + const siteIdentifier = siteId || CoreSites.getCurrentSiteId(); - if (!siteId) { + if (!siteIdentifier) { return; } - const records = await this.h5pCore.h5pFramework.getAllContentData(siteId); + const records = await this.h5pCore.h5pFramework.getAllContentData(siteIdentifier); await Promise.all(records.map(async (record) => { - await CoreUtils.ignoreErrors(this.h5pCore.h5pFS.deleteContentIndex(record.foldername, siteId!)); + await CoreUtils.ignoreErrors(this.h5pCore.h5pFS.deleteContentIndex(record.foldername, siteIdentifier)); })); } diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index e4fdd4a6c..ca3879c07 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -217,12 +217,13 @@ export class CoreLoginEmailSignupPage implements OnInit { this.countryControl.setValue(this.settings.country || ''); } - this.namefieldsErrors = {}; + const namefieldsErrors = {}; if (this.settings.namefields) { this.settings.namefields.forEach((field) => { - this.namefieldsErrors![field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field); + namefieldsErrors[field] = CoreLoginHelper.getErrorMessages('core.login.missing' + field); }); } + this.namefieldsErrors = namefieldsErrors; this.countries = await CoreUtils.getCountryListSorted(); } diff --git a/src/core/features/login/pages/forgotten-password/forgotten-password.ts b/src/core/features/login/pages/forgotten-password/forgotten-password.ts index e6ca60a45..4d20cf2c9 100644 --- a/src/core/features/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/features/login/pages/forgotten-password/forgotten-password.ts @@ -18,7 +18,6 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreLoginHelper } from '@features/login/services/login-helper'; import { Translate } from '@singletons'; -import { CoreWSExternalWarning } from '@services/ws'; import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CorePlatform } from '@services/platform'; @@ -91,7 +90,7 @@ export class CoreLoginForgottenPasswordPage implements OnInit { } const modal = await CoreDomUtils.showModalLoading('core.sending', true); - const isMail = field == 'email'; + const isMail = field === 'email'; try { const response = await CoreLoginHelper.requestPasswordReset( @@ -100,10 +99,14 @@ export class CoreLoginForgottenPasswordPage implements OnInit { isMail ? value : '', ); - if (response.status == 'dataerror') { - // Error in the data sent. - this.showError(isMail, response.warnings!); - } else if (response.status == 'emailpasswordconfirmnotsent' || response.status == 'emailpasswordconfirmnoemail') { + if (response.status === 'dataerror') { + // Show an error from the warnings. + const warning = response.warnings?.find((warning) => + (warning.item === 'email' && isMail) || (warning.item === 'username' && !isMail)); + if (warning) { + CoreDomUtils.showErrorModal(warning.message); + } + } else if (response.status === 'emailpasswordconfirmnotsent' || response.status === 'emailpasswordconfirmnoemail') { // Error, not found. CoreDomUtils.showErrorModal(response.notice); } else { @@ -121,15 +124,4 @@ export class CoreLoginForgottenPasswordPage implements OnInit { } } - // Show an error from the warnings. - protected showError(isMail: boolean, warnings: CoreWSExternalWarning[]): void { - for (let i = 0; i < warnings.length; i++) { - const warning = warnings[i]; - if ((warning.item == 'email' && isMail) || (warning.item == 'username' && !isMail)) { - CoreDomUtils.showErrorModal(warning.message); - break; - } - } - } - } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index b692af1a4..d860cddc7 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -87,24 +87,24 @@ export class CoreLoginHelperProvider { const result = await site.write('core_user_agree_site_policy', {}); - if (!result.status) { - // Error. - if (result.warnings && result.warnings.length) { - // Check if there is a warning 'alreadyagreed'. - for (const i in result.warnings) { - const warning = result.warnings[i]; - if (warning.warningcode == 'alreadyagreed') { - // Policy already agreed, treat it as a success. - return; - } - } - - // Another warning, reject. - throw new CoreWSError(result.warnings[0]); - } else { - throw new CoreError('Cannot agree site policy'); - } + if (result.status) { + return; } + + if (!result.warnings?.length) { + throw new CoreError('Cannot agree site policy'); + } + + // Check if there is a warning 'alreadyagreed'. + const found = result.warnings.some((warning) => warning.warningcode === 'alreadyagreed'); + if (found) { + // Policy already agreed, treat it as a success. + return; + } + + // Another warning, reject. + throw new CoreWSError(result.warnings[0]); + } /** @@ -1111,7 +1111,11 @@ export class CoreLoginHelperProvider { ); if (!result.status) { - throw new CoreWSError(result.warnings![0]); + if (result.warnings?.length) { + throw new CoreWSError(result.warnings[0]); + } + + throw new CoreError('Error sending confirmation email'); } const message = Translate.instant('core.login.emailconfirmsentsuccess'); @@ -1178,16 +1182,16 @@ export class CoreLoginHelperProvider { treatUserTokenError(siteUrl: string, error: CoreWSError, username?: string, password?: string): void { switch (error.errorcode) { case 'forcepasswordchangenotice': - this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error)!); + this.openChangePassword(siteUrl, CoreTextUtils.getErrorMessageFromError(error) ?? ''); break; case 'usernotconfirmed': this.showNotConfirmedModal(siteUrl, undefined, username, password); break; case 'connecttomoodleapp': - this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); + this.showMoodleAppNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? ''); break; case 'connecttoworkplaceapp': - this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error)!); + this.showWorkplaceNoticeModal(CoreTextUtils.getErrorMessageFromError(error) ?? ''); break; case 'invalidlogin': this.showInvalidLoginModal(error); diff --git a/src/core/features/question/classes/base-question-component.ts b/src/core/features/question/classes/base-question-component.ts index b30cf8ac3..232f1b3b4 100644 --- a/src/core/features/question/classes/base-question-component.ts +++ b/src/core/features/question/classes/base-question-component.ts @@ -85,12 +85,12 @@ export class CoreQuestionBaseComponent { */ protected treatCalculatedRadioUnits(questionEl: HTMLElement): boolean { // Check if the question has radio buttons for units. - const radios = Array.from(questionEl.querySelectorAll('input[type="radio"]')); - if (!radios.length) { + const radios = Array.from(questionEl.querySelectorAll('input[type="radio"]')); + if (!radios.length || !this.question) { return false; } - const question = this.question!; + const question: AddonModQuizCalculatedQuestion = this.question; question.options = []; for (const i in radios) { @@ -104,7 +104,7 @@ export class CoreQuestionBaseComponent { disabled: radioEl.disabled, }; // Get the label with the question text. - const label = questionEl.querySelector('label[for="' + option.id + '"]'); + const label = questionEl.querySelector('label[for="' + option.id + '"]'); question.optionsName = option.name; @@ -129,7 +129,7 @@ export class CoreQuestionBaseComponent { if (question.parsedSettings && question.parsedSettings.unitsleft !== null) { question.optionsFirst = question.parsedSettings.unitsleft == '1'; } else { - const input = questionEl.querySelector('input[type="text"][name*=answer]'); + const input = questionEl.querySelector('input[type="text"][name*=answer]'); question.optionsFirst = questionEl.innerHTML.indexOf(input?.outerHTML || '') > questionEl.innerHTML.indexOf(radios[0].outerHTML); } @@ -145,14 +145,14 @@ export class CoreQuestionBaseComponent { */ protected treatCalculatedSelectUnits(questionEl: HTMLElement): boolean { // Check if the question has a select for units. - const select = questionEl.querySelector('select[name*=unit]'); + const select = questionEl.querySelector('select[name*=unit]'); const options = select && Array.from(select.querySelectorAll('option')); - if (!select || !options?.length) { + if (!select || !options?.length || !this.question) { return false; } - const question = this.question!; + const question: AddonModQuizCalculatedQuestion = this.question; const selectModel: AddonModQuizQuestionSelect = { id: select.id, name: select.name, @@ -190,7 +190,7 @@ export class CoreQuestionBaseComponent { } // Get the accessibility label. - const accessibilityLabel = questionEl.querySelector('label[for="' + select.id + '"]'); + const accessibilityLabel = questionEl.querySelector('label[for="' + select.id + '"]'); selectModel.accessibilityLabel = accessibilityLabel?.innerHTML; question.select = selectModel; @@ -199,7 +199,7 @@ export class CoreQuestionBaseComponent { if (question.parsedSettings && question.parsedSettings.unitsleft !== null) { question.selectFirst = question.parsedSettings.unitsleft == '1'; } else { - const input = questionEl.querySelector('input[type="text"][name*=answer]'); + const input = questionEl.querySelector('input[type="text"][name*=answer]'); question.selectFirst = questionEl.innerHTML.indexOf(input?.outerHTML || '') > questionEl.innerHTML.indexOf(select.outerHTML); } @@ -242,12 +242,12 @@ export class CoreQuestionBaseComponent { */ initEssayComponent(review?: boolean): void | HTMLElement { const questionEl = this.initComponent(); - if (!questionEl) { + if (!questionEl || !this.question) { return; } - const question = this.question!; - const answerDraftIdInput = questionEl.querySelector('input[name*="_answer:itemid"]'); + const question: AddonModQuizEssayQuestion = this.question; + const answerDraftIdInput = questionEl.querySelector('input[name*="_answer:itemid"]'); if (question.parsedSettings) { question.allowsAttachments = question.parsedSettings.attachments != '0'; @@ -283,7 +283,7 @@ export class CoreQuestionBaseComponent { return questionEl; } - const textarea = questionEl.querySelector('textarea[name*=_answer]'); + const textarea = questionEl.querySelector('textarea[name*=_answer]'); question.hasDraftFiles = question.allowsAnswerFiles && CoreQuestionHelper.hasDraftFileUrls(questionEl.innerHTML); if (!textarea && (question.hasInlineText || !question.allowsAttachments)) { @@ -297,12 +297,12 @@ export class CoreQuestionBaseComponent { } if (textarea) { - const input = questionEl.querySelector('input[type="hidden"][name*=answerformat]'); + const input = questionEl.querySelector('input[type="hidden"][name*=answerformat]'); let content = CoreTextUtils.decodeHTML(textarea.innerHTML || ''); if (question.hasDraftFiles && question.responsefileareas) { content = CoreTextUtils.replaceDraftfileUrls( - CoreSites.getCurrentSite()!.getURL(), + CoreSites.getRequiredCurrentSite().getURL(), content, CoreQuestionHelper.getResponseFileAreaFiles(question, 'answer'), ).text; @@ -330,8 +330,8 @@ export class CoreQuestionBaseComponent { } if (question.allowsAttachments) { - const attachmentsInput = questionEl.querySelector('.attachments input[name*=_attachments]'); - const objectElement = questionEl.querySelector('.attachments object'); + const attachmentsInput = questionEl.querySelector('.attachments input[name*=_attachments]'); + const objectElement = questionEl.querySelector('.attachments object'); const fileManagerUrl = objectElement && objectElement.data; if (attachmentsInput) { @@ -365,32 +365,40 @@ export class CoreQuestionBaseComponent { * @param questionEl Element with the question html. */ protected handleEssayPlagiarism(questionEl: HTMLElement): void { - const question = this.question!; + if (!this.question) { + return; + } + + const question: AddonModQuizEssayQuestion = this.question; const answerPlagiarism = questionEl.querySelector('.answer .core_plagiarism_links'); if (answerPlagiarism) { question.answerPlagiarism = answerPlagiarism.innerHTML; } - if (!question.attachments?.length) { + const attachments = question.attachments; + if (!attachments?.length) { return; } - const attachmentsPlagiarisms = questionEl.querySelectorAll('.attachments .core_plagiarism_links'); - question.attachmentsPlagiarisms = []; + const attachmentsPlagiarisms = Array.from( + questionEl.querySelectorAll('.attachments .core_plagiarism_links'), + ); + const questionAttachmentsPlagiarisms: string[] = []; - Array.from(attachmentsPlagiarisms).forEach((plagiarism) => { + attachmentsPlagiarisms.forEach((plagiarism) => { // Search the URL of the attachment it affects. const attachmentUrl = plagiarism.parentElement?.querySelector('a')?.href; if (!attachmentUrl) { return; } - const position = question.attachments!.findIndex((file) => CoreFileHelper.getFileUrl(file) == attachmentUrl); - + const position = attachments.findIndex((file) => CoreFileHelper.getFileUrl(file) === attachmentUrl); if (position >= 0) { - question.attachmentsPlagiarisms![position] = plagiarism.innerHTML; + questionAttachmentsPlagiarisms[position] = plagiarism.innerHTML; } }); + + question.attachmentsPlagiarisms = questionAttachmentsPlagiarisms; } /** @@ -440,15 +448,15 @@ export class CoreQuestionBaseComponent { */ initInputTextComponent(): void | HTMLElement { const questionEl = this.initComponent(); - if (!questionEl) { + if (!questionEl || !this.question) { return; } // Get the input element. - const question = this.question!; - const input = questionEl.querySelector('input[type="text"][name*=answer]'); + const question: AddonModQuizTextQuestion = this.question; + const input = questionEl.querySelector('input[type="text"][name*=answer]'); if (!input) { - this.logger.warn('Aborting because couldn\'t find input.', this.question!.slot); + this.logger.warn('Aborting because couldn\'t find input.', this.question.slot); return CoreQuestionHelper.showComponentError(this.onAbort); } @@ -482,12 +490,13 @@ export class CoreQuestionBaseComponent { if (question.input.isInline) { // Handle correct/incorrect classes and icons. - const content = questionEl.querySelector('.qtext'); + const content = questionEl.querySelector('.qtext'); + if (content) { + CoreQuestionHelper.replaceCorrectnessClasses(content); + CoreQuestionHelper.treatCorrectnessIcons(content); - CoreQuestionHelper.replaceCorrectnessClasses(content); - CoreQuestionHelper.treatCorrectnessIcons(content); - - question.text = content.innerHTML; + question.text = content.innerHTML; + } } return questionEl; @@ -500,13 +509,13 @@ export class CoreQuestionBaseComponent { */ initMatchComponent(): void | HTMLElement { const questionEl = this.initComponent(); - if (!questionEl) { + if (!questionEl || !this.question) { return; } // Find rows. - const question = this.question!; - const rows = Array.from(questionEl.querySelectorAll('table.answer tr')); + const question: AddonModQuizMatchQuestion = this.question; + const rows = Array.from(questionEl.querySelectorAll('table.answer tr')); if (!rows || !rows.length) { this.logger.warn('Aborting because couldn\'t find any row.', question.slot); @@ -517,7 +526,7 @@ export class CoreQuestionBaseComponent { for (const i in rows) { const row = rows[i]; - const columns = Array.from(row.querySelectorAll('td')); + const columns = Array.from(row.querySelectorAll('td')); if (!columns || columns.length < 2) { this.logger.warn('Aborting because couldn\'t the right columns.', question.slot); @@ -526,8 +535,8 @@ export class CoreQuestionBaseComponent { } // Get the select and the options. - const select = columns[1].querySelector('select'); - const options = Array.from(columns[1].querySelectorAll('option')); + const select = columns[1].querySelector('select'); + const options = Array.from(columns[1].querySelectorAll('option')); if (!select || !options || !options.length) { this.logger.warn('Aborting because couldn\'t find select or options.', question.slot); @@ -574,7 +583,7 @@ export class CoreQuestionBaseComponent { } // Get the accessibility label. - const accessibilityLabel = columns[1].querySelector('label.accesshide'); + const accessibilityLabel = columns[1].querySelector('label.accesshide'); rowModel.accessibilityLabel = accessibilityLabel?.innerHTML; question.rows.push(rowModel); @@ -592,20 +601,20 @@ export class CoreQuestionBaseComponent { */ initMultichoiceComponent(): void | HTMLElement { const questionEl = this.initComponent(); - if (!questionEl) { + if (!questionEl || !this.question) { return; } // Get the prompt. - const question = this.question!; + const question: AddonModQuizMultichoiceQuestion = this.question; question.prompt = CoreDomUtils.getContentsOfElement(questionEl, '.prompt'); // Search radio buttons first (single choice). - let options = Array.from(questionEl.querySelectorAll('input[type="radio"]')); + let options = Array.from(questionEl.querySelectorAll('input[type="radio"]')); if (!options || !options.length) { // Radio buttons not found, it should be a multi answer. Search for checkbox. question.multi = true; - options = Array.from(questionEl.querySelectorAll('input[type="checkbox"]')); + options = Array.from(questionEl.querySelectorAll('input[type="checkbox"]')); if (!options || !options.length) { // No checkbox found either. Abort. diff --git a/src/core/features/question/services/question-helper.ts b/src/core/features/question/services/question-helper.ts index cfc9e35cc..4e0cac8ae 100644 --- a/src/core/features/question/services/question-helper.ts +++ b/src/core/features/question/services/question-helper.ts @@ -304,14 +304,16 @@ export class CoreQuestionHelperProvider { // Search init_question functions for this type. const initMatches = scriptCode.match(new RegExp('M.qtype_' + question.type + '.init_question\\(.*?}\\);', 'mg')); if (initMatches) { - let initMatch = initMatches.pop()!; + let initMatch = initMatches.pop(); - // Remove start and end of the match, we only want the object. - initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', ''); - initMatch = initMatch.substring(0, initMatch.length - 2); + if (initMatch) { + // Remove start and end of the match, we only want the object. + initMatch = initMatch.replace('M.qtype_' + question.type + '.init_question(', ''); + initMatch = initMatch.substring(0, initMatch.length - 2); - // Try to convert it to an object and add it to the question. - question.initObjects = CoreTextUtils.parseJSON(initMatch, null); + // Try to convert it to an object and add it to the question. + question.initObjects = CoreTextUtils.parseJSON(initMatch, null); + } } const amdRegExp = new RegExp('require\\(\\[["\']qtype_' + question.type + '/question["\']\\],[^f]*' + diff --git a/src/core/features/rating/components/aggregate/aggregate.ts b/src/core/features/rating/components/aggregate/aggregate.ts index 8b8c56594..c9c511286 100644 --- a/src/core/features/rating/components/aggregate/aggregate.ts +++ b/src/core/features/rating/components/aggregate/aggregate.ts @@ -60,13 +60,13 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy { } /** - * Detect changes on input properties. + * @inheritdoc */ ngOnChanges(): void { this.aggregateObserver?.off(); delete this.aggregateObserver; - this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId); + this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid === this.itemId); if (!this.item) { return; } @@ -99,11 +99,11 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy { this.aggregateObserver = CoreEvents.on(CoreRatingProvider.AGGREGATE_CHANGED_EVENT, (data) => { if (this.item && - data.contextLevel == this.contextLevel && - data.instanceId == this.instanceId && - data.component == this.ratingInfo.component && - data.ratingArea == this.ratingInfo.ratingarea && - data.itemId == this.itemId) { + data.contextLevel === this.contextLevel && + data.instanceId === this.instanceId && + data.component === this.ratingInfo.component && + data.ratingArea === this.ratingInfo.ratingarea && + data.itemId === this.itemId) { this.item.aggregatestr = data.aggregate; this.item.count = data.count; } @@ -114,7 +114,7 @@ export class CoreRatingAggregateComponent implements OnChanges, OnDestroy { * Open the individual ratings page. */ async openRatings(): Promise { - if (!this.ratingInfo.canviewall || !this.item!.count || this.disabled) { + if (!this.ratingInfo.canviewall || !this.item?.count || this.disabled) { return; } diff --git a/src/core/features/rating/components/rate/rate.ts b/src/core/features/rating/components/rate/rate.ts index 238c28be5..fdd30b0e5 100644 --- a/src/core/features/rating/components/rate/rate.ts +++ b/src/core/features/rating/components/rate/rate.ts @@ -69,7 +69,7 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy { } /** - * Detect changes on input properties. + * @inheritdoc */ async ngOnChanges(): Promise { this.item = (this.ratingInfo.ratings || []).find((rating) => rating.itemid == this.itemId); @@ -125,6 +125,10 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy { * Send or save the user rating when changed. */ async userRatingChanged(): Promise { + if (this.rating === undefined) { + return; + } + const modal = await CoreDomUtils.showModalLoading('core.sending', true); try { @@ -137,7 +141,7 @@ export class CoreRatingRateComponent implements OnChanges, OnDestroy { this.itemSetId, this.courseId, this.scaleId, - this.rating!, + this.rating, this.userId, this.aggregateMethod, ); diff --git a/src/core/features/search/services/search-history.service.ts b/src/core/features/search/services/search-history.service.ts index b4516897e..f27c9786e 100644 --- a/src/core/features/search/services/search-history.service.ts +++ b/src/core/features/search/services/search-history.service.ts @@ -58,10 +58,13 @@ export class CoreSearchHistoryProvider { if (items.length > CoreSearchHistoryProvider.HISTORY_LIMIT) { // Over the limit. Remove the last. const lastItem = items.pop(); + if (!lastItem) { + return; + } const searchItem = { - searcharea: lastItem!.searcharea, - searchedtext: lastItem!.searchedtext, + searcharea: lastItem.searcharea, + searchedtext: lastItem.searchedtext, }; await db.deleteRecords(SEARCH_HISTORY_TABLE_NAME, searchItem); diff --git a/src/core/features/sharedfiles/components/list/list.ts b/src/core/features/sharedfiles/components/list/list.ts index 5e1eff050..694301ea7 100644 --- a/src/core/features/sharedfiles/components/list/list.ts +++ b/src/core/features/sharedfiles/components/list/list.ts @@ -43,7 +43,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { @Output() onFilePicked = new EventEmitter(); filesLoaded = false; - files?: (FileEntry | DirectoryEntry)[]; + files: (FileEntry | DirectoryEntry)[] = []; protected shareObserver?: CoreEventObserver; @@ -57,7 +57,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { // Listen for new files shared with the app. this.shareObserver = CoreEvents.on(CoreEvents.FILE_SHARED, (data) => { - if (data.siteId == this.siteId) { + if (data.siteId === this.siteId) { // File was stored in current site, refresh the list. this.filesLoaded = false; this.loadFiles().finally(() => { @@ -94,7 +94,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { * @param index Position of the file. */ fileDeleted(index: number): void { - this.files!.splice(index, 1); + this.files.splice(index, 1); } /** @@ -104,7 +104,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { * @param data Data containing the new FileEntry. */ fileRenamed(index: number, data: { file: FileEntry }): void { - this.files![index] = data.file; + this.files[index] = data.file; } /** @@ -161,7 +161,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { } /** - * Component destroyed. + * @inheritdoc */ ngOnDestroy(): void { this.shareObserver?.off(); diff --git a/src/core/features/sharedfiles/pages/choose-site/choose-site.ts b/src/core/features/sharedfiles/pages/choose-site/choose-site.ts index 3d810aabe..b42d1fded 100644 --- a/src/core/features/sharedfiles/pages/choose-site/choose-site.ts +++ b/src/core/features/sharedfiles/pages/choose-site/choose-site.ts @@ -19,7 +19,6 @@ import { CoreFile } from '@services/file'; import { CoreNavigator } from '@services/navigator'; import { CoreSiteBasicInfo, CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreUtils } from '@services/utils/utils'; /** * Page to display the list of sites to choose one to store a shared file. @@ -42,19 +41,20 @@ export class CoreSharedFilesChooseSitePage implements OnInit { * @inheritdoc */ async ngOnInit(): Promise { - this.filePath = CoreNavigator.getRouteParam('filePath'); - this.isInbox = !!CoreNavigator.getRouteBooleanParam('isInbox'); - - if (!this.filePath) { - CoreDomUtils.showErrorModal('Error reading file.'); - await CoreUtils.nextTick(); + try { + this.filePath = CoreNavigator.getRequiredRouteParam('filePath'); + this.isInbox = !!CoreNavigator.getRouteBooleanParam('isInbox'); + } catch (error) { + CoreDomUtils.showErrorModalDefault(error, 'Error reading file.'); CoreNavigator.back(); return; } - const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.filePath); - this.fileName = fileAndDir.name; + if (this.filePath) { + const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.filePath); + this.fileName = fileAndDir.name; + } try { await Promise.all([ @@ -75,7 +75,11 @@ export class CoreSharedFilesChooseSitePage implements OnInit { * @returns Promise resolved when done. */ protected async loadFile(): Promise { - this.fileEntry = await CoreFile.getExternalFile(this.filePath!); + if (!this.filePath) { + return; + } + + this.fileEntry = await CoreFile.getExternalFile(this.filePath); this.fileName = this.fileEntry.name; } @@ -94,10 +98,14 @@ export class CoreSharedFilesChooseSitePage implements OnInit { * @param siteId Site ID. */ async storeInSite(siteId: string): Promise { + if (!this.fileEntry) { + return; + } + this.loaded = false; try { - await CoreSharedFilesHelper.storeSharedFileInSite(this.fileEntry!, siteId, this.isInbox); + await CoreSharedFilesHelper.storeSharedFileInSite(this.fileEntry, siteId, this.isInbox); CoreNavigator.back(); } finally { diff --git a/src/core/features/siteplugins/classes/handlers/user-profile-field-handler.ts b/src/core/features/siteplugins/classes/handlers/user-profile-field-handler.ts index 5ffab7ef5..d8a8c897d 100644 --- a/src/core/features/siteplugins/classes/handlers/user-profile-field-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/user-profile-field-handler.ts @@ -50,7 +50,7 @@ export class CoreSitePluginsUserProfileFieldHandler extends CoreSitePluginsBaseH const name = 'profile_field_' + field.shortname; return { - type: 'type' in field ? field.type : field.datatype!, + type: ('type' in field ? field.type : field.datatype) || '', name: name, value: formValues[name], }; diff --git a/src/core/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-helper.ts index afda35bdc..9b485bf3a 100644 --- a/src/core/features/siteplugins/services/siteplugins-helper.ts +++ b/src/core/features/siteplugins/services/siteplugins-helper.ts @@ -185,7 +185,7 @@ export class CoreSitePluginsHelperProvider { ); files?.forEach((file) => { - if (file.url != url) { + if (file.url !== url) { // It's not the current file, delete it. CoreUtils.ignoreErrors(CoreFilepool.removeFileByUrl(site.getId(), file.url)); } @@ -489,32 +489,32 @@ export class CoreSitePluginsHelperProvider { return; } - if (cssCode) { + if (cssCode && handlerSchema.styles?.url) { // Load the styles. - this.loadStyles(plugin, handlerName, handlerSchema.styles!.url!, cssCode, handlerSchema.styles!.version, siteId); + this.loadStyles(plugin, handlerName, handlerSchema.styles.url, cssCode, handlerSchema.styles.version, siteId); } let uniqueName: string | undefined; switch (handlerSchema.delegate) { case 'CoreMainMenuDelegate': - uniqueName = await this.registerMainMenuHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerMainMenuHandler(plugin, handlerName, handlerSchema, initResult); break; case 'CoreCourseModuleDelegate': - uniqueName = await this.registerModuleHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerModuleHandler(plugin, handlerName, handlerSchema, initResult); break; case 'CoreUserDelegate': - uniqueName = await this.registerUserProfileHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerUserProfileHandler(plugin, handlerName, handlerSchema, initResult); break; case 'CoreCourseOptionsDelegate': - uniqueName = await this.registerCourseOptionHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerCourseOptionHandler(plugin, handlerName, handlerSchema, initResult); break; case 'CoreCourseFormatDelegate': - uniqueName = await this.registerCourseFormatHandler(plugin, handlerName, handlerSchema); + uniqueName = this.registerCourseFormatHandler(plugin, handlerName, handlerSchema); break; case 'CoreUserProfileFieldDelegate': @@ -522,7 +522,7 @@ export class CoreSitePluginsHelperProvider { break; case 'CoreSettingsDelegate': - uniqueName = await this.registerSettingsHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerSettingsHandler(plugin, handlerName, handlerSchema, initResult); break; case 'CoreQuestionDelegate': @@ -534,11 +534,11 @@ export class CoreSitePluginsHelperProvider { break; case 'CoreBlockDelegate': - uniqueName = await this.registerBlockHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerBlockHandler(plugin, handlerName, handlerSchema, initResult); break; case 'AddonMessageOutputDelegate': - uniqueName = await this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerMessageOutputHandler(plugin, handlerName, handlerSchema, initResult); break; case 'AddonModQuizAccessRuleDelegate': @@ -558,7 +558,7 @@ export class CoreSitePluginsHelperProvider { break; case 'CoreMainMenuHomeDelegate': - uniqueName = await this.registerMainMenuHomeHandler(plugin, handlerName, handlerSchema, initResult); + uniqueName = this.registerMainMenuHomeHandler(plugin, handlerName, handlerSchema, initResult); break; default: diff --git a/src/core/features/styles/services/styles.ts b/src/core/features/styles/services/styles.ts index f966c76bd..3414d71c2 100644 --- a/src/core/features/styles/services/styles.ts +++ b/src/core/features/styles/services/styles.ts @@ -101,7 +101,7 @@ export class CoreStylesService { this.styleHandlers.push(styleHandler); // Sort them by priority, greatest go last because style loaded last it's more important. - this.styleHandlers = this.styleHandlers.sort((a, b) => a.priority! >= b.priority! ? 1 : -1); + this.styleHandlers = this.styleHandlers.sort((a, b) => a.priority >= b.priority ? 1 : -1); } /** @@ -221,7 +221,7 @@ export class CoreStylesService { const hash = Md5.hashAsciiStr(contents); // Update the styles only if they have changed. - if (this.stylesEls[siteId!][handler.name] === hash) { + if (this.stylesEls[siteId][handler.name] === hash) { return; } @@ -353,21 +353,21 @@ export class CoreStylesService { * @returns Promise resolved when styles are loaded. */ protected async load(siteId?: string, disabled = false): Promise { - siteId = siteId || CoreSites.getCurrentSiteId(); + const siteIdentifier = siteId || CoreSites.getCurrentSiteId(); - if (!siteId || !this.stylesEls[siteId]) { + if (!siteIdentifier || !this.stylesEls[siteIdentifier]) { throw new CoreError('Cannot load styles, site not found: ${siteId}'); } - this.logger.debug('Load site', siteId, disabled); + this.logger.debug('Load site', siteIdentifier, disabled); // Enable or disable the styles. - for (const sourceName in this.stylesEls[siteId]) { - this.disableStyleElementByName(siteId, sourceName, disabled); + for (const sourceName in this.stylesEls[siteIdentifier]) { + this.disableStyleElementByName(siteIdentifier, sourceName, disabled); } await CoreUtils.allPromises(this.styleHandlers.map(async (handler) => { - await this.setStyle(siteId!, handler, !!disabled); + await this.setStyle(siteIdentifier, handler, disabled); })); if (!disabled) { diff --git a/src/core/features/tag/pages/index-area/index-area.page.ts b/src/core/features/tag/pages/index-area/index-area.page.ts index b6d6d382d..0c8577e05 100644 --- a/src/core/features/tag/pages/index-area/index-area.page.ts +++ b/src/core/features/tag/pages/index-area/index-area.page.ts @@ -53,7 +53,7 @@ export class CoreTagIndexAreaPage implements OnInit { ) { } /** - * View loaded. + * @inheritdoc */ async ngOnInit(): Promise { this.route.queryParams.subscribe(async () => { @@ -80,7 +80,9 @@ export class CoreTagIndexAreaPage implements OnInit { await this.fetchData(true); } - this.areaComponent = await CoreTagAreaDelegate.getComponent(this.componentName!, this.itemType!); + if (this.componentName && this.itemType) { + this.areaComponent = await CoreTagAreaDelegate.getComponent(this.componentName, this.itemType); + } } finally { this.loaded = true; } diff --git a/src/core/features/user/services/user-sync.ts b/src/core/features/user/services/user-sync.ts index 1079f7fc3..31f6d0d9f 100644 --- a/src/core/features/user/services/user-sync.ts +++ b/src/core/features/user/services/user-sync.ts @@ -91,7 +91,10 @@ export class CoreUserSyncProvider extends CoreSyncBaseProvider { await CoreUser.setUserPreference(preference.name, preference.value, siteId); } catch (error) { if (CoreUtils.isWebServiceError(error)) { - warnings.push(CoreTextUtils.getErrorMessageFromError(error)!); + const warning = CoreTextUtils.getErrorMessageFromError(error); + if (warning) { + warnings.push(warning); + } } else { // Couldn't connect to server, reject. throw error; diff --git a/src/core/initializers/initialize-urlscheme.ts b/src/core/initializers/initialize-urlscheme.ts index c04f63216..af0aee7dd 100644 --- a/src/core/initializers/initialize-urlscheme.ts +++ b/src/core/initializers/initialize-urlscheme.ts @@ -20,6 +20,7 @@ export default async function(): Promise { const lastUrls: Record = {}; // Handle app launched with a certain URL (custom URL scheme). + // eslint-disable-next-line @typescript-eslint/no-explicit-any ( window).handleOpenURL = (url: string): void => { // Execute the callback in the Angular zone, so change detection doesn't stop working. NgZone.run(() => { diff --git a/src/core/services/cron.ts b/src/core/services/cron.ts index 5cb3aff2b..811eeaf40 100644 --- a/src/core/services/cron.ts +++ b/src/core/services/cron.ts @@ -54,7 +54,7 @@ export class CoreCronDelegateService { async initializeDatabase(): Promise { try { await CoreApp.createTablesFromSchema(APP_SCHEMA); - } catch (e) { + } catch { // Ignore errors. } @@ -144,21 +144,24 @@ export class CoreCronDelegateService { * @param siteId Site ID. If not defined, all sites. * @returns Promise resolved when the handler finishes or reaches max time, rejected if it fails. */ - protected executeHandler(name: string, force?: boolean, siteId?: string): Promise { - return new Promise((resolve, reject): void => { - this.logger.debug('Executing handler: ' + name); + protected async executeHandler(name: string, force?: boolean, siteId?: string): Promise { + this.logger.debug('Executing handler: ' + name); + try { // Wrap the call in Promise.resolve to make sure it's a promise. - Promise.resolve(this.handlers[name].execute!(siteId, force)).then(resolve).catch(reject).finally(() => { - clearTimeout(cancelTimeout); - }); + const promise = Promise.resolve(this.handlers[name].execute?.(siteId, force)); - const cancelTimeout = setTimeout(() => { + await CoreUtils.timeoutPromise(promise, CoreCronDelegateService.MAX_TIME_PROCESS); + } catch (error) { + if (error.timeout) { // The handler took too long. Resolve because we don't want to retry soon. this.logger.debug(`Resolving execution of handler '${name}' because it took too long.`); - resolve(); - }, CoreCronDelegateService.MAX_TIME_PROCESS); - }); + + return; + } + + throw error; + } } /** @@ -210,20 +213,21 @@ export class CoreCronDelegateService { * @returns Handler's interval. */ protected getHandlerInterval(name: string): number { - if (!this.handlers[name] || !this.handlers[name].getInterval) { + if (this.handlers[name] === undefined) { // Invalid, return default. return CoreCronDelegateService.DEFAULT_INTERVAL; } // Don't allow intervals lower than the minimum. - const minInterval = CoreCronDelegateService.MIN_INTERVAL; - const handlerInterval = this.handlers[name].getInterval!(); + const handlerInterval = this.handlers[name].getInterval?.(); if (!handlerInterval) { return CoreCronDelegateService.DEFAULT_INTERVAL; - } else { - return Math.max(minInterval, handlerInterval); } + + const minInterval = CoreCronDelegateService.MIN_INTERVAL; + + return Math.max(minInterval, handlerInterval); } /** @@ -251,7 +255,7 @@ export class CoreCronDelegateService { const time = Number(entry.value); return isNaN(time) ? 0 : time; - } catch (err) { + } catch { return 0; // Not set, return 0. } } @@ -263,12 +267,7 @@ export class CoreCronDelegateService { * @returns True if handler uses network or not defined, false otherwise. */ protected handlerUsesNetwork(name: string): boolean { - if (!this.handlers[name] || !this.handlers[name].usesNetwork) { - // Invalid, return default. - return true; - } - - return this.handlers[name].usesNetwork!(); + return this.handlers[name]?.usesNetwork?.() ?? true; } /** @@ -308,12 +307,7 @@ export class CoreCronDelegateService { * @returns True if handler is a sync process and can be manually executed or not defined, false otherwise. */ protected isHandlerManualSync(name: string): boolean { - if (!this.handlers[name] || !this.handlers[name].canManualSync) { - // Invalid, return default. - return this.isHandlerSync(name); - } - - return this.handlers[name].canManualSync!(); + return this.handlers[name]?.canManualSync?.() ?? this.isHandlerSync(name); } /** @@ -323,12 +317,7 @@ export class CoreCronDelegateService { * @returns True if handler is a sync process or not defined, false otherwise. */ protected isHandlerSync(name: string): boolean { - if (!this.handlers[name] || !this.handlers[name].isSync) { - // Invalid, return default. - return true; - } - - return this.handlers[name].isSync!(); + return this.handlers[name]?.isSync?.() ?? true; } /** diff --git a/src/core/services/local-notifications.ts b/src/core/services/local-notifications.ts index 2b3e9d820..d10df4c94 100644 --- a/src/core/services/local-notifications.ts +++ b/src/core/services/local-notifications.ts @@ -111,8 +111,8 @@ export class CoreLocalNotificationsProvider { }); CoreEvents.on(CoreEvents.SITE_DELETED, (site) => { - if (site) { - this.cancelSiteNotifications(site.id!); + if (site?.id) { + this.cancelSiteNotifications(site.id); } }); } @@ -364,15 +364,16 @@ export class CoreLocalNotificationsProvider { } return stored.at === triggered; - } catch (err) { + } catch { + const notificationId = notification.id || 0; if (useQueue) { - const queueId = 'isTriggered-' + notification.id; + const queueId = 'isTriggered-' + notificationId; - return this.queueRunner.run(queueId, () => LocalNotifications.isTriggered(notification.id!), { + return this.queueRunner.run(queueId, () => LocalNotifications.isTriggered(notificationId), { allowRepeated: true, }); } else { - return LocalNotifications.isTriggered(notification.id || 0); + return LocalNotifications.isTriggered(notificationId); } } } diff --git a/src/core/services/plugin-file-delegate.ts b/src/core/services/plugin-file-delegate.ts index 67038bc81..8cfb3f32f 100644 --- a/src/core/services/plugin-file-delegate.ts +++ b/src/core/services/plugin-file-delegate.ts @@ -113,7 +113,7 @@ export class CorePluginFileDelegateService extends CoreDelegate[]; + let files: string[] = []; for (const component in this.enabledHandlers) { const handler = this.enabledHandlers[component]; @@ -134,19 +134,19 @@ export class CorePluginFileDelegateService extends CoreDelegate { - siteId = siteId || CoreSites.getCurrentSiteId(); + const siteIdentifier = siteId || CoreSites.getCurrentSiteId(); - const filteredFiles = []; + const filteredFiles: CoreWSFile[] = []; await Promise.all(files.map(async (file) => { - const state = await CoreFilepool.getFileStateByUrl(siteId!, CoreFileHelper.getFileUrl(file), file.timemodified); + const state = await CoreFilepool.getFileStateByUrl(siteIdentifier, CoreFileHelper.getFileUrl(file), file.timemodified); - if (state != CoreConstants.DOWNLOADED && state != CoreConstants.NOT_DOWNLOADABLE) { + if (state !== CoreConstants.DOWNLOADED && state !== CoreConstants.NOT_DOWNLOADABLE) { filteredFiles.push(file); } })); - return this.getFilesSize(filteredFiles, siteId); + return this.getFilesSize(filteredFiles, siteIdentifier); } /** @@ -157,7 +157,7 @@ export class CorePluginFileDelegateService extends CoreDelegate { - const result = { + const result: CoreFileSizeSum = { size: 0, total: true, }; diff --git a/src/core/services/utils/dom.ts b/src/core/services/utils/dom.ts index f91e453e4..57f430892 100644 --- a/src/core/services/utils/dom.ts +++ b/src/core/services/utils/dom.ts @@ -1443,7 +1443,7 @@ export class CoreDomUtilsProvider { } return this.showErrorModal( - typeof errorMessage == 'string' && errorMessage ? error! : defaultError, + typeof errorMessage == 'string' && errorMessage && error ? error : defaultError, needsTranslate, autocloseTime, ); diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 1d79a73f0..7cd61dc00 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -87,7 +87,7 @@ export class CoreIframeUtilsProvider { }); return true; - } else if (element.classList.contains('core-iframe-offline-disabled')) { + } else if (element.classList.contains('core-iframe-offline-disabled') && element.parentElement) { // Reload the frame. if ('src' in element) { // eslint-disable-next-line no-self-assign @@ -98,7 +98,7 @@ export class CoreIframeUtilsProvider { } // Remove the warning and show the iframe - CoreDomUtils.removeElement(element.parentElement!, 'div.core-iframe-offline-warning'); + CoreDomUtils.removeElement(element.parentElement, 'div.core-iframe-offline-warning'); element.classList.remove('core-iframe-offline-disabled'); if (isSubframe) { @@ -207,14 +207,13 @@ export class CoreIframeUtilsProvider { const finalUrl = await currentSite.getAutoLoginUrl(url, false); // Resolve the promise once the iframe is loaded, or after a certain time. - let unblocked = false; const unblock = () => { - if (unblocked) { + if (!this.waitAutoLoginDefer) { + // Not blocked. return; } - unblocked = true; - this.waitAutoLoginDefer!.resolve(); + this.waitAutoLoginDefer.resolve(); delete this.waitAutoLoginDefer; }; diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 96fdce92a..80b4cef8c 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -807,7 +807,10 @@ export class CoreTextUtilsProvider { // Index the pluginfile URLs by file name. const pluginfileMap: {[name: string]: string} = {}; files.forEach((file) => { - pluginfileMap[file.filename!] = CoreFileHelper.getFileUrl(file); + if (!file.filename) { + return; + } + pluginfileMap[file.filename] = CoreFileHelper.getFileUrl(file); }); // Replace each draftfile with the corresponding pluginfile URL. @@ -872,9 +875,13 @@ export class CoreTextUtilsProvider { const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/'; files.forEach((file) => { + if (!file.filename) { + return; + } + // Search the draftfile URL in the original text. const matches = originalText.match( - new RegExp(draftfileUrlRegexPrefix + this.escapeForRegex(file.filename!) + '[^\'" ]*', 'i'), + new RegExp(draftfileUrlRegexPrefix + this.escapeForRegex(file.filename) + '[^\'" ]*', 'i'), ); if (!matches || !matches[0]) { diff --git a/src/theme/globals.variables.scss b/src/theme/globals.variables.scss index dda1c0c67..8412a46bd 100644 --- a/src/theme/globals.variables.scss +++ b/src/theme/globals.variables.scss @@ -70,7 +70,7 @@ $colors: ( // The minimum dimensions at which your layout will change, // adapting to different screen sizes, for use in media queries $screen-breakpoints: ( - xs: 0, + xs: 0px, sm: 576px, md: 768px, lg: 992px,