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 syncObserver?: CoreEventObserver;
protected isDestroyed = false; protected isDestroyed = false;
protected modulesHaveCompletion = false; protected modulesHaveCompletion = false;
protected isGuest?: boolean;
protected debouncedUpdateCachedCompletion?: () => void; // Update the cached completion after a certain time. 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.sectionId = CoreNavigator.getRouteNumberParam('sectionId');
this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber'); this.sectionNumber = CoreNavigator.getRouteNumberParam('sectionNumber');
this.moduleId = CoreNavigator.getRouteNumberParam('moduleId'); this.moduleId = CoreNavigator.getRouteNumberParam('moduleId');
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() && this.displayEnableDownload = !CoreSites.getCurrentSite()?.isOfflineDisabled() &&
CoreCourseFormatDelegate.displayEnableDownload(this.course); CoreCourseFormatDelegate.displayEnableDownload(this.course);
@ -309,7 +311,7 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
protected async loadMenuHandlers(refresh?: boolean): Promise<void> { 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( await CoreCourseHelper.confirmAndPrefetchCourse(
this.prefetchCourseData, this.prefetchCourseData,
this.course, this.course,
this.sections, {
undefined, sections: this.sections,
this.courseMenuHandlers, menuHandlers: this.courseMenuHandlers,
isGuest: this.isGuest,
},
); );
} catch (error) { } catch (error) {
if (this.isDestroyed) { if (this.isDestroyed) {

View File

@ -48,6 +48,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
protected firstTabName?: string; protected firstTabName?: string;
protected module?: CoreCourseWSModule; protected module?: CoreCourseWSModule;
protected modParams?: Params; protected modParams?: Params;
protected isGuest?: boolean;
protected contentsTab: CoreTabsOutletTab = { protected contentsTab: CoreTabsOutletTab = {
page: CONTENTS_PAGE_NAME, page: CONTENTS_PAGE_NAME,
title: 'core.course.contents', title: 'core.course.contents',
@ -91,6 +92,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
this.firstTabName = CoreNavigator.getRouteParam('selectedTab'); this.firstTabName = CoreNavigator.getRouteParam('selectedTab');
this.module = CoreNavigator.getRouteParam<CoreCourseWSModule>('module'); this.module = CoreNavigator.getRouteParam<CoreCourseWSModule>('module');
this.modParams = CoreNavigator.getRouteParam<Params>('modParams'); this.modParams = CoreNavigator.getRouteParam<Params>('modParams');
this.isGuest = CoreNavigator.getRouteBooleanParam('isGuest');
this.currentPagePath = CoreNavigator.getCurrentPath(); this.currentPagePath = CoreNavigator.getCurrentPath();
this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page); this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page);
@ -98,6 +100,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
course: this.course, course: this.course,
sectionId: CoreNavigator.getRouteNumberParam('sectionId'), sectionId: CoreNavigator.getRouteNumberParam('sectionId'),
sectionNumber: CoreNavigator.getRouteNumberParam('sectionNumber'), sectionNumber: CoreNavigator.getRouteNumberParam('sectionNumber'),
isGuest: this.isGuest,
}; };
if (this.module) { if (this.module) {
@ -132,7 +135,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
*/ */
protected async loadCourseHandlers(): Promise<void> { protected async loadCourseHandlers(): Promise<void> {
// Load the course handlers. // 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; let tabToLoad: number | undefined;

View File

@ -67,6 +67,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
isMobile: boolean; isMobile: boolean;
protected isGuestEnabled = false; protected isGuestEnabled = false;
protected useGuestAccess = false;
protected guestInstanceId?: number; protected guestInstanceId?: number;
protected enrolmentMethods: CoreCourseEnrolmentMethod[] = []; protected enrolmentMethods: CoreCourseEnrolmentMethod[] = [];
protected waitStart = 0; protected waitStart = 0;
@ -209,10 +210,12 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
this.course!.fullname = course.fullname || this.course!.fullname; this.course!.fullname = course.fullname || this.course!.fullname;
this.course!.summary = course.summary || this.course!.summary; this.course!.summary = course.summary || this.course!.summary;
this.canAccessCourse = true; this.canAccessCourse = true;
this.useGuestAccess = false;
} catch { } catch {
// The user is not an admin/manager. Check if we can provide guest access to the course. // The user is not an admin/manager. Check if we can provide guest access to the course.
try { try {
this.canAccessCourse = !(await this.canAccessAsGuest()); this.canAccessCourse = !(await this.canAccessAsGuest());
this.useGuestAccess = this.canAccessCourse;
} catch { } catch {
this.canAccessCourse = false; this.canAccessCourse = false;
} }
@ -243,7 +246,7 @@ export class CoreCoursePreviewPage implements OnInit, OnDestroy {
return; 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. * Prefetch the course.
*/ */
prefetchCourse(): void { async prefetchCourse(): Promise<void> {
CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!).catch((error) => { try {
await CoreCourseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course!, {
isGuest: this.useGuestAccess,
});
} catch (error) {
if (!this.pageDestroyed) { if (!this.pageDestroyed) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); 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 data An object where to store the course icon and title: "prefetchCourseIcon", "title" and "downloadSucceeded".
* @param course Course to prefetch. * @param course Course to prefetch.
* @param sections List of course sections. * @param options Other options.
* @param courseHandlers List of course handlers.
* @param menuHandlers List of course menu handlers.
* @return Promise resolved when the download finishes, rejected if an error occurs or the user cancels. * @return Promise resolved when the download finishes, rejected if an error occurs or the user cancels.
*/ */
async confirmAndPrefetchCourse( async confirmAndPrefetchCourse(
data: CorePrefetchStatusInfo, data: CorePrefetchStatusInfo,
course: CoreCourseAnyCourseData, course: CoreCourseAnyCourseData,
sections?: CoreCourseWSSection[], options: CoreCoursePrefetchCourseOptions = {},
courseHandlers?: CoreCourseOptionsHandlerToDisplay[],
menuHandlers?: CoreCourseOptionsMenuHandlerToDisplay[],
): Promise<void> { ): Promise<void> {
const initialIcon = data.icon; const initialIcon = data.icon;
const initialStatus = data.status; const initialStatus = data.status;
@ -386,23 +382,23 @@ export class CoreCourseHelperProvider {
try { try {
// Get the sections first if needed. // Get the sections first if needed.
if (!sections) { if (!options.sections) {
sections = await CoreCourse.getSections(course.id, false, true); options.sections = await CoreCourse.getSections(course.id, false, true);
} }
// Confirm the download. // 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. // User confirmed, get the course handlers if needed.
if (!courseHandlers) { if (!options.courseHandlers) {
courseHandlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(course); options.courseHandlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, options.isGuest);
} }
if (!menuHandlers) { if (!options.menuHandlers) {
menuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course); options.menuHandlers = await CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, options.isGuest);
} }
// Now we have all the data, download the course. // 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. // Download successful.
data.downloadSucceeded = true; data.downloadSucceeded = true;
@ -422,12 +418,12 @@ export class CoreCourseHelperProvider {
* Confirm and prefetches a list of courses. * Confirm and prefetches a list of courses.
* *
* @param courses List of courses to download. * @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. * @return Resolved when downloaded, rejected if error or canceled.
*/ */
async confirmAndPrefetchCourses( async confirmAndPrefetchCourses(
courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
onProgress?: (data: CoreCourseCoursesProgress) => void, options: CoreCourseConfirmPrefetchCoursesOptions = {},
): Promise<void> { ): Promise<void> {
const siteId = CoreSites.getCurrentSiteId(); const siteId = CoreSites.getCurrentSiteId();
@ -437,12 +433,18 @@ export class CoreCourseHelperProvider {
const total = courses.length; const total = courses.length;
let count = 0; let count = 0;
const promises = courses.map((course) => { const promises = courses.map(async (course) => {
const subPromises: Promise<void>[] = []; const subPromises: Promise<void>[] = [];
let sections: CoreCourseWSSection[]; let sections: CoreCourseWSSection[];
let handlers: CoreCourseOptionsHandlerToDisplay[] = []; let handlers: CoreCourseOptionsHandlerToDisplay[] = [];
let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = []; let menuHandlers: CoreCourseOptionsMenuHandlerToDisplay[] = [];
let success = true; 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. // Get the sections and the handlers.
subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => { subPromises.push(CoreCourse.getSections(course.id, false, true).then((courseSections) => {
@ -451,12 +453,12 @@ export class CoreCourseHelperProvider {
return; return;
})); }));
subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course).then((cHandlers) => { subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, isGuest).then((cHandlers) => {
handlers = cHandlers; handlers = cHandlers;
return; return;
})); }));
subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course).then((mHandlers) => { subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, isGuest).then((mHandlers) => {
menuHandlers = mHandlers; menuHandlers = mHandlers;
return; return;
@ -470,15 +472,15 @@ export class CoreCourseHelperProvider {
}).finally(() => { }).finally(() => {
// Course downloaded or failed, notify the progress. // Course downloaded or failed, notify the progress.
count++; count++;
if (onProgress) { if (options.onProgress) {
onProgress({ count: count, total: total, courseId: course.id, success: success }); options.onProgress({ count: count, total: total, courseId: course.id, success: success });
} }
}); });
}); });
if (onProgress) { if (options.onProgress) {
// Notify the start of the download. // 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); 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". * Create and return a section for "All sections".
* *
@ -1257,23 +1308,30 @@ export class CoreCourseHelperProvider {
* *
* @param courses Courses array to prefetch. * @param courses Courses array to prefetch.
* @param prefetch Prefetch information to be updated. * @param prefetch Prefetch information to be updated.
* @param options Other options.
* @return Promise resolved when done. * @return Promise resolved when done.
*/ */
async prefetchCourses( async prefetchCourses(
courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
prefetch: CorePrefetchStatusInfo, prefetch: CorePrefetchStatusInfo,
options: CoreCoursePrefetchCoursesOptions = {},
): Promise<void> { ): Promise<void> {
prefetch.loading = true; prefetch.loading = true;
prefetch.icon = CoreConstants.ICON_DOWNLOADING; prefetch.icon = CoreConstants.ICON_DOWNLOADING;
prefetch.badge = ''; prefetch.badge = '';
try { const prefetchOptions = {
await this.confirmAndPrefetchCourses(courses, (progress) => { ...options,
onProgress: (progress) => {
prefetch.badge = progress.count + ' / ' + progress.total; prefetch.badge = progress.count + ' / ' + progress.total;
prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress); prefetch.badgeA11yText = Translate.instant('core.course.downloadcoursesprogressdescription', progress);
prefetch.count = progress.count; prefetch.count = progress.count;
prefetch.total = progress.total; prefetch.total = progress.total;
}); },
};
try {
await this.confirmAndPrefetchCourses(courses, prefetchOptions);
prefetch.icon = CoreConstants.ICON_OUTDATED; prefetch.icon = CoreConstants.ICON_OUTDATED;
} finally { } finally {
prefetch.loading = false; prefetch.loading = false;
@ -2059,6 +2117,30 @@ export type CoreCourseModuleCompletionData = CoreCourseModuleWSCompletionData &
offline?: boolean; 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 = { type ComponentWithContextMenu = {
prefetchStatusIcon?: string; prefetchStatusIcon?: string;
isDestroyed?: boolean; isDestroyed?: boolean;

View File

@ -185,13 +185,13 @@ export class CoreCoursesMyCoursesPage implements OnInit, OnDestroy {
this.downloadAllCoursesLoading = true; this.downloadAllCoursesLoading = true;
try { try {
await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, (progress) => { await CoreCourseHelper.confirmAndPrefetchCourses(this.courses, { onProgress: (progress) => {
this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total; this.downloadAllCoursesBadge = progress.count + ' / ' + progress.total;
this.downloadAllCoursesBadgeA11yText = this.downloadAllCoursesBadgeA11yText =
Translate.instant('core.course.downloadcoursesprogressdescription', progress); Translate.instant('core.course.downloadcoursesprogressdescription', progress);
this.downloadAllCoursesCount = progress.count; this.downloadAllCoursesCount = progress.count;
this.downloadAllCoursesTotal = progress.total; this.downloadAllCoursesTotal = progress.total;
}); } });
} catch (error) { } catch (error) {
if (!this.isDestroyed) { if (!this.isDestroyed) {
CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); CoreDomUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true);

View File

@ -1,6 +1,10 @@
This files describes API changes in the Moodle Mobile app, This files describes API changes in the Moodle Mobile app,
information provided here is intended especially for developers. 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 === === 3.9.5 ===
- Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters. - Several functions inside AddonNotificationsProvider have been modified to accept an "options" parameter instead of having several optional parameters.