From 7d3162585c586be1f48539daa428a3a4382c9505 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 13 Feb 2018 08:25:52 +0100 Subject: [PATCH] MOBILE-2333 siteaddons: Fetch and register basic site addons --- src/app/app.module.ts | 9 +- src/classes/delegate.ts | 2 +- src/core/siteaddons/providers/siteaddons.ts | 299 ++++++++++++++++++++ src/core/siteaddons/siteaddons.module.ts | 28 ++ src/providers/addonmanager.ts | 88 ++++++ src/providers/events.ts | 2 +- 6 files changed, 424 insertions(+), 4 deletions(-) create mode 100644 src/core/siteaddons/providers/siteaddons.ts create mode 100644 src/core/siteaddons/siteaddons.module.ts create mode 100644 src/providers/addonmanager.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 7b0be5c01..376a2b963 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -49,6 +49,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreUpdateManagerProvider } from '@providers/update-manager'; import { CorePluginFileDelegate } from '@providers/plugin-file-delegate'; import { CoreSyncProvider } from '@providers/sync'; +import { CoreAddonManagerProvider } from '@providers/addonmanager'; // Core modules. import { CoreComponentsModule } from '@components/components.module'; @@ -64,6 +65,7 @@ import { CoreContentLinksModule } from '@core/contentlinks/contentlinks.module'; import { CoreUserModule } from '@core/user/user.module'; import { CoreGradesModule } from '@core/grades/grades.module'; import { CoreSettingsModule } from '@core/settings/settings.module'; +import { CoreSiteAddonsModule } from '@core/siteaddons/siteaddons.module'; // Addon modules. import { AddonCalendarModule } from '@addon/calendar/calendar.module'; @@ -111,6 +113,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { CoreUserModule, CoreGradesModule, CoreSettingsModule, + CoreSiteAddonsModule, AddonCalendarModule, AddonUserProfileFieldModule, AddonFilesModule, @@ -153,12 +156,14 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { CoreFilepoolProvider, CoreUpdateManagerProvider, CorePluginFileDelegate, - CoreSyncProvider + CoreSyncProvider, + CoreAddonManagerProvider ] }) export class AppModule { constructor(platform: Platform, initDelegate: CoreInitDelegate, updateManager: CoreUpdateManagerProvider, - sitesProvider: CoreSitesProvider) { + sitesProvider: CoreSitesProvider, addonManagerProvider: CoreAddonManagerProvider) { + // Inject CoreAddonManagerProvider even if it's not used to make sure it's initialized. // Register a handler for platform ready. initDelegate.registerProcess({ name: 'CorePlatformReady', diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index 00e1bf791..ac08c86eb 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -91,7 +91,7 @@ export class CoreDelegate { // Update handlers on this cases. eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this)); - eventsProvider.on(CoreEventsProvider.REMOTE_ADDONS_LOADED, this.updateHandlers.bind(this)); + eventsProvider.on(CoreEventsProvider.SITE_ADDONS_LOADED, this.updateHandlers.bind(this)); } } diff --git a/src/core/siteaddons/providers/siteaddons.ts b/src/core/siteaddons/providers/siteaddons.ts new file mode 100644 index 000000000..e483a992c --- /dev/null +++ b/src/core/siteaddons/providers/siteaddons.ts @@ -0,0 +1,299 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NavController, NavOptions } from 'ionic-angular'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSite } from '../../../classes/site'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreUtilsProvider } from '../../../providers/utils/utils'; +import { CoreMainMenuDelegate, CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate'; +import { + CoreCourseModuleDelegate, CoreCourseModuleHandler, CoreCourseModuleHandlerData +} from '../../../core/course/providers/module-delegate'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../../core/user/providers/user-delegate'; +import { CoreDelegateHandler } from '../../../classes/delegate'; + +/** + * Service to provide functionalities regarding site addons. + */ +@Injectable() +export class CoreSiteAddonsProvider { + protected ROOT_CACHE_KEY = 'CoreSiteAddons:'; + + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, + private mainMenuDelegate: CoreMainMenuDelegate, private moduleDelegate: CoreCourseModuleDelegate, + private userDelegate: CoreUserDelegate) { + this.logger = logger.getInstance('CoreUserProvider'); + } + + /** + * Create a base handler for a site addon. + * + * @param {string} name Name of the handler. + * @return {CoreDelegateHandler} The base handler. + */ + protected getBaseHandler(name: string): CoreDelegateHandler { + return { + name: name, + isEnabled: (): boolean => { + return true; + } + }; + } + + /** + * Given a handler's unique name and the key of a string, return the full string key (prefixed). + * + * @param {string} handlerName Handler's unique name (result of getHandlerUniqueName). + * @param {string} key The key of the string. + * @return {string} Full string key. + */ + protected getHandlerPrefixedString(handlerName: string, key: string): string { + if (name) { + return 'addon.' + handlerName + '.' + key; + } + + return ''; + } + + /** + * Get the unique name of a handler (addon + handler). + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler inside the addon. + * @return {string} Unique name. + */ + protected getHandlerUniqueName(addon: any, handlerName: string): string { + return addon.addon + '_' + handlerName; + } + + /** + * Check if a certain addon is a site addon and it's enabled in a certain site. + * + * @param {any} addon Data of the addon. + * @param {CoreSite} site Site affected. + * @return {boolean} Whether it's a site addon and it's enabled. + */ + isSiteAddonEnabled(addon: any, site: CoreSite): boolean { + if (!site.isFeatureDisabled('siteAddOn_' + addon.component + '_' + addon.addon) && addon.handlers) { + // Site addon not disabled. Check if it has handlers. + try { + if (!addon.parsedHandlers) { + addon.parsedHandlers = JSON.parse(addon.handlers); + } + + return !!(addon.parsedHandlers && Object.keys(addon.parsedHandlers).length); + } catch (ex) { + this.logger.warn('Error parsing site addon', ex); + } + } + + return false; + } + + /** + * Load a site addon. + * + * @param {any} addon Data of the addon. + */ + loadSiteAddon(addon: any): void { + try { + if (!addon.parsedHandlers) { + addon.parsedHandlers = JSON.parse(addon.handlers); + } + + // Register all the handlers. + for (const name in addon.parsedHandlers) { + this.registerHandler(addon, name, addon.parsedHandlers[name]); + } + } catch (ex) { + this.logger.warn('Error parsing site addon', ex); + } + } + + /** + * Register a site addon handler in the right delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + */ + registerHandler(addon: any, handlerName: string, handlerSchema: any): void { + switch (handlerSchema.delegate) { + case 'CoreMainMenuDelegate': + this.registerMainMenuHandler(addon, handlerName, handlerSchema); + break; + + case 'CoreCourseModuleDelegate': + this.registerModuleHandler(addon, handlerName, handlerSchema); + break; + + case 'CoreUserDelegate': + this.registerUserProfileHandler(addon, handlerName, handlerSchema); + break; + + default: + // Nothing to do. + } + } + + /** + * Given a handler in an addon, register it in the main menu delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + */ + protected registerMainMenuHandler(addon: any, handlerName: string, handlerSchema: any): void { + if (!handlerSchema || !handlerSchema.displaydata) { + // Required data not provided, stop. + return; + } + + // Create the base handler. + const baseHandler = this.getBaseHandler(this.getHandlerUniqueName(addon, handlerName)), + prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title); + let mainMenuHandler: CoreMainMenuHandler; + + // Extend the base handler, adding the properties required by the delegate. + mainMenuHandler = Object.assign(baseHandler, { + priority: handlerSchema.priority, + getDisplayData: (): CoreMainMenuHandlerData => { + return { + title: prefixedTitle, + icon: handlerSchema.displaydata.icon, + class: handlerSchema.displaydata.class, + page: 'CoreSiteAddonsAddonPage', + pageParams: { + title: prefixedTitle, + component: addon.component, + callback: handlerSchema.mainfunction, + contextId: handlerSchema.contextid + } + }; + } + }); + + this.mainMenuDelegate.registerHandler(mainMenuHandler); + } + + /** + * Given a handler in an addon, register it in the module delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + */ + protected registerModuleHandler(addon: any, handlerName: string, handlerSchema: any): void { + if (!handlerSchema || !handlerSchema.displaydata) { + // Required data not provided, stop. + return; + } + + // Create the base handler. + const baseHandler = this.getBaseHandler(addon.component.replace('mod_', '')); + let moduleHandler: CoreCourseModuleHandler; + + // Extend the base handler, adding the properties required by the delegate. + moduleHandler = Object.assign(baseHandler, { + getData: (module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData => { + return { + title: module.name, + icon: handlerSchema.displaydata.icon, + class: handlerSchema.displaydata.class, + showDownloadButton: handlerSchema.offlinefunctions && handlerSchema.offlinefunctions.length, + action: (event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void => { + event.preventDefault(); + event.stopPropagation(); + + navCtrl.push('CoreSiteAddonsAddonPage', { + title: module.name, + component: addon.component, + callback: handlerSchema.mainfunction, + contextId: handlerSchema.contextid, + args: { + course: courseId, + cmid: module.id + } + }, options); + } + }; + }, + getMainComponent: (course: any, module: any): any => { + // Singleactivity course format not supported with site addons. + } + }); + + this.moduleDelegate.registerHandler(moduleHandler); + } + + /** + * Given a handler in an addon, register it in the user profile delegate. + * + * @param {any} addon Data of the addon. + * @param {string} handlerName Name of the handler in the addon. + * @param {any} handlerSchema Data about the handler. + */ + protected registerUserProfileHandler(addon: any, handlerName: string, handlerSchema: any): void { + if (!handlerSchema || !handlerSchema.displaydata) { + // Required data not provided, stop. + return; + } + + // Create the base handler. + const baseHandler = this.getBaseHandler(this.getHandlerUniqueName(addon, handlerName)), + prefixedTitle = this.getHandlerPrefixedString(baseHandler.name, handlerSchema.displaydata.title); + let userHandler: CoreUserProfileHandler; + + // Extend the base handler, adding the properties required by the delegate. + userHandler = Object.assign(baseHandler, { + priority: handlerSchema.priority, + type: handlerSchema.type, + isEnabledForUser: (user: any, courseId: number, navOptions?: any, admOptions?: any): boolean => { + if (handlerSchema.restricted == 'current' && user.id != this.sitesProvider.getCurrentSite().getUserId()) { + return false; + } + + return true; + }, + getDisplayData: (user: any, courseId: number): CoreUserProfileHandlerData => { + return { + title: prefixedTitle, + icon: handlerSchema.displaydata.icon, + class: handlerSchema.displaydata.class, + action: (event: Event, navCtrl: NavController, user: any, courseId?: number): void => { + event.preventDefault(); + event.stopPropagation(); + + navCtrl.push('CoreSiteAddonsAddonPage', { + title: prefixedTitle, + component: addon.component, + callback: handlerSchema.mainfunction, + contextId: handlerSchema.contextid, + args: { + course: courseId, + userid: user.id + } + }); + } + }; + } + }); + + this.userDelegate.registerHandler(userHandler); + } +} diff --git a/src/core/siteaddons/siteaddons.module.ts b/src/core/siteaddons/siteaddons.module.ts new file mode 100644 index 000000000..00275ba92 --- /dev/null +++ b/src/core/siteaddons/siteaddons.module.ts @@ -0,0 +1,28 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { Platform } from 'ionic-angular'; +import { CoreSiteAddonsProvider } from './providers/siteaddons'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreSiteAddonsProvider + ] +}) +export class CoreSiteAddonsModule { } diff --git a/src/providers/addonmanager.ts b/src/providers/addonmanager.ts new file mode 100644 index 000000000..106e3cc25 --- /dev/null +++ b/src/providers/addonmanager.ts @@ -0,0 +1,88 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreEventsProvider } from './events'; +import { CoreLoggerProvider } from './logger'; +import { CoreSitesProvider } from './sites'; +import { CoreSiteWSPreSets } from '../classes/site'; +import { CoreSiteAddonsProvider } from '../core/siteaddons/providers/siteaddons'; + +/** + * Provider with some helper functions regarding addons. + */ +@Injectable() +export class CoreAddonManagerProvider { + + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, + private siteAddonsProvider: CoreSiteAddonsProvider) { + logger = logger.getInstance('CoreAddonManagerProvider'); + + // Fetch the addons on login. + eventsProvider.on(CoreEventsProvider.LOGIN, () => { + const siteId = this.sitesProvider.getCurrentSiteId(); + this.fetchSiteAddons(siteId).then((addons) => { + // Addons fetched, check that site hasn't changed. + if (siteId == this.sitesProvider.getCurrentSiteId()) { + // Site is still the same. Load the addons and trigger the event. + this.loadSiteAddons(addons); + + eventsProvider.trigger(CoreEventsProvider.SITE_ADDONS_LOADED, {}, siteId); + } + }); + }); + + // Unload addons on logout if any. + eventsProvider.on(CoreEventsProvider.LOGOUT, () => { + // @todo: Unload site addons. + }); + } + + /** + * Fetch site addons. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when done. Returns the list of addons to load. + */ + fetchSiteAddons(siteId?: string): Promise { + const addons = []; + + return this.sitesProvider.getSite(siteId).then((site) => { + // Get the list of addons. Try not to use cache. + return site.read('tool_mobile_get_plugins_supporting_mobile', {}, { getFromCache: false }).then((data) => { + data.plugins.forEach((addon: any) => { + // Check if it's a site addon and it's enabled. + if (this.siteAddonsProvider.isSiteAddonEnabled(addon, site)) { + addons.push(addon); + } + }); + + return addons; + }); + }); + } + + /** + * Load site addons. + * + * @param {any[]} addons The addons to load. + */ + loadSiteAddons(addons: any[]): void { + addons.forEach((addon) => { + this.siteAddonsProvider.loadSiteAddon(addon); + }); + } +} diff --git a/src/providers/events.ts b/src/providers/events.ts index bbe557636..2dff17b08 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -47,7 +47,7 @@ export class CoreEventsProvider { static PACKAGE_STATUS_CHANGED = 'package_status_changed'; static COURSE_STATUS_CHANGED = 'course_status_changed'; static SECTION_STATUS_CHANGED = 'section_status_changed'; - static REMOTE_ADDONS_LOADED = 'remote_addons_loaded'; + static SITE_ADDONS_LOADED = 'site_addons_loaded'; static LOGIN_SITE_CHECKED = 'login_site_checked'; static LOGIN_SITE_UNCHECKED = 'login_site_unchecked'; static IAB_LOAD_START = 'inappbrowser_load_start';