2143 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			2143 lines
		
	
	
		
			78 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
// (C) Copyright 2015 Moodle Pty Ltd.
 | 
						|
//
 | 
						|
// Licensed under the Apache License, Version 2.0 (the "License");
 | 
						|
// you may not use this file except in compliance with the License.
 | 
						|
// You may obtain a copy of the License at
 | 
						|
//
 | 
						|
//     http://www.apache.org/licenses/LICENSE-2.0
 | 
						|
//
 | 
						|
// Unless required by applicable law or agreed to in writing, software
 | 
						|
// distributed under the License is distributed on an "AS IS" BASIS,
 | 
						|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
import { Injectable } from '@angular/core';
 | 
						|
import { Params } from '@angular/router';
 | 
						|
import moment from 'moment';
 | 
						|
 | 
						|
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
 | 
						|
import {
 | 
						|
    CoreCourse,
 | 
						|
    CoreCourseCompletionActivityStatus,
 | 
						|
    CoreCourseModuleWSCompletionData,
 | 
						|
    CoreCourseModuleContentFile,
 | 
						|
    CoreCourseProvider,
 | 
						|
    CoreCourseWSSection,
 | 
						|
    CoreCourseModuleCompletionTracking,
 | 
						|
    CoreCourseModuleCompletionStatus,
 | 
						|
    CoreCourseGetContentsWSModule,
 | 
						|
} from './course';
 | 
						|
import { CoreConstants } from '@/core/constants';
 | 
						|
import { CoreLogger } from '@singletons/logger';
 | 
						|
import { makeSingleton, Translate } from '@singletons';
 | 
						|
import { CoreFilepool } from '@services/filepool';
 | 
						|
import { CoreDomUtils } from '@services/utils/dom';
 | 
						|
import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils';
 | 
						|
import {
 | 
						|
    CoreCourseAnyCourseData,
 | 
						|
    CoreCourseBasicData,
 | 
						|
    CoreCourses,
 | 
						|
    CoreCourseSearchedData,
 | 
						|
    CoreEnrolledCourseData,
 | 
						|
} from '@features/courses/services/courses';
 | 
						|
import { CoreArray } from '@singletons/array';
 | 
						|
import { CoreIonLoadingElement } from '@classes/ion-loading';
 | 
						|
import { CoreCourseOffline } from './course-offline';
 | 
						|
import {
 | 
						|
    CoreCourseOptionsDelegate,
 | 
						|
    CoreCourseOptionsHandlerToDisplay,
 | 
						|
    CoreCourseOptionsMenuHandlerToDisplay,
 | 
						|
} from './course-options-delegate';
 | 
						|
import { CoreCourseModuleDelegate, CoreCourseModuleHandlerData } from './module-delegate';
 | 
						|
import { CoreError } from '@classes/errors/error';
 | 
						|
import {
 | 
						|
    CoreCourseModulePrefetchDelegate,
 | 
						|
    CoreCourseModulePrefetchHandler,
 | 
						|
    CoreCourseModulesStatus,
 | 
						|
} from './module-prefetch-delegate';
 | 
						|
import { CoreFileSizeSum } from '@services/plugin-file-delegate';
 | 
						|
import { CoreFileHelper } from '@services/file-helper';
 | 
						|
import { CoreNetwork } from '@services/network';
 | 
						|
import { CoreSite } from '@classes/site';
 | 
						|
import { CoreFile } from '@services/file';
 | 
						|
import { CoreUrlUtils } from '@services/utils/url';
 | 
						|
import { CoreTextUtils } from '@services/utils/text';
 | 
						|
import { CoreTimeUtils } from '@services/utils/time';
 | 
						|
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
 | 
						|
import { CoreNetworkError } from '@classes/errors/network-error';
 | 
						|
import { CoreSiteHome } from '@features/sitehome/services/sitehome';
 | 
						|
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
						|
import { CoreSiteHomeHomeHandlerService } from '@features/sitehome/services/handlers/sitehome-home';
 | 
						|
import { CoreStatusWithWarningsWSResponse } from '@services/ws';
 | 
						|
 | 
						|
/**
 | 
						|
 * Prefetch info of a module.
 | 
						|
 */
 | 
						|
export type CoreCourseModulePrefetchInfo = CoreCourseModulePackageLastDownloaded & {
 | 
						|
    size: number; // Downloaded size.
 | 
						|
    sizeReadable: string; // Downloadable size in a readable format.
 | 
						|
    status: string; // Module status.
 | 
						|
    statusIcon?: string; // Icon's name of the module status.
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Prefetch info of a module.
 | 
						|
 */
 | 
						|
export type CoreCourseModulePackageLastDownloaded = {
 | 
						|
    downloadTime: number; // Time when the module was last downloaded.
 | 
						|
    downloadTimeReadable: string; // Download time in a readable format.
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Progress of downloading a list of courses.
 | 
						|
 */
 | 
						|
export type CoreCourseCoursesProgress = {
 | 
						|
    /**
 | 
						|
     * Number of courses downloaded so far.
 | 
						|
     */
 | 
						|
    count: number;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Toal of courses to download.
 | 
						|
     */
 | 
						|
    total: number;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Whether the download has been successful so far.
 | 
						|
     */
 | 
						|
    success: boolean;
 | 
						|
 | 
						|
    /**
 | 
						|
     * Last downloaded course.
 | 
						|
     */
 | 
						|
    courseId?: number;
 | 
						|
};
 | 
						|
 | 
						|
export type CorePrefetchStatusInfo = {
 | 
						|
    status: string; // Status of the prefetch.
 | 
						|
    statusTranslatable: string; // Status translatable string.
 | 
						|
    icon: string; // Icon based on the status.
 | 
						|
    loading: boolean; // If it's a loading status.
 | 
						|
    badge?: string; // Progress badge string if any.
 | 
						|
    badgeA11yText?: string; // Description of the badge if any.
 | 
						|
    count?: number; // Amount of already downloaded courses.
 | 
						|
    total?: number; // Total of courses.
 | 
						|
    downloadSucceeded?: boolean; // Whether download has succeeded (in case it's downloaded).
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Helper to gather some common course functions.
 | 
						|
 */
 | 
						|
@Injectable({ providedIn: 'root' })
 | 
						|
export class CoreCourseHelperProvider {
 | 
						|
 | 
						|
    protected courseDwnPromises: { [s: string]: { [id: number]: Promise<void> } } = {};
 | 
						|
    protected logger: CoreLogger;
 | 
						|
 | 
						|
    constructor() {
 | 
						|
        this.logger = CoreLogger.getInstance('CoreCourseHelperProvider');
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * This function treats every module on the sections provided to load the handler data, treat completion
 | 
						|
     * and navigate to a module page if required. It also returns if sections has content.
 | 
						|
     *
 | 
						|
     * @param sections List of sections to treat modules.
 | 
						|
     * @param courseId Course ID of the modules.
 | 
						|
     * @param completionStatus List of completion status.
 | 
						|
     * @param courseName Not used since 4.0
 | 
						|
     * @param forCoursePage Whether the data will be used to render the course page.
 | 
						|
     * @return Whether the sections have content.
 | 
						|
     */
 | 
						|
    async addHandlerDataForModules(
 | 
						|
        sections: CoreCourseWSSection[],
 | 
						|
        courseId: number,
 | 
						|
        completionStatus?: Record<string, CoreCourseCompletionActivityStatus>,
 | 
						|
        courseName?: string,
 | 
						|
        forCoursePage = false,
 | 
						|
    ): Promise<{ hasContent: boolean; sections: CoreCourseSection[] }> {
 | 
						|
 | 
						|
        let hasContent = false;
 | 
						|
 | 
						|
        const formattedSections = await Promise.all(
 | 
						|
            sections.map<Promise<CoreCourseSection>>(async (courseSection) => {
 | 
						|
                const section = {
 | 
						|
                    ...courseSection,
 | 
						|
                    hasContent: this.sectionHasContent(courseSection),
 | 
						|
                };
 | 
						|
 | 
						|
                if (!section.hasContent) {
 | 
						|
                    return section;
 | 
						|
                }
 | 
						|
 | 
						|
                hasContent = true;
 | 
						|
 | 
						|
                await Promise.all(section.modules.map(async (module) => {
 | 
						|
                    module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
 | 
						|
                        module.modname,
 | 
						|
                        module,
 | 
						|
                        courseId,
 | 
						|
                        section.id,
 | 
						|
                        forCoursePage,
 | 
						|
                    );
 | 
						|
 | 
						|
                    if (!module.completiondata && completionStatus && completionStatus[module.id] !== undefined) {
 | 
						|
                        // Should not happen on > 3.6. Check if activity has completions and if it's marked.
 | 
						|
                        const activityStatus = completionStatus[module.id];
 | 
						|
 | 
						|
                        module.completiondata = {
 | 
						|
                            state: activityStatus.state,
 | 
						|
                            timecompleted: activityStatus.timecompleted,
 | 
						|
                            overrideby: activityStatus.overrideby || 0,
 | 
						|
                            valueused: activityStatus.valueused,
 | 
						|
                            tracking: activityStatus.tracking,
 | 
						|
                            courseId,
 | 
						|
                            cmid: module.id,
 | 
						|
                        };
 | 
						|
                    }
 | 
						|
 | 
						|
                    // Check if the module is stealth.
 | 
						|
                    module.isStealth = CoreCourseHelper.isModuleStealth(module, section);
 | 
						|
                }));
 | 
						|
 | 
						|
                return section;
 | 
						|
            }),
 | 
						|
        );
 | 
						|
 | 
						|
        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.
 | 
						|
     *
 | 
						|
     * @deprecated since 4.0.
 | 
						|
     * @param module Module.
 | 
						|
     */
 | 
						|
    calculateModuleCompletionData(module: CoreCourseModuleData): void {
 | 
						|
        if (!module.completiondata || !module.completion) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        module.completiondata.courseId = module.course;
 | 
						|
        module.completiondata.tracking = module.completion;
 | 
						|
        module.completiondata.cmid = module.id;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Calculate the status of a section.
 | 
						|
     *
 | 
						|
     * @param section Section to calculate its status. It can't be "All sections".
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @param refresh True if it shouldn't use module status cache (slower).
 | 
						|
     * @param checkUpdates Whether to use the WS to check updates. Defaults to true.
 | 
						|
     * @return Promise resolved when the status is calculated.
 | 
						|
     */
 | 
						|
    async calculateSectionStatus(
 | 
						|
        section: CoreCourseSection,
 | 
						|
        courseId: number,
 | 
						|
        refresh?: boolean,
 | 
						|
        checkUpdates: boolean = true,
 | 
						|
    ): Promise<{statusData: CoreCourseModulesStatus; section: CoreCourseSectionWithStatus}> {
 | 
						|
        if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
            throw new CoreError('Invalid section');
 | 
						|
        }
 | 
						|
 | 
						|
        const sectionWithStatus = <CoreCourseSectionWithStatus> section;
 | 
						|
 | 
						|
        // Get the status of this section.
 | 
						|
        const result = await CoreCourseModulePrefetchDelegate.getModulesStatus(
 | 
						|
            section.modules,
 | 
						|
            courseId,
 | 
						|
            section.id,
 | 
						|
            refresh,
 | 
						|
            true,
 | 
						|
            checkUpdates,
 | 
						|
        );
 | 
						|
 | 
						|
        // Check if it's being downloaded.
 | 
						|
        const downloadId = this.getSectionDownloadId(section);
 | 
						|
        if (CoreCourseModulePrefetchDelegate.isBeingDownloaded(downloadId)) {
 | 
						|
            result.status = CoreConstants.DOWNLOADING;
 | 
						|
        }
 | 
						|
 | 
						|
        sectionWithStatus.downloadStatus = result.status;
 | 
						|
        sectionWithStatus.canCheckUpdates = true;
 | 
						|
 | 
						|
        // Set this section data.
 | 
						|
        if (result.status !== CoreConstants.DOWNLOADING) {
 | 
						|
            sectionWithStatus.isDownloading = false;
 | 
						|
            sectionWithStatus.total = 0;
 | 
						|
        } else {
 | 
						|
            // Section is being downloaded.
 | 
						|
            sectionWithStatus.isDownloading = true;
 | 
						|
            CoreCourseModulePrefetchDelegate.setOnProgress(downloadId, (data) => {
 | 
						|
                sectionWithStatus.count = data.count;
 | 
						|
                sectionWithStatus.total = data.total;
 | 
						|
            });
 | 
						|
        }
 | 
						|
 | 
						|
        return { statusData: result, section: sectionWithStatus };
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Calculate the status of a list of sections, setting attributes to determine the icons/data to be shown.
 | 
						|
     *
 | 
						|
     * @param sections Sections to calculate their status.
 | 
						|
     * @param courseId Course ID the sections belong to.
 | 
						|
     * @param refresh True if it shouldn't use module status cache (slower).
 | 
						|
     * @param checkUpdates Whether to use the WS to check updates. Defaults to true.
 | 
						|
     * @return Promise resolved when the states are calculated.
 | 
						|
     */
 | 
						|
    async calculateSectionsStatus(
 | 
						|
        sections: CoreCourseSection[],
 | 
						|
        courseId: number,
 | 
						|
        refresh?: boolean,
 | 
						|
        checkUpdates: boolean = true,
 | 
						|
    ): Promise<CoreCourseSectionWithStatus[]> {
 | 
						|
        let allSectionsSection: CoreCourseSectionWithStatus | undefined;
 | 
						|
        let allSectionsStatus = CoreConstants.NOT_DOWNLOADABLE;
 | 
						|
 | 
						|
        const promises = sections.map(async (section: CoreCourseSectionWithStatus) => {
 | 
						|
            section.isCalculating = true;
 | 
						|
 | 
						|
            if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
                // "All sections" section status is calculated using the status of the rest of sections.
 | 
						|
                allSectionsSection = section;
 | 
						|
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            try {
 | 
						|
                const result = await this.calculateSectionStatus(section, courseId, refresh, checkUpdates);
 | 
						|
 | 
						|
                // Calculate "All sections" status.
 | 
						|
                allSectionsStatus = CoreFilepool.determinePackagesStatus(allSectionsStatus, result.statusData.status);
 | 
						|
            } finally {
 | 
						|
                section.isCalculating = false;
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        try {
 | 
						|
            await Promise.all(promises);
 | 
						|
 | 
						|
            if (allSectionsSection) {
 | 
						|
                // Set "All sections" data.
 | 
						|
                allSectionsSection.downloadStatus = allSectionsStatus;
 | 
						|
                allSectionsSection.canCheckUpdates = true;
 | 
						|
                allSectionsSection.isDownloading = allSectionsStatus === CoreConstants.DOWNLOADING;
 | 
						|
            }
 | 
						|
 | 
						|
            return sections;
 | 
						|
        } finally {
 | 
						|
            if (allSectionsSection) {
 | 
						|
                allSectionsSection.isCalculating = false;
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Show a confirm and prefetch a course. It will retrieve the sections and the course options if not provided.
 | 
						|
     * This function will set the icon to "spinner" when starting and it will also set it back to the initial icon if the
 | 
						|
     * user cancels. All the other updates of the icon should be made when CoreEvents.COURSE_STATUS_CHANGED is received.
 | 
						|
     *
 | 
						|
     * @param data An object where to store the course icon and title: "prefetchCourseIcon", "title" and "downloadSucceeded".
 | 
						|
     * @param course Course to prefetch.
 | 
						|
     * @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,
 | 
						|
        options: CoreCoursePrefetchCourseOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        const initialIcon = data.icon;
 | 
						|
        const initialStatus = data.status;
 | 
						|
        const initialStatusTranslatable = data.statusTranslatable;
 | 
						|
        const siteId = CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        data.downloadSucceeded = false;
 | 
						|
        data.icon = CoreConstants.ICON_DOWNLOADING;
 | 
						|
        data.status = CoreConstants.DOWNLOADING;
 | 
						|
        data.loading = true;
 | 
						|
        data.statusTranslatable = 'core.downloading';
 | 
						|
 | 
						|
        try {
 | 
						|
            // Get the sections first if needed.
 | 
						|
            if (!options.sections) {
 | 
						|
                options.sections = await CoreCourse.getSections(course.id, false, true);
 | 
						|
            }
 | 
						|
 | 
						|
            // Confirm the download.
 | 
						|
            await this.confirmDownloadSizeSection(course.id, undefined, options.sections, true);
 | 
						|
 | 
						|
            // User confirmed, get the course handlers if needed.
 | 
						|
            if (!options.courseHandlers) {
 | 
						|
                options.courseHandlers = await CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, options.isGuest);
 | 
						|
            }
 | 
						|
            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, options.sections, options.courseHandlers, options.menuHandlers, siteId);
 | 
						|
 | 
						|
            // Download successful.
 | 
						|
            data.downloadSucceeded = true;
 | 
						|
            data.loading = false;
 | 
						|
        } catch (error) {
 | 
						|
            // User cancelled or there was an error.
 | 
						|
            data.icon = initialIcon;
 | 
						|
            data.status = initialStatus;
 | 
						|
            data.statusTranslatable = initialStatusTranslatable;
 | 
						|
            data.loading = false;
 | 
						|
 | 
						|
            throw error;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Confirm and prefetches a list of courses.
 | 
						|
     *
 | 
						|
     * @param courses List of courses to download.
 | 
						|
     * @param options Other options.
 | 
						|
     * @return Resolved when downloaded, rejected if error or canceled.
 | 
						|
     */
 | 
						|
    async confirmAndPrefetchCourses(
 | 
						|
        courses: CoreCourseAnyCourseData[],
 | 
						|
        options: CoreCourseConfirmPrefetchCoursesOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        const siteId = CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        // Confirm the download without checking size because it could take a while.
 | 
						|
        await CoreDomUtils.showConfirm(Translate.instant('core.areyousure'), Translate.instant('core.courses.downloadcourses'));
 | 
						|
 | 
						|
        const total = courses.length;
 | 
						|
        let count = 0;
 | 
						|
 | 
						|
        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) => {
 | 
						|
                sections = courseSections;
 | 
						|
 | 
						|
                return;
 | 
						|
            }));
 | 
						|
 | 
						|
            subPromises.push(CoreCourseOptionsDelegate.getHandlersToDisplay(course, false, isGuest).then((cHandlers) => {
 | 
						|
                handlers = cHandlers;
 | 
						|
 | 
						|
                return;
 | 
						|
            }));
 | 
						|
            subPromises.push(CoreCourseOptionsDelegate.getMenuHandlersToDisplay(course, false, isGuest).then((mHandlers) => {
 | 
						|
                menuHandlers = mHandlers;
 | 
						|
 | 
						|
                return;
 | 
						|
            }));
 | 
						|
 | 
						|
            return Promise.all(subPromises).then(() => this.prefetchCourse(course, sections, handlers, menuHandlers, siteId))
 | 
						|
                .catch((error) => {
 | 
						|
                    success = false;
 | 
						|
 | 
						|
                    throw error;
 | 
						|
                }).finally(() => {
 | 
						|
                // Course downloaded or failed, notify the progress.
 | 
						|
                    count++;
 | 
						|
                    if (options.onProgress) {
 | 
						|
                        options.onProgress({ count: count, total: total, courseId: course.id, success: success });
 | 
						|
                    }
 | 
						|
                });
 | 
						|
        });
 | 
						|
 | 
						|
        if (options.onProgress) {
 | 
						|
            // Notify the start of the download.
 | 
						|
            options.onProgress({ count: 0, total: total, success: true });
 | 
						|
        }
 | 
						|
 | 
						|
        return CoreUtils.allPromises(promises);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Show confirmation dialog and then remove a module files.
 | 
						|
     *
 | 
						|
     * @param module Module to remove the files.
 | 
						|
     * @param courseId Course ID the module belongs to.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     * @deprecated since 4.0
 | 
						|
     */
 | 
						|
    async confirmAndRemoveFiles(module: CoreCourseModuleData, courseId: number): Promise<void> {
 | 
						|
        let modal: CoreIonLoadingElement | undefined;
 | 
						|
 | 
						|
        try {
 | 
						|
            await CoreDomUtils.showDeleteConfirm('addon.storagemanager.confirmdeletedatafrom', { name: module.name });
 | 
						|
 | 
						|
            modal = await CoreDomUtils.showModalLoading();
 | 
						|
 | 
						|
            await this.removeModuleStoredData(module, courseId);
 | 
						|
        } catch (error) {
 | 
						|
            if (error) {
 | 
						|
                CoreDomUtils.showErrorModal(error);
 | 
						|
            }
 | 
						|
        } finally {
 | 
						|
            modal?.dismiss();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Calculate the size to download a section and show a confirm modal if needed.
 | 
						|
     *
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @param section Section. If not provided, all sections.
 | 
						|
     * @param sections List of sections. Used when downloading all the sections.
 | 
						|
     * @param alwaysConfirm True to show a confirm even if the size isn't high, false otherwise.
 | 
						|
     * @return Promise resolved if the user confirms or there's no need to confirm.
 | 
						|
     */
 | 
						|
    async confirmDownloadSizeSection(
 | 
						|
        courseId: number,
 | 
						|
        section?: CoreCourseWSSection,
 | 
						|
        sections?: CoreCourseWSSection[],
 | 
						|
        alwaysConfirm?: boolean,
 | 
						|
    ): Promise<void> {
 | 
						|
        let hasEmbeddedFiles = false;
 | 
						|
        let sizeSum: CoreFileSizeSum = {
 | 
						|
            size: 0,
 | 
						|
            total: true,
 | 
						|
        };
 | 
						|
 | 
						|
        // Calculate the size of the download.
 | 
						|
        if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
            sizeSum = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId);
 | 
						|
 | 
						|
            // Check if the section has embedded files in the description.
 | 
						|
            hasEmbeddedFiles = CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0;
 | 
						|
        } else if (sections) {
 | 
						|
            await Promise.all(sections.map(async (section) => {
 | 
						|
                if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
 | 
						|
                const sectionSize = await CoreCourseModulePrefetchDelegate.getDownloadSize(section.modules, courseId);
 | 
						|
 | 
						|
                sizeSum.total = sizeSum.total && sectionSize.total;
 | 
						|
                sizeSum.size += sectionSize.size;
 | 
						|
 | 
						|
                // Check if the section has embedded files in the description.
 | 
						|
                if (!hasEmbeddedFiles && CoreFilepool.extractDownloadableFilesFromHtml(section.summary).length > 0) {
 | 
						|
                    hasEmbeddedFiles = true;
 | 
						|
                }
 | 
						|
            }));
 | 
						|
        } else {
 | 
						|
            throw new CoreError('Either section or list of sections needs to be supplied.');
 | 
						|
        }
 | 
						|
 | 
						|
        if (hasEmbeddedFiles) {
 | 
						|
            sizeSum.total = false;
 | 
						|
        }
 | 
						|
 | 
						|
        // Show confirm modal if needed.
 | 
						|
        await CoreDomUtils.confirmDownloadSize(sizeSum, undefined, undefined, undefined, undefined, alwaysConfirm);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * 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".
 | 
						|
     *
 | 
						|
     * @return Created section.
 | 
						|
     */
 | 
						|
    createAllSectionsSection(): CoreCourseSection {
 | 
						|
        return {
 | 
						|
            id: CoreCourseProvider.ALL_SECTIONS_ID,
 | 
						|
            name: Translate.instant('core.course.allsections'),
 | 
						|
            hasContent: true,
 | 
						|
            summary: '',
 | 
						|
            summaryformat: 1,
 | 
						|
            modules: [],
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Determine the status of a list of courses.
 | 
						|
     *
 | 
						|
     * @param courses Courses
 | 
						|
     * @return Promise resolved with the status.
 | 
						|
     */
 | 
						|
    async determineCoursesStatus(courses: CoreCourseBasicData[]): Promise<string> {
 | 
						|
        // Get the status of each course.
 | 
						|
        const promises: Promise<string>[] = [];
 | 
						|
        const siteId = CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        courses.forEach((course) => {
 | 
						|
            promises.push(CoreCourse.getCourseStatus(course.id, siteId));
 | 
						|
        });
 | 
						|
 | 
						|
        const statuses = await Promise.all(promises);
 | 
						|
 | 
						|
        // Now determine the status of the whole list.
 | 
						|
        let status = statuses[0];
 | 
						|
        const filepool = CoreFilepool.instance;
 | 
						|
        for (let i = 1; i < statuses.length; i++) {
 | 
						|
            status = filepool.determinePackagesStatus(status, statuses[i]);
 | 
						|
        }
 | 
						|
 | 
						|
        return status;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenience function to open a module main file, downloading the package if needed.
 | 
						|
     * This is meant for modules like mod_resource.
 | 
						|
     *
 | 
						|
     * @param module The module to download.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param component The component to link the files to.
 | 
						|
     * @param componentId An ID to use in conjunction with the component.
 | 
						|
     * @param files List of files of the module. If not provided, use module.contents.
 | 
						|
     * @param siteId The site ID. If not defined, current site.
 | 
						|
     * @param options Options to open the file.
 | 
						|
     * @return Resolved on success.
 | 
						|
     */
 | 
						|
    async downloadModuleAndOpenFile(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        component?: string,
 | 
						|
        componentId?: string | number,
 | 
						|
        files?: CoreCourseModuleContentFile[],
 | 
						|
        siteId?: string,
 | 
						|
        options: CoreUtilsOpenFileOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        if (!files || !files.length) {
 | 
						|
            // Try to use module contents.
 | 
						|
            files = await CoreCourse.getModuleContents(module);
 | 
						|
        }
 | 
						|
 | 
						|
        if (!files.length) {
 | 
						|
            throw new CoreError(Translate.instant('core.filenotfound'));
 | 
						|
        }
 | 
						|
 | 
						|
        const mainFile = files[0];
 | 
						|
 | 
						|
        if (!CoreFileHelper.isOpenableInApp(mainFile)) {
 | 
						|
            await CoreFileHelper.showConfirmOpenUnsupportedFile();
 | 
						|
        }
 | 
						|
 | 
						|
        const site = await CoreSites.getSite(siteId);
 | 
						|
 | 
						|
        // Check if the file should be opened in browser.
 | 
						|
        if (CoreFileHelper.shouldOpenInBrowser(mainFile)) {
 | 
						|
            return this.openModuleFileInBrowser(mainFile.fileurl, site, module, courseId, component, componentId, files, options);
 | 
						|
        }
 | 
						|
 | 
						|
        // File shouldn't be opened in browser. Download the module if it needs to be downloaded.
 | 
						|
        const result = await this.downloadModuleWithMainFileIfNeeded(
 | 
						|
            module,
 | 
						|
            courseId,
 | 
						|
            component || '',
 | 
						|
            componentId,
 | 
						|
            files,
 | 
						|
            siteId,
 | 
						|
            options,
 | 
						|
        );
 | 
						|
 | 
						|
        if (CoreUrlUtils.isLocalFileUrl(result.path)) {
 | 
						|
            return CoreUtils.openFile(result.path, options);
 | 
						|
        }
 | 
						|
 | 
						|
        /* In iOS, if we use the same URL in embedded browser and background download then the download only
 | 
						|
        downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
 | 
						|
        result.path = result.path + '#moodlemobile-embedded';
 | 
						|
 | 
						|
        try {
 | 
						|
            await CoreUtils.openOnlineFile(result.path);
 | 
						|
        } catch (error) {
 | 
						|
            // Error opening the file, some apps don't allow opening online files.
 | 
						|
            if (!CoreFile.isAvailable()) {
 | 
						|
                throw error;
 | 
						|
            } else if (result.status === CoreConstants.DOWNLOADING) {
 | 
						|
                throw new CoreError(Translate.instant('core.erroropenfiledownloading'));
 | 
						|
            }
 | 
						|
 | 
						|
            let path: string | undefined;
 | 
						|
            if (result.status === CoreConstants.NOT_DOWNLOADED) {
 | 
						|
                // Not downloaded, download it now and return the local file.
 | 
						|
                await this.downloadModule(module, courseId, component, componentId, files, siteId);
 | 
						|
 | 
						|
                path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl);
 | 
						|
            } else {
 | 
						|
                // File is outdated or stale and can't be opened in online, return the local URL.
 | 
						|
                path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl);
 | 
						|
            }
 | 
						|
 | 
						|
            await CoreUtils.openFile(path, options);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenience function to open a module main file in case it needs to be opened in browser.
 | 
						|
     *
 | 
						|
     * @param fileUrl URL of the main file.
 | 
						|
     * @param site Site instance.
 | 
						|
     * @param module The module to download.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param component The component to link the files to.
 | 
						|
     * @param componentId An ID to use in conjunction with the component.
 | 
						|
     * @param files List of files of the module. If not provided, use module.contents.
 | 
						|
     * @param options Options to open the file. Only used if not opened in browser.
 | 
						|
     * @return Resolved on success.
 | 
						|
     */
 | 
						|
    protected async openModuleFileInBrowser(
 | 
						|
        fileUrl: string,
 | 
						|
        site: CoreSite,
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        component?: string,
 | 
						|
        componentId?: string | number,
 | 
						|
        files?: CoreCourseModuleContentFile[],
 | 
						|
        options: CoreUtilsOpenFileOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        if (!CoreNetwork.isOnline()) {
 | 
						|
            // Not online, get the offline file. It will fail if not found.
 | 
						|
            let path: string | undefined;
 | 
						|
            try {
 | 
						|
                path = await CoreFilepool.getInternalUrlByUrl(site.getId(), fileUrl);
 | 
						|
            } catch {
 | 
						|
                throw new CoreNetworkError();
 | 
						|
            }
 | 
						|
 | 
						|
            return CoreUtils.openFile(path, options);
 | 
						|
        }
 | 
						|
 | 
						|
        // Open in browser.
 | 
						|
        let fixedUrl = await site.checkAndFixPluginfileURL(fileUrl);
 | 
						|
 | 
						|
        fixedUrl = fixedUrl.replace('&offline=1', '');
 | 
						|
        // Remove forcedownload when followed by another param.
 | 
						|
        fixedUrl = fixedUrl.replace(/forcedownload=\d+&/, '');
 | 
						|
        // Remove forcedownload when not followed by any param.
 | 
						|
        fixedUrl = fixedUrl.replace(/[?|&]forcedownload=\d+/, '');
 | 
						|
 | 
						|
        CoreUtils.openInBrowser(fixedUrl);
 | 
						|
 | 
						|
        if (CoreFile.isAvailable()) {
 | 
						|
            // Download the file if needed (file outdated or not downloaded).
 | 
						|
            // Download will be in background, don't return the promise.
 | 
						|
            this.downloadModule(module, courseId, component, componentId, files, site.getId());
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenience function to download a module that has a main file and return the local file's path and other info.
 | 
						|
     * This is meant for modules like mod_resource.
 | 
						|
     *
 | 
						|
     * @param module The module to download.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param component The component to link the files to.
 | 
						|
     * @param componentId An ID to use in conjunction with the component.
 | 
						|
     * @param files List of files of the module. If not provided, use module.contents.
 | 
						|
     * @param siteId The site ID. If not defined, current site.
 | 
						|
     * @param options Options to open the file.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async downloadModuleWithMainFileIfNeeded(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        component: string,
 | 
						|
        componentId?: string | number,
 | 
						|
        files?: CoreCourseModuleContentFile[],
 | 
						|
        siteId?: string,
 | 
						|
        options: CoreUtilsOpenFileOptions = {},
 | 
						|
    ): Promise<{ fixedUrl: string; path: string; status?: string }> {
 | 
						|
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        if (!files || !files.length) {
 | 
						|
            // Module not valid, stop.
 | 
						|
            throw new CoreError('File list not supplied.');
 | 
						|
        }
 | 
						|
 | 
						|
        const mainFile = files[0];
 | 
						|
        const site = await CoreSites.getSite(siteId);
 | 
						|
 | 
						|
        const fixedUrl = await site.checkAndFixPluginfileURL(mainFile.fileurl);
 | 
						|
 | 
						|
        if (!CoreFile.isAvailable()) {
 | 
						|
            return {
 | 
						|
                path: fixedUrl, // Use the online URL.
 | 
						|
                fixedUrl,
 | 
						|
            };
 | 
						|
        }
 | 
						|
 | 
						|
        // The file system is available.
 | 
						|
        const status = await CoreFilepool.getPackageStatus(siteId, component, componentId);
 | 
						|
 | 
						|
        let path = '';
 | 
						|
 | 
						|
        if (status === CoreConstants.DOWNLOADING) {
 | 
						|
            // Use the online URL.
 | 
						|
            path = fixedUrl;
 | 
						|
        } else if (status === CoreConstants.DOWNLOADED) {
 | 
						|
            try {
 | 
						|
                // Get the local file URL.
 | 
						|
                path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl);
 | 
						|
            } catch (error){
 | 
						|
                // File not found, mark the module as not downloaded.
 | 
						|
                await CoreFilepool.storePackageStatus(siteId, CoreConstants.NOT_DOWNLOADED, component, componentId);
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        if (!path) {
 | 
						|
            try {
 | 
						|
                path = await this.downloadModuleWithMainFile(
 | 
						|
                    module,
 | 
						|
                    courseId,
 | 
						|
                    fixedUrl,
 | 
						|
                    files,
 | 
						|
                    status,
 | 
						|
                    component,
 | 
						|
                    componentId,
 | 
						|
                    siteId,
 | 
						|
                    options,
 | 
						|
                );
 | 
						|
            } catch (error) {
 | 
						|
                if (status !== CoreConstants.OUTDATED) {
 | 
						|
                    throw error;
 | 
						|
                }
 | 
						|
 | 
						|
                // Use the local file even if it's outdated.
 | 
						|
                try {
 | 
						|
                    path = await CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl);
 | 
						|
                } catch {
 | 
						|
                    throw error;
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
 | 
						|
        return {
 | 
						|
            path,
 | 
						|
            fixedUrl,
 | 
						|
            status,
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenience function to download a module that has a main file and return the local file's path and other info.
 | 
						|
     * This is meant for modules like mod_resource.
 | 
						|
     *
 | 
						|
     * @param module The module to download.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param fixedUrl Main file's fixed URL.
 | 
						|
     * @param files List of files of the module.
 | 
						|
     * @param status The package status.
 | 
						|
     * @param component The component to link the files to.
 | 
						|
     * @param componentId An ID to use in conjunction with the component.
 | 
						|
     * @param siteId The site ID. If not defined, current site.
 | 
						|
     * @param options Options to open the file.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    protected async downloadModuleWithMainFile(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        fixedUrl: string,
 | 
						|
        files: CoreCourseModuleContentFile[],
 | 
						|
        status: string,
 | 
						|
        component?: string,
 | 
						|
        componentId?: string | number,
 | 
						|
        siteId?: string,
 | 
						|
        options: CoreUtilsOpenFileOptions = {},
 | 
						|
    ): Promise<string> {
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        const isOnline = CoreNetwork.isOnline();
 | 
						|
        const mainFile = files[0];
 | 
						|
        const timemodified = mainFile.timemodified || 0;
 | 
						|
 | 
						|
        if (!isOnline && status === CoreConstants.NOT_DOWNLOADED) {
 | 
						|
            // Not downloaded and we're offline, reject.
 | 
						|
            throw new CoreNetworkError();
 | 
						|
        }
 | 
						|
 | 
						|
        const shouldDownloadFirst = await CoreFilepool.shouldDownloadFileBeforeOpen(fixedUrl, mainFile.filesize, options);
 | 
						|
 | 
						|
        if (shouldDownloadFirst) {
 | 
						|
            // Download and then return the local URL.
 | 
						|
            await this.downloadModule(module, courseId, component, componentId, files, siteId);
 | 
						|
 | 
						|
            return CoreFilepool.getInternalUrlByUrl(siteId, mainFile.fileurl);
 | 
						|
        }
 | 
						|
 | 
						|
        // Start the download if in wifi, but return the URL right away so the file is opened.
 | 
						|
        if (CoreNetwork.isWifi()) {
 | 
						|
            this.downloadModule(module, courseId, component, componentId, files, siteId);
 | 
						|
        }
 | 
						|
 | 
						|
        if (!CoreFileHelper.isStateDownloaded(status) || isOnline) {
 | 
						|
            // Not downloaded or online, return the online URL.
 | 
						|
            return fixedUrl;
 | 
						|
        } else {
 | 
						|
            // Outdated but offline, so we return the local URL. Use getUrlByUrl so it's added to the queue.
 | 
						|
            return CoreFilepool.getUrlByUrl(
 | 
						|
                siteId,
 | 
						|
                mainFile.fileurl,
 | 
						|
                component,
 | 
						|
                componentId,
 | 
						|
                timemodified,
 | 
						|
                false,
 | 
						|
                false,
 | 
						|
                mainFile,
 | 
						|
            );
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Convenience function to download a module.
 | 
						|
     *
 | 
						|
     * @param module The module to download.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param component The component to link the files to.
 | 
						|
     * @param componentId An ID to use in conjunction with the component.
 | 
						|
     * @param files List of files of the module. If not provided, use module.contents.
 | 
						|
     * @param siteId The site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async downloadModule(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        component?: string,
 | 
						|
        componentId?: string | number,
 | 
						|
        files?: CoreCourseModuleContentFile[],
 | 
						|
        siteId?: string,
 | 
						|
    ): Promise<void> {
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        const prefetchHandler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
 | 
						|
 | 
						|
        if (prefetchHandler) {
 | 
						|
            // Use the prefetch handler to download the module.
 | 
						|
            if (prefetchHandler.download) {
 | 
						|
                return await prefetchHandler.download(module, courseId);
 | 
						|
            }
 | 
						|
 | 
						|
            return await prefetchHandler.prefetch(module, courseId, true);
 | 
						|
        }
 | 
						|
 | 
						|
        // There's no prefetch handler for the module, just download the files.
 | 
						|
        files = files || module.contents || [];
 | 
						|
 | 
						|
        await CoreFilepool.downloadOrPrefetchFiles(siteId, files, false, false, component, componentId);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a course. It will first check the user courses, and fallback to another WS if not enrolled.
 | 
						|
     *
 | 
						|
     * @param courseId Course ID.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved with the course.
 | 
						|
     */
 | 
						|
    async getCourse(
 | 
						|
        courseId: number,
 | 
						|
        siteId?: string,
 | 
						|
    ): Promise<{ enrolled: boolean; course: CoreEnrolledCourseData | CoreCourseSearchedData }> {
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        // Try with enrolled courses first.
 | 
						|
        try {
 | 
						|
            const course = await CoreCourses.getUserCourse(courseId, false, siteId);
 | 
						|
 | 
						|
            return ({ enrolled: true, course: course });
 | 
						|
        } catch {
 | 
						|
            // Not enrolled or an error happened. Try to use another WebService.
 | 
						|
        }
 | 
						|
 | 
						|
        const course = await CoreCourses.getCourseByField('id', courseId, siteId);
 | 
						|
 | 
						|
        return ({ enrolled: false, course: course });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a course, wait for any course format plugin to load, and open the course page. It basically chains the functions
 | 
						|
     * getCourse and openCourse.
 | 
						|
     *
 | 
						|
     * @param courseId Course ID.
 | 
						|
     * @param params Other params to pass to the course page.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async getAndOpenCourse(courseId: number, params?: Params, siteId?: string): Promise<void> {
 | 
						|
        const modal = await CoreDomUtils.showModalLoading();
 | 
						|
 | 
						|
        let course: CoreCourseAnyCourseData | { id: number };
 | 
						|
 | 
						|
        try {
 | 
						|
            const data = await this.getCourse(courseId, siteId);
 | 
						|
 | 
						|
            course = data.course;
 | 
						|
        } catch {
 | 
						|
            // Cannot get course, return a "fake".
 | 
						|
            course = { id: courseId };
 | 
						|
        }
 | 
						|
 | 
						|
        modal?.dismiss();
 | 
						|
 | 
						|
        return this.openCourse(course, { params , siteId });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if the course has a block with that name.
 | 
						|
     *
 | 
						|
     * @param courseId Course ID.
 | 
						|
     * @param name Block name to search.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved with true if the block exists or false otherwise.
 | 
						|
     * @since 3.7
 | 
						|
     */
 | 
						|
    async hasABlockNamed(courseId: number, name: string, siteId?: string): Promise<boolean> {
 | 
						|
        try {
 | 
						|
            const blocks = await CoreCourse.getCourseBlocks(courseId, siteId);
 | 
						|
 | 
						|
            return blocks.some((block) => block.name == name);
 | 
						|
        } catch {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Initialize the prefetch icon for selected courses.
 | 
						|
     *
 | 
						|
     * @param courses Courses array to get info from.
 | 
						|
     * @param prefetch Prefetch information.
 | 
						|
     * @return Resolved with the prefetch information updated when done.
 | 
						|
     */
 | 
						|
    async initPrefetchCoursesIcons(
 | 
						|
        courses: CoreCourseBasicData[],
 | 
						|
        prefetch: CorePrefetchStatusInfo,
 | 
						|
    ): Promise<CorePrefetchStatusInfo> {
 | 
						|
        if (!courses || courses.length <= 0) {
 | 
						|
            // Not enough courses.
 | 
						|
            prefetch.icon = '';
 | 
						|
 | 
						|
            return prefetch;
 | 
						|
        }
 | 
						|
 | 
						|
        const status = await this.determineCoursesStatus(courses);
 | 
						|
 | 
						|
        prefetch = this.getCoursesPrefetchStatusInfo(status);
 | 
						|
 | 
						|
        if (prefetch.loading) {
 | 
						|
            // It seems all courses are being downloaded, show a download button instead.
 | 
						|
            prefetch.icon = CoreConstants.ICON_NOT_DOWNLOADED;
 | 
						|
        }
 | 
						|
 | 
						|
        return prefetch;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Load offline completion into a list of sections.
 | 
						|
     * This should be used in 3.6 sites or higher, where the course contents already include the completion.
 | 
						|
     *
 | 
						|
     * @param courseId The course to get the completion.
 | 
						|
     * @param sections List of sections of the course.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async loadOfflineCompletion(courseId: number, sections: CoreCourseWSSection[], siteId?: string): Promise<void> {
 | 
						|
        const offlineCompletions = await CoreCourseOffline.getCourseManualCompletions(courseId, siteId);
 | 
						|
 | 
						|
        if (!offlineCompletions || !offlineCompletions.length) {
 | 
						|
            // No offline completion.
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const totalOffline = offlineCompletions.length;
 | 
						|
        let loaded = 0;
 | 
						|
        const offlineCompletionsMap = CoreUtils.arrayToObject(offlineCompletions, 'cmid');
 | 
						|
        // Load the offline data in the modules.
 | 
						|
        for (let i = 0; i < sections.length; i++) {
 | 
						|
            const section = sections[i];
 | 
						|
            if (!section.modules || !section.modules.length) {
 | 
						|
                // Section has no modules, ignore it.
 | 
						|
                continue;
 | 
						|
            }
 | 
						|
 | 
						|
            for (let j = 0; j < section.modules.length; j++) {
 | 
						|
                const module = section.modules[j];
 | 
						|
                const offlineCompletion = offlineCompletionsMap[module.id];
 | 
						|
 | 
						|
                if (offlineCompletion && module.completiondata !== undefined &&
 | 
						|
                    offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) {
 | 
						|
                    // The module has offline completion. Load it.
 | 
						|
                    module.completiondata.state = offlineCompletion.completed;
 | 
						|
                    module.completiondata.offline = true;
 | 
						|
 | 
						|
                    // If all completions have been loaded, stop.
 | 
						|
                    loaded++;
 | 
						|
                    if (loaded == totalOffline) {
 | 
						|
                        break;
 | 
						|
                    }
 | 
						|
                }
 | 
						|
            }
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Load offline completion for a certain module.
 | 
						|
     * This should be used in 3.6 sites or higher, where the course contents already include the completion.
 | 
						|
     *
 | 
						|
     * @param courseId The course to get the completion.
 | 
						|
     * @param mmodule The module.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async loadModuleOfflineCompletion(courseId: number, module: CoreCourseModuleData, siteId?: string): Promise<void> {
 | 
						|
        if (!module.completiondata) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const offlineCompletions = await CoreCourseOffline.getCourseManualCompletions(courseId, siteId);
 | 
						|
 | 
						|
        const offlineCompletion = offlineCompletions.find(completion => completion.cmid == module.id);
 | 
						|
 | 
						|
        if (offlineCompletion && offlineCompletion.timecompleted >= module.completiondata.timecompleted * 1000) {
 | 
						|
            // The module has offline completion. Load it.
 | 
						|
            module.completiondata.state = offlineCompletion.completed;
 | 
						|
            module.completiondata.offline = true;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prefetch all the courses in the array.
 | 
						|
     *
 | 
						|
     * @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: CoreCourseAnyCourseData[],
 | 
						|
        prefetch: CorePrefetchStatusInfo,
 | 
						|
        options: CoreCoursePrefetchCoursesOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        prefetch.loading = true;
 | 
						|
        prefetch.icon = CoreConstants.ICON_DOWNLOADING;
 | 
						|
        prefetch.badge = '';
 | 
						|
 | 
						|
        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;
 | 
						|
            prefetch.badge = '';
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a course download promise (if any).
 | 
						|
     *
 | 
						|
     * @param courseId Course ID.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Download promise, undefined if not found.
 | 
						|
     */
 | 
						|
    getCourseDownloadPromise(courseId: number, siteId?: string): Promise<void> {
 | 
						|
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        return this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][courseId];
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a course status icon and the langkey to use as a title.
 | 
						|
     *
 | 
						|
     * @param courseId Course ID.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved with the icon name and the title key.
 | 
						|
     */
 | 
						|
    async getCourseStatusIconAndTitle(courseId: number, siteId?: string): Promise<CorePrefetchStatusInfo> {
 | 
						|
        const status = await CoreCourse.getCourseStatus(courseId, siteId);
 | 
						|
 | 
						|
        return this.getCoursePrefetchStatusInfo(status);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a course status icon and the langkey to use as a title from status.
 | 
						|
     *
 | 
						|
     * @param status Course status.
 | 
						|
     * @return Prefetch status info.
 | 
						|
     */
 | 
						|
    getCoursePrefetchStatusInfo(status: string): CorePrefetchStatusInfo {
 | 
						|
        const prefetchStatus: CorePrefetchStatusInfo = {
 | 
						|
            status: status,
 | 
						|
            icon: this.getPrefetchStatusIcon(status, false),
 | 
						|
            statusTranslatable: '',
 | 
						|
            loading: false,
 | 
						|
        };
 | 
						|
 | 
						|
        if (status == CoreConstants.DOWNLOADED) {
 | 
						|
            // Always show refresh icon, we cannot know if there's anything new in course options.
 | 
						|
            prefetchStatus.statusTranslatable = 'core.course.refreshcourse';
 | 
						|
        } else if (status == CoreConstants.DOWNLOADING) {
 | 
						|
            prefetchStatus.statusTranslatable = 'core.downloading';
 | 
						|
            prefetchStatus.loading = true;
 | 
						|
        } else {
 | 
						|
            prefetchStatus.statusTranslatable = 'core.course.downloadcourse';
 | 
						|
        }
 | 
						|
 | 
						|
        return prefetchStatus;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get a courses status icon and the langkey to use as a title from status.
 | 
						|
     *
 | 
						|
     * @param status Courses status.
 | 
						|
     * @return Prefetch status info.
 | 
						|
     */
 | 
						|
    getCoursesPrefetchStatusInfo(status: string): CorePrefetchStatusInfo {
 | 
						|
        const prefetchStatus: CorePrefetchStatusInfo = {
 | 
						|
            status: status,
 | 
						|
            icon: this.getPrefetchStatusIcon(status, false),
 | 
						|
            statusTranslatable: '',
 | 
						|
            loading: false,
 | 
						|
        };
 | 
						|
 | 
						|
        if (status == CoreConstants.DOWNLOADED) {
 | 
						|
            // Always show refresh icon, we cannot know if there's anything new in course options.
 | 
						|
            prefetchStatus.statusTranslatable = 'core.courses.refreshcourses';
 | 
						|
        } else if (status == CoreConstants.DOWNLOADING) {
 | 
						|
            prefetchStatus.statusTranslatable = 'core.downloading';
 | 
						|
            prefetchStatus.loading = true;
 | 
						|
        } else {
 | 
						|
            prefetchStatus.statusTranslatable = 'core.courses.downloadcourses';
 | 
						|
        }
 | 
						|
 | 
						|
        return prefetchStatus;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the icon given the status and if trust the download status.
 | 
						|
     *
 | 
						|
     * @param status Status constant.
 | 
						|
     * @param trustDownload True to show download success, false to show an outdated status when downloaded.
 | 
						|
     * @return Icon name.
 | 
						|
     */
 | 
						|
    getPrefetchStatusIcon(status: string, trustDownload: boolean = false): string {
 | 
						|
        if (status == CoreConstants.NOT_DOWNLOADED) {
 | 
						|
            return CoreConstants.ICON_NOT_DOWNLOADED;
 | 
						|
        }
 | 
						|
        if (status == CoreConstants.OUTDATED || (status == CoreConstants.DOWNLOADED && !trustDownload)) {
 | 
						|
            return CoreConstants.ICON_OUTDATED;
 | 
						|
        }
 | 
						|
        if (status == CoreConstants.DOWNLOADED && trustDownload) {
 | 
						|
            return CoreConstants.ICON_DOWNLOADED;
 | 
						|
        }
 | 
						|
        if (status == CoreConstants.DOWNLOADING) {
 | 
						|
            return CoreConstants.ICON_DOWNLOADING;
 | 
						|
        }
 | 
						|
 | 
						|
        return CoreConstants.ICON_DOWNLOADING;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the course ID from a module instance ID, showing an error message if it can't be retrieved.
 | 
						|
     *
 | 
						|
     * @deprecated since 4.0.
 | 
						|
     * @param instanceId Instance ID.
 | 
						|
     * @param moduleName Name of the module. E.g. 'glossary'.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved with the module's course ID.
 | 
						|
     */
 | 
						|
    async getModuleCourseIdByInstance(instanceId: number, moduleName: string, siteId?: string): Promise<number> {
 | 
						|
        try {
 | 
						|
            const cm = await CoreCourse.getModuleBasicInfoByInstance(
 | 
						|
                instanceId,
 | 
						|
                moduleName,
 | 
						|
                { siteId, readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
 | 
						|
            );
 | 
						|
 | 
						|
            return cm.course;
 | 
						|
        } catch (error) {
 | 
						|
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
						|
 | 
						|
            throw error;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get prefetch info for a module.
 | 
						|
     *
 | 
						|
     * @param module Module to get the info from.
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @param invalidateCache Invalidates the cache first.
 | 
						|
     * @param component Component of the module.
 | 
						|
     * @return Promise resolved with the info.
 | 
						|
     */
 | 
						|
    async getModulePrefetchInfo(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        courseId: number,
 | 
						|
        invalidateCache = false,
 | 
						|
        component = '',
 | 
						|
    ): Promise<CoreCourseModulePrefetchInfo> {
 | 
						|
        if (invalidateCache) {
 | 
						|
            // Currently, some modules pass invalidateCache=false because they already invalidate data in downloadResourceIfNeeded.
 | 
						|
            // If this function is changed to do more actions if invalidateCache=true, please review those modules.
 | 
						|
            CoreCourseModulePrefetchDelegate.invalidateModuleStatusCache(module);
 | 
						|
 | 
						|
            await CoreUtils.ignoreErrors(CoreCourseModulePrefetchDelegate.invalidateCourseUpdates(courseId));
 | 
						|
        }
 | 
						|
 | 
						|
        const results = await Promise.all([
 | 
						|
            CoreCourseModulePrefetchDelegate.getModuleStoredSize(module, courseId),
 | 
						|
            CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId),
 | 
						|
            this.getModulePackageLastDownloaded(module, component),
 | 
						|
        ]);
 | 
						|
 | 
						|
        // Treat stored size.
 | 
						|
        const size = results[0];
 | 
						|
        const sizeReadable = CoreTextUtils.bytesToSize(results[0], 2);
 | 
						|
 | 
						|
        // Treat module status.
 | 
						|
        const status = results[1];
 | 
						|
        let statusIcon: string | undefined;
 | 
						|
        switch (results[1]) {
 | 
						|
            case CoreConstants.NOT_DOWNLOADED:
 | 
						|
                statusIcon = CoreConstants.ICON_NOT_DOWNLOADED;
 | 
						|
                break;
 | 
						|
            case CoreConstants.DOWNLOADING:
 | 
						|
                statusIcon = CoreConstants.ICON_DOWNLOADING;
 | 
						|
                break;
 | 
						|
            case CoreConstants.OUTDATED:
 | 
						|
                statusIcon = CoreConstants.ICON_OUTDATED;
 | 
						|
                break;
 | 
						|
            case CoreConstants.DOWNLOADED:
 | 
						|
                break;
 | 
						|
            default:
 | 
						|
                statusIcon = '';
 | 
						|
                break;
 | 
						|
        }
 | 
						|
 | 
						|
        const packageData = results[2];
 | 
						|
 | 
						|
        return {
 | 
						|
            size,
 | 
						|
            sizeReadable,
 | 
						|
            status,
 | 
						|
            statusIcon,
 | 
						|
            downloadTime: packageData.downloadTime,
 | 
						|
            downloadTimeReadable: packageData.downloadTimeReadable,
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get prefetch info for a module.
 | 
						|
     *
 | 
						|
     * @param module Module to get the info from.
 | 
						|
     * @param component Component of the module.
 | 
						|
     * @return Promise resolved with the info.
 | 
						|
     */
 | 
						|
    async getModulePackageLastDownloaded(
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        component = '',
 | 
						|
    ): Promise<CoreCourseModulePackageLastDownloaded> {
 | 
						|
        const siteId = CoreSites.getCurrentSiteId();
 | 
						|
        const packageData = await CoreUtils.ignoreErrors(CoreFilepool.getPackageData(siteId, component, module.id));
 | 
						|
 | 
						|
        // Treat download time.
 | 
						|
        if (!packageData || !packageData.downloadTime || !CoreFileHelper.isStateDownloaded(packageData.status || '')) {
 | 
						|
            // Not downloaded.
 | 
						|
            return {
 | 
						|
                downloadTime: 0,
 | 
						|
                downloadTimeReadable: '',
 | 
						|
            };
 | 
						|
        }
 | 
						|
 | 
						|
        const now = CoreTimeUtils.timestamp();
 | 
						|
        const downloadTime = packageData.downloadTime;
 | 
						|
        let downloadTimeReadable = '';
 | 
						|
        if (now - downloadTime < 7 * 86400) {
 | 
						|
            downloadTimeReadable = moment(downloadTime * 1000).fromNow();
 | 
						|
        } else {
 | 
						|
            downloadTimeReadable = moment(downloadTime * 1000).calendar();
 | 
						|
        }
 | 
						|
 | 
						|
        return {
 | 
						|
            downloadTime,
 | 
						|
            downloadTimeReadable,
 | 
						|
        };
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Get the download ID of a section. It's used to interact with CoreCourseModulePrefetchDelegate.
 | 
						|
     *
 | 
						|
     * @param section Section.
 | 
						|
     * @return Section download ID.
 | 
						|
     */
 | 
						|
    getSectionDownloadId(section: {id: number}): string {
 | 
						|
        return 'Section-' + section.id;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Navigate to a module using instance ID and module name.
 | 
						|
     *
 | 
						|
     * @param instanceId Activity instance ID.
 | 
						|
     * @param modName Module name of the activity.
 | 
						|
     * @param options Other options.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async navigateToModuleByInstance(
 | 
						|
        instanceId: number,
 | 
						|
        modName: string,
 | 
						|
        options: CoreCourseNavigateToModuleByInstanceOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
 | 
						|
        const modal = await CoreDomUtils.showModalLoading();
 | 
						|
 | 
						|
        try {
 | 
						|
            const module = await CoreCourse.getModuleBasicInfoByInstance(instanceId, modName, { siteId: options.siteId });
 | 
						|
 | 
						|
            this.navigateToModule(
 | 
						|
                module.id,
 | 
						|
                {
 | 
						|
                    ...options,
 | 
						|
                    courseId: module.course,
 | 
						|
                    modName: options.useModNameToGetModule ? modName : undefined,
 | 
						|
                },
 | 
						|
            );
 | 
						|
        } catch (error) {
 | 
						|
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
						|
        } finally {
 | 
						|
            // Just in case. In fact we need to dismiss the modal before showing a toast or error message.
 | 
						|
            modal.dismiss();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Navigate to a module.
 | 
						|
     *
 | 
						|
     * @param moduleId Module's ID.
 | 
						|
     * @param options Other options.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async navigateToModule(
 | 
						|
        moduleId: number,
 | 
						|
        options: CoreCourseNavigateToModuleOptions = {},
 | 
						|
    ): Promise<void> {
 | 
						|
        const siteId = options.siteId || CoreSites.getCurrentSiteId();
 | 
						|
        let courseId = options.courseId;
 | 
						|
        let sectionId = options.sectionId;
 | 
						|
 | 
						|
        const modal = await CoreDomUtils.showModalLoading();
 | 
						|
 | 
						|
        try {
 | 
						|
            if (!courseId || !sectionId) {
 | 
						|
                const module = await CoreCourse.getModuleBasicInfo(
 | 
						|
                    moduleId,
 | 
						|
                    { siteId, readingStrategy: CoreSitesReadingStrategy.PREFER_CACHE },
 | 
						|
                );
 | 
						|
 | 
						|
                courseId = module.course;
 | 
						|
                sectionId = module.section;
 | 
						|
            }
 | 
						|
 | 
						|
            // Get the site.
 | 
						|
            const site = await CoreSites.getSite(siteId);
 | 
						|
 | 
						|
            // Get the module.
 | 
						|
            const module = await CoreCourse.getModule(moduleId, courseId, sectionId, false, false, siteId, options.modName);
 | 
						|
 | 
						|
            if (CoreSites.getCurrentSiteId() === site.getId()) {
 | 
						|
                // Try to use the module's handler to navigate cleanly.
 | 
						|
                module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
 | 
						|
                    module.modname,
 | 
						|
                    module,
 | 
						|
                    courseId,
 | 
						|
                    sectionId,
 | 
						|
                    false,
 | 
						|
                );
 | 
						|
 | 
						|
                if (module.handlerData?.action) {
 | 
						|
                    modal.dismiss();
 | 
						|
 | 
						|
                    return module.handlerData.action(new Event('click'), module, courseId, options.modNavOptions);
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            const params: Params = {
 | 
						|
                course: { id: courseId },
 | 
						|
                module,
 | 
						|
                sectionId,
 | 
						|
                modNavOptions: options.modNavOptions,
 | 
						|
            };
 | 
						|
 | 
						|
            if (courseId == site.getSiteHomeId()) {
 | 
						|
                // Check if site home is available.
 | 
						|
                const isAvailable = await CoreSiteHome.isAvailable();
 | 
						|
 | 
						|
                if (isAvailable) {
 | 
						|
                    await CoreNavigator.navigateToSitePath(CoreSiteHomeHomeHandlerService.PAGE_NAME, { params, siteId });
 | 
						|
 | 
						|
                    return;
 | 
						|
                }
 | 
						|
            }
 | 
						|
 | 
						|
            modal.dismiss();
 | 
						|
 | 
						|
            await this.getAndOpenCourse(courseId, params, siteId);
 | 
						|
        } catch (error) {
 | 
						|
            CoreDomUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true);
 | 
						|
        } finally {
 | 
						|
            modal.dismiss();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Open a module.
 | 
						|
     *
 | 
						|
     * @param module The module to open.
 | 
						|
     * @param courseId The course ID of the module.
 | 
						|
     * @param options Other options.
 | 
						|
     * @param True if module can be opened, false otherwise.
 | 
						|
     */
 | 
						|
    async openModule(module: CoreCourseModuleData, courseId: number, options: CoreCourseOpenModuleOptions = {}): Promise<boolean> {
 | 
						|
        if (!module.handlerData) {
 | 
						|
            module.handlerData = await CoreCourseModuleDelegate.getModuleDataFor(
 | 
						|
                module.modname,
 | 
						|
                module,
 | 
						|
                courseId,
 | 
						|
                options.sectionId,
 | 
						|
                false,
 | 
						|
            );
 | 
						|
        }
 | 
						|
 | 
						|
        if (module.handlerData?.action) {
 | 
						|
            module.handlerData.action(new Event('click'), module, courseId, {
 | 
						|
                animated: false,
 | 
						|
                ...options.modNavOptions,
 | 
						|
            });
 | 
						|
 | 
						|
            return true;
 | 
						|
        }
 | 
						|
 | 
						|
        return false;
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prefetch all the activities in a course and also the course addons.
 | 
						|
     *
 | 
						|
     * @param course The course to prefetch.
 | 
						|
     * @param sections List of course sections.
 | 
						|
     * @param courseHandlers List of course options handlers.
 | 
						|
     * @param courseMenuHandlers List of course menu handlers.
 | 
						|
     * @param siteId Site ID. If not defined, current site.
 | 
						|
     * @return Promise resolved when the download finishes.
 | 
						|
     */
 | 
						|
    async prefetchCourse(
 | 
						|
        course: CoreCourseAnyCourseData,
 | 
						|
        sections: CoreCourseWSSection[],
 | 
						|
        courseHandlers: CoreCourseOptionsHandlerToDisplay[],
 | 
						|
        courseMenuHandlers: CoreCourseOptionsMenuHandlerToDisplay[],
 | 
						|
        siteId?: string,
 | 
						|
    ): Promise<void> {
 | 
						|
        const requiredSiteId = siteId || CoreSites.getRequiredCurrentSite().getId();
 | 
						|
 | 
						|
        if (this.courseDwnPromises[requiredSiteId] && this.courseDwnPromises[requiredSiteId][course.id] !== undefined) {
 | 
						|
            // There's already a download ongoing for this course, return the promise.
 | 
						|
            return this.courseDwnPromises[requiredSiteId][course.id];
 | 
						|
        } else if (!this.courseDwnPromises[requiredSiteId]) {
 | 
						|
            this.courseDwnPromises[requiredSiteId] = {};
 | 
						|
        }
 | 
						|
 | 
						|
        // First of all, mark the course as being downloaded.
 | 
						|
        this.courseDwnPromises[requiredSiteId][course.id] = CoreCourse.setCourseStatus(
 | 
						|
            course.id,
 | 
						|
            CoreConstants.DOWNLOADING,
 | 
						|
            requiredSiteId,
 | 
						|
        ).then(async () => {
 | 
						|
 | 
						|
            const promises: Promise<unknown>[] = [];
 | 
						|
 | 
						|
            // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections".
 | 
						|
            let allSectionsSection: CoreCourseWSSection = sections[0];
 | 
						|
            if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
                allSectionsSection = this.createAllSectionsSection();
 | 
						|
            }
 | 
						|
            promises.push(this.prefetchSection(allSectionsSection, course.id, sections));
 | 
						|
 | 
						|
            // Prefetch course options.
 | 
						|
            courseHandlers.forEach((handler) => {
 | 
						|
                if (handler.prefetch) {
 | 
						|
                    promises.push(handler.prefetch(course));
 | 
						|
                }
 | 
						|
            });
 | 
						|
            courseMenuHandlers.forEach((handler) => {
 | 
						|
                if (handler.prefetch) {
 | 
						|
                    promises.push(handler.prefetch(course));
 | 
						|
                }
 | 
						|
            });
 | 
						|
 | 
						|
            // Prefetch other data needed to render the course.
 | 
						|
            promises.push(CoreCourses.getCoursesByField('id', course.id));
 | 
						|
 | 
						|
            const sectionWithModules = sections.find((section) => section.modules && section.modules.length > 0);
 | 
						|
            if (!sectionWithModules || sectionWithModules.modules[0].completion === undefined) {
 | 
						|
                promises.push(CoreCourse.getActivitiesCompletionStatus(course.id));
 | 
						|
            }
 | 
						|
 | 
						|
            promises.push(CoreFilterHelper.getFilters('course', course.id));
 | 
						|
 | 
						|
            await CoreUtils.allPromises(promises);
 | 
						|
 | 
						|
            // Download success, mark the course as downloaded.
 | 
						|
            return CoreCourse.setCourseStatus(course.id, CoreConstants.DOWNLOADED, requiredSiteId);
 | 
						|
        }).catch(async (error) => {
 | 
						|
            // Error, restore previous status.
 | 
						|
            await CoreCourse.setCoursePreviousStatus(course.id, requiredSiteId);
 | 
						|
 | 
						|
            throw error;
 | 
						|
        }).finally(() => {
 | 
						|
            delete this.courseDwnPromises[requiredSiteId][course.id];
 | 
						|
        });
 | 
						|
 | 
						|
        return this.courseDwnPromises[requiredSiteId][course.id];
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Helper function to prefetch a module, showing a confirmation modal if the size is big
 | 
						|
     * and invalidating contents if refreshing.
 | 
						|
     *
 | 
						|
     * @param handler Prefetch handler to use.
 | 
						|
     * @param module Module to download.
 | 
						|
     * @param size Size to download.
 | 
						|
     * @param courseId Course ID of the module.
 | 
						|
     * @param refresh True if refreshing, false otherwise.
 | 
						|
     * @return Promise resolved when downloaded.
 | 
						|
     */
 | 
						|
    async prefetchModule(
 | 
						|
        handler: CoreCourseModulePrefetchHandler,
 | 
						|
        module: CoreCourseModuleData,
 | 
						|
        size: CoreFileSizeSum,
 | 
						|
        courseId: number,
 | 
						|
        refresh?: boolean,
 | 
						|
    ): Promise<void> {
 | 
						|
        // Show confirmation if needed.
 | 
						|
        await CoreDomUtils.confirmDownloadSize(size);
 | 
						|
 | 
						|
        // Invalidate content if refreshing and download the data.
 | 
						|
        if (refresh) {
 | 
						|
            await CoreUtils.ignoreErrors(handler.invalidateContent(module.id, courseId));
 | 
						|
        }
 | 
						|
 | 
						|
        await CoreCourseModulePrefetchDelegate.prefetchModule(module, courseId, true);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prefetch one section or all the sections.
 | 
						|
     * If the section is "All sections" it will prefetch all the sections.
 | 
						|
     *
 | 
						|
     * @param section Section.
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @param sections List of sections. Used when downloading all the sections.
 | 
						|
     * @return Promise resolved when the prefetch is finished.
 | 
						|
     */
 | 
						|
    async prefetchSection(
 | 
						|
        section: CoreCourseSectionWithStatus,
 | 
						|
        courseId: number,
 | 
						|
        sections?: CoreCourseSectionWithStatus[],
 | 
						|
    ): Promise<void> {
 | 
						|
        if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
            try {
 | 
						|
                // Download only this section.
 | 
						|
                await this.prefetchSingleSectionIfNeeded(section, courseId);
 | 
						|
            } finally {
 | 
						|
                // Calculate the status of the section that finished.
 | 
						|
                await this.calculateSectionStatus(section, courseId, false, false);
 | 
						|
            }
 | 
						|
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (!sections) {
 | 
						|
            throw new CoreError('List of sections is required when downloading all sections.');
 | 
						|
        }
 | 
						|
 | 
						|
        // Download all the sections except "All sections".
 | 
						|
        let allSectionsStatus = CoreConstants.NOT_DOWNLOADABLE;
 | 
						|
 | 
						|
        section.isDownloading = true;
 | 
						|
        const promises = sections.map(async (section) => {
 | 
						|
            if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
                return;
 | 
						|
            }
 | 
						|
 | 
						|
            try {
 | 
						|
                await this.prefetchSingleSectionIfNeeded(section, courseId);
 | 
						|
            } finally {
 | 
						|
                // Calculate the status of the section that finished.
 | 
						|
                const result = await this.calculateSectionStatus(section, courseId, false, false);
 | 
						|
 | 
						|
                // Calculate "All sections" status.
 | 
						|
                allSectionsStatus = CoreFilepool.determinePackagesStatus(allSectionsStatus, result.statusData.status);
 | 
						|
            }
 | 
						|
        });
 | 
						|
 | 
						|
        try {
 | 
						|
            await CoreUtils.allPromises(promises);
 | 
						|
 | 
						|
            // Set "All sections" data.
 | 
						|
            section.downloadStatus = allSectionsStatus;
 | 
						|
            section.canCheckUpdates = true;
 | 
						|
            section.isDownloading = allSectionsStatus === CoreConstants.DOWNLOADING;
 | 
						|
        } finally {
 | 
						|
            section.isDownloading = false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Prefetch a certain section if it needs to be prefetched.
 | 
						|
     * If the section is "All sections" it will be ignored.
 | 
						|
     *
 | 
						|
     * @param section Section to prefetch.
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @return Promise resolved when the section is prefetched.
 | 
						|
     */
 | 
						|
    protected async prefetchSingleSectionIfNeeded(section: CoreCourseSectionWithStatus, courseId: number): Promise<void> {
 | 
						|
        if (section.id == CoreCourseProvider.ALL_SECTIONS_ID || section.hiddenbynumsections) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        const promises: Promise<void>[] = [];
 | 
						|
        const siteId = CoreSites.getCurrentSiteId();
 | 
						|
 | 
						|
        section.isDownloading = true;
 | 
						|
 | 
						|
        // Download the modules.
 | 
						|
        promises.push(this.syncModulesAndPrefetchSection(section, courseId));
 | 
						|
 | 
						|
        // Download the files in the section description.
 | 
						|
        const introFiles = CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary);
 | 
						|
        promises.push(CoreUtils.ignoreErrors(
 | 
						|
            CoreFilepool.addFilesToQueue(siteId, introFiles, CoreCourseProvider.COMPONENT, courseId),
 | 
						|
        ));
 | 
						|
 | 
						|
        try {
 | 
						|
            await Promise.all(promises);
 | 
						|
        } finally {
 | 
						|
            section.isDownloading = false;
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Sync modules in a section and prefetch them.
 | 
						|
     *
 | 
						|
     * @param section Section to prefetch.
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @return Promise resolved when the section is prefetched.
 | 
						|
     */
 | 
						|
    protected async syncModulesAndPrefetchSection(section: CoreCourseSectionWithStatus, courseId: number): Promise<void> {
 | 
						|
        // Sync the modules first.
 | 
						|
        await CoreCourseModulePrefetchDelegate.syncModules(section.modules, courseId);
 | 
						|
 | 
						|
        // Validate the section needs to be downloaded and calculate amount of modules that need to be downloaded.
 | 
						|
        const result = await CoreCourseModulePrefetchDelegate.getModulesStatus(section.modules, courseId, section.id);
 | 
						|
 | 
						|
        if (result.status == CoreConstants.DOWNLOADED || result.status == CoreConstants.NOT_DOWNLOADABLE) {
 | 
						|
            // Section is downloaded or not downloadable, nothing to do.
 | 
						|
            return ;
 | 
						|
        }
 | 
						|
 | 
						|
        await this.prefetchSingleSection(section, result, courseId);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Start or restore the prefetch of a section.
 | 
						|
     * If the section is "All sections" it will be ignored.
 | 
						|
     *
 | 
						|
     * @param section Section to download.
 | 
						|
     * @param result Result of CoreCourseModulePrefetchDelegate.getModulesStatus for this section.
 | 
						|
     * @param courseId Course ID the section belongs to.
 | 
						|
     * @return Promise resolved when the section has been prefetched.
 | 
						|
     */
 | 
						|
    protected async prefetchSingleSection(
 | 
						|
        section: CoreCourseSectionWithStatus,
 | 
						|
        result: CoreCourseModulesStatus,
 | 
						|
        courseId: number,
 | 
						|
    ): Promise<void> {
 | 
						|
        if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (section.total && section.total > 0) {
 | 
						|
            // Already being downloaded.
 | 
						|
            return ;
 | 
						|
        }
 | 
						|
 | 
						|
        // We only download modules with status notdownloaded, downloading or outdated.
 | 
						|
        const modules = result[CoreConstants.OUTDATED].concat(result[CoreConstants.NOT_DOWNLOADED])
 | 
						|
            .concat(result[CoreConstants.DOWNLOADING]);
 | 
						|
        const downloadId = this.getSectionDownloadId(section);
 | 
						|
 | 
						|
        section.isDownloading = true;
 | 
						|
 | 
						|
        // Prefetch all modules to prevent incoeherences in download count and to download stale data not marked as outdated.
 | 
						|
        await CoreCourseModulePrefetchDelegate.prefetchModules(downloadId, modules, courseId, (data) => {
 | 
						|
            section.count = data.count;
 | 
						|
            section.total = data.total;
 | 
						|
        });
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Check if a section has content.
 | 
						|
     *
 | 
						|
     * @param section Section to check.
 | 
						|
     * @return Whether the section has content.
 | 
						|
     */
 | 
						|
    sectionHasContent(section: CoreCourseWSSection): boolean {
 | 
						|
        if (!section.modules) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        if (section.hiddenbynumsections) {
 | 
						|
            return false;
 | 
						|
        }
 | 
						|
 | 
						|
        return (section.availabilityinfo !== undefined && section.availabilityinfo != '') ||
 | 
						|
            section.summary != '' || (section.modules && section.modules.length > 0);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Wait for any course format plugin to load, and open the course page.
 | 
						|
     *
 | 
						|
     * If the plugin's promise is resolved, the course page will be opened. If it is rejected, they will see an error.
 | 
						|
     * If the promise for the plugin is still in progress when the user tries to open the course, a loader
 | 
						|
     * will be displayed until it is complete, before the course page is opened. If the promise is already complete,
 | 
						|
     * they will see the result immediately.
 | 
						|
     *
 | 
						|
     * @param course Course to open
 | 
						|
     * @param navOptions Navigation options that includes params to pass to the page.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async openCourse(
 | 
						|
        course: CoreCourseAnyCourseData | { id: number },
 | 
						|
        navOptions?: CoreNavigationOptions & { siteId?: string },
 | 
						|
    ): Promise<void> {
 | 
						|
        const siteId = navOptions?.siteId;
 | 
						|
        if (!siteId || siteId == CoreSites.getCurrentSiteId()) {
 | 
						|
            // Current site, we can open the course.
 | 
						|
            return CoreCourse.openCourse(course, navOptions);
 | 
						|
        } else {
 | 
						|
            // We need to load the site first.
 | 
						|
            navOptions = navOptions || {};
 | 
						|
 | 
						|
            navOptions.params = navOptions.params || {};
 | 
						|
            Object.assign(navOptions.params, { course: course });
 | 
						|
 | 
						|
            await CoreNavigator.navigateToSitePath(`course/${course.id}`, navOptions);
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Delete course files.
 | 
						|
     *
 | 
						|
     * @param courseId Course id.
 | 
						|
     * @return Promise to be resolved once the course files are deleted.
 | 
						|
     */
 | 
						|
    async deleteCourseFiles(courseId: number): Promise<void> {
 | 
						|
        const sections = await CoreCourse.getSections(courseId);
 | 
						|
        const modules = CoreArray.flatten(sections.map((section) => section.modules));
 | 
						|
 | 
						|
        await Promise.all(
 | 
						|
            modules.map((module) => this.removeModuleStoredData(module, courseId)),
 | 
						|
        );
 | 
						|
 | 
						|
        await CoreCourse.setCourseStatus(courseId, CoreConstants.NOT_DOWNLOADED);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Remove module stored data.
 | 
						|
     *
 | 
						|
     * @param module Module to remove the files.
 | 
						|
     * @param courseId Course ID the module belongs to.
 | 
						|
     * @return Promise resolved when done.
 | 
						|
     */
 | 
						|
    async removeModuleStoredData(module: CoreCourseModuleData, courseId: number): Promise<void> {
 | 
						|
        const promises: Promise<void>[] = [];
 | 
						|
 | 
						|
        promises.push(CoreCourseModulePrefetchDelegate.removeModuleFiles(module, courseId));
 | 
						|
 | 
						|
        const handler = CoreCourseModulePrefetchDelegate.getPrefetchHandlerFor(module.modname);
 | 
						|
        const site = CoreSites.getCurrentSite();
 | 
						|
        if (handler && site) {
 | 
						|
            promises.push(site.deleteComponentFromCache(handler.component, module.id));
 | 
						|
        }
 | 
						|
 | 
						|
        await Promise.all(promises);
 | 
						|
    }
 | 
						|
 | 
						|
    /**
 | 
						|
     * Completion clicked.
 | 
						|
     *
 | 
						|
     * @param completion The completion.
 | 
						|
     * @param event The click event.
 | 
						|
     * @return Promise resolved with the result.
 | 
						|
     */
 | 
						|
    async changeManualCompletion(
 | 
						|
        completion: CoreCourseModuleCompletionData,
 | 
						|
        event?: Event,
 | 
						|
    ): Promise<CoreStatusWithWarningsWSResponse | void> {
 | 
						|
        if (!completion) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (completion.cmid === undefined ||
 | 
						|
            completion.tracking !== CoreCourseModuleCompletionTracking.COMPLETION_TRACKING_MANUAL) {
 | 
						|
            return;
 | 
						|
        }
 | 
						|
 | 
						|
        event?.preventDefault();
 | 
						|
        event?.stopPropagation();
 | 
						|
 | 
						|
        const modal = await CoreDomUtils.showModalLoading();
 | 
						|
        completion.state = completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE
 | 
						|
            ? CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE
 | 
						|
            : CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE;
 | 
						|
 | 
						|
        try {
 | 
						|
            const response = await CoreCourse.markCompletedManually(
 | 
						|
                completion.cmid,
 | 
						|
                completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE,
 | 
						|
                completion.courseId,
 | 
						|
            );
 | 
						|
 | 
						|
            if (response.offline) {
 | 
						|
                completion.offline = true;
 | 
						|
            }
 | 
						|
 | 
						|
            return response;
 | 
						|
        } catch (error) {
 | 
						|
            // Restore previous state.
 | 
						|
            completion.state = completion.state === CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE
 | 
						|
                ? CoreCourseModuleCompletionStatus.COMPLETION_INCOMPLETE
 | 
						|
                : CoreCourseModuleCompletionStatus.COMPLETION_COMPLETE;
 | 
						|
 | 
						|
            CoreDomUtils.showErrorModalDefault(error, 'core.errorchangecompletion', true);
 | 
						|
        } finally {
 | 
						|
            modal.dismiss();
 | 
						|
        }
 | 
						|
    }
 | 
						|
 | 
						|
}
 | 
						|
 | 
						|
export const CoreCourseHelper = makeSingleton(CoreCourseHelperProvider);
 | 
						|
 | 
						|
/**
 | 
						|
 * Section with calculated data.
 | 
						|
 */
 | 
						|
export type CoreCourseSection = CoreCourseWSSection & {
 | 
						|
    hasContent?: boolean;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Section with data about prefetch.
 | 
						|
 */
 | 
						|
export type CoreCourseSectionWithStatus = CoreCourseSection & {
 | 
						|
    downloadStatus?: string; // Section status.
 | 
						|
    canCheckUpdates?: boolean; // Whether can check updates. @deprecated since app 4.0
 | 
						|
    isDownloading?: boolean; // Whether section is being downloaded.
 | 
						|
    total?: number; // Total of modules being downloaded.
 | 
						|
    count?: number; // Number of downloaded modules.
 | 
						|
    isCalculating?: boolean; // Whether status is being calculated.
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Module with calculated data.
 | 
						|
 */
 | 
						|
export type CoreCourseModuleData = Omit<CoreCourseGetContentsWSModule, 'completiondata'> & {
 | 
						|
    course: number; // The course id.
 | 
						|
    isStealth?: boolean;
 | 
						|
    handlerData?: CoreCourseModuleHandlerData;
 | 
						|
    completiondata?: CoreCourseModuleCompletionData;
 | 
						|
    section: number;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Module with calculated data.
 | 
						|
 *
 | 
						|
 * @deprecated since 4.0. Use CoreCourseModuleData instead.
 | 
						|
 */
 | 
						|
export type CoreCourseModule = CoreCourseModuleData;
 | 
						|
 | 
						|
/**
 | 
						|
 * Module completion with calculated data.
 | 
						|
 */
 | 
						|
export type CoreCourseModuleCompletionData = CoreCourseModuleWSCompletionData & {
 | 
						|
    courseId: number;
 | 
						|
    tracking: CoreCourseModuleCompletionTracking;
 | 
						|
    cmid: number;
 | 
						|
    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;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Common options for navigate to module functions.
 | 
						|
 */
 | 
						|
type CoreCourseNavigateToModuleCommonOptions = {
 | 
						|
    courseId?: number; // Course ID. If not defined we'll try to retrieve it from the site.
 | 
						|
    sectionId?: number; // Section the module belongs to. If not defined we'll try to retrieve it from the site.
 | 
						|
    modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
 | 
						|
    siteId?: string; // Site ID. If not defined, current site.
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Options for navigate to module by instance function.
 | 
						|
 */
 | 
						|
export type CoreCourseNavigateToModuleByInstanceOptions = CoreCourseNavigateToModuleCommonOptions & {
 | 
						|
    // True to retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
 | 
						|
    useModNameToGetModule?: boolean;
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Options for navigate to module function.
 | 
						|
 */
 | 
						|
export type CoreCourseNavigateToModuleOptions = CoreCourseNavigateToModuleCommonOptions & {
 | 
						|
    modName?: string; // To retrieve all instances with a single WS call. Not recommended if can return a lot of contents.
 | 
						|
};
 | 
						|
 | 
						|
/**
 | 
						|
 * Options for open module function.
 | 
						|
 */
 | 
						|
export type CoreCourseOpenModuleOptions = {
 | 
						|
    sectionId?: number; // Section the module belongs to.
 | 
						|
    modNavOptions?: CoreNavigationOptions; // Navigation options to open the module, including params to pass to the module.
 | 
						|
};
 |