forked from EVOgeek/Vmeda.Online
		
	MOBILE-3594 course: Implement some course services
This commit is contained in:
		
							parent
							
								
									4c7545fb46
								
							
						
					
					
						commit
						cc5378aee7
					
				| @ -71,6 +71,12 @@ export class CoreConstants { | ||||
|     static readonly OUTDATED = 'outdated'; | ||||
|     static readonly NOT_DOWNLOADABLE = 'notdownloadable'; | ||||
| 
 | ||||
|     static readonly DOWNLOADED_ICON = 'cloud-done'; | ||||
|     static readonly DOWNLOADING_ICON = 'spinner'; | ||||
|     static readonly NOT_DOWNLOADED_ICON = 'cloud-download'; | ||||
|     static readonly OUTDATED_ICON = 'fas-redo-alt'; | ||||
|     static readonly NOT_DOWNLOADABLE_ICON = ''; | ||||
| 
 | ||||
|     // Constants from Moodle's resourcelib.
 | ||||
|     static readonly RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
 | ||||
|     static readonly RESOURCELIB_DISPLAY_EMBED = 1; // Display using object tag.
 | ||||
|  | ||||
							
								
								
									
										37
									
								
								src/core/features/course/course.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								src/core/features/course/course.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,37 @@ | ||||
| // (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 { NgModule } from '@angular/core'; | ||||
| 
 | ||||
| import { CORE_SITE_SCHEMAS } from '@/core/services/sites'; | ||||
| 
 | ||||
| import { | ||||
|     SITE_SCHEMA as COURSE_SITE_SCHEMA, | ||||
|     OFFLINE_SITE_SCHEMA as COURSE_OFFLINE_SITE_SCHEMA, | ||||
| } from './services/course-db'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     providers: [ | ||||
|         { | ||||
|             provide: CORE_SITE_SCHEMAS, | ||||
|             useValue: [ | ||||
|                 COURSE_SITE_SCHEMA, | ||||
|                 COURSE_OFFLINE_SITE_SCHEMA, | ||||
|             ], | ||||
|             multi: true, | ||||
|         }, | ||||
|     ], | ||||
| }) | ||||
| export class CoreCourseModule { | ||||
| } | ||||
							
								
								
									
										111
									
								
								src/core/features/course/services/course-db.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/core/features/course/services/course-db.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,111 @@ | ||||
| // (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 { CoreSiteSchema } from '@services/sites'; | ||||
| 
 | ||||
| /** | ||||
|  * Database variables for CoreCourse service. | ||||
|  */ | ||||
| export const COURSE_STATUS_TABLE = 'course_status'; | ||||
| export const SITE_SCHEMA: CoreSiteSchema = { | ||||
|     name: 'CoreCourseProvider', | ||||
|     version: 1, | ||||
|     tables: [ | ||||
|         { | ||||
|             name: COURSE_STATUS_TABLE, | ||||
|             columns: [ | ||||
|                 { | ||||
|                     name: 'id', | ||||
|                     type: 'INTEGER', | ||||
|                     primaryKey: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'status', | ||||
|                     type: 'TEXT', | ||||
|                     notNull: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'previous', | ||||
|                     type: 'TEXT', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'updated', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'downloadTime', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'previousDownloadTime', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Database variables for CoreCourseOffline service. | ||||
|  */ | ||||
| export const MANUAL_COMPLETION_TABLE = 'course_manual_completion'; | ||||
| export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = { | ||||
|     name: 'CoreCourseOfflineProvider', | ||||
|     version: 1, | ||||
|     tables: [ | ||||
|         { | ||||
|             name: MANUAL_COMPLETION_TABLE, | ||||
|             columns: [ | ||||
|                 { | ||||
|                     name: 'cmid', | ||||
|                     type: 'INTEGER', | ||||
|                     primaryKey: true, | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'completed', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'courseid', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'coursename', | ||||
|                     type: 'TEXT', | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'timecompleted', | ||||
|                     type: 'INTEGER', | ||||
|                 }, | ||||
|             ], | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
| export type CoreCourseStatusDBRecord = { | ||||
|     id: number; | ||||
|     status: string; | ||||
|     previous: string; | ||||
|     updated: number; | ||||
|     downloadTime: number; | ||||
|     previousDownloadTime: number; | ||||
| }; | ||||
| 
 | ||||
| export type CoreCourseManualCompletionDBRecord = { | ||||
|     cmid: number; | ||||
|     completed: number; | ||||
|     courseid: number; | ||||
|     coursename: string; | ||||
|     timecompleted: number; | ||||
| }; | ||||
							
								
								
									
										117
									
								
								src/core/features/course/services/course-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										117
									
								
								src/core/features/course/services/course-offline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,117 @@ | ||||
| // (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 { makeSingleton } from '@singletons/core.singletons'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreCourseManualCompletionDBRecord, MANUAL_COMPLETION_TABLE } from './course-db'; | ||||
| import { CoreStatusWithWarningsWSResponse } from '@services/ws'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle offline data for courses. | ||||
|  */ | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreCourseOfflineProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Delete a manual completion stored. | ||||
|      * | ||||
|      * @param cmId The module ID to remove the completion. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async deleteManualCompletion(cmId: number, siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         await site.getDb().deleteRecords(MANUAL_COMPLETION_TABLE, { cmid: cmId }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all offline manual completions for a certain course. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the list of completions. | ||||
|      */ | ||||
|     async getAllManualCompletions(siteId?: string): Promise<CoreCourseManualCompletionDBRecord[]> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return await site.getDb().getRecords(MANUAL_COMPLETION_TABLE); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all offline manual completions for a certain course. | ||||
|      * | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the list of completions. | ||||
|      */ | ||||
|     async getCourseManualCompletions(courseId: number, siteId?: string): Promise<CoreCourseManualCompletionDBRecord[]> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return await site.getDb().getRecords(MANUAL_COMPLETION_TABLE, { courseid: courseId }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the offline manual completion for a certain module. | ||||
|      * | ||||
|      * @param cmId The module ID to remove the completion. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the completion, rejected if failure or not found. | ||||
|      */ | ||||
|     async getManualCompletion(cmId: number, siteId?: string): Promise<CoreCourseManualCompletionDBRecord> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return await site.getDb().getRecord(MANUAL_COMPLETION_TABLE, { cmid: cmId }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Offline version for manually marking a module as completed. | ||||
|      * | ||||
|      * @param cmId The module ID to store the completion. | ||||
|      * @param completed Whether the module is completed or not. | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @param courseName Course name. Recommended, it is used to display a better warning message. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when completion is successfully stored. | ||||
|      */ | ||||
|     async markCompletedManually( | ||||
|         cmId: number, | ||||
|         completed: boolean, | ||||
|         courseId: number, | ||||
|         courseName?: string, | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreStatusWithWarningsWSResponse> { | ||||
| 
 | ||||
|         // Store the offline data.
 | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
|         const entry: CoreCourseManualCompletionDBRecord = { | ||||
|             cmid: cmId, | ||||
|             completed: completed ? 1 : 0, | ||||
|             courseid: courseId, | ||||
|             coursename: courseName || '', | ||||
|             timecompleted: Date.now(), | ||||
|         }; | ||||
|         await site.getDb().insertRecord(MANUAL_COMPLETION_TABLE, entry); | ||||
| 
 | ||||
|         return ({ | ||||
|             status: true, | ||||
|             offline: true, | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreCourseOffline extends makeSingleton(CoreCourseOfflineProvider) { } | ||||
							
								
								
									
										990
									
								
								src/core/features/course/services/course.helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										990
									
								
								src/core/features/course/services/course.helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,990 @@ | ||||
| // (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 { CoreSites } from '@services/sites'; | ||||
| import { CoreCourse, CoreCourseSection } from './course'; | ||||
| import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { makeSingleton, Translate } from '@singletons/core.singletons'; | ||||
| import { CoreFilepool } from '@services/filepool'; | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { | ||||
|     CoreCourseBasicData, | ||||
|     CoreCourseGetCoursesData, | ||||
|     CoreCourses, | ||||
|     CoreCourseSearchedData, | ||||
|     CoreEnrolledCourseBasicData, | ||||
|     CoreEnrolledCourseData, | ||||
| } from '@features/courses/services/courses'; | ||||
| import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses.helper'; | ||||
| import { CoreArray } from '@singletons/array'; | ||||
| import { CoreLoginHelper, CoreLoginHelperProvider } from '@features/login/services/login.helper'; | ||||
| import { CoreIonLoadingElement } from '@classes/ion-loading'; | ||||
| import { CoreCourseOffline } from './course-offline'; | ||||
| 
 | ||||
| /** | ||||
|  * Prefetch info of a module. | ||||
|  */ | ||||
| export type CoreCourseModulePrefetchInfo = { | ||||
|     /** | ||||
|      * Downloaded size. | ||||
|      */ | ||||
|     size?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Downloadable size in a readable format. | ||||
|      */ | ||||
|     sizeReadable?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Module status. | ||||
|      */ | ||||
|     status?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Icon's name of the module status. | ||||
|      */ | ||||
|     statusIcon?: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Time when the module was last downloaded. | ||||
|      */ | ||||
|     downloadTime?: number; | ||||
| 
 | ||||
|     /** | ||||
|      * Download time in a readable format. | ||||
|      */ | ||||
|     downloadTimeReadable?: string; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 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.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 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 Course name. Recommended if completionStatus is supplied. | ||||
|      * @param forCoursePage Whether the data will be used to render the course page. | ||||
|      * @return Whether the sections have content. | ||||
|      */ | ||||
|     addHandlerDataForModules(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     calculateSectionStatus(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     calculateSectionsStatus(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 sections List of course sections. | ||||
|      * @param courseHandlers List of course handlers. | ||||
|      * @param menuHandlers List of course menu handlers. | ||||
|      * @return Promise resolved when the download finishes, rejected if an error occurs or the user cancels. | ||||
|      */ | ||||
|     confirmAndPrefetchCourse(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Confirm and prefetches a list of courses. | ||||
|      * | ||||
|      * @param courses List of courses to download. | ||||
|      * @param onProgress Function to call everytime a course is downloaded. | ||||
|      * @return Resolved when downloaded, rejected if error or canceled. | ||||
|      */ | ||||
|     async confirmAndPrefetchCourses( | ||||
|         courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], | ||||
|         onProgress?: (data: CoreCourseCoursesProgress) => void, | ||||
|     ): Promise<void> { | ||||
|         const siteId = CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         // Confirm the download without checking size because it could take a while.
 | ||||
|         await CoreDomUtils.instance.showConfirm(Translate.instance.instant('core.areyousure')); | ||||
| 
 | ||||
|         const total = courses.length; | ||||
|         let count = 0; | ||||
| 
 | ||||
|         const promises = courses.map((course) => { | ||||
|             const subPromises: Promise<void>[] = []; | ||||
|             let sections: CoreCourseSection[]; | ||||
|             let handlers: any; | ||||
|             let menuHandlers: any; | ||||
|             let success = true; | ||||
| 
 | ||||
|             // Get the sections and the handlers.
 | ||||
|             subPromises.push(CoreCourse.instance.getSections(course.id, false, true).then((courseSections) => { | ||||
|                 sections = courseSections; | ||||
| 
 | ||||
|                 return; | ||||
|             })); | ||||
| 
 | ||||
|             /** | ||||
|              * @todo | ||||
|             subPromises.push(this.courseOptionsDelegate.getHandlersToDisplay(this.injector, course).then((cHandlers: any) => { | ||||
|                 handlers = cHandlers; | ||||
|             })); | ||||
|             subPromises.push(this.courseOptionsDelegate.getMenuHandlersToDisplay(this.injector, course).then((mHandlers: any) => { | ||||
|                 menuHandlers = mHandlers; | ||||
|             })); | ||||
|              */ | ||||
| 
 | ||||
|             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 (onProgress) { | ||||
|                         onProgress({ count: count, total: total, courseId: course.id, success: success }); | ||||
|                     } | ||||
|                 }); | ||||
|         }); | ||||
| 
 | ||||
|         if (onProgress) { | ||||
|             // Notify the start of the download.
 | ||||
|             onProgress({ count: 0, total: total, success: true }); | ||||
|         } | ||||
| 
 | ||||
|         return CoreUtils.instance.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. | ||||
|      * @param done Function to call when done. It will close the context menu. | ||||
|      * @return Promise resolved when done. | ||||
|      * @todo module type. | ||||
|      */ | ||||
|     async confirmAndRemoveFiles(module: any, courseId: number, done?: () => void): Promise<void> { | ||||
|         let modal: CoreIonLoadingElement | undefined; | ||||
| 
 | ||||
|         try { | ||||
| 
 | ||||
|             await CoreDomUtils.instance.showDeleteConfirm('core.course.confirmdeletestoreddata'); | ||||
| 
 | ||||
|             modal = await CoreDomUtils.instance.showModalLoading(); | ||||
| 
 | ||||
|             await this.removeModuleStoredData(module, courseId); | ||||
| 
 | ||||
|             done && done(); | ||||
| 
 | ||||
|         } catch (error) { | ||||
|             if (error) { | ||||
|                 CoreDomUtils.instance.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. | ||||
|      */ | ||||
|     confirmDownloadSizeSection(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Helper function to prefetch a module, showing a confirmation modal if the size is big. | ||||
|      * This function is meant to be called from a context menu option. It will also modify some data like the prefetch icon. | ||||
|      * | ||||
|      * @param instance The component instance that has the context menu. It should have prefetchStatusIcon and isDestroyed. | ||||
|      * @param module Module to be prefetched | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @param done Function to call when done. It will close the context menu. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     contextMenuPrefetch(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         courses.forEach((course) => { | ||||
|             promises.push(CoreCourse.instance.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. | ||||
|      * @return Resolved on success. | ||||
|      */ | ||||
|     downloadModuleAndOpenFile(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     downloadModuleWithMainFileIfNeeded(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     protected downloadModuleWithMainFile(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      */ | ||||
|     downloadModule(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Fill the Context Menu for a certain module. | ||||
|      * | ||||
|      * @param instance The component instance that has the context menu. | ||||
|      * @param module Module to be prefetched | ||||
|      * @param courseId Course ID the module belongs to. | ||||
|      * @param invalidateCache Invalidates the cache first. | ||||
|      * @param component Component of the module. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     fillContextMenu(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 | CoreCourseGetCoursesData }> { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         let course: CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData; | ||||
| 
 | ||||
|         // Try with enrolled courses first.
 | ||||
|         try { | ||||
|             course = await CoreCourses.instance.getUserCourse(courseId, false, siteId); | ||||
| 
 | ||||
|             return ({ enrolled: true, course: course }); | ||||
|         } catch { | ||||
|             // Not enrolled or an error happened. Try to use another WebService.
 | ||||
|         } | ||||
| 
 | ||||
|         const available = await CoreCourses.instance.isGetCoursesByFieldAvailableInSite(siteId); | ||||
| 
 | ||||
|         if (available) { | ||||
|             course = await CoreCourses.instance.getCourseByField('id', courseId, siteId); | ||||
|         } else { | ||||
|             course = await CoreCourses.instance.getCourse(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. | ||||
|      */ | ||||
|     async getAndOpenCourse(courseId: number, params?: Params, siteId?: string): Promise<any> { | ||||
|         const modal = await CoreDomUtils.instance.showModalLoading(); | ||||
| 
 | ||||
|         let course: CoreEnrolledCourseData | CoreCourseSearchedData | CoreCourseGetCoursesData | { 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.3 | ||||
|      */ | ||||
|     async hasABlockNamed(courseId: number, name: string, siteId?: string): Promise<boolean> { | ||||
|         try { | ||||
|             const blocks = await CoreCourse.instance.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. | ||||
|      * @param minCourses Min course to show icon. | ||||
|      * @return Resolved with the prefetch information updated when done. | ||||
|      */ | ||||
|     async initPrefetchCoursesIcons( | ||||
|         courses: CoreCourseBasicData[], | ||||
|         prefetch: CorePrefetchStatusInfo, | ||||
|         minCourses: number = 2, | ||||
|     ): Promise<CorePrefetchStatusInfo> { | ||||
|         if (!courses || courses.length < minCourses) { | ||||
|             // Not enough courses.
 | ||||
|             prefetch.icon = ''; | ||||
| 
 | ||||
|             return prefetch; | ||||
|         } | ||||
| 
 | ||||
|         const status = await this.determineCoursesStatus(courses); | ||||
| 
 | ||||
|         prefetch = this.getCourseStatusIconAndTitleFromStatus(status); | ||||
| 
 | ||||
|         if (prefetch.loading) { | ||||
|             // It seems all courses are being downloaded, show a download button instead.
 | ||||
|             prefetch.icon = CoreConstants.NOT_DOWNLOADED_ICON; | ||||
|         } | ||||
| 
 | ||||
|         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: any[], siteId?: string): Promise<void> { | ||||
|         const offlineCompletions = await CoreCourseOffline.instance.getCourseManualCompletions(courseId, siteId); | ||||
| 
 | ||||
|         if (!offlineCompletions || !offlineCompletions.length) { | ||||
|             // No offline completion.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const totalOffline = offlineCompletions.length; | ||||
|         let loaded = 0; | ||||
|         const offlineCompletionsMap = CoreUtils.instance.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 && typeof 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; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Prefetch all the courses in the array. | ||||
|      * | ||||
|      * @param courses Courses array to prefetch. | ||||
|      * @param prefetch Prefetch information to be updated. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async prefetchCourses( | ||||
|         courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[], | ||||
|         prefetch: CorePrefetchStatusInfo, | ||||
|     ): Promise<void> { | ||||
|         prefetch.loading = true; | ||||
|         prefetch.icon = CoreConstants.DOWNLOADING_ICON; | ||||
|         prefetch.badge = ''; | ||||
| 
 | ||||
|         try { | ||||
|             await this.confirmAndPrefetchCourses(courses, (progress) => { | ||||
|                 prefetch.badge = progress.count + ' / ' + progress.total; | ||||
|             }); | ||||
|             prefetch.icon = CoreConstants.OUTDATED_ICON; | ||||
|         } 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.instance.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.instance.getCourseStatus(courseId, siteId); | ||||
| 
 | ||||
|         return this.getCourseStatusIconAndTitleFromStatus(status); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get a course status icon and the langkey to use as a title from status. | ||||
|      * | ||||
|      * @param status Course status. | ||||
|      * @return Title and icon name. | ||||
|      */ | ||||
|     getCourseStatusIconAndTitleFromStatus(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 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.NOT_DOWNLOADED_ICON; | ||||
|         } | ||||
|         if (status == CoreConstants.OUTDATED || (status == CoreConstants.DOWNLOADED && !trustDownload)) { | ||||
|             return CoreConstants.OUTDATED_ICON; | ||||
|         } | ||||
|         if (status == CoreConstants.DOWNLOADED && trustDownload) { | ||||
|             return CoreConstants.DOWNLOADED_ICON; | ||||
|         } | ||||
|         if (status == CoreConstants.DOWNLOADING) { | ||||
|             return CoreConstants.DOWNLOADING_ICON; | ||||
|         } | ||||
| 
 | ||||
|         return CoreConstants.DOWNLOADING_ICON; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the course ID from a module instance ID, showing an error message if it can't be retrieved. | ||||
|      * | ||||
|      * @param id Instance ID. | ||||
|      * @param module 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. | ||||
|      * @todo module type. | ||||
|      */ | ||||
|     async getModuleCourseIdByInstance(id: number, module: any, siteId?: string): Promise<number> { | ||||
|         try { | ||||
|             const cm = await CoreCourse.instance.getModuleBasicInfoByInstance(id, module, siteId); | ||||
| 
 | ||||
|             return cm.course; | ||||
|         } catch (error) { | ||||
|             CoreDomUtils.instance.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. | ||||
|      */ | ||||
|     getModulePrefetchInfo(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the download ID of a section. It's used to interact with CoreCourseModulePrefetchDelegate. | ||||
|      * | ||||
|      * @param section Section. | ||||
|      * @return Section download ID. | ||||
|      * @todo section type. | ||||
|      */ | ||||
|     getSectionDownloadId(section: any): 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 siteId Site ID. If not defined, current site. | ||||
|      * @param courseId Course ID. If not defined we'll try to retrieve it from the site. | ||||
|      * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site. | ||||
|      * @param useModNameToGetModule If true, the app will retrieve all modules of this type with a single WS call. This reduces the | ||||
|      *                              number of WS calls, but it isn't recommended for modules that can return a lot of contents. | ||||
|      * @param modParams Params to pass to the module | ||||
|      * @param navCtrl NavController for adding new pages to the current history. Optional for legacy support, but | ||||
|      *                generates a warning if omitted. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     navigateToModuleByInstance(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Navigate to a module. | ||||
|      * | ||||
|      * @param moduleId Module's ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @param courseId Course ID. If not defined we'll try to retrieve it from the site. | ||||
|      * @param sectionId Section the module belongs to. If not defined we'll try to retrieve it from the site. | ||||
|      * @param modName If set, the app will retrieve all modules of this type with a single WS call. This reduces the | ||||
|      *                number of WS calls, but it isn't recommended for modules that can return a lot of contents. | ||||
|      * @param modParams Params to pass to the module | ||||
|      * @param navCtrl NavController for adding new pages to the current history. Optional for legacy support, but | ||||
|      *                generates a warning if omitted. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     navigateToModule(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Open a module. | ||||
|      * | ||||
|      * @param navCtrl The NavController to use. | ||||
|      * @param module The module to open. | ||||
|      * @param courseId The course ID of the module. | ||||
|      * @param sectionId The section ID of the module. | ||||
|      * @param modParams Params to pass to the module | ||||
|      * @param True if module can be opened, false otherwise. | ||||
|      */ | ||||
|     openModule(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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: CoreEnrolledCourseDataWithExtraInfoAndOptions, | ||||
|         sections: CoreCourseSection[], | ||||
|         courseHandlers: any[], // @todo CoreCourseOptionsHandlerToDisplay[],
 | ||||
|         courseMenuHandlers: any[], // @todo CoreCourseOptionsMenuHandlerToDisplay[],
 | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         siteId = siteId || CoreSites.instance.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][course.id]) { | ||||
|             // There's already a download ongoing for this course, return the promise.
 | ||||
|             return this.courseDwnPromises[siteId][course.id]; | ||||
|         } else if (!this.courseDwnPromises[siteId]) { | ||||
|             this.courseDwnPromises[siteId] = {}; | ||||
|         } | ||||
| 
 | ||||
|         // First of all, mark the course as being downloaded.
 | ||||
|         this.courseDwnPromises[siteId][course.id] = CoreCourse.instance.setCourseStatus( | ||||
|             course.id, | ||||
|             CoreConstants.DOWNLOADING, | ||||
|             siteId, | ||||
|         ).then(async () => { | ||||
| 
 | ||||
|             const promises: Promise<any>[] = []; | ||||
| 
 | ||||
|             // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections".
 | ||||
|             /* | ||||
|              * @todo | ||||
|             let allSectionsSection = sections[0]; | ||||
|              if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) { | ||||
|                 allSectionsSection = { id: CoreCourseProvider.ALL_SECTIONS_ID }; | ||||
|             } | ||||
|             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.
 | ||||
|             if (CoreCourses.instance.isGetCoursesByFieldAvailable()) { | ||||
|                 promises.push(CoreCourses.instance.getCoursesByField('id', course.id)); | ||||
|             } | ||||
| 
 | ||||
|             const sectionWithModules = sections.find((section) => section.modules && section.modules.length > 0); | ||||
|             if (!sectionWithModules || typeof sectionWithModules.modules[0].completion == 'undefined') { | ||||
|                 promises.push(CoreCourse.instance.getActivitiesCompletionStatus(course.id)); | ||||
|             } | ||||
| 
 | ||||
|             // @todo promises.push(this.filterHelper.getFilters('course', course.id));
 | ||||
| 
 | ||||
|             return CoreUtils.instance.allPromises(promises); | ||||
|         }).then(() => | ||||
|             // Download success, mark the course as downloaded.
 | ||||
|             CoreCourse.instance.setCourseStatus(course.id, CoreConstants.DOWNLOADED, siteId)).catch(async (error) => { | ||||
|             // Error, restore previous status.
 | ||||
|             await CoreCourse.instance.setCoursePreviousStatus(course.id, siteId); | ||||
| 
 | ||||
|             throw error; | ||||
|         }).finally(() => { | ||||
|             delete this.courseDwnPromises[siteId!][course.id]; | ||||
|         }); | ||||
| 
 | ||||
|         return this.courseDwnPromises[siteId][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. Must implement 'prefetch' and 'invalidateContent'. | ||||
|      * @param module Module to download. | ||||
|      * @param size Object containing size to download (in bytes) and a boolean to indicate if its totally calculated. | ||||
|      * @param courseId Course ID of the module. | ||||
|      * @param refresh True if refreshing, false otherwise. | ||||
|      * @return Promise resolved when downloaded. | ||||
|      */ | ||||
|     prefetchModule(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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(): Promise<void> { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 prefetchSingleSectionIfNeeded(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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 prefetchSingleSection(): void { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a section has content. | ||||
|      * | ||||
|      * @param section Section to check. | ||||
|      * @return Whether the section has content. | ||||
|      * @todo section type. | ||||
|      */ | ||||
|     sectionHasContent(section: any): boolean { | ||||
|         if (section.hiddenbynumsections) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return (typeof 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 params Params to pass to the course page. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     openCourse(course: CoreEnrolledCourseBasicData | { id: number }, params?: Params, siteId?: string): Promise<void> { | ||||
|         if (!siteId || siteId == CoreSites.instance.getCurrentSiteId()) { | ||||
|             // Current site, we can open the course.
 | ||||
|             return CoreCourse.instance.openCourse(course, params); | ||||
|         } else { | ||||
|             // We need to load the site first.
 | ||||
|             params = params || {}; | ||||
|             Object.assign(params, { course: course }); | ||||
| 
 | ||||
|             return CoreLoginHelper.instance.redirect(CoreLoginHelperProvider.OPEN_COURSE, params, siteId); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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.instance.getSections(courseId); | ||||
|         const modules = CoreArray.flatten(sections.map((section) => section.modules)); | ||||
| 
 | ||||
|         await Promise.all( | ||||
|             modules.map((module) => this.removeModuleStoredData(module, courseId)), | ||||
|         ); | ||||
| 
 | ||||
|         await CoreCourse.instance.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. | ||||
|      */ | ||||
|     // @todo remove when done.
 | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async removeModuleStoredData(module: any, courseId: number): Promise<void> { | ||||
|         const promises: Promise<void>[] = []; | ||||
| 
 | ||||
|         // @todo
 | ||||
|         // promises.push(this.prefetchDelegate.removeModuleFiles(module, courseId));
 | ||||
| 
 | ||||
|         // @todo
 | ||||
|         // const handler = this.prefetchDelegate.getPrefetchHandlerFor(module);
 | ||||
|         // if (handler) {
 | ||||
|         //   promises.push(CoreSites.instance.getCurrentSite().deleteComponentFromCache(handler.component, module.id));
 | ||||
|         // }
 | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreCourseHelper extends makeSingleton(CoreCourseHelperProvider) {} | ||||
							
								
								
									
										1505
									
								
								src/core/features/course/services/course.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1505
									
								
								src/core/features/course/services/course.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										181
									
								
								src/core/features/courses/services/courses.helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/core/features/courses/services/courses.helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,181 @@ | ||||
| // (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 { PopoverController } from '@ionic/angular';
 | ||||
| import { CoreUtils } from '@services/utils/utils'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreCourses, CoreCourseSearchedData, CoreCourseUserAdminOrNavOptionIndexed, CoreEnrolledCourseData } from './courses'; | ||||
| import { makeSingleton } from '@singletons/core.singletons'; | ||||
| // import { AddonCourseCompletionProvider } from '@addon/coursecompletion/providers/coursecompletion';
 | ||||
| // import { CoreCoursePickerMenuPopoverComponent } from '@components/course-picker-menu/course-picker-menu-popover';
 | ||||
| 
 | ||||
| /** | ||||
|  * Helper to gather some common courses functions. | ||||
|  */ | ||||
| @Injectable({ | ||||
|     providedIn: 'root', | ||||
| }) | ||||
| export class CoreCoursesHelperProvider { | ||||
| 
 | ||||
|     /** | ||||
|      * Get the courses to display the course picker popover. If a courseId is specified, it will also return its categoryId. | ||||
|      * | ||||
|      * @param courseId Course ID to get the category. | ||||
|      * @return Promise resolved with the list of courses and the category. | ||||
|      */ | ||||
|     async getCoursesForPopover(): Promise<void> { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a course object returned by core_enrol_get_users_courses and another one returned by core_course_get_courses_by_field, | ||||
|      * load some extra data to the first one. | ||||
|      * | ||||
|      * @param course Course returned by core_enrol_get_users_courses. | ||||
|      * @param courseByField Course returned by core_course_get_courses_by_field. | ||||
|      * @param addCategoryName Whether add category name or not. | ||||
|      */ | ||||
|     loadCourseExtraInfo( | ||||
|         course: CoreEnrolledCourseDataWithExtraInfo, | ||||
|         courseByField: CoreCourseSearchedData, | ||||
|         addCategoryName: boolean = false, | ||||
|     ): void { | ||||
|         if (courseByField) { | ||||
|             course.displayname = courseByField.displayname; | ||||
|             course.categoryname = addCategoryName ? courseByField.categoryname : undefined; | ||||
| 
 | ||||
|             if (courseByField.overviewfiles && courseByField.overviewfiles[0]) { | ||||
|                 course.courseImage = courseByField.overviewfiles[0].fileurl; | ||||
|             } else { | ||||
|                 delete course.courseImage; | ||||
|             } | ||||
|         } else { | ||||
|             delete course.displayname; | ||||
|             delete course.courseImage; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a list of courses returned by core_enrol_get_users_courses, load some extra data using the WebService | ||||
|      * core_course_get_courses_by_field if available. | ||||
|      * | ||||
|      * @param courses List of courses. | ||||
|      * @param loadCategoryNames Whether load category names or not. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async loadCoursesExtraInfo(courses: CoreEnrolledCourseDataWithExtraInfo[], loadCategoryNames: boolean = false): Promise<void> { | ||||
|         if (!courses.length ) { | ||||
|             // No courses or cannot get the data, stop.
 | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         let coursesInfo = {}; | ||||
|         let courseInfoAvailable = false; | ||||
| 
 | ||||
|         const site = CoreSites.instance.getCurrentSite(); | ||||
|         const promises: Promise<void>[] = []; | ||||
|         const colors: (string | undefined)[] = []; | ||||
| 
 | ||||
|         if (site?.isVersionGreaterEqualThan('3.8')) { | ||||
|             promises.push(site.getConfig().then((configs) => { | ||||
|                 for (let x = 0; x < 10; x++) { | ||||
|                     colors[x] = configs['core_admin_coursecolor' + (x + 1)] || undefined; | ||||
|                 } | ||||
| 
 | ||||
|                 return; | ||||
|             }).catch(() => { | ||||
|                 // Ignore errors.
 | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         if (CoreCourses.instance.isGetCoursesByFieldAvailable() && (loadCategoryNames || | ||||
|                 (typeof courses[0].overviewfiles == 'undefined' && typeof courses[0].displayname == 'undefined'))) { | ||||
|             const courseIds = courses.map((course) => course.id).join(','); | ||||
| 
 | ||||
|             courseInfoAvailable = true; | ||||
| 
 | ||||
|             // Get the extra data for the courses.
 | ||||
|             promises.push(CoreCourses.instance.getCoursesByField('ids', courseIds).then((coursesInfos) => { | ||||
|                 coursesInfo = CoreUtils.instance.arrayToObject(coursesInfos, 'id'); | ||||
| 
 | ||||
|                 return; | ||||
|             })); | ||||
|         } | ||||
| 
 | ||||
|         await Promise.all(promises); | ||||
| 
 | ||||
|         courses.forEach((course) => { | ||||
|             this.loadCourseExtraInfo(course, courseInfoAvailable ? coursesInfo[course.id] : course, loadCategoryNames); | ||||
| 
 | ||||
|             if (!course.courseImage) { | ||||
|                 course.colorNumber = course.id % 10; | ||||
|                 course.color = colors.length ? colors[course.colorNumber] : undefined; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get user courses with admin and nav options. | ||||
|      * | ||||
|      * @param sort Sort courses after get them. If sort is not defined it won't be sorted. | ||||
|      * @param slice Slice results to get the X first one. If slice > 0 it will be done after sorting. | ||||
|      * @param filter Filter using some field. | ||||
|      * @param loadCategoryNames Whether load category names or not. | ||||
|      * @return Courses filled with options. | ||||
|      */ | ||||
|     async getUserCoursesWithOptions(): Promise<void> { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Show a context menu to select a course, and return the courseId and categoryId of the selected course (-1 for all courses). | ||||
|      * Returns an empty object if popover closed without picking a course. | ||||
|      * | ||||
|      * @param event Click event. | ||||
|      * @param courses List of courses, from CoreCoursesHelperProvider.getCoursesForPopover. | ||||
|      * @param courseId The course to select at start. | ||||
|      * @return Promise resolved with the course ID and category ID. | ||||
|      */ | ||||
|     async selectCourse(): Promise<void> { | ||||
|         // @todo params and logic
 | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreCoursesHelper extends makeSingleton(CoreCoursesHelperProvider) { } | ||||
| 
 | ||||
| /** | ||||
|  * Enrolled course data with extra rendering info. | ||||
|  */ | ||||
| export type CoreEnrolledCourseDataWithExtraInfo = CoreEnrolledCourseData & { | ||||
|     colorNumber?: number; // Color index number.
 | ||||
|     color?: string; // Color RGB.
 | ||||
|     courseImage?: string; // Course thumbnail.
 | ||||
|     categoryname?: string; // Category name,
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Enrolled course data with admin and navigation option availability. | ||||
|  */ | ||||
| export type CoreEnrolledCourseDataWithOptions = CoreEnrolledCourseData & { | ||||
|     navOptions?: CoreCourseUserAdminOrNavOptionIndexed; | ||||
|     admOptions?: CoreCourseUserAdminOrNavOptionIndexed; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Enrolled course data with admin and navigation option availability and extra rendering info. | ||||
|  */ | ||||
| export type CoreEnrolledCourseDataWithExtraInfoAndOptions = CoreEnrolledCourseDataWithExtraInfo & CoreEnrolledCourseDataWithOptions; | ||||
| 
 | ||||
							
								
								
									
										1592
									
								
								src/core/features/courses/services/courses.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1592
									
								
								src/core/features/courses/services/courses.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -14,6 +14,7 @@ | ||||
| 
 | ||||
| import { NgModule } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreCourseModule } from './course/course.module'; | ||||
| import { CoreCoursesModule } from './courses/courses.module'; | ||||
| import { CoreEmulatorModule } from './emulator/emulator.module'; | ||||
| import { CoreFileUploaderInitModule } from './fileuploader/fileuploader-init.module'; | ||||
| @ -24,6 +25,7 @@ import { CoreSettingsInitModule } from './settings/settings-init.module'; | ||||
|     imports: [ | ||||
|         CoreEmulatorModule, | ||||
|         CoreLoginModule, | ||||
|         CoreCourseModule, | ||||
|         CoreCoursesModule, | ||||
|         CoreSettingsInitModule, | ||||
|         CoreFileUploaderInitModule, | ||||
|  | ||||
| @ -24,7 +24,7 @@ import { CoreConstants } from '@/core/constants'; | ||||
| import { CoreConfig } from '@services/config'; | ||||
| // import { CoreFilterProvider } from '@features/filter/providers/filter';
 | ||||
| import { CoreDomUtils } from '@services/utils/dom'; | ||||
| // import { CoreCourseProvider } from '@features/course/providers/course';
 | ||||
| import { CoreCourse } from '@features/course/services/course'; | ||||
| import { makeSingleton, Translate } from '@singletons/core.singletons'; | ||||
| import { CoreError } from '@classes/errors/error'; | ||||
| 
 | ||||
| @ -58,7 +58,6 @@ export class CoreSettingsHelperProvider { | ||||
| 
 | ||||
|     constructor() { | ||||
|         // protected filterProvider: CoreFilterProvider,
 | ||||
|         // protected courseProvider: CoreCourseProvider,
 | ||||
| 
 | ||||
|         if (!CoreConstants.CONFIG.forceColorScheme) { | ||||
|             // Update color scheme when a user enters or leaves a site, or when the site info is updated.
 | ||||
| @ -116,7 +115,7 @@ export class CoreSettingsHelperProvider { | ||||
|         promises.push(site.deleteFolder().then(() => { | ||||
|             filepoolService.clearAllPackagesStatus(siteId); | ||||
|             filepoolService.clearFilepool(siteId); | ||||
|             // this.courseProvider.clearAllCoursesStatus(siteId);
 | ||||
|             CoreCourse.instance.clearAllCoursesStatus(siteId); | ||||
| 
 | ||||
|             siteInfo.spaceUsage = 0; | ||||
| 
 | ||||
|  | ||||
| @ -990,6 +990,13 @@ export type CoreStatusWithWarningsWSResponse = { | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Special response structure of many webservices that contains only warnings. | ||||
|  */ | ||||
| export type CoreWarningsWSResponse = { | ||||
|     warnings?: CoreWSExternalWarning[]; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Structure of files returned by WS. | ||||
|  */ | ||||
|  | ||||
| @ -228,3 +228,11 @@ export type CoreEventLoadPageMainMenuData = { | ||||
|     redirectPage: string; | ||||
|     redirectParams?: Params; | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Data passed to COURSE_STATUS_CHANGED event. | ||||
|  */ | ||||
| export type CoreEventCourseStatusChanged = { | ||||
|     courseId: number; // Course Id.
 | ||||
|     status: string; | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user