MOBILE-3782 course: Fix guest access options and prefetch

main
Dani Palou 2021-07-26 11:07:33 +02:00
parent 9a63793cf2
commit 1512ab72ed
6 changed files with 137 additions and 37 deletions

View File

@ -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<void> {
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) {

View File

@ -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<CoreCourseWSModule>('module');
this.modParams = CoreNavigator.getRouteParam<Params>('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<void> {
// 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;

View File

@ -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<void> {
try {
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, {
isGuest: this.useGuestAccess,
});
} catch (error) {
if (!this.pageDestroyed) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);
}
});
}
}
/**

View File

@ -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<void> {
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<void> {
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<void>[] = [];
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<boolean> {
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<void> {
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;

View File

@ -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);

View File

@ -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.