forked from CIT/Vmeda.Online
		
	MOBILE-3782 course: Fix guest access options and prefetch
This commit is contained in:
		
							parent
							
								
									9a63793cf2
								
							
						
					
					
						commit
						1512ab72ed
					
				@ -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) {
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        });
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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);
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user