From 28dc1232ad23d609f10e10261f78ccc2dac150f9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 2 May 2018 12:15:17 +0200 Subject: [PATCH 1/4] MOBILE-2333 siteplugins: Rename bootstrap to init & treat exceptions --- src/classes/site.ts | 15 ++++ .../classes/course-option-handler.ts | 4 +- .../siteplugins/classes/main-menu-handler.ts | 4 +- src/core/siteplugins/classes/user-handler.ts | 8 +-- .../course-format/course-format.html | 2 +- .../components/course-format/course-format.ts | 4 +- .../course-option/course-option.html | 2 +- .../components/course-option/course-option.ts | 4 +- .../components/module-index/module-index.html | 2 +- .../components/module-index/module-index.ts | 4 +- .../plugin-content/plugin-content.ts | 4 +- .../directives/call-ws-new-content.ts | 2 +- .../siteplugins/directives/new-content.ts | 2 +- .../pages/plugin-page/plugin-page.html | 2 +- .../pages/plugin-page/plugin-page.ts | 4 +- src/core/siteplugins/providers/helper.ts | 68 ++++++++++--------- src/core/siteplugins/providers/siteplugins.ts | 20 +++--- src/providers/utils/utils.ts | 6 +- 18 files changed, 90 insertions(+), 67 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index d01a31ac2..dc0ebd97f 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -78,6 +78,12 @@ export interface CoreSiteWSPreSets { */ getEmergencyCacheUsingCacheKey?: boolean; + /** + * If true, the cache entry will be deleted if the WS call returns an exception. + * @type {boolean} + */ + deleteCacheIfWSError?: boolean; + /** * Whether it should only be 1 entry for this cache key (all entries with same key will be deleted). * @type {boolean} @@ -633,6 +639,15 @@ export class CoreSite { return Promise.reject(error); } + if (preSets.deleteCacheIfWSError && this.utils.isWebServiceError(error)) { + // Delete the cache entry and return the entry. Don't block the user with the delete. + this.deleteFromCache(method, data, preSets).catch(() => { + // Ignore errors. + }); + + return Promise.reject(error); + } + this.logger.debug(`WS call '${method}' failed. Trying to use the emergency cache.`); preSets.omitExpires = true; preSets.getFromCache = true; diff --git a/src/core/siteplugins/classes/course-option-handler.ts b/src/core/siteplugins/classes/course-option-handler.ts index c54f049a3..66b2c5c6f 100644 --- a/src/core/siteplugins/classes/course-option-handler.ts +++ b/src/core/siteplugins/classes/course-option-handler.ts @@ -25,7 +25,7 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl priority: number; constructor(name: string, protected title: string, protected plugin: any, protected handlerSchema: any, - protected bootstrapResult: any, protected sitePluginsProvider: CoreSitePluginsProvider) { + protected initResult: any, protected sitePluginsProvider: CoreSitePluginsProvider) { super(name); this.priority = handlerSchema.priority; @@ -42,7 +42,7 @@ export class CoreSitePluginsCourseOptionHandler extends CoreSitePluginsBaseHandl */ isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { return this.sitePluginsProvider.isHandlerEnabledForCourse( - courseId, this.handlerSchema.restricttoenrolledcourses, this.bootstrapResult.restrict); + courseId, this.handlerSchema.restricttoenrolledcourses, this.initResult.restrict); } /** diff --git a/src/core/siteplugins/classes/main-menu-handler.ts b/src/core/siteplugins/classes/main-menu-handler.ts index 7be3245ac..d837d7525 100644 --- a/src/core/siteplugins/classes/main-menu-handler.ts +++ b/src/core/siteplugins/classes/main-menu-handler.ts @@ -22,7 +22,7 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i priority: number; constructor(name: string, protected title: string, protected plugin: any, protected handlerSchema: any, - protected bootstrapResult: any) { + protected initResult: any) { super(name); this.priority = handlerSchema.priority; @@ -43,7 +43,7 @@ export class CoreSitePluginsMainMenuHandler extends CoreSitePluginsBaseHandler i title: this.title, component: this.plugin.component, method: this.handlerSchema.method, - bootstrapResult: this.bootstrapResult + initResult: this.initResult } }; } diff --git a/src/core/siteplugins/classes/user-handler.ts b/src/core/siteplugins/classes/user-handler.ts index 0b003e56e..fb4851f61 100644 --- a/src/core/siteplugins/classes/user-handler.ts +++ b/src/core/siteplugins/classes/user-handler.ts @@ -38,7 +38,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle type: string; constructor(name: string, protected title: string, protected plugin: any, protected handlerSchema: any, - protected bootstrapResult: any, protected sitePluginsProvider: CoreSitePluginsProvider) { + protected initResult: any, protected sitePluginsProvider: CoreSitePluginsProvider) { super(name); this.priority = handlerSchema.priority; @@ -59,14 +59,14 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { // First check if it's enabled for the user. const enabledForUser = this.sitePluginsProvider.isHandlerEnabledForUser(user.id, this.handlerSchema.restricttocurrentuser, - this.bootstrapResult.restrict); + this.initResult.restrict); if (!enabledForUser) { return false; } // Enabled for user, check if it's enabled for the course. return this.sitePluginsProvider.isHandlerEnabledForCourse( - courseId, this.handlerSchema.restricttoenrolledcourses, this.bootstrapResult.restrict); + courseId, this.handlerSchema.restricttoenrolledcourses, this.initResult.restrict); } /** @@ -92,7 +92,7 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle courseid: courseId, userid: user.id }, - bootstrapResult: this.bootstrapResult + initResult: this.initResult }); } }; diff --git a/src/core/siteplugins/components/course-format/course-format.html b/src/core/siteplugins/components/course-format/course-format.html index b4dd6868b..cd6008506 100644 --- a/src/core/siteplugins/components/course-format/course-format.html +++ b/src/core/siteplugins/components/course-format/course-format.html @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/core/siteplugins/components/course-format/course-format.ts b/src/core/siteplugins/components/course-format/course-format.ts index 72a5972f4..964b9300f 100644 --- a/src/core/siteplugins/components/course-format/course-format.ts +++ b/src/core/siteplugins/components/course-format/course-format.ts @@ -33,7 +33,7 @@ export class CoreSitePluginsCourseFormatComponent implements OnInit { component: string; method: string; args: any; - bootstrapResult: any; + initResult: any; constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { } @@ -50,7 +50,7 @@ export class CoreSitePluginsCourseFormatComponent implements OnInit { courseid: this.course.id, downloadenabled: this.downloadEnabled }; - this.bootstrapResult = handler.bootstrapResult; + this.initResult = handler.initResult; } } } diff --git a/src/core/siteplugins/components/course-option/course-option.html b/src/core/siteplugins/components/course-option/course-option.html index fcda25ccd..b2191c305 100644 --- a/src/core/siteplugins/components/course-option/course-option.html +++ b/src/core/siteplugins/components/course-option/course-option.html @@ -2,5 +2,5 @@ - + \ No newline at end of file diff --git a/src/core/siteplugins/components/course-option/course-option.ts b/src/core/siteplugins/components/course-option/course-option.ts index b14e67b76..27725faba 100644 --- a/src/core/siteplugins/components/course-option/course-option.ts +++ b/src/core/siteplugins/components/course-option/course-option.ts @@ -32,7 +32,7 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { component: string; method: string; args: any; - bootstrapResult: any; + initResult: any; constructor(protected sitePluginsProvider: CoreSitePluginsProvider) { } @@ -48,7 +48,7 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { this.args = { courseid: this.courseId, }; - this.bootstrapResult = handler.bootstrapResult; + this.initResult = handler.initResult; } } } diff --git a/src/core/siteplugins/components/module-index/module-index.html b/src/core/siteplugins/components/module-index/module-index.html index bee280179..6e09aa864 100644 --- a/src/core/siteplugins/components/module-index/module-index.html +++ b/src/core/siteplugins/components/module-index/module-index.html @@ -9,4 +9,4 @@ - + diff --git a/src/core/siteplugins/components/module-index/module-index.ts b/src/core/siteplugins/components/module-index/module-index.ts index c3cfb5e01..d1b77d2a6 100644 --- a/src/core/siteplugins/components/module-index/module-index.ts +++ b/src/core/siteplugins/components/module-index/module-index.ts @@ -37,7 +37,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C component: string; method: string; args: any; - bootstrapResult: any; + initResult: any; // Data for context menu. externalUrl: string; @@ -69,7 +69,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C courseid: this.courseId, cmid: this.module.id }; - this.bootstrapResult = handler.bootstrapResult; + this.initResult = handler.initResult; } // Get the data for the context menu. diff --git a/src/core/siteplugins/components/plugin-content/plugin-content.ts b/src/core/siteplugins/components/plugin-content/plugin-content.ts index 0e31421ea..60e01389a 100644 --- a/src/core/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/siteplugins/components/plugin-content/plugin-content.ts @@ -28,7 +28,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { @Input() component: string; @Input() method: string; @Input() args: any; - @Input() bootstrapResult: any; // Result of the bootstrap WS call of the handler. + @Input() initResult: any; // Result of the init WS call of the handler. @Output() onContentLoaded?: EventEmitter; // Emits an event when the content is loaded. @Output() onLoadingContent?: EventEmitter; // Emits an event when starts to load the content. @@ -65,7 +65,7 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { this.content = result.templates.length ? result.templates[0].html : ''; // Load first template. this.javascript = result.javascript; this.otherData = result.otherdata; - this.jsData = this.sitePluginsProvider.createDataForJS(this.bootstrapResult, result); + this.jsData = this.sitePluginsProvider.createDataForJS(this.initResult, result); this.onContentLoaded.emit(refresh); }).catch((error) => { diff --git a/src/core/siteplugins/directives/call-ws-new-content.ts b/src/core/siteplugins/directives/call-ws-new-content.ts index 7f1346ef5..44f918f60 100644 --- a/src/core/siteplugins/directives/call-ws-new-content.ts +++ b/src/core/siteplugins/directives/call-ws-new-content.ts @@ -92,7 +92,7 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal component: this.component, method: this.method, args: args, - bootstrapResult: this.parentContent && this.parentContent.bootstrapResult + initResult: this.parentContent && this.parentContent.initResult }); } } diff --git a/src/core/siteplugins/directives/new-content.ts b/src/core/siteplugins/directives/new-content.ts index 6503ed5a2..3f7e5933d 100644 --- a/src/core/siteplugins/directives/new-content.ts +++ b/src/core/siteplugins/directives/new-content.ts @@ -89,7 +89,7 @@ export class CoreSitePluginsNewContentDirective implements OnInit { component: this.component, method: this.method, args: args, - bootstrapResult: this.parentContent && this.parentContent.bootstrapResult + initResult: this.parentContent && this.parentContent.initResult }); } }); diff --git a/src/core/siteplugins/pages/plugin-page/plugin-page.html b/src/core/siteplugins/pages/plugin-page/plugin-page.html index 33f22af85..22c2ebe07 100644 --- a/src/core/siteplugins/pages/plugin-page/plugin-page.html +++ b/src/core/siteplugins/pages/plugin-page/plugin-page.html @@ -11,5 +11,5 @@ - + diff --git a/src/core/siteplugins/pages/plugin-page/plugin-page.ts b/src/core/siteplugins/pages/plugin-page/plugin-page.ts index 38d516a47..0b02ad00a 100644 --- a/src/core/siteplugins/pages/plugin-page/plugin-page.ts +++ b/src/core/siteplugins/pages/plugin-page/plugin-page.ts @@ -32,14 +32,14 @@ export class CoreSitePluginsPluginPage { component: string; method: string; args: any; - bootstrapResult: any; + initResult: any; constructor(params: NavParams) { this.title = params.get('title'); this.component = params.get('component'); this.method = params.get('method'); this.args = params.get('args'); - this.bootstrapResult = params.get('bootstrapResult'); + this.initResult = params.get('initResult'); } /** diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index 95cffee2d..9d6fa24e5 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -89,19 +89,19 @@ export class CoreSitePluginsHelperProvider { } /** - * Bootstrap a handler if it has some bootstrap method. + * Execute a handler's init method if it has any. * * @param {any} plugin Data of the plugin. * @param {any} handlerSchema Data about the handler. * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by - * the bootstrap JS (if any). + * the init JS (if any). */ - protected bootstrapHandler(plugin: any, handlerSchema: any): Promise { - if (!handlerSchema.bootstrap) { + protected executeHandlerInit(plugin: any, handlerSchema: any): Promise { + if (!handlerSchema.init) { return Promise.resolve({}); } - return this.executeMethodAndJS(plugin, handlerSchema.bootstrap); + return this.executeMethodAndJS(plugin, handlerSchema.init, true); } /** @@ -109,12 +109,16 @@ export class CoreSitePluginsHelperProvider { * * @param {any} plugin Data of the plugin. * @param {string} method The method to call. + * @param {boolean} [isInit] Whether it's the init method. * @return {Promise} Promise resolved when done. It returns the results of the getContent call and the data returned by * the JS (if any). */ - protected executeMethodAndJS(plugin: any, method: string): Promise { + protected executeMethodAndJS(plugin: any, method: string, isInit?: boolean): Promise { const siteId = this.sitesProvider.getCurrentSiteId(), - preSets = {getFromCache: false}; // Try to ignore cache. + preSets = { + getFromCache: false, // Try to ignore cache. + deleteCacheIfWSError: isInit // If the init WS call returns an exception we won't use cached data. + }; return this.sitePluginsProvider.getContent(plugin.component, method, {}, preSets).then((result) => { if (!result.javascript || this.sitesProvider.getCurrentSiteId() != siteId) { @@ -290,8 +294,8 @@ export class CoreSitePluginsHelperProvider { */ registerHandler(plugin: any, handlerName: string, handlerSchema: any): Promise { - // Wait for the bootstrap JS to be executed. - return this.bootstrapHandler(plugin, handlerSchema).then((result) => { + // Wait for the init JS to be executed. + return this.executeHandlerInit(plugin, handlerSchema).then((result) => { let promise; switch (handlerSchema.delegate) { @@ -331,12 +335,12 @@ export class CoreSitePluginsHelperProvider { plugin: plugin, handlerName: handlerName, handlerSchema: handlerSchema, - bootstrapResult: result + initResult: result }); } }); }).catch((err) => { - this.logger.error('Error executing bootstrap method', handlerSchema.bootstrap, err); + this.logger.error('Error executing init method', handlerSchema.init, err); }); } @@ -346,11 +350,11 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string} A string to identify the handler. */ - protected registerCourseFormatHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any): string { - this.logger.debug('Register site plugin in course format delegate:', plugin, handlerSchema, bootstrapResult); + protected registerCourseFormatHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { + this.logger.debug('Register site plugin in course format delegate:', plugin, handlerSchema, initResult); // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), @@ -366,10 +370,10 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string} A string to identify the handler. */ - protected registerCourseOptionHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any): string { + protected registerCourseOptionHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { // Required data not provided, stop. this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema); @@ -377,14 +381,14 @@ export class CoreSitePluginsHelperProvider { return; } - this.logger.debug('Register site plugin in course option delegate:', plugin, handlerSchema, bootstrapResult); + this.logger.debug('Register site plugin in course option delegate:', plugin, handlerSchema, initResult); // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title); this.courseOptionsDelegate.registerHandler(new CoreSitePluginsCourseOptionHandler(uniqueName, prefixedTitle, plugin, - handlerSchema, bootstrapResult, this.sitePluginsProvider)); + handlerSchema, initResult, this.sitePluginsProvider)); return uniqueName; } @@ -395,10 +399,10 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string} A string to identify the handler. */ - protected registerMainMenuHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any): string { + protected registerMainMenuHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { // Required data not provided, stop. this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema); @@ -406,14 +410,14 @@ export class CoreSitePluginsHelperProvider { return; } - this.logger.debug('Register site plugin in main menu delegate:', plugin, handlerSchema, bootstrapResult); + this.logger.debug('Register site plugin in main menu delegate:', plugin, handlerSchema, initResult); // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title); this.mainMenuDelegate.registerHandler( - new CoreSitePluginsMainMenuHandler(uniqueName, prefixedTitle, plugin, handlerSchema, bootstrapResult)); + new CoreSitePluginsMainMenuHandler(uniqueName, prefixedTitle, plugin, handlerSchema, initResult)); return uniqueName; } @@ -424,10 +428,10 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string} A string to identify the handler. */ - protected registerModuleHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any): string { + protected registerModuleHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { // Required data not provided, stop. this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema); @@ -435,7 +439,7 @@ export class CoreSitePluginsHelperProvider { return; } - this.logger.debug('Register site plugin in module delegate:', plugin, handlerSchema, bootstrapResult); + this.logger.debug('Register site plugin in module delegate:', plugin, handlerSchema, initResult); // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), @@ -458,10 +462,10 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string} A string to identify the handler. */ - protected registerUserProfileHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any): string { + protected registerUserProfileHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any): string { if (!handlerSchema.displaydata) { // Required data not provided, stop. this.logger.warn('Ignore site plugin because it doesn\'t provide displaydata', plugin, handlerSchema); @@ -469,14 +473,14 @@ export class CoreSitePluginsHelperProvider { return; } - this.logger.debug('Register site plugin in user profile delegate:', plugin, handlerSchema, bootstrapResult); + this.logger.debug('Register site plugin in user profile delegate:', plugin, handlerSchema, initResult); // Create and register the handler. const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), prefixedTitle = this.getPrefixedString(plugin.addon, handlerSchema.displaydata.title); this.userDelegate.registerHandler(new CoreSitePluginsUserProfileHandler(uniqueName, prefixedTitle, plugin, handlerSchema, - bootstrapResult, this.sitePluginsProvider)); + initResult, this.sitePluginsProvider)); return uniqueName; } @@ -487,10 +491,10 @@ export class CoreSitePluginsHelperProvider { * @param {any} plugin Data of the plugin. * @param {string} handlerName Name of the handler in the plugin. * @param {any} handlerSchema Data about the handler. - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @return {string|Promise} A string (or a promise resolved with a string) to identify the handler. */ - protected registerUserProfileFieldHandler(plugin: any, handlerName: string, handlerSchema: any, bootstrapResult: any) + protected registerUserProfileFieldHandler(plugin: any, handlerName: string, handlerSchema: any, initResult: any) : string | Promise { if (!handlerSchema.method) { // Required data not provided, stop. @@ -499,7 +503,7 @@ export class CoreSitePluginsHelperProvider { return; } - this.logger.debug('Register site plugin in user profile field delegate:', plugin, handlerSchema, bootstrapResult); + this.logger.debug('Register site plugin in user profile field delegate:', plugin, handlerSchema, initResult); // Execute the main method and its JS. The template returned will be used in the profile field component. return this.executeMethodAndJS(plugin, handlerSchema.method).then((result) => { diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index dd3eb2ad5..bd7519cc9 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -47,10 +47,10 @@ export interface CoreSitePluginsHandler { handlerSchema: any; /** - * Result of the bootstrap WS call. + * Result of the init WS call. * @type {any} */ - bootstrapResult?: any; + initResult?: any; } /** @@ -137,23 +137,23 @@ export class CoreSitePluginsProvider { } /** - * Given the result of a bootstrap get_content and, optionally, the result of another get_content, + * Given the result of a init get_content and, optionally, the result of another get_content, * build an object with the data to pass to the JS of the get_content. * - * @param {any} bootstrapResult Result of the bootstrap WS call. + * @param {any} initResult Result of the init WS call. * @param {any} [contentResult] Result of the content WS call (if any). * @return {any} An object with the data to pass to the JS. */ - createDataForJS(bootstrapResult: any, contentResult?: any): any { - // First of all, add the data returned by the bootstrap JS (if any). - let data = this.utils.clone(bootstrapResult.jsResult || {}); + createDataForJS(initResult: any, contentResult?: any): any { + // First of all, add the data returned by the init JS (if any). + let data = this.utils.clone(initResult.jsResult || {}); if (typeof data == 'boolean') { data = {}; } - // Now add some data returned by the bootstrap WS call. - data.BOOTSTRAP_TEMPLATES = this.utils.objectToKeyValueMap(bootstrapResult.templates, 'id', 'html'); - data.BOOTSTRAP_OTHERDATA = bootstrapResult.otherdata; + // Now add some data returned by the init WS call. + data.INIT_TEMPLATES = this.utils.objectToKeyValueMap(initResult.templates, 'id', 'html'); + data.INIT_OTHERDATA = initResult.otherdata; if (contentResult) { // Now add the data returned by the content WS call. diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index 598415aa9..4fe7814fb 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -626,7 +626,11 @@ export class CoreUtilsProvider { * @return {boolean} Whether the error was returned by the WebService. */ isWebServiceError(error: any): boolean { - return typeof error.errorcode == 'undefined' && typeof error.warningcode == 'undefined'; + return typeof error.warningcode != 'undefined' || (typeof error.errorcode != 'undefined' && + error.errorcode != 'invalidtoken' && error.errorcode != 'userdeleted' && error.errorcode != 'upgraderunning' && + error.errorcode != 'forcepasswordchangenotice' && error.errorcode != 'usernotfullysetup' && + error.errorcode != 'sitepolicynotagreed' && error.errorcode != 'sitemaintenance' && + (error.errorcode != 'accessexception' || error.message.indexOf('Invalid token - token expired') == -1)); } /** From eff08198e1e4570dde6df031e2b3218fda551ea4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 2 May 2018 14:52:19 +0200 Subject: [PATCH 2/4] MOBILE-2333 siteplugins: Support CSS files --- .../remotethemes/providers/remotethemes.ts | 58 +-------- src/core/siteplugins/providers/helper.ts | 120 +++++++++++++++++- src/core/siteplugins/providers/siteplugins.ts | 2 + src/providers/filepool.ts | 108 ++++++++++++---- 4 files changed, 205 insertions(+), 83 deletions(-) diff --git a/src/addon/remotethemes/providers/remotethemes.ts b/src/addon/remotethemes/providers/remotethemes.ts index 18aa85ff1..067bca557 100644 --- a/src/addon/remotethemes/providers/remotethemes.ts +++ b/src/addon/remotethemes/providers/remotethemes.ts @@ -18,8 +18,6 @@ import { CoreFileProvider } from '@providers/file'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreConstants } from '@core/constants'; import { Md5 } from 'ts-md5/dist/md5'; @@ -36,8 +34,7 @@ export class AddonRemoteThemesProvider { protected stylesEls: {[siteId: string]: {element: HTMLStyleElement, hash: string}} = {}; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private fileProvider: CoreFileProvider, - private filepoolProvider: CoreFilepoolProvider, private http: Http, private utils: CoreUtilsProvider, - private domUtils: CoreDomUtilsProvider, private textUtils: CoreTextUtilsProvider) { + private filepoolProvider: CoreFilepoolProvider, private http: Http, private utils: CoreUtilsProvider) { this.logger = logger.getInstance('AddonRemoteThemesProvider'); } @@ -225,7 +222,8 @@ export class AddonRemoteThemesProvider { } // Styles have been loaded, now treat the CSS. - this.treatCSSCode(siteId, data.fileUrl, data.styles).catch(() => { + this.filepoolProvider.treatCSSCode(siteId, data.fileUrl, data.styles, AddonRemoteThemesProvider.COMPONENT, 2) + .catch(() => { // Ignore errors. }); }); @@ -304,56 +302,6 @@ export class AddonRemoteThemesProvider { } } - /** - * Search for files in a CSS code and try to download them. Once downloaded, replace their URLs - * and store the result in the CSS file. - * - * @param {string} siteId Site ID. - * @param {string} fileUrl CSS file URL. - * @param {string} cssCode CSS code. - * @return {Promise} Promise resolved with the CSS code. - */ - protected treatCSSCode(siteId: string, fileUrl: string, cssCode: string): Promise { - if (!this.fileProvider.isAvailable()) { - return Promise.reject(null); - } - - const urls = this.domUtils.extractUrlsFromCSS(cssCode), - promises = []; - let filePath, - updated = false; - - // Get the path of the CSS file. - promises.push(this.filepoolProvider.getFilePathByUrl(siteId, fileUrl).then((path) => { - filePath = path; - })); - - urls.forEach((url) => { - // Download the file only if it's an online URL. - if (url.indexOf('http') == 0) { - promises.push(this.filepoolProvider.downloadUrl(siteId, url, false, AddonRemoteThemesProvider.COMPONENT, 2) - .then((fileUrl) => { - if (fileUrl != url) { - cssCode = cssCode.replace(new RegExp(this.textUtils.escapeForRegex(url), 'g'), fileUrl); - updated = true; - } - }).catch((error) => { - // It shouldn't happen. Ignore errors. - this.logger.warn('Error treating file ', url, error); - })); - } - }); - - return Promise.all(promises).then(() => { - // All files downloaded. Store the result if it has changed. - if (updated) { - return this.fileProvider.writeFile(filePath, cssCode); - } - }).then(() => { - return cssCode; - }); - } - /** * Unload styles for a temporary site. */ diff --git a/src/core/siteplugins/providers/helper.ts b/src/core/siteplugins/providers/helper.ts index 9d6fa24e5..ba2915e56 100644 --- a/src/core/siteplugins/providers/helper.ts +++ b/src/core/siteplugins/providers/helper.ts @@ -13,12 +13,15 @@ // limitations under the License. import { Injectable, Injector } from '@angular/core'; +import { Http } from '@angular/http'; import { CoreEventsProvider } from '@providers/events'; +import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreLangProvider } from '@providers/lang'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSite } from '@classes/site'; import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreUrlUtilsProvider } from '@providers/utils/url'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreSitePluginsProvider } from './siteplugins'; import { CoreCompileProvider } from '@core/compile/providers/compile'; @@ -56,12 +59,12 @@ export class CoreSitePluginsHelperProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private injector: Injector, private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate, - private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, + private userDelegate: CoreUserDelegate, private langProvider: CoreLangProvider, private http: Http, private sitePluginsProvider: CoreSitePluginsProvider, private prefetchDelegate: CoreCourseModulePrefetchDelegate, - private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, + private compileProvider: CoreCompileProvider, private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate, eventsProvider: CoreEventsProvider, private courseFormatDelegate: CoreCourseFormatDelegate, private profileFieldDelegate: CoreUserProfileFieldDelegate, - private textUtils: CoreTextUtilsProvider) { + private textUtils: CoreTextUtilsProvider, private filepoolProvider: CoreFilepoolProvider) { this.logger = logger.getInstance('CoreSitePluginsHelperProvider'); // Fetch the plugins on login. @@ -88,6 +91,64 @@ export class CoreSitePluginsHelperProvider { }); } + /** + * Download the styles for a handler (if any). + * + * @param {any} plugin Data of the plugin. + * @param {string} handlerName Name of the handler in the plugin. + * @param {any} handlerSchema Data about the handler. + * @param {string} [siteId] Site ID. If not provided, current site. + * @return {Promise} Promise resolved with the CSS code. + */ + downloadStyles(plugin: any, handlerName: string, handlerSchema: any, siteId?: string): Promise { + + return this.sitesProvider.getSite(siteId).then((site) => { + // Get the absolute URL. If it's a relative URL, add the site URL to it. + let url = handlerSchema.styles && handlerSchema.styles.url; + if (url && !this.urlUtils.isAbsoluteURL(url)) { + url = this.textUtils.concatenatePaths(site.getURL(), url); + } + + const uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName), + componentId = uniqueName + '#main'; + + // Remove the CSS files for this handler that aren't used anymore. Don't block the call for this. + this.filepoolProvider.getFilesByComponent(site.id, CoreSitePluginsProvider.COMPONENT, componentId).then((files) => { + files.forEach((file) => { + if (file.url != url) { + // It's not the current file, delete it. + this.filepoolProvider.removeFileByUrl(site.id, file.url).catch(() => { + // Ignore errors. + }); + } + }); + }).catch(() => { + // Ignore errors. + }); + + if (!url) { + // No styles. + return ''; + } + + // Download the file if not downloaded or the version changed. + return this.filepoolProvider.downloadUrl(site.id, url, false, CoreSitePluginsProvider.COMPONENT, componentId, 0, + undefined, undefined, undefined, handlerSchema.styles.version).then((url) => { + + // File is downloaded, get the contents. + return this.http.get(url).toPromise(); + }).then((response): any => { + const text = response && response.text(); + + if (typeof text == 'string') { + return text; + } else { + return Promise.reject(null); + } + }); + }); + } + /** * Execute a handler's init method if it has any. * @@ -284,6 +345,35 @@ export class CoreSitePluginsHelperProvider { return this.utils.allPromises(promises); } + /** + * Load the styles for a handler. + * + * @param {any} plugin Data of the plugin. + * @param {string} handlerName Name of the handler in the plugin. + * @param {string} fileUrl CSS file URL. + * @param {string} cssCode CSS code. + * @param {number} [version] Styles version. + * @param {string} [siteId] Site ID. If not provided, current site. + */ + loadStyles(plugin: any, handlerName: string, fileUrl: string, cssCode: string, version?: number, siteId?: string): void { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + // Create the style and add it to the header. + const styleEl = document.createElement('style'), + uniqueName = this.sitePluginsProvider.getHandlerUniqueName(plugin, handlerName); + + styleEl.setAttribute('id', 'siteplugin-' + uniqueName); + styleEl.innerHTML = cssCode; + + document.head.appendChild(styleEl); + + // Styles have been loaded, now treat the CSS. + this.filepoolProvider.treatCSSCode(siteId, fileUrl, cssCode, CoreSitePluginsProvider.COMPONENT, uniqueName, version) + .catch(() => { + // Ignore errors. + }); + } + /** * Register a site plugin handler in the right delegate. * @@ -294,8 +384,28 @@ export class CoreSitePluginsHelperProvider { */ registerHandler(plugin: any, handlerName: string, handlerSchema: any): Promise { - // Wait for the init JS to be executed. - return this.executeHandlerInit(plugin, handlerSchema).then((result) => { + // Wait for the init JS to be executed and for the styles to be downloaded. + const promises = [], + siteId = this.sitesProvider.getCurrentSiteId(); + let result, + cssCode; + + promises.push(this.downloadStyles(plugin, handlerName, handlerSchema, siteId).then((code) => { + cssCode = code; + }).catch((error) => { + this.logger.error('Error getting styles for plugin', handlerName, handlerSchema, error); + })); + + promises.push(this.executeHandlerInit(plugin, handlerSchema).then((initResult) => { + result = initResult; + })); + + return Promise.all(promises).then(() => { + if (cssCode) { + // Load the styles. + this.loadStyles(plugin, handlerName, handlerSchema.styles.url, cssCode, handlerSchema.styles.version, siteId); + } + let promise; switch (handlerSchema.delegate) { diff --git a/src/core/siteplugins/providers/siteplugins.ts b/src/core/siteplugins/providers/siteplugins.ts index bd7519cc9..ac3f02c10 100644 --- a/src/core/siteplugins/providers/siteplugins.ts +++ b/src/core/siteplugins/providers/siteplugins.ts @@ -58,6 +58,8 @@ export interface CoreSitePluginsHandler { */ @Injectable() export class CoreSitePluginsProvider { + static COMPONENT = 'CoreSitePlugins'; + protected ROOT_CACHE_KEY = 'CoreSitePlugins:'; protected logger; diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 673606dad..e92f148c7 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -22,6 +22,7 @@ import { CoreLoggerProvider } from './logger'; import { CorePluginFileDelegate } from './plugin-file-delegate'; import { CoreSitesProvider } from './sites'; import { CoreWSProvider } from './ws'; +import { CoreDomUtilsProvider } from './utils/dom'; import { CoreMimetypeUtilsProvider } from './utils/mimetype'; import { CoreTextUtilsProvider } from './utils/text'; import { CoreTimeUtilsProvider } from './utils/time'; @@ -435,7 +436,7 @@ export class CoreFilepoolProvider { private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate, - network: Network, private pluginFileDelegate: CorePluginFileDelegate) { + network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider) { this.logger = logger.getInstance('CoreFilepoolProvider'); this.appDB = this.appProvider.getDB(); @@ -625,13 +626,14 @@ export class CoreFilepoolProvider { * @param {Function} [onProgress] Function to call on progress. * @param {number} [priority=0] The priority this file should get in the queue (range 0-999). * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} 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 = {}): Promise { + filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}, revision?: number) + : Promise { let fileId, link, - revision, queueDeferred; if (!this.fileProvider.isAvailable()) { @@ -646,7 +648,7 @@ export class CoreFilepoolProvider { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const primaryKey = { siteId: siteId, fileId: fileId }; - revision = this.getRevisionFromUrl(fileUrl); + revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); // Set up the component. @@ -747,10 +749,12 @@ export class CoreFilepoolProvider { * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * Ignored if checkSize=false. * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Promise resolved when the file is downloaded. */ protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number, - timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { + timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number) + : Promise { let promise; if (checkSize) { @@ -779,16 +783,17 @@ export class CoreFilepoolProvider { if (sizeUnknown) { if (downloadUnknown && isWifi) { return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); + siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); } } else if (size <= this.DOWNLOAD_THRESHOLD || (isWifi && size <= this.WIFI_DOWNLOAD_THRESHOLD)) { return this.addToQueueByUrl( - siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); + siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, revision); } }); } else { // No need to check size, just add it to the queue. - return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options); + return this.addToQueueByUrl(siteId, fileUrl, component, componentId, timemodified, undefined, undefined, 0, options, + revision); } } @@ -1155,6 +1160,7 @@ export class CoreFilepoolProvider { * @param {number} [timemodified=0] The time this file was modified. Can be used to check file state. * @param {string} [filePath] Filepath to download the file to. If not defined, download to the filepool folder. * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Resolved with internal URL on success, rejected otherwise. * @description * Downloads a file on the spot. @@ -1164,7 +1170,8 @@ export class CoreFilepoolProvider { * invalidateFileByUrl to trigger a download. */ downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string | number, - timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}): Promise { + timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}, revision?: number) + : Promise { let fileId, promise; @@ -1174,7 +1181,7 @@ export class CoreFilepoolProvider { options = Object.assign({}, options); // Create a copy to prevent modifying the original object. options.timemodified = timemodified || 0; - options.revision = this.getRevisionFromUrl(fileUrl); + options.revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); return this.hasFileInPool(siteId, fileId).then((fileObject) => { @@ -1585,15 +1592,16 @@ export class CoreFilepoolProvider { * @param {string} fileUrl File URL. * @param {number} [timemodified=0] The time this file was modified. * @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added. + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Promise resolved with the file state. */ - getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string): Promise { - let fileId, - revision; + getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string, revision?: number) + : Promise { + let fileId; return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { fileUrl = fixedUrl; - revision = this.getRevisionFromUrl(fileUrl); + revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); // Check if the file is in queue (waiting to be downloaded). @@ -1638,6 +1646,7 @@ export class CoreFilepoolProvider { * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * Ignored if checkSize=false. * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. @@ -1647,21 +1656,20 @@ export class CoreFilepoolProvider { */ protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, - options: any = {}): Promise { + options: any = {}, revision?: number): Promise { - let fileId, - revision; + let fileId; const addToQueue = (fileUrl): void => { // Add the file to queue if needed and ignore errors. this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize, - downloadUnknown, options).catch(() => { + downloadUnknown, options, revision).catch(() => { // Ignore errors. }); }; return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { fileUrl = fixedUrl; - revision = this.getRevisionFromUrl(fileUrl); + revision = revision || this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); return this.hasFileInPool(siteId, fileId).then((entry) => { @@ -2083,15 +2091,16 @@ export class CoreFilepoolProvider { * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * Ignored if checkSize=false. * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with IMG tags. */ getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, - checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { + checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number): Promise { return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'src', - timemodified, checkSize, downloadUnknown, options); + timemodified, checkSize, downloadUnknown, options, revision); } /** @@ -2125,15 +2134,16 @@ export class CoreFilepoolProvider { * @param {boolean} [downloadUnknown] True to download file in WiFi if their size is unknown, false otherwise. * Ignored if checkSize=false. * @param {any} [options] Extra options (isexternalfile, repositorytype). + * @param {number} [revision] File revision. If not defined, it will be calculated using the URL. * @return {Promise} Resolved with the URL to use. * @description * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with a local browser. */ getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, - checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { + checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}, revision?: number): Promise { return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'url', - timemodified, checkSize, downloadUnknown, options); + timemodified, checkSize, downloadUnknown, options, revision); } /** @@ -2803,6 +2813,58 @@ export class CoreFilepoolProvider { }); } + /** + * Search for files in a CSS code and try to download them. Once downloaded, replace their URLs + * and store the result in the CSS file. + * + * @param {string} siteId Site ID. + * @param {string} fileUrl CSS file URL. + * @param {string} cssCode CSS code. + * @param {string} [component] The component to link the file to. + * @param {string|number} [componentId] An ID to use in conjunction with the component. + * @param {number} [revision] Revision to use in all files. If not defined, it will be calculated using the URL of each file. + * @return {Promise} Promise resolved with the CSS code. + */ + treatCSSCode(siteId: string, fileUrl: string, cssCode: string, component?: string, componentId?: string | number, + revision?: number): Promise { + + const urls = this.domUtils.extractUrlsFromCSS(cssCode), + promises = []; + let filePath, + updated = false; + + // Get the path of the CSS file. + promises.push(this.getFilePathByUrl(siteId, fileUrl).then((path) => { + filePath = path; + })); + + urls.forEach((url) => { + // Download the file only if it's an online URL. + if (url.indexOf('http') == 0) { + promises.push(this.downloadUrl(siteId, url, false, component, componentId, 0, undefined, undefined, undefined, + revision).then((fileUrl) => { + + if (fileUrl != url) { + cssCode = cssCode.replace(new RegExp(this.textUtils.escapeForRegex(url), 'g'), fileUrl); + updated = true; + } + }).catch((error) => { + // It shouldn't happen. Ignore errors. + this.logger.warn('Error treating file ', url, error); + })); + } + }); + + return Promise.all(promises).then(() => { + // All files downloaded. Store the result if it has changed. + if (updated) { + return this.fileProvider.writeFile(filePath, cssCode); + } + }).then(() => { + return cssCode; + }); + } + /** * Remove extension from fileId in queue, used to migrate from previous file handling. * From 2db76566669bce9669481578ad31b68f18cf6f9c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 2 May 2018 16:32:55 +0200 Subject: [PATCH 3/4] MOBILE-2333 siteplugins: Make content functions available in view --- .../components/course-format/course-format.ts | 2 +- .../components/course-option/course-option.ts | 2 +- .../components/module-index/module-index.ts | 2 +- .../plugin-content/plugin-content.ts | 43 +++++++++++++++---- .../directives/call-ws-new-content.ts | 10 ++--- src/core/siteplugins/directives/call-ws.ts | 2 +- .../siteplugins/directives/new-content.ts | 10 ++--- .../pages/plugin-page/plugin-page.ts | 2 +- 8 files changed, 49 insertions(+), 24 deletions(-) diff --git a/src/core/siteplugins/components/course-format/course-format.ts b/src/core/siteplugins/components/course-format/course-format.ts index 964b9300f..1ed9bdf59 100644 --- a/src/core/siteplugins/components/course-format/course-format.ts +++ b/src/core/siteplugins/components/course-format/course-format.ts @@ -63,6 +63,6 @@ export class CoreSitePluginsCourseFormatComponent implements OnInit { * @return {Promise} Promise resolved when done. */ doRefresh(refresher?: any, done?: () => void): Promise { - return Promise.resolve(this.content.refreshData()); + return Promise.resolve(this.content.refreshContent(false)); } } diff --git a/src/core/siteplugins/components/course-option/course-option.ts b/src/core/siteplugins/components/course-option/course-option.ts index 27725faba..e577e6202 100644 --- a/src/core/siteplugins/components/course-option/course-option.ts +++ b/src/core/siteplugins/components/course-option/course-option.ts @@ -59,7 +59,7 @@ export class CoreSitePluginsCourseOptionComponent implements OnInit { * @param {any} refresher Refresher. */ refreshData(refresher: any): void { - this.content.refreshData().finally(() => { + this.content.refreshContent(false).finally(() => { refresher.complete(); }); } diff --git a/src/core/siteplugins/components/module-index/module-index.ts b/src/core/siteplugins/components/module-index/module-index.ts index d1b77d2a6..5f7c07602 100644 --- a/src/core/siteplugins/components/module-index/module-index.ts +++ b/src/core/siteplugins/components/module-index/module-index.ts @@ -89,7 +89,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C if (this.content) { this.refreshIcon = 'spinner'; - return Promise.resolve(this.content.refreshData()).finally(() => { + return Promise.resolve(this.content.refreshContent(false)).finally(() => { refresher && refresher.complete(); done && done(); }); diff --git a/src/core/siteplugins/components/plugin-content/plugin-content.ts b/src/core/siteplugins/components/plugin-content/plugin-content.ts index 60e01389a..1f89b1ea3 100644 --- a/src/core/siteplugins/components/plugin-content/plugin-content.ts +++ b/src/core/siteplugins/components/plugin-content/plugin-content.ts @@ -12,7 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; +import { Component, OnInit, Input, Output, EventEmitter, Optional } from '@angular/core'; +import { NavController } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreSitePluginsProvider } from '../../providers/siteplugins'; import { Subject } from 'rxjs'; @@ -39,7 +40,8 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { invalidateObservable: Subject; // An observable to notify observers when to invalidate data. jsData: any; // Data to pass to the component. - constructor(protected domUtils: CoreDomUtilsProvider, protected sitePluginsProvider: CoreSitePluginsProvider) { + constructor(protected domUtils: CoreDomUtilsProvider, protected sitePluginsProvider: CoreSitePluginsProvider, + @Optional() protected navCtrl: NavController) { this.onContentLoaded = new EventEmitter(); this.onLoadingContent = new EventEmitter(); this.invalidateObservable = new Subject(); @@ -67,6 +69,11 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { this.otherData = result.otherdata; this.jsData = this.sitePluginsProvider.createDataForJS(this.initResult, result); + // Pass some methods as jsData so they can be called from the template too. + this.jsData.openContent = this.openContent.bind(this); + this.jsData.refreshContent = this.refreshContent.bind(this); + this.jsData.updateContent = this.updateContent.bind(this); + this.onContentLoaded.emit(refresh); }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.errorloadingcontent', true); @@ -75,12 +82,30 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { }); } + /** + * Open a new page with a new content. + * + * @param {string} title The title to display with the new content. + * @param {any} args New params. + * @param {string} [component] New component. If not provided, current component + * @param {string} [method] New method. If not provided, current method + */ + openContent(title: string, args: any, component?: string, method?: string): void { + this.navCtrl.push('CoreSitePluginsPluginPage', { + title: title, + component: component || this.component, + method: method || this.method, + args: args, + initResult: this.initResult + }); + } + /** * Refresh the data. * - * @param {boolean} [showSpinner] Whether to show spinner while refreshing. + * @param {boolean} [showSpinner=true] Whether to show spinner while refreshing. */ - refreshData(showSpinner?: boolean): Promise { + refreshContent(showSpinner: boolean = true): Promise { if (showSpinner) { this.dataLoaded = false; } @@ -95,13 +120,13 @@ export class CoreSitePluginsPluginContentComponent implements OnInit { /** * Update the content, usually with a different method or params. * - * @param {string} component New component. - * @param {string} method New method. * @param {any} args New params. + * @param {string} [component] New component. If not provided, current component + * @param {string} [method] New method. If not provided, current method */ - updateContent(component: string, method: string, args: any): void { - this.component = component; - this.method = method; + updateContent(args: any, component?: string, method?: string): void { + this.component = component || this.component; + this.method = method || this.method; this.args = args; this.dataLoaded = false; diff --git a/src/core/siteplugins/directives/call-ws-new-content.ts b/src/core/siteplugins/directives/call-ws-new-content.ts index 44f918f60..9d67ccba0 100644 --- a/src/core/siteplugins/directives/call-ws-new-content.ts +++ b/src/core/siteplugins/directives/call-ws-new-content.ts @@ -53,8 +53,8 @@ import { CoreSitePluginsPluginContentComponent } from '../components/plugin-cont selector: '[core-site-plugins-call-ws-new-content]' }) export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCallWSOnClickBaseDirective { - @Input() component: string; // The component of the new content. - @Input() method: string; // The method to get the new content. + @Input() component: string; // The component of the new content. If not provided, use the same component as current page. + @Input() method: string; // The method to get the new content. If not provided, use the same method as current page. @Input() args: any; // The params to get the new content. @Input() title: string; // The title to display with the new content. Only if samePage=false. @Input() samePage: boolean | string; // Whether to display the content in same page or open a new one. Defaults to new page. @@ -84,13 +84,13 @@ export class CoreSitePluginsCallWSNewContentDirective extends CoreSitePluginsCal if (this.utils.isTrueOrOne(this.samePage)) { // Update the parent content (if it exists). if (this.parentContent) { - this.parentContent.updateContent(this.component, this.method, args); + this.parentContent.updateContent(args, this.component, this.method); } } else { this.navCtrl.push('CoreSitePluginsPluginPage', { title: this.title, - component: this.component, - method: this.method, + component: this.component || (this.parentContent && this.parentContent.component), + method: this.method || (this.parentContent && this.parentContent.method), args: args, initResult: this.parentContent && this.parentContent.initResult }); diff --git a/src/core/siteplugins/directives/call-ws.ts b/src/core/siteplugins/directives/call-ws.ts index ee6034380..7880ad64a 100644 --- a/src/core/siteplugins/directives/call-ws.ts +++ b/src/core/siteplugins/directives/call-ws.ts @@ -74,7 +74,7 @@ export class CoreSitePluginsCallWSDirective extends CoreSitePluginsCallWSOnClick if (this.utils.isTrueOrOne(this.goBackOnSuccess)) { this.navCtrl.pop(); } else if (this.utils.isTrueOrOne(this.refreshOnSuccess) && this.parentContent) { - this.parentContent.refreshData(true); + this.parentContent.refreshContent(true); } } } diff --git a/src/core/siteplugins/directives/new-content.ts b/src/core/siteplugins/directives/new-content.ts index 3f7e5933d..edb7d5f3e 100644 --- a/src/core/siteplugins/directives/new-content.ts +++ b/src/core/siteplugins/directives/new-content.ts @@ -43,8 +43,8 @@ import { CoreSitePluginsPluginContentComponent } from '../components/plugin-cont selector: '[core-site-plugins-new-content]' }) export class CoreSitePluginsNewContentDirective implements OnInit { - @Input() component: string; // The component of the new content. - @Input() method: string; // The method to get the new content. + @Input() component: string; // The component of the new content. If not provided, use the same component as current page. + @Input() method: string; // The method to get the new content. If not provided, use the same method as current page. @Input() args: any; // The params to get the new content. @Input() title: string; // The title to display with the new content. Only if samePage=false. @Input() samePage: boolean | string; // Whether to display the content in same page or open a new one. Defaults to new page. @@ -81,13 +81,13 @@ export class CoreSitePluginsNewContentDirective implements OnInit { if (this.utils.isTrueOrOne(this.samePage)) { // Update the parent content (if it exists). if (this.parentContent) { - this.parentContent.updateContent(this.component, this.method, args); + this.parentContent.updateContent(args, this.component, this.method); } } else { this.navCtrl.push('CoreSitePluginsPluginPage', { title: this.title, - component: this.component, - method: this.method, + component: this.component || (this.parentContent && this.parentContent.component), + method: this.method || (this.parentContent && this.parentContent.method), args: args, initResult: this.parentContent && this.parentContent.initResult }); diff --git a/src/core/siteplugins/pages/plugin-page/plugin-page.ts b/src/core/siteplugins/pages/plugin-page/plugin-page.ts index 0b02ad00a..32f5db12e 100644 --- a/src/core/siteplugins/pages/plugin-page/plugin-page.ts +++ b/src/core/siteplugins/pages/plugin-page/plugin-page.ts @@ -48,7 +48,7 @@ export class CoreSitePluginsPluginPage { * @param {any} refresher Refresher. */ refreshData(refresher: any): void { - this.content.refreshData().finally(() => { + this.content.refreshContent(false).finally(() => { refresher.complete(); }); } From 6d142fbc7b646c7af9841d6339c0d712109b2313 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 May 2018 10:24:07 +0200 Subject: [PATCH 4/4] MOBILE-2333 tabs: Fix scroll issue with tabs in course contents --- src/components/tabs/tabs.ts | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 5d57b056a..932e34d5a 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -107,9 +107,25 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { if (this.getIndex(tab) == -1) { this.tabs.push(tab); this.sortTabs(); + + if (this.initialized && this.tabs.length > 1 && this.tabBarHeight == 0) { + // Calculate the tabBarHeight again now that there is more than 1 tab and the bar will be seen. + // Use timeout to wait for the view to be rendered. 0 ms should be enough, use 50 to be sure. + setTimeout(() => { + this.calculateTabBarHeight(); + }, 50); + } } } + /** + * Calculate the tab bar height. + */ + calculateTabBarHeight(): void { + this.tabBarHeight = this.topTabsElement.offsetHeight; + this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px'; + } + /** * Get the index of tab. * @@ -161,8 +177,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { } // Setup tab scrolling. - this.tabBarHeight = this.topTabsElement.offsetHeight; - this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px'; + this.calculateTabBarHeight(); if (this.content) { if (!this.parentScrollable) { // Parent scroll element (if core-tabs is inside a ion-content). @@ -184,6 +199,11 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * @param {any} e Scroll event. */ showHideTabs(e: any): void { + if (!this.tabBarHeight) { + // We don't have the tab bar height, this means the tab bar isn't shown. + return; + } + if (this.tabsShown && e.target.scrollTop - this.tabBarHeight > this.tabBarHeight) { this.tabBarElement.classList.add('tabs-hidden'); this.tabsShown = false;