diff --git a/src/core/features/course/pages/contents/contents.ts b/src/core/features/course/pages/contents/contents.ts index 0fafde5ef..3bb1ac9fc 100644 --- a/src/core/features/course/pages/contents/contents.ts +++ b/src/core/features/course/pages/contents/contents.ts @@ -82,6 +82,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { protected syncObserver?: CoreEventObserver; protected isDestroyed = false; protected modulesHaveCompletion = false; + protected isGuest?: boolean; protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time. /** @@ -101,6 +102,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { this.sectionId = CoreNavigator.getRouteNumberParam('sectionId'); this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber'); this.moduleId = CoreNavigator.getRouteNumberParam('moduleId'); + this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest'); this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() && CoreCourseFormatDelegate.displayEnableDownload(this.course); @@ -309,7 +311,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { * @return Promise resolved when done. */ protected async loadMenuHandlers(refresh?: boolean): Promise { - this.courseMenuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(this.course, refresh); + this.courseMenuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(this.course, refresh, this.isGuest); } /** @@ -450,9 +452,11 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy { await CoreCourseHelper.confirmAndPrefetchCourse( this.prefetchCourseData, this.course, - this.sections, - undefined, - this.courseMenuHandlers, + { + sections: this.sections, + menuHandlers: this.courseMenuHandlers, + isGuest: this.isGuest, + }, ); } catch (error) { if (this.isDestroyed) { diff --git a/src/core/features/course/pages/index/index.page.ts b/src/core/features/course/pages/index/index.page.ts index cf9a9a892..98dd85dd1 100644 --- a/src/core/features/course/pages/index/index.page.ts +++ b/src/core/features/course/pages/index/index.page.ts @@ -48,6 +48,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { protected firstTabName?: string; protected module?: CoreCourseWSModule; protected modParams?: Params; + protected isGuest?: boolean; protected contentsTab: CoreTabsOutletTab = { page: CONTENTS_PAGE_NAME, title: 'core.course.contents', @@ -91,6 +92,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { this.firstTabName = CoreNavigator.getRouteParam('selectedTab'); this.module = CoreNavigator.getRouteParam('module'); this.modParams = CoreNavigator.getRouteParam('modParams'); + this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest'); this.currentPagePath = CoreNavigator.getCurrentPath(); this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page); @@ -98,6 +100,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { course: this.course, sectionId: CoreNavigator.getRouteNumberParam('sectionId'), sectionNumber: CoreNavigator.getRouteNumberParam('sectionNumber'), + isGuest: this.isGuest, }; if (this.module) { @@ -132,7 +135,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { */ protected async loadCourseHandlers(): Promise { // Load the course handlers. - const handlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(this.course!, false, false); + const handlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(this.course!, false, this.isGuest); let tabToLoad: number | undefined; diff --git a/src/core/features/course/pages/preview/preview.page.ts b/src/core/features/course/pages/preview/preview.page.ts index 32ddc47e2..122557ace 100644 --- a/src/core/features/course/pages/preview/preview.page.ts +++ b/src/core/features/course/pages/preview/preview.page.ts @@ -67,6 +67,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { isMobile: boolean; protected isGuestEnabled = false; + protected useGuestAccess = false; protected guestInstanceId?: number; protected enrolmentMethods: CoreCourseEnrolmentMethod[] = []; protected waitStart = 0; @@ -209,10 +210,12 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { this.course!.fullname = course.fullname || this.course!.fullname; this.course!.summary = course.summary || this.course!.summary; this.canAccessCourse = true; + this.useGuestAccess = false; } catch { // The user is not an admin/manager. Check if we can provide guest access to the course. try { this.canAccessCourse = !(await this.canAccessAsGuest()); + this.useGuestAccess = this.canAccessCourse; } catch { this.canAccessCourse = false; } @@ -243,7 +246,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { return; } - CoreCourseHelper.openCourse(this.course!); + CoreCourseHelper.openCourse(this.course!, { isGuest: this.useGuestAccess }); } /** @@ -452,12 +455,16 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy { /** * Prefetch the course. */ - prefetchCourse(): void { - CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!).catch((error) => { + async prefetchCourse(): Promise { + try { + await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, { + isGuest: this.useGuestAccess, + }); + } catch (error) { if (!this.pageDestroyed) { CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); } - }); + } } /** diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index d4127950e..61f2c9df7 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -361,17 +361,13 @@ export class CoreCourseHelperProvider { * * @param data An object where to store the course icon and title: "prefetchCourseIcon", "title" and "downloadSucceeded". * @param course Course to prefetch. - * @param sections List of course sections. - * @param courseHandlers List of course handlers. - * @param menuHandlers List of course menu handlers. + * @param options Other options. * @return Promise resolved when the download finishes, rejected if an error occurs or the user cancels. */ async confirmAndPrefetchCourse( data: CorePrefetchStatusInfo, course: CoreCourseAnyCourseData, - sections?: CoreCourseWSSection[], - courseHandlers?: CoreCourseOptionsHandlerToDisplay[], - menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[], + options: CoreCoursePrefetchCourseOptions = {}, ): Promise { const initialIcon = data.icon; const initialStatus = data.status; @@ -386,23 +382,23 @@ export class CoreCourseHelperProvider { try { // Get the sections first if needed. - if (!sections) { - sections = await CoreCourse.getSections(course.id, false, true); + if (!options.sections) { + options.sections = await CoreCourse.getSections(course.id, false, true); } // Confirm the download. - await this.confirmDownloadSizeSection(course.id, undefined, sections, true); + await this.confirmDownloadSizeSection(course.id, undefined, options.sections, true); // User confirmed, get the course handlers if needed. - if (!courseHandlers) { - courseHandlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(course); + if (!options.courseHandlers) { + options.courseHandlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, options.isGuest); } - if (!menuHandlers) { - menuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course); + if (!options.menuHandlers) { + options.menuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, options.isGuest); } // Now we have all the data, download the course. - await this.prefetchCourse(course, sections, courseHandlers, menuHandlers, siteId); + await this.prefetchCourse(course, options.sections, options.courseHandlers, options.menuHandlers, siteId); // Download successful. data.downloadSucceeded = true; @@ -422,12 +418,12 @@ export class CoreCourseHelperProvider { * Confirm and prefetches a list of courses. * * @param courses List of courses to download. - * @param onProgress Function to call everytime a course is downloaded. + * @param options Other options. * @return Resolved when downloaded, rejected if error or canceled. */ async confirmAndPrefetchCourses( courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], - onProgress?: (data: CoreCourseCoursesProgress) => void, + options: CoreCourseConfirmPrefetchCoursesOptions = {}, ): Promise { const siteId = CoreSites.getCurrentSiteId(); @@ -437,12 +433,18 @@ export class CoreCourseHelperProvider { const total = courses.length; let count = 0; - const promises = courses.map((course) => { + const promises = courses.map(async (course) => { const subPromises: Promise[] = []; let sections: CoreCourseWSSection[]; let handlers: CoreCourseOptionsHandlerToDisplay[] = []; let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = []; let success = true; + let isGuest = false; + + if (options.canHaveGuestCourses) { + // Check if the user can only access as guest. + isGuest = await this.courseUsesGuestAccess(course.id, siteId); + } // Get the sections and the handlers. subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => { @@ -451,12 +453,12 @@ export class CoreCourseHelperProvider { return; })); - subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course).then((cHandlers) => { + subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, isGuest).then((cHandlers) => { handlers = cHandlers; return; })); - subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course).then((mHandlers) => { + subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, isGuest).then((mHandlers) => { menuHandlers = mHandlers; return; @@ -470,15 +472,15 @@ export class CoreCourseHelperProvider { }).finally(() => { // Course downloaded or failed, notify the progress. count++; - if (onProgress) { - onProgress({ count: count, total: total, courseId: course.id, success: success }); + if (options.onProgress) { + options.onProgress({ count: count, total: total, courseId: course.id, success: success }); } }); }); - if (onProgress) { + if (options.onProgress) { // Notify the start of the download. - onProgress({ count: 0, total: total, success: true }); + options.onProgress({ count: 0, total: total, success: true }); } return CoreUtils.allPromises(promises); @@ -609,6 +611,55 @@ export class CoreCourseHelperProvider { } } + /** + * Check whether a course is accessed using guest access. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether course is accessed using guest access. + */ + async courseUsesGuestAccess(courseId: number, siteId?: string): Promise { + try { + try { + // Check if user is enrolled. If enrolled, no guest access. + await CoreCourses.getUserCourse(courseId, false, siteId); + + return false; + } catch { + // Ignore errors. + } + + try { + // The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course. + await CoreCourses.getCourse(courseId, siteId); + + return false; + } catch { + // Ignore errors. + } + + // Check if guest access is enabled. + const enrolmentMethods = await CoreCourses.getCourseEnrolmentMethods(courseId, siteId); + + const method = enrolmentMethods.find((method) => method.type === 'guest'); + + if (!method) { + return false; + } + + const info = await CoreCourses.getCourseGuestEnrolmentInfo(method.id); + if (!info.status) { + // Not active, reject. + return false; + } + + // Don't allow guest access if it requires a password. + return !info.passwordrequired; + } catch { + return false; + } + } + /** * Create and return a section for "All sections". * @@ -1257,23 +1308,30 @@ export class CoreCourseHelperProvider { * * @param courses Courses array to prefetch. * @param prefetch Prefetch information to be updated. + * @param options Other options. * @return Promise resolved when done. */ async prefetchCourses( courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], prefetch: CorePrefetchStatusInfo, + options: CoreCoursePrefetchCoursesOptions = {}, ): Promise { prefetch.loading = true; prefetch.icon = CoreConstants.ICON_DOWNLOADING; prefetch.badge = ''; - try { - await this.confirmAndPrefetchCourses(courses, (progress) => { + const prefetchOptions = { + ...options, + onProgress: (progress) => { prefetch.badge = progress.count + ' / ' + progress.total; prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress); prefetch.count = progress.count; prefetch.total = progress.total; - }); + }, + }; + + try { + await this.confirmAndPrefetchCourses(courses, prefetchOptions); prefetch.icon = CoreConstants.ICON_OUTDATED; } finally { prefetch.loading = false; @@ -2059,6 +2117,30 @@ export type CoreCourseModuleCompletionData = CoreCourseModuleWSCompletionData & offline?: boolean; }; +/** + * Options for prefetch course function. + */ +export type CoreCoursePrefetchCourseOptions = { + sections?: CoreCourseWSSection[]; // List of course sections. + courseHandlers?: CoreCourseOptionsHandlerToDisplay[]; // List of course handlers. + menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[]; // List of course menu handlers. + isGuest?: boolean; // Whether the user is guest. +}; + +/** + * Options for prefetch courses function. + */ +export type CoreCoursePrefetchCoursesOptions = { + canHaveGuestCourses?: boolean; // Whether the list of courses can contain courses with only guest access. +}; + +/** + * Options for confirm and prefetch courses function. + */ +export type CoreCourseConfirmPrefetchCoursesOptions = CoreCoursePrefetchCoursesOptions & { + onProgress?: (data: CoreCourseCoursesProgress) => void; +}; + type ComponentWithContextMenu = { prefetchStatusIcon?: string; isDestroyed?: boolean; diff --git a/src/core/features/courses/pages/my-courses/my-courses.ts b/src/core/features/courses/pages/my-courses/my-courses.ts index 657e4e84c..c7697acb8 100644 --- a/src/core/features/courses/pages/my-courses/my-courses.ts +++ b/src/core/features/courses/pages/my-courses/my-courses.ts @@ -185,13 +185,13 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy { this.downloadAllCoursesLoading = true; try { - await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, (progress) => { + await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, { onProgress: (progress) => { this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total; this.downloadAllCoursesBadgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress); this.downloadAllCoursesCount = progress.count; this.downloadAllCoursesTotal = progress.total; - }); + } }); } catch (error) { if (!this.isDestroyed) { CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); diff --git a/upgrade.txt b/upgrade.txt index 4ee9ede3e..19393ed60 100644 --- a/upgrade.txt +++ b/upgrade.txt @@ -1,6 +1,10 @@ This files describes API changes in the Moodle Mobile app, information provided here is intended especially for developers. +=== 3.9.6 === + +- The parameters of the functions confirmAndPrefetchCourse and confirmAndPrefetchCourses have changed, they now accept an object with options. + === 3.9.5 === - Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters.