commit
						337b92aed6
					
				| @ -126,7 +126,7 @@ const appConfig = { | ||||
|                 ignoreParameters: true, | ||||
|             }, | ||||
|         ], | ||||
|         '@typescript-eslint/no-non-null-assertion': 'off', | ||||
|         '@typescript-eslint/no-non-null-assertion': 'warn', | ||||
|         '@typescript-eslint/no-redeclare': 'error', | ||||
|         '@typescript-eslint/no-this-alias': 'error', | ||||
|         '@typescript-eslint/no-unused-vars': 'error', | ||||
|  | ||||
| @ -133,8 +133,11 @@ export class AddonModBookIndexComponent extends CoreCourseModuleMainResourceComp | ||||
| 
 | ||||
|             await this.loadBookData(); | ||||
| 
 | ||||
|             this.contentsMap = AddonModBook.getContentsMap(this.module.contents); | ||||
|             this.chapters = AddonModBook.getTocList(this.module.contents); | ||||
|             // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | ||||
|             const contents = await CoreCourse.getModuleContents(this.module, this.courseId); | ||||
| 
 | ||||
|             this.contentsMap = AddonModBook.getContentsMap(contents); | ||||
|             this.chapters = AddonModBook.getTocList(contents); | ||||
| 
 | ||||
|             if (typeof this.currentChapter == 'undefined' && typeof this.initialChapterId != 'undefined' && this.chapters) { | ||||
|                 // Initial chapter set. Validate that the chapter exists.
 | ||||
|  | ||||
| @ -34,8 +34,8 @@ | ||||
|         contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> | ||||
|     </core-course-module-description> | ||||
| 
 | ||||
|     <ion-list *ngIf="contents && (contents!.files.length + contents!.folders.length > 0)"> | ||||
|         <ng-container *ngFor="let folder of contents!.folders"> | ||||
|     <ion-list *ngIf="contents && (contents.files.length + contents.folders.length > 0)"> | ||||
|         <ng-container *ngFor="let folder of contents.folders"> | ||||
|             <ion-item class="item-file" (click)="openFolder(folder)" detail="true" button> | ||||
|                 <ion-icon name="fas-folder" slot="start" [attr.aria-label]="'core.folder' | translate"></ion-icon> | ||||
|                 <ion-label> | ||||
| @ -43,12 +43,12 @@ | ||||
|                 </ion-label> | ||||
|             </ion-item> | ||||
|         </ng-container> | ||||
|         <ng-container *ngFor="let file of contents!.files"> | ||||
|         <ng-container *ngFor="let file of contents.files"> | ||||
|             <core-file [file]="file" [component]="component" [componentId]="componentId"></core-file> | ||||
|         </ng-container> | ||||
|     </ion-list> | ||||
| 
 | ||||
|     <core-empty-box *ngIf="!contents || (contents!.files.length + contents!.folders.length == 0)" icon="far-folder-open" | ||||
|     <core-empty-box *ngIf="!contents || (contents.files.length + contents.folders.length == 0)" icon="far-folder-open" | ||||
|         [message]=" 'addon.mod_folder.emptyfilelist' | translate"></core-empty-box> | ||||
| 
 | ||||
| </core-loading> | ||||
|  | ||||
| @ -95,12 +95,13 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo | ||||
|     protected async fetchContent(refresh = false): Promise<void> { | ||||
|         try { | ||||
|             this.folderInstance = await AddonModFolder.getFolder(this.courseId, this.module.id); | ||||
|             await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
| 
 | ||||
|             const contents = await CoreCourse.getModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
| 
 | ||||
|             this.dataRetrieved.emit(this.folderInstance || this.module); | ||||
| 
 | ||||
|             this.description = this.folderInstance ? this.folderInstance.intro : this.module.description; | ||||
|             this.contents = AddonModFolderHelper.formatContents(this.module.contents); | ||||
|             this.contents = AddonModFolderHelper.formatContents(contents); | ||||
|         } finally { | ||||
|             this.fillContextMenu(refresh); | ||||
|         } | ||||
|  | ||||
| @ -85,7 +85,10 @@ export class AddonModImscpIndexComponent extends CoreCourseModuleMainResourceCom | ||||
|             this.description = imscp.intro; | ||||
|             this.dataRetrieved.emit(imscp); | ||||
| 
 | ||||
|             this.items = AddonModImscp.createItemList(this.module.contents); | ||||
|             // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | ||||
|             const contents = await CoreCourse.getModuleContents(this.module, this.courseId); | ||||
| 
 | ||||
|             this.items = AddonModImscp.createItemList(contents); | ||||
| 
 | ||||
|             if (this.items.length && typeof this.currentItem == 'undefined') { | ||||
|                 this.currentItem = this.items[0].href; | ||||
|  | ||||
| @ -229,8 +229,10 @@ export class AddonModImscpProvider { | ||||
|      * @return Promise resolved with the item src. | ||||
|      */ | ||||
|     async getIframeSrc(module: CoreCourseModule, itemHref?: string): Promise<string> { | ||||
|         const contents = await CoreCourse.getModuleContents(module); | ||||
| 
 | ||||
|         if (!itemHref) { | ||||
|             const toc = this.getToc(module.contents); | ||||
|             const toc = this.getToc(contents); | ||||
|             if (!toc.length) { | ||||
|                 throw new CoreError('Empty TOC'); | ||||
|             } | ||||
| @ -246,7 +248,7 @@ export class AddonModImscpProvider { | ||||
|         } catch (error) { | ||||
|             // Error getting directory, there was an error downloading or we're in browser. Return online URL if connected.
 | ||||
|             if (CoreApp.isOnline()) { | ||||
|                 const indexUrl = this.getFileUrlFromContents(module.contents, itemHref); | ||||
|                 const indexUrl = this.getFileUrlFromContents(contents, itemHref); | ||||
| 
 | ||||
|                 if (indexUrl) { | ||||
|                     const site = await CoreSites.getSite(siteId); | ||||
|  | ||||
| @ -81,9 +81,12 @@ export class AddonModPageIndexComponent extends CoreCourseModuleMainResourceComp | ||||
|             // Download the resource if it needs to be downloaded.
 | ||||
|             const downloadResult = await this.downloadResourceIfNeeded(refresh); | ||||
| 
 | ||||
|             // Get contents. No need to refresh, it has been done in downloadResourceIfNeeded.
 | ||||
|             const contents = await CoreCourse.getModuleContents(this.module, this.courseId); | ||||
| 
 | ||||
|             const results = await Promise.all([ | ||||
|                 this.loadPageData(), | ||||
|                 AddonModPageHelper.getPageHtml(this.module.contents, this.module.id), | ||||
|                 AddonModPageHelper.getPageHtml(contents, this.module.id), | ||||
|             ]); | ||||
| 
 | ||||
|             this.contents = results[1]; | ||||
|  | ||||
| @ -101,9 +101,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|      */ | ||||
|     protected async fetchContent(refresh?: boolean): Promise<void> { | ||||
|         // Load module contents if needed. Passing refresh is needed to force reloading contents.
 | ||||
|         await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
|         const contents = await CoreCourse.getModuleContents(this.module, this.courseId, undefined, false, refresh); | ||||
| 
 | ||||
|         if (!this.module.contents || !this.module.contents.length) { | ||||
|         if (!contents.length) { | ||||
|             throw new CoreError(Translate.instant('core.filenotfound')); | ||||
|         } | ||||
| 
 | ||||
| @ -155,10 +155,10 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource | ||||
|                 this.warning = ''; | ||||
| 
 | ||||
|                 if (this.isIOS) { | ||||
|                     this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(this.module.contents[0]); | ||||
|                     this.shouldOpenInBrowser = CoreFileHelper.shouldOpenInBrowser(contents[0]); | ||||
|                 } | ||||
| 
 | ||||
|                 const mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(this.module.contents[0])); | ||||
|                 const mimetype = await CoreUtils.getMimeTypeFromUrl(CoreFileHelper.getFileUrl(contents[0])); | ||||
| 
 | ||||
|                 this.isStreamedFile = CoreMimetypeUtils.isStreamedMimetype(mimetype); | ||||
|             } | ||||
|  | ||||
| @ -26,7 +26,7 @@ import { CoreDomUtils } from '@services/utils/dom'; | ||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { makeSingleton, Translate } from '@singletons'; | ||||
| import { AddonModResource, AddonModResourceProvider } from './resource'; | ||||
| 
 | ||||
| /** | ||||
| @ -43,15 +43,17 @@ export class AddonModResourceHelperProvider { | ||||
|      * @return Promise resolved with the HTML. | ||||
|      */ | ||||
|     async getEmbeddedHtml(module: CoreCourseWSModule, courseId: number): Promise<string> { | ||||
|         const contents = await CoreCourse.getModuleContents(module, courseId); | ||||
| 
 | ||||
|         const result = await CoreCourseHelper.downloadModuleWithMainFileIfNeeded( | ||||
|             module, | ||||
|             courseId, | ||||
|             AddonModResourceProvider.COMPONENT, | ||||
|             module.id, | ||||
|             module.contents, | ||||
|             contents, | ||||
|         ); | ||||
| 
 | ||||
|         return CoreMimetypeUtils.getEmbeddedHtml(module.contents[0], result.path); | ||||
|         return CoreMimetypeUtils.getEmbeddedHtml(contents[0], result.path); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
| @ -61,7 +63,7 @@ export class AddonModResourceHelperProvider { | ||||
|      * @return Promise resolved with the iframe src. | ||||
|      */ | ||||
|     async getIframeSrc(module: CoreCourseWSModule): Promise<string> { | ||||
|         if (!module.contents.length) { | ||||
|         if (!module.contents?.length) { | ||||
|             throw new CoreError('No contents available in module'); | ||||
|         } | ||||
| 
 | ||||
| @ -98,15 +100,19 @@ export class AddonModResourceHelperProvider { | ||||
|     isDisplayedEmbedded(module: CoreCourseWSModule, display: number): boolean { | ||||
|         const currentSite = CoreSites.getCurrentSite(); | ||||
| 
 | ||||
|         if ((!module.contents.length && !module.contentsinfo) || | ||||
|             !CoreFile.isAvailable() || | ||||
|             (currentSite && !currentSite.isVersionGreaterEqualThan('3.7') && this.isNextcloudFile(module))) { | ||||
|         if (!CoreFile.isAvailable() || | ||||
|                 (currentSite && !currentSite.isVersionGreaterEqualThan('3.7') && this.isNextcloudFile(module))) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         const ext = module.contentsinfo | ||||
|             ? CoreMimetypeUtils.getExtension(module.contentsinfo.mimetypes[0]) | ||||
|             : CoreMimetypeUtils.getFileExtension(module.contents[0].filename); | ||||
|         let ext: string | undefined; | ||||
|         if (module.contentsinfo) { | ||||
|             ext = CoreMimetypeUtils.getExtension(module.contentsinfo.mimetypes[0]); | ||||
|         } else if (module.contents?.length) { | ||||
|             ext = CoreMimetypeUtils.getFileExtension(module.contents[0].filename); | ||||
|         } else { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return (display == CoreConstants.RESOURCELIB_DISPLAY_EMBED || display == CoreConstants.RESOURCELIB_DISPLAY_AUTO) && | ||||
|             CoreMimetypeUtils.canBeEmbedded(ext); | ||||
| @ -144,10 +150,15 @@ export class AddonModResourceHelperProvider { | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether main file is downloadable. | ||||
|      */ | ||||
|     isMainFileDownloadable(module: CoreCourseWSModule, siteId?: string): Promise<boolean> { | ||||
|     async isMainFileDownloadable(module: CoreCourseWSModule, siteId?: string): Promise<boolean> { | ||||
|         const contents = await CoreCourse.getModuleContents(module); | ||||
|         if (!contents.length) { | ||||
|             throw new CoreError(Translate.instant('core.filenotfound')); | ||||
|         } | ||||
| 
 | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         const mainFile = module.contents[0]; | ||||
|         const mainFile = contents[0]; | ||||
|         const timemodified = CoreFileHelper.getFileTimemodified(mainFile); | ||||
| 
 | ||||
|         return CoreFilepool.isFileDownloadable(siteId, mainFile.fileurl, timemodified); | ||||
|  | ||||
| @ -95,11 +95,19 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|                 this.displayDescription = typeof unserialized.printintro == 'undefined' || !!unserialized.printintro; | ||||
|             } | ||||
| 
 | ||||
|             // Try to load module contents, it's needed to get the URL with parameters.
 | ||||
|             await CoreCourse.loadModuleContents(this.module, this.courseId, undefined, false, refresh, undefined, 'url'); | ||||
|             // Try to get module contents, it's needed to get the URL with parameters.
 | ||||
|             const contents = await CoreCourse.getModuleContents( | ||||
|                 this.module, | ||||
|                 this.courseId, | ||||
|                 undefined, | ||||
|                 false, | ||||
|                 refresh, | ||||
|                 undefined, | ||||
|                 'url', | ||||
|             ); | ||||
| 
 | ||||
|             // Always use the URL from the module because it already includes the parameters.
 | ||||
|             this.url = this.module.contents[0] && this.module.contents[0].fileurl ? this.module.contents[0].fileurl : undefined; | ||||
|             this.url = contents[0] && contents[0].fileurl ? contents[0].fileurl : undefined; | ||||
| 
 | ||||
|             await this.calculateDisplayOptions(url); | ||||
| 
 | ||||
| @ -112,12 +120,12 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo | ||||
|             this.description = mod.description; | ||||
|             this.dataRetrieved.emit(mod); | ||||
| 
 | ||||
|             if (!mod.contents.length) { | ||||
|             if (!mod.contents?.length) { | ||||
|                 // If the data was cached maybe we don't have contents. Reject.
 | ||||
|                 throw new CoreError('No contents found in module.'); | ||||
|             } | ||||
| 
 | ||||
|             this.url = mod.contents && mod.contents[0] && mod.contents[0].fileurl ? mod.contents[0].fileurl : undefined; | ||||
|             this.url = mod.contents[0].fileurl ? mod.contents[0].fileurl : undefined; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|  | ||||
| @ -77,7 +77,8 @@ export class AddonModUrlModuleHandlerService implements CoreCourseModuleHandler | ||||
|                 // Ignore errors.
 | ||||
|             } | ||||
| 
 | ||||
|             AddonModUrlHelper.open(module.contents[0].fileurl); | ||||
|             const contents = await CoreCourse.getModuleContents(module, courseId); | ||||
|             AddonModUrlHelper.open(contents[0].fileurl); | ||||
|         }; | ||||
| 
 | ||||
|         const handlerData: CoreCourseModuleHandlerData = { | ||||
| @ -141,9 +142,9 @@ export class AddonModUrlModuleHandlerService implements CoreCourseModuleHandler | ||||
|      */ | ||||
|     protected async hideLinkButton(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> { | ||||
|         try { | ||||
|             await CoreCourse.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName); | ||||
|             const contents = await CoreCourse.getModuleContents(module, courseId, undefined, false, false, undefined, this.modName); | ||||
| 
 | ||||
|             return !(module.contents && module.contents[0] && module.contents[0].fileurl); | ||||
|             return !(contents[0] && contents[0].fileurl); | ||||
|         } catch { | ||||
|             // Module contents could not be loaded, most probably device is offline.
 | ||||
|             return true; | ||||
| @ -166,11 +167,10 @@ export class AddonModUrlModuleHandlerService implements CoreCourseModuleHandler | ||||
|      */ | ||||
|     protected async shouldOpenLink(module: CoreCourseModule, courseId: number): Promise<boolean> { | ||||
|         try { | ||||
|             // First of all, make sure module contents are loaded.
 | ||||
|             await CoreCourse.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName); | ||||
|             const contents = await CoreCourse.getModuleContents(module, courseId, undefined, false, false, undefined, this.modName); | ||||
| 
 | ||||
|             // Check if the URL can be handled by the app. If so, always open it directly.
 | ||||
|             const canHandle = await CoreContentLinksHelper.canHandleLink(module.contents[0].fileurl, courseId, undefined, true); | ||||
|             const canHandle = await CoreContentLinksHelper.canHandleLink(contents[0].fileurl, courseId, undefined, true); | ||||
| 
 | ||||
|             if (canHandle) { | ||||
|                 // URL handled by the app, open it directly.
 | ||||
|  | ||||
| @ -100,6 +100,9 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|         this.showCompletion = !!CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.11'); | ||||
| 
 | ||||
|         if (this.showCompletion) { | ||||
|             CoreCourseHelper.calculateModuleCompletionData(this.module, this.courseId); | ||||
|             CoreCourseHelper.loadModuleOfflineCompletion(this.courseId, this.module); | ||||
| 
 | ||||
|             this.completionObserver = CoreEvents.on(CoreEvents.COMPLETION_MODULE_VIEWED, async (data) => { | ||||
|                 if (data && data.cmId == this.module.id) { | ||||
|                     await CoreCourse.invalidateModule(this.module.id); | ||||
| @ -387,7 +390,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (!this.module.contents.length || (refresh && !contentsAlreadyLoaded)) { | ||||
|         if (!this.module.contents?.length || (refresh && !contentsAlreadyLoaded)) { | ||||
|             // Try to load the contents.
 | ||||
|             const ignoreCache = refresh && CoreApp.isOnline(); | ||||
| 
 | ||||
|  | ||||
| @ -730,13 +730,11 @@ export class CoreCourseHelperProvider { | ||||
|         siteId = siteId || CoreSites.getCurrentSiteId(); | ||||
| 
 | ||||
|         if (!files || !files.length) { | ||||
|             // Make sure that module contents are loaded.
 | ||||
|             await CoreCourse.loadModuleContents(module, courseId); | ||||
| 
 | ||||
|             files = module.contents; | ||||
|             // Try to use module contents.
 | ||||
|             files = await CoreCourse.getModuleContents(module, courseId); | ||||
|         } | ||||
| 
 | ||||
|         if (!files || !files.length) { | ||||
|         if (!files.length) { | ||||
|             throw new CoreError(Translate.instant('core.filenotfound')); | ||||
|         } | ||||
| 
 | ||||
| @ -1033,7 +1031,7 @@ export class CoreCourseHelperProvider { | ||||
|         } | ||||
| 
 | ||||
|         // There's no prefetch handler for the module, just download the files.
 | ||||
|         files = files || module.contents; | ||||
|         files = files || module.contents || []; | ||||
| 
 | ||||
|         await CoreFilepool.downloadOrPrefetchFiles(siteId, files, false, false, component, componentId); | ||||
|     } | ||||
|  | ||||
| @ -885,9 +885,47 @@ export class CoreCourseProvider { | ||||
|         } | ||||
| 
 | ||||
|         const mod = await this.getModule(module.id, courseId, sectionId, preferCache, ignoreCache, siteId, modName); | ||||
| 
 | ||||
|         if (!mod.contents) { | ||||
|             throw new CoreError(Translate.instant('core.course.modulenotfound')); | ||||
|         } | ||||
| 
 | ||||
|         module.contents = mod.contents; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get module contents. If not present, this function will try to load them into module.contents. | ||||
|      * It will throw an error if contents cannot be loaded. | ||||
|      * | ||||
|      * @param module Module to get its contents. | ||||
|      * @param courseId The course ID. Recommended to speed up the process and minimize data usage. | ||||
|      * @param sectionId The section ID. | ||||
|      * @param preferCache True if shouldn't call WS if data is cached, false otherwise. | ||||
|      * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down). | ||||
|      * @param siteId Site ID. If not defined, current 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. | ||||
|      * @return Promise resolved when loaded. | ||||
|      */ | ||||
|     async getModuleContents( | ||||
|         module: CoreCourseAnyModuleData, | ||||
|         courseId?: number, | ||||
|         sectionId?: number, | ||||
|         preferCache?: boolean, | ||||
|         ignoreCache?: boolean, | ||||
|         siteId?: string, | ||||
|         modName?: string, | ||||
|     ): Promise<CoreCourseModuleContentFile[]> { | ||||
|         // Make sure contents are loaded.
 | ||||
|         await this.loadModuleContents(module, courseId, sectionId, preferCache, ignoreCache, siteId, modName); | ||||
| 
 | ||||
|         if (!module.contents) { | ||||
|             throw new CoreError(Translate.instant('core.course.modulenotfound')); | ||||
|         } | ||||
| 
 | ||||
|         return module.contents; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Report a course and section as being viewed. | ||||
|      * | ||||
| @ -1450,7 +1488,7 @@ export type CoreCourseWSModule = { | ||||
|     noviewlink?: boolean; // Whether the module has no view page.
 | ||||
|     completion?: number; // Type of completion tracking: 0 means none, 1 manual, 2 automatic.
 | ||||
|     completiondata?: CoreCourseModuleWSCompletionData; // Module completion data.
 | ||||
|     contents: CoreCourseModuleContentFile[]; | ||||
|     contents?: CoreCourseModuleContentFile[]; | ||||
|     dates?: { | ||||
|         label: string; | ||||
|         timestamp: number; | ||||
| @ -1589,5 +1627,5 @@ type CoreCompletionUpdateActivityCompletionStatusManuallyWSParams = { | ||||
|  * Any of the possible module WS data. | ||||
|  */ | ||||
| export type CoreCourseAnyModuleData = CoreCourseWSModule | CoreCourseModuleBasicInfo & { | ||||
|     contents?: CoreCourseModuleContentFile[]; // Calculated in the app in loadModuleContents.
 | ||||
|     contents?: CoreCourseModuleContentFile[]; // If needed, calculated in the app in loadModuleContents.
 | ||||
| }; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user