diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index 722907a73..defb40723 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -100,7 +100,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, this.showCompletion = !!CoreSites.getRequiredCurrentSite().isVersionGreaterEqualThan('3.11'); if (this.showCompletion) { - CoreCourseHelper.calculateModuleCompletionData(this.module, this.courseId); + CoreCourseHelper.calculateModuleCompletionData(this.module); CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, this.module); this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => { @@ -428,7 +428,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, protected async fetchModule(): Promise { const module = await CoreCourse.getModule(this.module.id, this.courseId); - CoreCourseHelper.calculateModuleCompletionData(module, this.courseId); + CoreCourseHelper.calculateModuleCompletionData(module); await CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, module); diff --git a/src/core/features/course/components/format/format.ts b/src/core/features/course/components/format/format.ts index 6e8ae36bc..f351df992 100644 --- a/src/core/features/course/components/format/format.ts +++ b/src/core/features/course/components/format/format.ts @@ -33,6 +33,7 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreCourse, + CoreCourseModuleCompletionStatus, CoreCourseProvider, } from '@features/course/services/course'; import { @@ -643,7 +644,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { const moduleProgressPercent = 100 / (completionModules || 1); // Use min/max here to avoid floating point rounding errors over/under-flowing the progress bar. - if (completionData.state === CoreCourseProvider.COMPLETION_COMPLETE) { + if (completionData.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) { this.course.progress = Math.min(100, this.course.progress + moduleProgressPercent); } else { this.course.progress = Math.max(0, this.course.progress - moduleProgressPercent); diff --git a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts index 8eca20fe0..1b9e0b453 100644 --- a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts +++ b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts @@ -15,7 +15,7 @@ import { Component } from '@angular/core'; import { CoreUser } from '@features/user/services/user'; -import { CoreCourseProvider } from '@features/course/services/course'; +import { CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking } from '@features/course/services/course'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { Translate } from '@singletons'; import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion'; @@ -52,28 +52,28 @@ export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleC let langKey: string | undefined; let image: string | undefined; - if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL && - this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) { + if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) { image = 'completion-manual-n'; langKey = 'core.completion-alt-manual-n'; - } else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_MANUAL && - this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) { + } else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) { image = 'completion-manual-y'; langKey = 'core.completion-alt-manual-y'; - } else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && - this.completion.state === CoreCourseProvider.COMPLETION_INCOMPLETE) { + } else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) { image = 'completion-auto-n'; langKey = 'core.completion-alt-auto-n'; - } else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && - this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE) { + } else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE) { image = 'completion-auto-y'; langKey = 'core.completion-alt-auto-y'; - } else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && - this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_PASS) { + } else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) { image = 'completion-auto-pass'; langKey = 'core.completion-alt-auto-pass'; - } else if (this.completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && - this.completion.state === CoreCourseProvider.COMPLETION_COMPLETE_FAIL) { + } else if (this.completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC && + this.completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL) { image = 'completion-auto-fail'; langKey = 'core.completion-alt-auto-fail'; } diff --git a/src/core/features/course/components/module-completion/module-completion.ts b/src/core/features/course/components/module-completion/module-completion.ts index 92fbb0b0e..33fab2dd5 100644 --- a/src/core/features/course/components/module-completion/module-completion.ts +++ b/src/core/features/course/components/module-completion/module-completion.ts @@ -15,7 +15,7 @@ import { Component, Input } from '@angular/core'; import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classes/module-completion'; -import { CoreCourseModuleWSRuleDetails, CoreCourseProvider } from '@features/course/services/course'; +import { CoreCourseModuleCompletionStatus, CoreCourseModuleWSRuleDetails } from '@features/course/services/course'; import { CoreUser } from '@features/user/services/user'; import { Translate } from '@singletons'; @@ -51,10 +51,10 @@ export class CoreCourseModuleCompletionComponent extends CoreCourseModuleComplet // Format rules. this.details = await Promise.all(this.completion.details.map(async (rule: CompletionRule) => { - rule.statuscomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE || - rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_PASS; - rule.statuscompletefail = rule.rulevalue.status == CoreCourseProvider.COMPLETION_COMPLETE_FAIL; - rule.statusincomplete = rule.rulevalue.status == CoreCourseProvider.COMPLETION_INCOMPLETE; + rule.statuscomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || + rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS; + rule.statuscompletefail = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL; + rule.statusincomplete = rule.rulevalue.status == CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE; rule.accessibleDescription = null; if (this.completion!.overrideby) { diff --git a/src/core/features/course/components/section-selector/section-selector.ts b/src/core/features/course/components/section-selector/section-selector.ts index c2fdc2069..e71790478 100644 --- a/src/core/features/course/components/section-selector/section-selector.ts +++ b/src/core/features/course/components/section-selector/section-selector.ts @@ -15,7 +15,11 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreCourseSection } from '@features/course/services/course-helper'; -import { CoreCourseProvider } from '@features/course/services/course'; +import { + CoreCourseModuleCompletionStatus, + CoreCourseModuleCompletionTracking, + CoreCourseProvider, +} from '@features/course/services/course'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { CoreUtils } from '@services/utils/utils'; import { ModalController } from '@singletons'; @@ -56,14 +60,14 @@ export class CoreCourseSectionSelectorComponent implements OnInit { let complete = 0; let total = 0; section.modules.forEach((module) => { - if (!module.uservisible || module.completiondata === undefined || module.completiondata.tracking === undefined || - module.completiondata.tracking <= CoreCourseProvider.COMPLETION_TRACKING_NONE) { + if (!module.uservisible || module.completiondata === undefined || + module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE) { return; } total++; - if (module.completiondata.state == CoreCourseProvider.COMPLETION_COMPLETE || - module.completiondata.state == CoreCourseProvider.COMPLETION_COMPLETE_PASS) { + if (module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE || + module.completiondata.state == CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS) { complete++; } }); diff --git a/src/core/features/course/pages/module-preview/module-preview.page.ts b/src/core/features/course/pages/module-preview/module-preview.page.ts index 51dc29b23..01b58cbe6 100644 --- a/src/core/features/course/pages/module-preview/module-preview.page.ts +++ b/src/core/features/course/pages/module-preview/module-preview.page.ts @@ -73,7 +73,7 @@ export class CoreCourseModulePreviewPage implements OnInit { this.module = await CoreCourse.getModule(this.module.id, this.courseId); } - CoreCourseHelper.calculateModuleCompletionData(this.module, this.courseId); + CoreCourseHelper.calculateModuleCompletionData(this.module); await CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, this.module); diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 768470957..8d246dcd2 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -25,6 +25,8 @@ import { CoreCourseWSModule, CoreCourseProvider, CoreCourseWSSection, + CoreCourseModuleCompletionTracking, + CoreCourseModuleCompletionStatus, } from './course'; import { CoreConstants } from '@/core/constants'; import { CoreLogger } from '@singletons/logger'; @@ -200,8 +202,8 @@ export class CoreCourseHelperProvider { ); if (module.completiondata) { - this.calculateModuleCompletionData(module, courseId, courseName); - } else if (completionStatus && typeof completionStatus[module.id] != 'undefined') { + this.calculateModuleCompletionData(module, undefined, courseName); + } else if (completionStatus && completionStatus[module.id] !== undefined) { // Should not happen on > 3.6. Check if activity has completions and if it's marked. const activityStatus = completionStatus[module.id]; @@ -229,18 +231,18 @@ export class CoreCourseHelperProvider { * Calculate completion data of a module. * * @param module Module. - * @param courseId Course ID of the module. + * @param courseId Not used since 4.0 * @param courseName Course name. */ - calculateModuleCompletionData(module: CoreCourseModule, courseId: number, courseName?: string): void { + calculateModuleCompletionData(module: CoreCourseModule, courseId?: number, courseName?: string): void { if (!module.completiondata || !module.completion) { return; } - module.completiondata.courseId = courseId; - module.completiondata.courseName = courseName; + module.completiondata.courseId = module.course; module.completiondata.tracking = module.completion; module.completiondata.cmid = module.id; + module.completiondata.courseName = courseName; } /** @@ -2069,7 +2071,8 @@ export class CoreCourseHelperProvider { return; } - if (typeof completion.cmid == 'undefined' || completion.tracking !== 1) { + if (completion.cmid === undefined || + completion.tracking !== CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL) { return; } @@ -2077,14 +2080,15 @@ export class CoreCourseHelperProvider { event?.stopPropagation(); const modal = await CoreDomUtils.showModalLoading(); - completion.state = completion.state === 1 ? 0 : 1; + completion.state = completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE + ? CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE + : CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE; try { const response = await CoreCourse.markCompletedManually( completion.cmid, - completion.state === 1, + completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE, completion.courseId!, - completion.courseName, ); if (response.offline) { @@ -2093,7 +2097,11 @@ export class CoreCourseHelperProvider { return response; } catch (error) { - completion.state = completion.state === 1 ? 0 : 1; + // Restore previous state. + completion.state = completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE + ? CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE + : CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE; + CoreDomUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true); } finally { modal.dismiss(); @@ -2139,7 +2147,7 @@ export type CoreCourseModule = Omit & { export type CoreCourseModuleCompletionData = CoreCourseModuleWSCompletionData & { courseId?: number; courseName?: string; - tracking?: number; + tracking?: CoreCourseModuleCompletionTracking; cmid?: number; offline?: boolean; }; diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 6d5b8ea86..7beed88f3 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -61,6 +61,25 @@ declare module '@singletons/events' { } +/** + * Completion status valid values. + */ +export enum CoreCourseModuleCompletionStatus { + COMPLETION_INCOMPLETE = 0, + COMPLETION_COMPLETE = 1, + COMPLETION_COMPLETE_PASS = 2, + COMPLETION_COMPLETE_FAIL = 3, +} + +/** + * Completion tracking valid values. + */ +export enum CoreCourseModuleCompletionTracking { + COMPLETION_TRACKING_NONE = 0, + COMPLETION_TRACKING_MANUAL = 1, + COMPLETION_TRACKING_AUTOMATIC = 2, +} + /** * Service that provides some features regarding a course. */ @@ -73,13 +92,34 @@ export class CoreCourseProvider { static readonly ACCESS_DEFAULT = 'courses_access_default'; static readonly ALL_COURSES_CLEARED = -1; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE instead. + */ static readonly COMPLETION_TRACKING_NONE = 0; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL instead. + */ static readonly COMPLETION_TRACKING_MANUAL = 1; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC instead. + */ static readonly COMPLETION_TRACKING_AUTOMATIC = 2; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE instead. + */ static readonly COMPLETION_INCOMPLETE = 0; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE instead. + */ static readonly COMPLETION_COMPLETE = 1; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_PASS instead. + */ static readonly COMPLETION_COMPLETE_PASS = 2; + /** + * @deprecated since 4.0, use CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE_FAIL instead. + */ static readonly COMPLETION_COMPLETE_FAIL = 3; static readonly COMPONENT = 'CoreCourse'; @@ -151,7 +191,8 @@ export class CoreCourseProvider { * @param completion Completion status of the module. */ checkModuleCompletion(courseId: number, completion?: CoreCourseModuleCompletionData): void { - if (completion && completion.tracking === CoreCourseProvider.COMPLETION_TRACKING_AUTOMATIC && completion.state === 0) { + if (completion && completion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_AUTOMATIC && + completion.state === CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE) { this.invalidateSections(courseId).finally(() => { CoreEvents.trigger(CoreEvents.COMPLETION_MODULE_VIEWED, { courseId: courseId, @@ -256,7 +297,7 @@ export class CoreCourseProvider { const onlineCompletion = completionStatus[offlineCompletion.cmid]; // If the activity uses manual completion, override the value with the offline one. - if (onlineCompletion.tracking === 1) { + if (onlineCompletion.tracking === CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL) { onlineCompletion.state = offlineCompletion.completed; onlineCompletion.offline = true; } @@ -986,7 +1027,7 @@ export class CoreCourseProvider { CoreCourseOffline.markCompletedManually(cmId, completed, courseId, courseName, siteId); // The offline function requires a courseId and it could be missing because it's a calculated field. - if (!CoreApp.isOnline() && courseId) { + if (!CoreApp.isOnline()) { // App is offline, store the action. return storeOffline(); } @@ -996,18 +1037,14 @@ export class CoreCourseProvider { const result = await this.markCompletedManuallyOnline(cmId, completed, siteId); // Data sent to server, if there is some offline data delete it now. - try { - await CoreCourseOffline.deleteManualCompletion(cmId, siteId); - } catch { - // Ignore errors, shouldn't happen. - } + await CoreUtils.ignoreErrors(CoreCourseOffline.deleteManualCompletion(cmId, siteId)); // Invalidate module now, completion has changed. await this.invalidateModule(cmId, siteId); return result; } catch (error) { - if (CoreUtils.isWebServiceError(error) || !courseId) { + if (CoreUtils.isWebServiceError(error)) { // The WebService has thrown an error, this means that responses cannot be submitted. throw error; } else { @@ -1346,7 +1383,7 @@ export type CoreCourseCompletionActivityStatus = { instance: number; // Instance ID. state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail. timecompleted: number; // Timestamp for completed activity. - tracking: number; // Type of tracking: 0 means none, 1 manual, 2 automatic. + tracking: CoreCourseModuleCompletionTracking; // Type of tracking: 0 means none, 1 manual, 2 automatic. overrideby?: number | null; // The user id who has overriden the status, or null. valueused?: boolean; // Whether the completion status affects the availability of another activity. hascompletion?: boolean; // @since 3.11. Whether this activity module has completion enabled. @@ -1497,7 +1534,7 @@ export type CoreCourseWSModule = { afterlink?: string; // After link info to be displayed. customdata?: string; // Custom data (JSON encoded). noviewlink?: boolean; // Whether the module has no view page. - completion?: number; // Type of completion tracking: 0 means none, 1 manual, 2 automatic. + completion?: CoreCourseModuleCompletionTracking; // Type of completion tracking: 0 means none, 1 manual, 2 automatic. completiondata?: CoreCourseModuleWSCompletionData; // Module completion data. contents?: CoreCourseModuleContentFile[]; dates?: { @@ -1517,7 +1554,7 @@ export type CoreCourseWSModule = { * Module completion data. */ export type CoreCourseModuleWSCompletionData = { - state: number; // Completion state value: 0 means incomplete, 1 complete, 2 complete pass, 3 complete fail. + state: CoreCourseModuleCompletionStatus; // Completion state value. timecompleted: number; // Timestamp for completion status. overrideby: number | null; // The user id who has overriden the status. valueused?: boolean; // Whether the completion status affects the availability of another activity.