diff --git a/src/addon/filter/displayh5p/providers/handler.ts b/src/addon/filter/displayh5p/providers/handler.ts index 3956d3299..abbfdb957 100644 --- a/src/addon/filter/displayh5p/providers/handler.ts +++ b/src/addon/filter/displayh5p/providers/handler.ts @@ -69,11 +69,14 @@ export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { * @param filter The filter. * @param options Options passed to the filters. * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, - viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise { const placeholders = Array.from(container.querySelectorAll('div.core-h5p-tmp-placeholder')); @@ -85,6 +88,8 @@ export class AddonFilterDisplayH5PHandler extends CoreFilterDefaultHandler { componentRef = viewContainerRef.createComponent(factory); componentRef.instance.src = url; + componentRef.instance.component = component; + componentRef.instance.componentId = componentId; // Move the component to its right position. placeholder.parentElement.replaceChild(componentRef.instance.elementRef.nativeElement, placeholder); diff --git a/src/addon/filter/mathjaxloader/providers/handler.ts b/src/addon/filter/mathjaxloader/providers/handler.ts index e1d514e05..0ef005bc7 100644 --- a/src/addon/filter/mathjaxloader/providers/handler.ts +++ b/src/addon/filter/mathjaxloader/providers/handler.ts @@ -162,11 +162,14 @@ export class AddonFilterMathJaxLoaderHandler extends CoreFilterDefaultHandler { * @param filter The filter. * @param options Options passed to the filters. * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, - viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise { return this.waitForReady().then(() => { this.window.M.filter_mathjaxloader.typeset(container); diff --git a/src/addon/mod/assign/providers/prefetch-handler.ts b/src/addon/mod/assign/providers/prefetch-handler.ts index 9552032af..56d0b4ba0 100644 --- a/src/addon/mod/assign/providers/prefetch-handler.ts +++ b/src/addon/mod/assign/providers/prefetch-handler.ts @@ -32,6 +32,7 @@ import { AddonModAssignSyncProvider } from './assign-sync'; import { AddonModAssignFeedbackDelegate } from './feedback-delegate'; import { AddonModAssignSubmissionDelegate } from './submission-delegate'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch assigns. @@ -51,6 +52,7 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected assignProvider: AddonModAssignProvider, protected textUtils: CoreTextUtilsProvider, protected feedbackDelegate: AddonModAssignFeedbackDelegate, @@ -62,7 +64,8 @@ export class AddonModAssignPrefetchHandler extends CoreCourseActivityPrefetchHan protected assignHelper: AddonModAssignHelperProvider, protected syncProvider: AddonModAssignSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/book/providers/prefetch-handler.ts b/src/addon/mod/book/providers/prefetch-handler.ts index 22c6bf8bb..d4eb5903a 100644 --- a/src/addon/mod/book/providers/prefetch-handler.ts +++ b/src/addon/mod/book/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModBookProvider } from './book'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch books. @@ -42,9 +43,11 @@ export class AddonModBookPrefetchHandler extends CoreCourseResourcePrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected bookProvider: AddonModBookProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/chat/providers/prefetch-handler.ts b/src/addon/mod/chat/providers/prefetch-handler.ts index 556d34982..ecf8cde63 100644 --- a/src/addon/mod/chat/providers/prefetch-handler.ts +++ b/src/addon/mod/chat/providers/prefetch-handler.ts @@ -25,6 +25,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModChatProvider, AddonModChatChat } from './chat'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch chats. @@ -43,11 +44,13 @@ export class AddonModChatPrefetchHandler extends CoreCourseActivityPrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, private groupsProvider: CoreGroupsProvider, private userProvider: CoreUserProvider, private chatProvider: AddonModChatProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/choice/providers/prefetch-handler.ts b/src/addon/mod/choice/providers/prefetch-handler.ts index cbe113b6a..656ab5efc 100644 --- a/src/addon/mod/choice/providers/prefetch-handler.ts +++ b/src/addon/mod/choice/providers/prefetch-handler.ts @@ -25,6 +25,7 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModChoiceSyncProvider } from './sync'; import { AddonModChoiceProvider } from './choice'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch choices. @@ -46,11 +47,13 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected choiceProvider: AddonModChoiceProvider, protected userProvider: CoreUserProvider, protected injector: Injector) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/data/providers/prefetch-handler.ts b/src/addon/mod/data/providers/prefetch-handler.ts index 63d531b8a..ecdf8927a 100644 --- a/src/addon/mod/data/providers/prefetch-handler.ts +++ b/src/addon/mod/data/providers/prefetch-handler.ts @@ -28,6 +28,7 @@ import { AddonModDataProvider, AddonModDataEntry } from './data'; import { AddonModDataSyncProvider } from './sync'; import { AddonModDataHelperProvider } from './helper'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch databases. @@ -47,6 +48,7 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected dataProvider: AddonModDataProvider, protected timeUtils: CoreTimeUtilsProvider, protected dataHelper: AddonModDataHelperProvider, @@ -54,7 +56,8 @@ export class AddonModDataPrefetchHandler extends CoreCourseActivityPrefetchHandl protected commentsProvider: CoreCommentsProvider, protected syncProvider: AddonModDataSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/feedback/providers/prefetch-handler.ts b/src/addon/mod/feedback/providers/prefetch-handler.ts index 2aed13f52..01c846d74 100644 --- a/src/addon/mod/feedback/providers/prefetch-handler.ts +++ b/src/addon/mod/feedback/providers/prefetch-handler.ts @@ -27,6 +27,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreGroupsProvider } from '@providers/groups'; import { AddonModFeedbackSyncProvider } from './sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch feedbacks. @@ -48,13 +49,15 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected feedbackProvider: AddonModFeedbackProvider, protected feedbackHelper: AddonModFeedbackHelperProvider, protected timeUtils: CoreTimeUtilsProvider, protected groupsProvider: CoreGroupsProvider, protected injector: Injector) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/folder/providers/prefetch-handler.ts b/src/addon/mod/folder/providers/prefetch-handler.ts index 3ad1f329e..9a5dfc307 100644 --- a/src/addon/mod/folder/providers/prefetch-handler.ts +++ b/src/addon/mod/folder/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModFolderProvider } from './folder'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch folders. @@ -41,9 +42,11 @@ export class AddonModFolderPrefetchHandler extends CoreCourseResourcePrefetchHan sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected folderProvider: AddonModFolderProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/forum/providers/prefetch-handler.ts b/src/addon/mod/forum/providers/prefetch-handler.ts index f9ab412c2..2f1e05b5d 100644 --- a/src/addon/mod/forum/providers/prefetch-handler.ts +++ b/src/addon/mod/forum/providers/prefetch-handler.ts @@ -26,6 +26,7 @@ import { CoreGroupsProvider } from '@providers/groups'; import { AddonModForumProvider } from './forum'; import { AddonModForumSyncProvider } from './sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch forums. @@ -45,12 +46,14 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, private userProvider: CoreUserProvider, private groupsProvider: CoreGroupsProvider, private forumProvider: AddonModForumProvider, private syncProvider: AddonModForumSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts index 3051f1467..34a94b142 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -25,6 +25,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { AddonModGlossaryProvider } from './glossary'; import { AddonModGlossarySyncProvider } from './sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch forums. @@ -44,11 +45,13 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected glossaryProvider: AddonModGlossaryProvider, protected commentsProvider: CoreCommentsProvider, protected syncProvider: AddonModGlossarySyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/imscp/providers/prefetch-handler.ts b/src/addon/mod/imscp/providers/prefetch-handler.ts index 0b9c5b57e..6fe5ab2b9 100644 --- a/src/addon/mod/imscp/providers/prefetch-handler.ts +++ b/src/addon/mod/imscp/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModImscpProvider } from './imscp'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch IMSCPs. @@ -41,9 +42,11 @@ export class AddonModImscpPrefetchHandler extends CoreCourseResourcePrefetchHand sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected imscpProvider: AddonModImscpProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/label/providers/prefetch-handler.ts b/src/addon/mod/label/providers/prefetch-handler.ts index 140c23538..e05f9ac0c 100644 --- a/src/addon/mod/label/providers/prefetch-handler.ts +++ b/src/addon/mod/label/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModLabelProvider } from './label'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch labels. @@ -43,9 +44,11 @@ export class AddonModLabelPrefetchHandler extends CoreCourseResourcePrefetchHand sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected labelProvider: AddonModLabelProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/lesson/providers/prefetch-handler.ts b/src/addon/mod/lesson/providers/prefetch-handler.ts index 834845d47..f0732535e 100644 --- a/src/addon/mod/lesson/providers/prefetch-handler.ts +++ b/src/addon/mod/lesson/providers/prefetch-handler.ts @@ -26,6 +26,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { AddonModLessonProvider } from './lesson'; import { AddonModLessonSyncProvider } from './lesson-sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch lessons. @@ -48,12 +49,14 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected modalCtrl: ModalController, protected groupsProvider: CoreGroupsProvider, protected lessonProvider: AddonModLessonProvider, protected injector: Injector) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** @@ -108,7 +111,9 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan let files = lesson.mediafiles || []; files = files.concat(this.getIntroFilesFromInstance(module, lesson)); - result = this.utils.sumFileSizes(files); + return this.pluginFileDelegate.getFilesSize(files); + }).then((res) => { + result = res; // Get the pages to calculate the size. return this.lessonProvider.getPages(lesson.id, password, false, false, siteId); diff --git a/src/addon/mod/lti/providers/prefetch-handler.ts b/src/addon/mod/lti/providers/prefetch-handler.ts index 14d3c2584..d2b02d014 100644 --- a/src/addon/mod/lti/providers/prefetch-handler.ts +++ b/src/addon/mod/lti/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { AddonModLtiProvider } from './lti'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch LTIs. LTIs cannot be prefetched, but the handler will be used to invalidate some data on course PTR. @@ -41,9 +42,11 @@ export class AddonModLtiPrefetchHandler extends CoreCourseActivityPrefetchHandle sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected ltiProvider: AddonModLtiProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/page/providers/prefetch-handler.ts b/src/addon/mod/page/providers/prefetch-handler.ts index 982b1d45b..5f72b882f 100644 --- a/src/addon/mod/page/providers/prefetch-handler.ts +++ b/src/addon/mod/page/providers/prefetch-handler.ts @@ -24,6 +24,7 @@ import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/reso import { AddonModPageProvider } from './page'; import { AddonModPageHelperProvider } from './helper'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch pages. @@ -43,10 +44,12 @@ export class AddonModPagePrefetchHandler extends CoreCourseResourcePrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected pageProvider: AddonModPageProvider, protected pageHelper: AddonModPageHelperProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/quiz/providers/prefetch-handler.ts b/src/addon/mod/quiz/providers/prefetch-handler.ts index 1874ce516..fdd66c7e5 100644 --- a/src/addon/mod/quiz/providers/prefetch-handler.ts +++ b/src/addon/mod/quiz/providers/prefetch-handler.ts @@ -29,6 +29,7 @@ import { AddonModQuizAccessRuleDelegate } from './access-rules-delegate'; import { AddonModQuizSyncProvider } from './quiz-sync'; import { CoreConstants } from '@core/constants'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch quizzes. @@ -50,6 +51,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected injector: Injector, protected quizProvider: AddonModQuizProvider, protected textUtils: CoreTextUtilsProvider, @@ -57,7 +59,8 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl protected accessRuleDelegate: AddonModQuizAccessRuleDelegate, protected questionHelper: CoreQuestionHelperProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/resource/providers/prefetch-handler.ts b/src/addon/mod/resource/providers/prefetch-handler.ts index 2f9bb6215..27c52989d 100644 --- a/src/addon/mod/resource/providers/prefetch-handler.ts +++ b/src/addon/mod/resource/providers/prefetch-handler.ts @@ -25,6 +25,7 @@ import { AddonModResourceProvider } from './resource'; import { AddonModResourceHelperProvider } from './helper'; import { CoreConstants } from '@core/constants'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch resources. @@ -43,10 +44,12 @@ export class AddonModResourcePrefetchHandler extends CoreCourseResourcePrefetchH sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected resourceProvider: AddonModResourceProvider, protected resourceHelper: AddonModResourceHelperProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/scorm/providers/prefetch-handler.ts b/src/addon/mod/scorm/providers/prefetch-handler.ts index fd2d34b6e..7c6dcccde 100644 --- a/src/addon/mod/scorm/providers/prefetch-handler.ts +++ b/src/addon/mod/scorm/providers/prefetch-handler.ts @@ -26,6 +26,7 @@ import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/acti import { AddonModScormProvider } from './scorm'; import { AddonModScormSyncProvider } from './scorm-sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Progress event used when downloading a SCORM. @@ -67,12 +68,14 @@ export class AddonModScormPrefetchHandler extends CoreCourseActivityPrefetchHand sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected fileProvider: CoreFileProvider, protected textUtils: CoreTextUtilsProvider, protected scormProvider: AddonModScormProvider, protected injector: Injector) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/survey/providers/prefetch-handler.ts b/src/addon/mod/survey/providers/prefetch-handler.ts index faa894364..026d00c0e 100644 --- a/src/addon/mod/survey/providers/prefetch-handler.ts +++ b/src/addon/mod/survey/providers/prefetch-handler.ts @@ -25,6 +25,7 @@ import { AddonModSurveyProvider } from './survey'; import { AddonModSurveySyncProvider } from './sync'; import { AddonModSurveyHelperProvider } from './helper'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch surveys. @@ -46,11 +47,13 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected surveyProvider: AddonModSurveyProvider, protected surveyHelper: AddonModSurveyHelperProvider, protected injector: Injector) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/url/providers/prefetch-handler.ts b/src/addon/mod/url/providers/prefetch-handler.ts index f904fc429..0063f8a25 100644 --- a/src/addon/mod/url/providers/prefetch-handler.ts +++ b/src/addon/mod/url/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseResourcePrefetchHandlerBase } from '@core/course/classes/resource-prefetch-handler'; import { AddonModUrlProvider } from './url'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch URLs. URLs cannot be prefetched, but the handler will be used to invalidate some data on course PTR. @@ -40,9 +41,11 @@ export class AddonModUrlPrefetchHandler extends CoreCourseResourcePrefetchHandle filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, - filterHelper: CoreFilterHelperProvider) { + filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/addon/mod/wiki/providers/prefetch-handler.ts b/src/addon/mod/wiki/providers/prefetch-handler.ts index 918ad0441..cfec49818 100644 --- a/src/addon/mod/wiki/providers/prefetch-handler.ts +++ b/src/addon/mod/wiki/providers/prefetch-handler.ts @@ -29,6 +29,7 @@ import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModWikiProvider } from './wiki'; import { AddonModWikiSyncProvider } from './wiki-sync'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch wikis. @@ -48,6 +49,7 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected wikiProvider: AddonModWikiProvider, protected userProvider: CoreUserProvider, protected textUtils: CoreTextUtilsProvider, @@ -56,7 +58,8 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl protected gradesHelper: CoreGradesHelperProvider, protected syncProvider: AddonModWikiSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** @@ -96,7 +99,7 @@ export class AddonModWikiPrefetchHandler extends CoreCourseActivityPrefetchHandl siteId = this.sitesProvider.getCurrentSiteId(); promises.push(this.getFiles(module, courseId, single, siteId).then((files) => { - return this.utils.sumFileSizes(files); + return this.pluginFileDelegate.getFilesSize(files); })); promises.push(this.getAllPages(module, courseId, false, true, siteId).then((pages) => { diff --git a/src/addon/mod/workshop/providers/prefetch-handler.ts b/src/addon/mod/workshop/providers/prefetch-handler.ts index 0b7b47eee..cf7b99d42 100644 --- a/src/addon/mod/workshop/providers/prefetch-handler.ts +++ b/src/addon/mod/workshop/providers/prefetch-handler.ts @@ -27,6 +27,7 @@ import { AddonModWorkshopProvider } from './workshop'; import { AddonModWorkshopSyncProvider } from './sync'; import { AddonModWorkshopHelperProvider } from './helper'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch workshops. @@ -47,13 +48,15 @@ export class AddonModWorkshopPrefetchHandler extends CoreCourseActivityPrefetchH sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, private groupsProvider: CoreGroupsProvider, private userProvider: CoreUserProvider, private workshopProvider: AddonModWorkshopProvider, private workshopHelper: AddonModWorkshopHelperProvider, private syncProvider: AddonModWorkshopSyncProvider) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); } /** diff --git a/src/components/file/file.ts b/src/components/file/file.ts index a1c97cf3e..b8e7a7965 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -23,6 +23,7 @@ import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreConstants } from '@core/constants'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Component to handle a remote file. Shows the file name, icon (depending on mimetype) and a button @@ -56,10 +57,16 @@ export class CoreFileComponent implements OnInit, OnDestroy { protected timemodified: number; protected observer; - constructor(private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, private domUtils: CoreDomUtilsProvider, - private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider, - private fileHelper: CoreFileHelperProvider, private mimeUtils: CoreMimetypeUtilsProvider, - private eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider) { + constructor(private sitesProvider: CoreSitesProvider, + private utils: CoreUtilsProvider, + private domUtils: CoreDomUtilsProvider, + private filepoolProvider: CoreFilepoolProvider, + private appProvider: CoreAppProvider, + private fileHelper: CoreFileHelperProvider, + private mimeUtils: CoreMimetypeUtilsProvider, + private eventsProvider: CoreEventsProvider, + private textUtils: CoreTextUtilsProvider, + private pluginFileDelegate: CorePluginFileDelegate) { this.onDelete = new EventEmitter(); } @@ -141,8 +148,6 @@ export class CoreFileComponent implements OnInit, OnDestroy { e && e.preventDefault(); e && e.stopPropagation(); - let promise; - if (this.isDownloading && !openAfterDownload) { return; } @@ -177,20 +182,26 @@ export class CoreFileComponent implements OnInit, OnDestroy { }); } else { // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big. - promise = this.fileSize ? this.domUtils.confirmDownloadSize({ size: this.fileSize, total: true }) : Promise.resolve(); - promise.then(() => { - // User confirmed, add the file to queue. - return this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { - this.isDownloading = true; + this.pluginFileDelegate.getFileSize({fileurl: this.fileUrl, filesize: this.fileSize}, this.siteId).then((size) => { - this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, - this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); - this.calculateState(); - }); + const promise = size ? this.domUtils.confirmDownloadSize({ size: size, total: true }) : Promise.resolve(); + + return promise.then(() => { + // User confirmed, add the file to queue. + return this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { + this.isDownloading = true; + + this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, + this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + this.calculateState(); + }); + }); + }).catch(() => { + // User cancelled. }); - }).catch(() => { - // Ignore error. + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); }); } } diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index 2e344104a..0448dcd33 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -21,6 +21,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseProvider } from '../providers/course'; import { CoreCourseModulePrefetchHandler } from '../providers/module-prefetch-delegate'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. Prefetch handlers should inherit either @@ -67,7 +68,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref protected filepoolProvider: CoreFilepoolProvider, protected sitesProvider: CoreSitesProvider, protected domUtils: CoreDomUtilsProvider, - protected filterHelper: CoreFilterHelperProvider) { } + protected filterHelper: CoreFilterHelperProvider, + protected pluginFileDelegate: CorePluginFileDelegate) { } /** * Add an ongoing download to the downloadPromises list. When the promise finishes it will be removed. @@ -137,7 +139,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref */ getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { return this.getFiles(module, courseId).then((files) => { - return this.utils.sumFileSizes(files); + return this.pluginFileDelegate.getFilesSize(files); }).catch(() => { return { size: -1, total: false }; }); diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts index 8b86c3028..f5f307500 100644 --- a/src/core/filter/providers/default-filter.ts +++ b/src/core/filter/providers/default-filter.ts @@ -51,11 +51,14 @@ export class CoreFilterDefaultHandler implements CoreFilterHandler { * @param filter The filter. * @param options Options passed to the filters. * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ handleHtml(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, - viewContainerRef: ViewContainerRef, siteId?: string): void | Promise { + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise { // To be overridden. } diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 9b6184536..d712ffb31 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -49,11 +49,14 @@ export interface CoreFilterHandler extends CoreDelegateHandler { * @param filter The filter. * @param options Options passed to the filters. * @param viewContainerRef The ViewContainerRef where the container is. + * @param component Component. + * @param componentId Component ID. * @param siteId Site ID. If not defined, current site. * @return If async, promise resolved when done. */ handleHtml?(container: HTMLElement, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, - viewContainerRef: ViewContainerRef, siteId?: string): void | Promise; + viewContainerRef: ViewContainerRef, component?: string, componentId?: string | number, siteId?: string) + : void | Promise; /** * Check if the filter should be applied in a certain site based on some filter options. @@ -160,11 +163,13 @@ export class CoreFilterDelegate extends CoreDelegate { * @param viewContainerRef The ViewContainerRef where the container is. * @param options Options passed to the filters. * @param skipFilters Names of filters that shouldn't be applied. + * @param component Component. + * @param componentId Component ID. * @param siteId Site ID. If not defined, current site. * @return Promise resolved when done. */ handleHtml(container: HTMLElement, filters: CoreFilterFilter[], viewContainerRef?: ViewContainerRef, options?: any, - skipFilters?: string[], siteId?: string): Promise { + skipFilters?: string[], component?: string, componentId?: string | number, siteId?: string): Promise { // Wait for filters to be initialized. return this.handlersInitPromise.then(() => { @@ -184,7 +189,7 @@ export class CoreFilterDelegate extends CoreDelegate { promise = promise.then(() => { return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'handleHtml', - [container, filter, options, viewContainerRef, siteId])).catch((error) => { + [container, filter, options, viewContainerRef, component, componentId, siteId])).catch((error) => { this.logger.error('Error handling HTML' + filter.filter, error); }); }); diff --git a/src/core/h5p/components/h5p-player/core-h5p-player.html b/src/core/h5p/components/h5p-player/core-h5p-player.html index af48001df..2fb1e9989 100644 --- a/src/core/h5p/components/h5p-player/core-h5p-player.html +++ b/src/core/h5p/components/h5p-player/core-h5p-player.html @@ -1,11 +1,9 @@
- - - -
{{ errorMessage }}
+
diff --git a/src/core/h5p/components/h5p-player/h5p-player.scss b/src/core/h5p/components/h5p-player/h5p-player.scss index b5ba7f82c..8a4875ac9 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.scss +++ b/src/core/h5p/components/h5p-player/h5p-player.scss @@ -42,13 +42,6 @@ ion-app.app-root core-h5p-player { } } - .core-h5p-placeholder-error { - position: absolute; - width: 100%; - text-align: center; - top: 50%; - } - ion-spinner circle { stroke: $core-h5p-placeholder-text-color; } diff --git a/src/core/h5p/components/h5p-player/h5p-player.ts b/src/core/h5p/components/h5p-player/h5p-player.ts index 0c4f3e937..e3eac156f 100644 --- a/src/core/h5p/components/h5p-player/h5p-player.ts +++ b/src/core/h5p/components/h5p-player/h5p-player.ts @@ -12,12 +12,17 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, ElementRef, OnInit, SimpleChange } from '@angular/core'; +import { Component, Input, ElementRef, OnInit, OnDestroy, OnChanges, SimpleChange } from '@angular/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; +import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreH5PProvider } from '@core/h5p/providers/h5p'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Component to render an H5P package. @@ -26,23 +31,38 @@ import { CoreH5PProvider } from '@core/h5p/providers/h5p'; selector: 'core-h5p-player', templateUrl: 'core-h5p-player.html' }) -export class CoreH5PPlayerComponent implements OnInit { +export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy { @Input() src: string; // The URL of the player to display the H5P package. + @Input() component?: string; // Component. + @Input() componentId?: string | number; // Component ID to use in conjunction with the component. playerSrc: string; showPackage = false; loading = false; - status: string; + state: string; canDownload: boolean; calculating = true; - errorMessage: string; + + protected siteId: string; + protected siteCanDownload: boolean; + protected observer; + protected urlParams; constructor(public elementRef: ElementRef, protected sitesProvider: CoreSitesProvider, protected urlUtils: CoreUrlUtilsProvider, protected utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, - protected h5pProvider: CoreH5PProvider) { } + protected h5pProvider: CoreH5PProvider, + protected filepoolProvider: CoreFilepoolProvider, + protected eventsProvider: CoreEventsProvider, + protected appProvider: CoreAppProvider, + protected domUtils: CoreDomUtilsProvider, + protected pluginFileDelegate: CorePluginFileDelegate) { + + this.siteId = sitesProvider.getCurrentSiteId(); + this.siteCanDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); + } /** * Component being initialized. @@ -85,37 +105,49 @@ export class CoreH5PPlayerComponent implements OnInit { /** * Download the package. */ - download(): void { - // @TODO: Implement package download. + download(e: Event): void { + e && e.preventDefault(); + e && e.stopPropagation(); + + if (!this.appProvider.isOnline()) { + this.domUtils.showErrorModal('core.networkerrormsg', true); + + return; + } + + // Get the file size and ask the user to confirm. + this.pluginFileDelegate.getFileSize({fileurl: this.urlParams.url}, this.siteId).then((size) => { + return this.domUtils.confirmDownloadSize({ size: size, total: true }).then(() => { + + // User confirmed, add to the queue. + return this.filepoolProvider.addToQueueByUrl(this.siteId, this.urlParams.url, this.component, this.componentId); + }, () => { + // User cancelled. + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + this.calculateState(); + }); } /** * Check if the package can be downloaded. */ protected checkCanDownload(): void { - if (this.src && this.h5pProvider.canGetTrustedH5PFileInSite()) { - const params = this.urlUtils.extractUrlParams(this.src); + this.observer && this.observer.off(); + this.urlParams = this.urlUtils.extractUrlParams(this.src); - // @todo: Check if H5P offline is disabled in the site. + if (this.src && this.siteCanDownload && this.h5pProvider.canGetTrustedH5PFileInSite()) { - // Now check if the package can be played. this.calculating = true; - const options = { - frame: this.utils.isTrueOrOne(params.frame), - export: this.utils.isTrueOrOne(params.export), - embed: this.utils.isTrueOrOne(params.embed), - copyright: this.utils.isTrueOrOne(params.copyright), - }; + this.calculateState(); - this.h5pProvider.getTrustedH5PFile(params.url, options).then((file) => { - this.canDownload = true; - this.errorMessage = undefined; - }).catch((error) => { - this.canDownload = false; - this.errorMessage = this.textUtils.getErrorMessageFromError(error); - }).finally(() => { - this.calculating = false; + // Listen for changes in the state. + this.filepoolProvider.getFileEventNameByUrl(this.siteId, this.urlParams.url).then((eventName) => { + this.observer = this.eventsProvider.on(eventName, () => { + this.calculateState(); + }); }); return; @@ -123,6 +155,29 @@ export class CoreH5PPlayerComponent implements OnInit { this.calculating = false; this.canDownload = false; - this.errorMessage = undefined; + } + + /** + * Calcuñate state of the file. + * + * @param fileUrl The H5P file URL. + */ + protected calculateState(): void { + // Get the status of the file. + this.filepoolProvider.getFileStateByUrl(this.siteId, this.urlParams.url).then((state) => { + this.canDownload = true; + this.state = state; + }).catch((error) => { + this.canDownload = false; + }).finally(() => { + this.calculating = false; + }); + } + + /** + * Component destroyed. + */ + ngOnDestroy(): void { + this.observer && this.observer.off(); } } diff --git a/src/core/h5p/h5p.module.ts b/src/core/h5p/h5p.module.ts index ddc625a0b..f05f16c29 100644 --- a/src/core/h5p/h5p.module.ts +++ b/src/core/h5p/h5p.module.ts @@ -15,6 +15,8 @@ import { NgModule } from '@angular/core'; import { CoreH5PComponentsModule } from './components/components.module'; import { CoreH5PProvider } from './providers/h5p'; +import { CoreH5PPluginFileHandler } from './providers/pluginfile-handler'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; // List of providers (without handlers). export const CORE_H5P_PROVIDERS: any[] = [ @@ -27,8 +29,15 @@ export const CORE_H5P_PROVIDERS: any[] = [ CoreH5PComponentsModule ], providers: [ - CoreH5PProvider + CoreH5PProvider, + CoreH5PPluginFileHandler ], exports: [] }) -export class CoreH5PModule { } +export class CoreH5PModule { + constructor(pluginfileDelegate: CorePluginFileDelegate, + pluginfileHandler: CoreH5PPluginFileHandler) { + + pluginfileDelegate.registerHandler(pluginfileHandler); + } +} diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts index 335e5025f..557d2c3c1 100644 --- a/src/core/h5p/providers/h5p.ts +++ b/src/core/h5p/providers/h5p.ts @@ -17,6 +17,7 @@ import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; import { CoreWSExternalWarning, CoreWSExternalFile } from '@providers/ws'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; /** * Service to provide H5P functionalities. @@ -29,7 +30,8 @@ export class CoreH5PProvider { protected logger; constructor(logger: CoreLoggerProvider, - private sitesProvider: CoreSitesProvider) { + private sitesProvider: CoreSitesProvider, + private textUtils: CoreTextUtilsProvider) { this.logger = logger.getInstance('CoreFilterProvider'); } @@ -69,13 +71,15 @@ export class CoreH5PProvider { * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the file data. */ - getTrustedH5PFile(url: string, options: CoreH5PGetTrustedFileOptions, ignoreCache?: boolean, siteId?: string) + getTrustedH5PFile(url: string, options?: CoreH5PGetTrustedFileOptions, ignoreCache?: boolean, siteId?: string) : Promise { + options = options || {}; + return this.sitesProvider.getSite(siteId).then((site) => { const data = { - url: url, + url: this.treatH5PUrl(url, site.getURL()), frame: options.frame ? 1 : 0, export: options.export ? 1 : 0, embed: options.embed ? 1 : 0, @@ -124,6 +128,21 @@ export class CoreH5PProvider { return this.ROOT_CACHE_KEY + 'trustedH5PFile:'; } + /** + * Treat an H5P url before sending it to WS. + * + * @param url H5P file URL. + * @param siteUrl Site URL. + * @return Treated url. + */ + protected treatH5PUrl(url: string, siteUrl: string): string { + if (url.indexOf(this.textUtils.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) { + url = url.replace('/webservice/pluginfile', '/pluginfile'); + } + + return url; + } + /** * Invalidates all trusted H5P file WS calls. * diff --git a/src/core/h5p/providers/pluginfile-handler.ts b/src/core/h5p/providers/pluginfile-handler.ts new file mode 100644 index 000000000..b61de5b00 --- /dev/null +++ b/src/core/h5p/providers/pluginfile-handler.ts @@ -0,0 +1,121 @@ +// (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 { CoreFileProvider } from '@providers/file'; +import { CorePluginFileHandler } from '@providers/plugin-file-delegate'; +import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreH5PProvider } from './h5p'; +import { CoreWSExternalFile } from '@providers/ws'; + +/** + * Handler to treat H5P files. + */ +@Injectable() +export class CoreH5PPluginFileHandler implements CorePluginFileHandler { + name = 'CoreH5PPluginFileHandler'; + + constructor(protected urlUtils: CoreUrlUtilsProvider, + protected mimeUtils: CoreMimetypeUtilsProvider, + protected textUtils: CoreTextUtilsProvider, + protected utils: CoreUtilsProvider, + protected fileProvider: CoreFileProvider, + protected h5pProvider: CoreH5PProvider) { } + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + canDownloadFile(file: CoreWSExternalFile, siteId?: string): Promise { + return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId); + } + + /** + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * + * @param container Container where to get the URLs from. + * @return {string[]} List of URLs. + */ + getDownloadableFiles(container: HTMLElement): string[] { + const iframes = Array.from(container.querySelectorAll('iframe.h5p-iframe')); + const urls = []; + + for (let i = 0; i < iframes.length; i++) { + const params = this.urlUtils.extractUrlParams(iframes[i].src); + + if (params.url) { + urls.push(params.url); + } + } + + return urls; + } + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + return this.h5pProvider.getTrustedH5PFile(file.fileurl, {}, false, siteId).then((file) => { + return file.filesize; + }).catch((error): any => { + if (this.utils.isWebServiceError(error)) { + // WS returned an error, it means it cannot be downloaded. + return 0; + } + + return Promise.reject(error); + }); + } + + /** + * Check whether the file should be treated by this handler. It is used in functions where the component isn't used. + * + * @param file The file data. + * @return Whether the file should be treated by this handler. + */ + shouldHandleFile(file: CoreWSExternalFile): boolean { + return this.mimeUtils.guessExtensionFromUrl(file.fileurl) == 'h5p'; + } + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + // Unzip the file. + const destFolder = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, + 'h5p/' + this.mimeUtils.removeExtension(file.name)); + + return this.fileProvider.createDir(destFolder).then(() => { + return this.fileProvider.unzipFile(file.toURL(), destFolder); + }).then(() => { + // @todo: Deploy the package. + }); + } +} diff --git a/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts b/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts index e2cc1d795..d4a22f79d 100644 --- a/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts +++ b/src/core/siteplugins/classes/handlers/module-prefetch-handler.ts @@ -22,6 +22,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSitePluginsProvider } from '../../providers/siteplugins'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; /** * Handler to prefetch a module site plugin. @@ -39,13 +40,15 @@ export class CoreSitePluginsModulePrefetchHandler extends CoreCourseActivityPref sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, filterHelper: CoreFilterHelperProvider, + pluginFileDelegate: CorePluginFileDelegate, protected sitePluginsProvider: CoreSitePluginsProvider, component: string, name: string, modName: string, protected handlerSchema: any) { - super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper); + super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils, filterHelper, + pluginFileDelegate); this.component = component; this.name = name; diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index 3ec5d444f..170ccf2aa 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -32,6 +32,7 @@ import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; +import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; // Delegates import { CoreMainMenuDelegate } from '@core/mainmenu/providers/delegate'; @@ -117,7 +118,8 @@ export class CoreSitePluginsHelperProvider { private workshopAssessmentStrategyDelegate: AddonWorkshopAssessmentStrategyDelegate, private courseProvider: CoreCourseProvider, private blockDelegate: CoreBlockDelegate, - private filterHelper: CoreFilterHelperProvider) { + private filterHelper: CoreFilterHelperProvider, + private pluginFileDelegate: CorePluginFileDelegate) { this.logger = loggerProvider.getInstance('CoreSitePluginsHelperProvider'); @@ -841,7 +843,7 @@ export class CoreSitePluginsHelperProvider { // Register the prefetch handler. this.prefetchDelegate.registerHandler(new CoreSitePluginsModulePrefetchHandler(this.translate, this.appProvider, this.utils, this.courseProvider, this.filepoolProvider, this.sitesProvider, this.domUtils, this.filterHelper, - this.sitePluginsProvider, plugin.component, uniqueName, modName, handlerSchema)); + this.pluginFileDelegate, this.sitePluginsProvider, plugin.component, uniqueName, modName, handlerSchema)); } return uniqueName; diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 1ae718161..54e627c9f 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -375,7 +375,7 @@ export class CoreFormatTextDirective implements OnChanges { if (result.options.filter) { // Let filters hnadle HTML. We do it here because we don't want them to block the render of the text. this.filterDelegate.handleHtml(this.element, result.filters, this.viewContainerRef, result.options, [], - result.siteId); + this.component, this.componentId, result.siteId); } this.element.classList.remove('core-disable-media-adapt'); diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index b025fbbc0..ddea2ddbb 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -21,7 +21,7 @@ import { CoreInitDelegate } from './init'; import { CoreLoggerProvider } from './logger'; import { CorePluginFileDelegate } from './plugin-file-delegate'; import { CoreSitesProvider, CoreSiteSchema } from './sites'; -import { CoreWSProvider } from './ws'; +import { CoreWSProvider, CoreWSExternalFile } from './ws'; import { CoreDomUtilsProvider } from './utils/dom'; import { CoreMimetypeUtilsProvider } from './utils/mimetype'; import { CoreTextUtilsProvider } from './utils/text'; @@ -473,8 +473,8 @@ export class CoreFilepoolProvider { * downloading a file automatically does this. Note that this method does not check if the file exists in the pool. */ addFileLinkByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.addFileLink(siteId, fileId, component, componentId); }); @@ -605,11 +605,12 @@ export class CoreFilepoolProvider { * @param priority The priority this file should get in the queue (range 0-999). * @param options Extra options (isexternalfile, repositorytype). * @param revision File revision. If not defined, it will be calculated using the URL. + * @param alreadyFixed Whether the URL has already been fixed. * @return Resolved on success. */ addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0, - filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number) - : Promise { + filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number, + alreadyFixed?: boolean): Promise { let fileId, link, queueDeferred; @@ -623,94 +624,102 @@ export class CoreFilepoolProvider { return Promise.reject(null); } - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const primaryKey = { siteId: siteId, fileId: fileId }; + if (alreadyFixed) { + // Already fixed, if we reached here it means it can be downloaded. + return {fileurl: fileUrl}; + } else { + return this.fixPluginfileURL(siteId, fileUrl); + } + }).then((file) => { - revision = revision || this.getRevisionFromUrl(fileUrl); - fileId = this.getFileIdByUrl(fileUrl); + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; + revision = revision || this.getRevisionFromUrl(fileUrl); + fileId = this.getFileIdByUrl(fileUrl); - // Set up the component. - if (typeof component != 'undefined') { - link = { - component: component, - componentId: this.fixComponentId(componentId) - }; - } + const primaryKey = { siteId: siteId, fileId: fileId }; - // Retrieve the queue deferred now if it exists. - // This is to prevent errors if file is removed from queue while we're checking if the file is in queue. - queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); + // Set up the component. + if (typeof component != 'undefined') { + link = { + component: component, + componentId: this.fixComponentId(componentId) + }; + } - return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { - const newData: any = {}; - let foundLink = false; + // Retrieve the queue deferred now if it exists. + // This is to prevent errors if file is removed from queue while we're checking if the file is in queue. + queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); - if (entry) { - // We already have the file in queue, we update the priority and links. - if (entry.priority < priority) { - newData.priority = priority; - } - if (revision && entry.revision !== revision) { - newData.revision = revision; - } - if (timemodified && entry.timemodified !== timemodified) { - newData.timemodified = timemodified; - } - if (filePath && entry.path !== filePath) { - newData.path = filePath; - } - if (entry.isexternalfile !== options.isexternalfile && (entry.isexternalfile || options.isexternalfile)) { - newData.isexternalfile = options.isexternalfile; - } - if (entry.repositorytype !== options.repositorytype && (entry.repositorytype || options.repositorytype)) { - newData.repositorytype = options.repositorytype; - } + return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { + const newData: any = {}; + let foundLink = false; - if (link) { - // We need to add the new link if it does not exist yet. - if (entry.links && entry.links.length) { - for (const i in entry.links) { - const fileLink = entry.links[i]; - if (fileLink.component == link.component && fileLink.componentId == link.componentId) { - foundLink = true; - break; - } + if (entry) { + // We already have the file in queue, we update the priority and links. + if (entry.priority < priority) { + newData.priority = priority; + } + if (revision && entry.revision !== revision) { + newData.revision = revision; + } + if (timemodified && entry.timemodified !== timemodified) { + newData.timemodified = timemodified; + } + if (filePath && entry.path !== filePath) { + newData.path = filePath; + } + if (entry.isexternalfile !== options.isexternalfile && (entry.isexternalfile || options.isexternalfile)) { + newData.isexternalfile = options.isexternalfile; + } + if (entry.repositorytype !== options.repositorytype && (entry.repositorytype || options.repositorytype)) { + newData.repositorytype = options.repositorytype; + } + + if (link) { + // We need to add the new link if it does not exist yet. + if (entry.links && entry.links.length) { + for (const i in entry.links) { + const fileLink = entry.links[i]; + if (fileLink.component == link.component && fileLink.componentId == link.componentId) { + foundLink = true; + break; } } - - if (!foundLink) { - newData.links = entry.links || []; - newData.links.push(link); - newData.links = JSON.stringify(entry.links); - } } - if (Object.keys(newData).length) { - // Update only when required. - this.logger.debug(`Updating file ${fileId} which is already in queue`); - - return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { - return this.getQueuePromise(siteId, fileId, true, onProgress); - }); + if (!foundLink) { + newData.links = entry.links || []; + newData.links.push(link); + newData.links = JSON.stringify(entry.links); } - - this.logger.debug(`File ${fileId} already in queue and does not require update`); - if (queueDeferred) { - // If we were able to retrieve the queue deferred before, we use that one. - return queueDeferred.promise; - } else { - // Create a new deferred and return its promise. - return this.getQueuePromise(siteId, fileId, true, onProgress); - } - } else { - return this.addToQueue( - siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); } - }, () => { - // Unsure why we could not get the record, let's add to the queue anyway. + + if (Object.keys(newData).length) { + // Update only when required. + this.logger.debug(`Updating file ${fileId} which is already in queue`); + + return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { + return this.getQueuePromise(siteId, fileId, true, onProgress); + }); + } + + this.logger.debug(`File ${fileId} already in queue and does not require update`); + if (queueDeferred) { + // If we were able to retrieve the queue deferred before, we use that one. + return queueDeferred.promise; + } else { + // Create a new deferred and return its promise. + return this.getQueuePromise(siteId, fileId, true, onProgress); + } + } else { return this.addToQueue( siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); - }); + } + }, () => { + // Unsure why we could not get the record, let's add to the queue anyway. + return this.addToQueue( + siteId, fileId, fileUrl, priority, revision, timemodified, filePath, onProgress, options, link); }); }); } @@ -719,7 +728,7 @@ export class CoreFilepoolProvider { * Adds a file to the queue if the size is allowed to be downloaded. * * @param siteId The site ID. - * @param fileUrl The absolute URL to the file. + * @param fileUrl The absolute URL to the file, already fixed. * @param component The component to link the file to. * @param componentId An ID to use in conjunction with the component. * @param timemodified The time this file was modified. @@ -760,18 +769,18 @@ export class CoreFilepoolProvider { // Check if the file should be downloaded. if (sizeUnknown) { if (downloadUnknown && isWifi) { - return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, + 0, options, revision, true); } } else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) { - return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, + options, revision, true); } }); } else { // No need to check size, just add it to the queue. return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, - revision); + revision, true); } } @@ -938,7 +947,13 @@ export class CoreFilepoolProvider { return Promise.reject(null); } - return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((fileEntry) => { + let fileEntry; + + return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((entry) => { + fileEntry = entry; + + return this.pluginFileDelegate.treatDownloadedFile(fileUrl, fileEntry, siteId); + }).then(() => { const data: CoreFilepoolFileEntry = poolFileObject || {}; data.downloadTime = Date.now(); @@ -1157,8 +1172,10 @@ export class CoreFilepoolProvider { promise; if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; options = Object.assign({}, options); // Create a copy to prevent modifying the original object. options.timemodified = timemodified || 0; @@ -1313,15 +1330,24 @@ export class CoreFilepoolProvider { } /** - * Add the wstoken url and points to the correct script. + * Check whether the file can be downloaded, add the wstoken url and points to the correct script. * * @param siteId The site ID. * @param fileUrl The file URL. - * @return Resolved with fixed URL on success, rejected otherwise. + * @param timemodified The timemodified of the file. + * @return Promise resolved with the file data to use. */ - protected fixPluginfileURL(siteId: string, fileUrl: string): Promise { - return this.sitesProvider.getSite(siteId).then((site) => { - return site.checkAndFixPluginfileURL(fileUrl); + protected fixPluginfileURL(siteId: string, fileUrl: string, timemodified: number = 0): Promise { + + return this.pluginFileDelegate.canDownloadFile({fileurl: fileUrl, timemodified: timemodified}).then((file) => { + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.checkAndFixPluginfileURL(file.fileurl); + }).then((fixedUrl) => { + file.fileurl = fixedUrl; + + return file; + }); }); } @@ -1351,8 +1377,8 @@ export class CoreFilepoolProvider { */ getDirectoryUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl), + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl), filePath = this.getFilePath(siteId, fileId, ''); // No extension, the function will return a string. return this.fileProvider.getDir(filePath).then((dirEntry) => { @@ -1394,8 +1420,8 @@ export class CoreFilepoolProvider { * @return Promise resolved with event name. */ getFileEventNameByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getFileEventName(siteId, fileId); }); @@ -1490,8 +1516,8 @@ export class CoreFilepoolProvider { * @return Promise resolved with the path to the file relative to storage root. */ getFilePathByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getFilePath(siteId, fileId); }); @@ -1587,8 +1613,10 @@ export class CoreFilepoolProvider { : Promise { let fileId; - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl, timemodified).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); @@ -1618,6 +1646,8 @@ export class CoreFilepoolProvider { }); }); }); + }, () => { + return CoreConstants.NOT_DOWNLOADABLE; }); } @@ -1655,8 +1685,10 @@ export class CoreFilepoolProvider { }); }; - return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { - fileUrl = fixedUrl; + return this.fixPluginfileURL(siteId, fileUrl, timemodified).then((file) => { + + fileUrl = file.fileurl; + timemodified = file.timemodified || timemodified; revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); @@ -1779,8 +1811,8 @@ export class CoreFilepoolProvider { */ getInternalUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.getInternalUrlById(siteId, fileId); }); @@ -1843,8 +1875,8 @@ export class CoreFilepoolProvider { * @return Promise resolved with the path of the package. */ getPackageDirPathByUrl(siteId: string, url: string): Promise { - return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { - const dirName = this.getPackageDirNameByUrl(fixedUrl); + return this.fixPluginfileURL(siteId, url).then((file) => { + const dirName = this.getPackageDirNameByUrl(file.fileurl); return this.getFilePath(siteId, dirName, ''); }); @@ -1859,8 +1891,8 @@ export class CoreFilepoolProvider { */ getPackageDirUrlByUrl(siteId: string, url: string): Promise { if (this.fileProvider.isAvailable()) { - return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { - const dirName = this.getPackageDirNameByUrl(fixedUrl), + return this.fixPluginfileURL(siteId, url).then((file) => { + const dirName = this.getPackageDirNameByUrl(file.fileurl), dirPath = this.getFilePath(siteId, dirName, ''); // No extension, the function will return a string. return this.fileProvider.getDir(dirPath).then((dirEntry) => { @@ -2270,8 +2302,8 @@ export class CoreFilepoolProvider { * Please note that, if a file is stale, the user will be presented the stale file if there is no network access. */ invalidateFileByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.sitesProvider.getSiteDb(siteId).then((db) => { return db.updateRecords(this.FILES_TABLE, { stale: 1 }, { fileId: fileId }); @@ -2318,8 +2350,8 @@ export class CoreFilepoolProvider { * @param Promise resolved if file is downloading, rejected otherwise. */ isFileDownloadingByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.hasFileInQueue(siteId, fileId); }); @@ -2667,8 +2699,8 @@ export class CoreFilepoolProvider { * @return Resolved on success, rejected on failure. */ removeFileByUrl(siteId: string, fileUrl: string): Promise { - return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - const fileId = this.getFileIdByUrl(fileUrl); + return this.fixPluginfileURL(siteId, fileUrl).then((file) => { + const fileId = this.getFileIdByUrl(file.fileurl); return this.removeFileById(siteId, fileId); }); diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index b6b9851b0..b1c33e1f0 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -14,6 +14,7 @@ import { Injectable } from '@angular/core'; import { CoreLoggerProvider } from './logger'; +import { CoreWSExternalFile } from '@providers/ws'; /** * Interface that all plugin file handlers must implement. @@ -26,8 +27,9 @@ export interface CorePluginFileHandler { /** * The "component" of the handler. It should match the "component" of pluginfile URLs. + * It is used to treat revision from URLs. */ - component: string; + component?: string; /** * Return the RegExp to match the revision on pluginfile URLs. @@ -44,6 +46,51 @@ export interface CorePluginFileHandler { * @return String to remove the revision on pluginfile url. */ getComponentRevisionReplace?(args: string[]): string; + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + canDownloadFile?(file: CoreWSExternalFile, siteId?: string): Promise; + + /** + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * + * @param container Container where to get the URLs from. + * @return {string[]} List of URLs. + */ + getDownloadableFiles?(container: HTMLElement): string[]; + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize?(file: CoreWSExternalFile, siteId?: string): Promise; + + /** + * Check whether the file should be treated by this handler. It is used in functions where the component isn't used. + * + * @param file The file data. + * @return Whether the file should be treated by this handler. + */ + shouldHandleFile?(file: CoreWSExternalFile): boolean; + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile?(fileUrl: string, file: FileEntry, siteId?: string): Promise; } /** @@ -58,6 +105,39 @@ export class CorePluginFileDelegate { this.logger = logger.getInstance('CorePluginFileDelegate'); } + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + canDownloadFile(file: CoreWSExternalFile, siteId?: string): Promise { + const handler = this.getHandlerForFile(file); + + return this.canHandlerDownloadFile(file, handler, siteId); + } + + /** + * Check whether a file can be downloaded. If so, return the file to download. + * + * @param file The file data. + * @param handler The handler to use. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the file to use. Rejected if cannot download. + */ + protected canHandlerDownloadFile(file: CoreWSExternalFile, handler: CorePluginFileHandler, siteId?: string) + : Promise { + + if (handler && handler.canDownloadFile) { + return handler.canDownloadFile(file, siteId).then((newFile) => { + return newFile || file; + }); + } + + return Promise.resolve(file); + } + /** * Get the handler for a certain pluginfile url. * @@ -85,6 +165,99 @@ export class CorePluginFileDelegate { } } + /** + * Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by + * CoreDomUtilsProvider.extractDownloadableFilesFromHtml. + * + * @param container Container where to get the URLs from. + * @return List of URLs. + */ + getDownloadableFiles(container: HTMLElement): string[] { + let files = []; + + for (const component in this.handlers) { + const handler = this.handlers[component]; + + if (handler && handler.getDownloadableFiles) { + files = files.concat(handler.getDownloadableFiles(container)); + } + } + + return files; + } + + /** + * Sum the filesizes from a list of files checking if the size will be partial or totally calculated. + * + * @param files List of files to sum its filesize. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with file size and a boolean to indicate if it is the total size or only partial. + */ + getFilesSize(files: CoreWSExternalFile[], siteId?: string): Promise<{ size: number, total: boolean }> { + const promises = [], + result = { + size: 0, + total: true + }; + + files.forEach((file) => { + promises.push(this.getFileSize(file, siteId).then((size) => { + if (typeof size == 'undefined') { + // We don't have the file size, cannot calculate its total size. + result.total = false; + } else { + result.size += size; + } + })); + }); + + return Promise.all(promises).then(() => { + return result; + }); + } + + /** + * Get a file size. + * + * @param file The file data. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the size. + */ + getFileSize(file: CoreWSExternalFile, siteId?: string): Promise { + const handler = this.getHandlerForFile(file); + + // First of all check if file can be downloaded. + return this.canHandlerDownloadFile(file, handler, siteId).then((canDownload) => { + if (!canDownload) { + return 0; + } + + if (handler && handler.getFileSize) { + return handler.getFileSize(file, siteId).catch(() => { + return file.filesize; + }); + } + + return Promise.resolve(file.filesize); + }); + } + + /** + * Get a handler to treat a certain file. + * + * @param file File data. + * @return Handler. + */ + protected getHandlerForFile(file: CoreWSExternalFile): CorePluginFileHandler { + for (const component in this.handlers) { + const handler = this.handlers[component]; + + if (handler && handler.shouldHandleFile && handler.shouldHandleFile(file)) { + return handler; + } + } + } + /** * Register a handler. * @@ -92,14 +265,14 @@ export class CorePluginFileDelegate { * @return True if registered successfully, false otherwise. */ registerHandler(handler: CorePluginFileHandler): boolean { - if (typeof this.handlers[handler.component] !== 'undefined') { + if (typeof this.handlers[handler.component || handler.name] !== 'undefined') { this.logger.log(`Handler '${handler.component}' already registered`); return false; } this.logger.log(`Registered handler '${handler.component}'`); - this.handlers[handler.component] = handler; + this.handlers[handler.component || handler.name] = handler; return true; } @@ -124,4 +297,22 @@ export class CorePluginFileDelegate { return url; } + + /** + * Treat a downloaded file. + * + * @param fileUrl The file URL used to download the file. + * @param file The file entry of the downloaded file. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved when done. + */ + treatDownloadedFile(fileUrl: string, file: FileEntry, siteId?: string): Promise { + const handler = this.getHandlerForFile({fileurl: fileUrl}); + + if (handler && handler.getFileSize) { + return handler.treatDownloadedFile(fileUrl, file, siteId); + } + + return Promise.resolve(); + } } diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index c8246dd1d..37bb85907 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -22,6 +22,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; import { CoreConfigProvider } from '../config'; +import { CorePluginFileDelegate } from '../plugin-file-delegate'; import { CoreUrlUtilsProvider } from './url'; import { CoreFileProvider } from '@providers/file'; import { CoreConstants } from '@core/constants'; @@ -62,11 +63,20 @@ export class CoreDomUtilsProvider { protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous. protected displayedAlerts = {}; // To prevent duplicated alerts. - constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController, - private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, - private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, - private modalCtrl: ModalController, private sanitizer: DomSanitizer, private popoverCtrl: PopoverController, - private fileProvider: CoreFileProvider) { + constructor(private translate: TranslateService, + private loadingCtrl: LoadingController, + private toastCtrl: ToastController, + private alertCtrl: AlertController, + private textUtils: CoreTextUtilsProvider, + private appProvider: CoreAppProvider, + private platform: Platform, + private configProvider: CoreConfigProvider, + private urlUtils: CoreUrlUtilsProvider, + private modalCtrl: ModalController, + private sanitizer: DomSanitizer, + private popoverCtrl: PopoverController, + private fileProvider: CoreFileProvider, + private pluginFileDelegate: CorePluginFileDelegate) { // Check if debug messages should be displayed. configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => { @@ -252,7 +262,7 @@ export class CoreDomUtilsProvider { * @return List of file urls. */ extractDownloadableFilesFromHtml(html: string): string[] { - const urls = []; + let urls = []; let elements; const element = this.convertToElement(html); @@ -275,6 +285,9 @@ export class CoreDomUtilsProvider { } } + // Now get other files from plugin file handlers. + urls = urls.concat(this.pluginFileDelegate.getDownloadableFiles(element)); + return urls; } diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 4f71eede7..c4f051645 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -1287,6 +1287,7 @@ export class CoreUtilsProvider { * * @param files List of files to sum its filesize. * @return File size and a boolean to indicate if it is the total size or only partial. + * @deprecated since 3.8.0. Use CorePluginFileDelegate.getFilesSize instead. */ sumFileSizes(files: any[]): { size: number, total: boolean } { const result = {