diff --git a/src/addons/mod/book/components/index/index.ts b/src/addons/mod/book/components/index/index.ts index 6cad64def..352b6cdd0 100644 --- a/src/addons/mod/book/components/index/index.ts +++ b/src/addons/mod/book/components/index/index.ts @@ -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. diff --git a/src/addons/mod/folder/components/index/addon-mod-folder-index.html b/src/addons/mod/folder/components/index/addon-mod-folder-index.html index 17b256dea..e3b1799f4 100644 --- a/src/addons/mod/folder/components/index/addon-mod-folder-index.html +++ b/src/addons/mod/folder/components/index/addon-mod-folder-index.html @@ -34,8 +34,8 @@ contextLevel="module" [contextInstanceId]="module.id" [courseId]="courseId"> - - + + @@ -43,12 +43,12 @@ - + - diff --git a/src/addons/mod/folder/components/index/index.ts b/src/addons/mod/folder/components/index/index.ts index 3c13d7cb4..18f99216c 100644 --- a/src/addons/mod/folder/components/index/index.ts +++ b/src/addons/mod/folder/components/index/index.ts @@ -95,12 +95,13 @@ export class AddonModFolderIndexComponent extends CoreCourseModuleMainResourceCo protected async fetchContent(refresh = false): Promise { 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); } diff --git a/src/addons/mod/imscp/components/index/index.ts b/src/addons/mod/imscp/components/index/index.ts index 6941f8acc..b905e10e8 100644 --- a/src/addons/mod/imscp/components/index/index.ts +++ b/src/addons/mod/imscp/components/index/index.ts @@ -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; diff --git a/src/addons/mod/imscp/services/imscp.ts b/src/addons/mod/imscp/services/imscp.ts index df87b4684..746cabcbc 100644 --- a/src/addons/mod/imscp/services/imscp.ts +++ b/src/addons/mod/imscp/services/imscp.ts @@ -229,8 +229,10 @@ export class AddonModImscpProvider { * @return Promise resolved with the item src. */ async getIframeSrc(module: CoreCourseModule, itemHref?: string): Promise { + 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); diff --git a/src/addons/mod/page/components/index/index.ts b/src/addons/mod/page/components/index/index.ts index f3034ce73..3017d9bf1 100644 --- a/src/addons/mod/page/components/index/index.ts +++ b/src/addons/mod/page/components/index/index.ts @@ -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]; diff --git a/src/addons/mod/resource/components/index/index.ts b/src/addons/mod/resource/components/index/index.ts index 90d47fa39..5a0d8a430 100644 --- a/src/addons/mod/resource/components/index/index.ts +++ b/src/addons/mod/resource/components/index/index.ts @@ -101,9 +101,9 @@ export class AddonModResourceIndexComponent extends CoreCourseModuleMainResource */ protected async fetchContent(refresh?: boolean): Promise { // 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); } diff --git a/src/addons/mod/resource/services/resource-helper.ts b/src/addons/mod/resource/services/resource-helper.ts index f8ae97537..b942f7e36 100644 --- a/src/addons/mod/resource/services/resource-helper.ts +++ b/src/addons/mod/resource/services/resource-helper.ts @@ -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 { + 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 { - 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 { + async isMainFileDownloadable(module: CoreCourseWSModule, siteId?: string): Promise { + 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); diff --git a/src/addons/mod/url/components/index/index.ts b/src/addons/mod/url/components/index/index.ts index dddcee782..e92c6541e 100644 --- a/src/addons/mod/url/components/index/index.ts +++ b/src/addons/mod/url/components/index/index.ts @@ -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; } } diff --git a/src/addons/mod/url/services/handlers/module.ts b/src/addons/mod/url/services/handlers/module.ts index 08ed9d915..642dc208a 100644 --- a/src/addons/mod/url/services/handlers/module.ts +++ b/src/addons/mod/url/services/handlers/module.ts @@ -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 { 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 { 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. diff --git a/src/core/features/course/classes/main-resource-component.ts b/src/core/features/course/classes/main-resource-component.ts index 856ac0375..e01d48d86 100644 --- a/src/core/features/course/classes/main-resource-component.ts +++ b/src/core/features/course/classes/main-resource-component.ts @@ -390,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(); diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index 14b7f8241..8e62b143e 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -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); } diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index e115afb9d..4a0752ff5 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -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 { + // 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. };