diff --git a/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts b/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts index 129c1ca6d..9b9a2f2bb 100644 --- a/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts +++ b/src/addons/block/activitymodules/components/activitymodules/activitymodules.ts @@ -21,6 +21,7 @@ import { ContextLevel, CoreConstants } from '@/core/constants'; import { Translate } from '@singletons'; import { CoreUtils } from '@services/utils/utils'; import { CoreNavigator } from '@services/navigator'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; /** * Component to render an "activity modules" block. @@ -67,7 +68,7 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i } section.modules.forEach((mod) => { - if (mod.uservisible === false || !CoreCourse.moduleHasView(mod) || + if (!CoreCourseHelper.canUserViewModule(mod, section) || !CoreCourse.moduleHasView(mod) || modFullNames[mod.modname] !== undefined) { // Ignore this module. return; 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 50460392d..453d03c2f 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -34,6 +34,7 @@ import { CoreCourseProvider, } from '@features/course/services/course'; import { + CoreCourseHelper, CoreCourseSection, } from '@features/course/services/course-helper'; import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; @@ -444,7 +445,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { await CoreCourseModuleDelegate.getModuleDataFor(module.modname, module, this.course.id); } - if (module.uservisible !== false && module.handlerData?.action) { + if (CoreCourseHelper.canUserViewModule(module, section) && module.handlerData?.action) { module.handlerData.action(data.event, module, module.course); } @@ -574,7 +575,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { continue; } - modulesLoaded += sections[i].modules.reduce((total, module) => module.visibleoncoursepage !== 0 ? total + 1 : total, 0); + modulesLoaded += sections[i].modules.reduce((total, module) => + !CoreCourseHelper.isModuleStealth(module, sections[i]) ? total + 1 : total, 0); if (modulesLoaded >= CoreCourseFormatComponent.LOAD_MORE_ACTIVITIES) { break; @@ -632,8 +634,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @return Whether the section can be viewed. */ canViewSection(section: CoreCourseSection): boolean { - return section.uservisible !== false && !section.hiddenbynumsections && - section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID; + return CoreCourseHelper.canUserViewSection(section) && !CoreCourseHelper.isSectionStealth(section); } } diff --git a/src/core/features/course/components/course-index/course-index.ts b/src/core/features/course/components/course-index/course-index.ts index 2c2503e7b..ce2c1667d 100644 --- a/src/core/features/course/components/course-index/course-index.ts +++ b/src/core/features/course/components/course-index/course-index.ts @@ -18,7 +18,7 @@ import { CoreCourseModuleCompletionTracking, CoreCourseProvider, } from '@features/course/services/course'; -import { CoreCourseSection } from '@features/course/services/course-helper'; +import { CoreCourseHelper, CoreCourseSection } from '@features/course/services/course-helper'; import { CoreCourseFormatDelegate } from '@features/course/services/format-delegate'; import { CoreCourseAnyCourseData } from '@features/courses/services/courses'; import { IonContent } from '@ionic/angular'; @@ -77,11 +77,10 @@ export class CoreCourseCourseIndexComponent implements OnInit { // Clone sections to add information. this.sectionsToRender = this.sections - .filter((section) => !section.hiddenbynumsections && - section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID) + .filter((section) => !CoreCourseHelper.isSectionStealth(section)) .map((section) => { const modules = section.modules - .filter((module) => module.visibleoncoursepage !== 0 && !module.noviewlink) + .filter((module) => !CoreCourseHelper.isModuleStealth(module, section) && !module.noviewlink) .map((module) => { const completionStatus = !completionEnabled || module.completiondata === undefined || module.completiondata.tracking == CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_NONE @@ -93,7 +92,7 @@ export class CoreCourseCourseIndexComponent implements OnInit { name: module.name, course: module.course, visible: !!module.visible, - uservisible: !!module.uservisible, + uservisible: CoreCourseHelper.canUserViewModule(module, section), completionStatus, }; }); @@ -103,7 +102,7 @@ export class CoreCourseCourseIndexComponent implements OnInit { name: section.name, availabilityinfo: !!section.availabilityinfo, visible: !!section.visible, - uservisible: section.uservisible !== false, + uservisible: CoreCourseHelper.canUserViewSection(section), expanded: section.id === this.selectedId, highlighted: currentSectionData.section.id === section.id, hasVisibleModules: modules.length > 0, diff --git a/src/core/features/course/components/module-navigation/module-navigation.ts b/src/core/features/course/components/module-navigation/module-navigation.ts index 132e8c27a..40a36ad23 100644 --- a/src/core/features/course/components/module-navigation/module-navigation.ts +++ b/src/core/features/course/components/module-navigation/module-navigation.ts @@ -13,8 +13,8 @@ // limitations under the License. import { Component, Input, OnDestroy, OnInit } from '@angular/core'; -import { CoreCourse, CoreCourseProvider, CoreCourseWSSection } from '@features/course/services/course'; -import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreCourse, CoreCourseWSSection } from '@features/course/services/course'; +import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate'; import { IonContent } from '@ionic/angular'; import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; @@ -187,7 +187,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { * @return Wether the module is available to the user or not. */ protected isSectionAvailable(section: CoreCourseWSSection): boolean { - return section.uservisible !== false && section.id != CoreCourseProvider.STEALTH_MODULES_SECTION_ID; + return CoreCourseHelper.canUserViewSection(section) && !CoreCourseHelper.isSectionStealth(section); } /** @@ -223,7 +223,7 @@ export class CoreCourseModuleNavigationComponent implements OnInit, OnDestroy { animationDirection: next ? 'forward' : 'back', }; - if (module.uservisible === false) { + if (!CoreCourseHelper.canUserViewModule(module)) { const section = next ? this.nextModuleSection : this.previousModuleSection; options.params = { module, diff --git a/src/core/features/course/components/module/module.ts b/src/core/features/course/components/module/module.ts index c90e0407d..d6078eaea 100644 --- a/src/core/features/course/components/module/module.ts +++ b/src/core/features/course/components/module/module.ts @@ -19,6 +19,7 @@ import { CoreCourseModuleData, CoreCourseModuleCompletionData, CoreCourseSection, + CoreCourseHelper, } from '@features/course/services/course-helper'; import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseModuleCompletionTracking } from '@features/course/services/course'; import { CoreCourseModuleDelegate, CoreCourseModuleHandlerButton } from '@features/course/services/module-delegate'; @@ -166,7 +167,7 @@ export class CoreCourseModuleComponent implements OnInit, OnDestroy { * @param event Click event. */ moduleClicked(event: Event): void { - if (this.module.uservisible !== false && this.module.handlerData?.action) { + if (CoreCourseHelper.canUserViewModule(this.module, this.section) && this.module.handlerData?.action) { this.module.handlerData.action(event, this.module, this.module.course); } } diff --git a/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts b/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts index fde5d1830..204abf7f8 100644 --- a/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts +++ b/src/core/features/course/pages/list-mod-type/list-mod-type.page.ts @@ -75,7 +75,7 @@ export class CoreCourseListModTypePage implements OnInit { } section.modules = section.modules.filter((mod) => { - if (mod.uservisible === false || !CoreCourse.moduleHasView(mod)) { + if (!CoreCourseHelper.canUserViewModule(mod, section) || !CoreCourse.moduleHasView(mod)) { // Ignore this module. return false; } diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index ad36f9899..422d4e3a6 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -198,7 +198,7 @@ export class CoreCourseHelperProvider { } // Check if the module is stealth. - module.isStealth = module.visibleoncoursepage === 0 || (!!module.visible && !section.visible); + module.isStealth = CoreCourseHelper.isModuleStealth(module, section); })); return section; @@ -208,6 +208,50 @@ export class CoreCourseHelperProvider { return { hasContent, sections: formattedSections }; } + /** + * Module is stealth. + * + * @param module Module to check. + * @param section Section to check. + * @return Wether the module is stealth. + */ + isModuleStealth(module: CoreCourseModuleData, section?: CoreCourseWSSection): boolean { + // visibleoncoursepage can be 1 for teachers when the section is hidden. + return !!module.visible && (!module.visibleoncoursepage || (!!section && !section.visible)); + } + + /** + * Module is visible by the user. + * + * @param module Module to check. + * @param section Section to check. Omitted if not defined. + * @return Wether the section is visible by the user. + */ + canUserViewModule(module: CoreCourseModuleData, section?: CoreCourseWSSection): boolean { + return module.uservisible !== false && (!section || CoreCourseHelper.canUserViewSection(section)); + } + + /** + * Section is stealth. + * This should not be true on Moodle 4.0 onwards. + * + * @param section Section to check. + * @return Wether section is stealth (accessible but not visible to students). + */ + isSectionStealth(section: CoreCourseWSSection): boolean { + return section.hiddenbynumsections === 1 || section.id === CoreCourseProvider.STEALTH_MODULES_SECTION_ID; + } + + /** + * Section is visible by the user. + * + * @param section Section to check. + * @return Wether the section is visible by the user. + */ + canUserViewSection(section: CoreCourseWSSection): boolean { + return section.uservisible !== false; + } + /** * Calculate completion data of a module. * diff --git a/src/core/features/course/services/module-prefetch-delegate.ts b/src/core/features/course/services/module-prefetch-delegate.ts index d75e36759..e0c83ec41 100644 --- a/src/core/features/course/services/module-prefetch-delegate.ts +++ b/src/core/features/course/services/module-prefetch-delegate.ts @@ -33,7 +33,7 @@ import { CoreError } from '@classes/errors/error'; import { CoreWSFile, CoreWSExternalWarning } from '@services/ws'; import { CHECK_UPDATES_TIMES_TABLE, CoreCourseCheckUpdatesDBRecord } from './database/module-prefetch'; import { CoreFileSizeSum } from '@services/plugin-file-delegate'; -import { CoreCourseModuleData } from './course-helper'; +import { CoreCourseHelper, CoreCourseModuleData } from './course-helper'; const ROOT_CACHE_KEY = 'mmCourse:'; @@ -956,7 +956,7 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate { - if ('uservisible' in module && module.uservisible === false) { + if ('uservisible' in module && !CoreCourseHelper.canUserViewModule(module)) { // Module isn't visible by the user, cannot be downloaded. return false; } diff --git a/src/core/features/filter/services/filter-helper.ts b/src/core/features/filter/services/filter-helper.ts index 574233d2c..bc6ed652e 100644 --- a/src/core/features/filter/services/filter-helper.ts +++ b/src/core/features/filter/services/filter-helper.ts @@ -30,6 +30,7 @@ import { makeSingleton } from '@singletons'; import { CoreEvents, CoreEventSiteData } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { CoreSite } from '@classes/site'; +import { CoreCourseHelper } from '@features/course/services/course-helper'; /** * Helper service to provide filter functionalities. @@ -159,7 +160,7 @@ export class CoreFilterHelperProvider { sections.forEach((section) => { if (section.modules) { section.modules.forEach((module) => { - if (module.uservisible) { + if (CoreCourseHelper.canUserViewModule(module, section)) { contexts.push({ contextlevel: 'module', instanceid: module.id, diff --git a/src/core/features/siteplugins/classes/handlers/module-handler.ts b/src/core/features/siteplugins/classes/handlers/module-handler.ts index 846fd28f9..e501e91c6 100644 --- a/src/core/features/siteplugins/classes/handlers/module-handler.ts +++ b/src/core/features/siteplugins/classes/handlers/module-handler.ts @@ -16,7 +16,7 @@ import { Type } from '@angular/core'; import { CoreConstants } from '@/core/constants'; import { CoreCourse } from '@features/course/services/course'; -import { CoreCourseModuleData } from '@features/course/services/course-helper'; +import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate'; import { CoreSitePluginsModuleIndexComponent } from '@features/siteplugins/components/module-index/module-index'; import { @@ -105,7 +105,7 @@ export class CoreSitePluginsModuleHandler extends CoreSitePluginsBaseHandler imp }; } - if (forCoursePage && this.handlerSchema.coursepagemethod && module.visibleoncoursepage !== 0) { + if (forCoursePage && this.handlerSchema.coursepagemethod && !CoreCourseHelper.isModuleStealth(module)) { // Call the method to get the course page template. const method = this.handlerSchema.coursepagemethod; this.loadCoursePageTemplate(module, courseId, handlerData, method);