2018-01-15 14:57:42 +00:00
|
|
|
// (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.
|
|
|
|
|
2018-03-01 15:55:49 +00:00
|
|
|
import { CoreLoggerProvider } from '@providers/logger';
|
|
|
|
import { CoreSitesProvider } from '@providers/sites';
|
|
|
|
import { CoreEventsProvider } from '@providers/events';
|
2018-01-15 14:57:42 +00:00
|
|
|
|
|
|
|
export interface CoreDelegateHandler {
|
|
|
|
/**
|
2018-01-25 16:28:47 +00:00
|
|
|
* Name of the handler, or name and sub context (AddonMessages, AddonMessages:blockContact, ...).
|
2018-03-23 12:24:31 +00:00
|
|
|
* This name will be used to check if the feature is disabled.
|
2018-01-15 14:57:42 +00:00
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
name: string;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Whether or not the handler is enabled on a site level.
|
|
|
|
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
isEnabled(): boolean | Promise<boolean>;
|
|
|
|
}
|
2018-01-15 14:57:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Superclass to help creating delegates
|
|
|
|
*/
|
|
|
|
export class CoreDelegate {
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Logger instance get from CoreLoggerProvider.
|
2018-01-25 12:19:11 +00:00
|
|
|
* @type {any}
|
2018-01-15 14:57:42 +00:00
|
|
|
*/
|
|
|
|
protected logger;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* List of registered handlers.
|
|
|
|
* @type {any}
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected handlers: { [s: string]: CoreDelegateHandler } = {};
|
2018-01-15 14:57:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* List of registered handlers enabled for the current site.
|
|
|
|
* @type {any}
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected enabledHandlers: { [s: string]: CoreDelegateHandler } = {};
|
2018-01-15 14:57:42 +00:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Default handler
|
|
|
|
* @type {CoreDelegateHandler}
|
|
|
|
*/
|
|
|
|
protected defaultHandler: CoreDelegateHandler;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Time when last updateHandler functions started.
|
|
|
|
* @type {number}
|
|
|
|
*/
|
|
|
|
protected lastUpdateHandlersStart: number;
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Feature prefix to check is feature is enabled or disabled in site.
|
|
|
|
* This check is only made if not false. Override on the subclass or override isFeatureDisabled function.
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
protected featurePrefix: string;
|
|
|
|
|
2018-03-23 12:24:31 +00:00
|
|
|
/**
|
|
|
|
* Name of the property to be used to index the handlers. By default, the handler's name will be used.
|
|
|
|
* If your delegate uses a Moodle component name to identify the handlers, please override this property.
|
|
|
|
* E.g. CoreCourseModuleDelegate uses 'modName' to index the handlers.
|
|
|
|
* @type {string}
|
|
|
|
*/
|
|
|
|
protected handlerNameProperty = 'name';
|
|
|
|
|
2018-03-22 13:51:00 +00:00
|
|
|
/**
|
|
|
|
* Set of promises to update a handler, to prevent doing the same operation twice.
|
|
|
|
* @type {{[siteId: string]: {[name: string]: Promise<any>}}}
|
|
|
|
*/
|
|
|
|
protected updatePromises: {[siteId: string]: {[name: string]: Promise<any>}} = {};
|
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
/**
|
|
|
|
* Constructor of the Delegate.
|
|
|
|
*
|
|
|
|
* @param {string} delegateName Delegate name used for logging purposes.
|
|
|
|
* @param {CoreLoggerProvider} loggerProvider CoreLoggerProvider instance, cannot be directly injected.
|
|
|
|
* @param {CoreSitesProvider} sitesProvider CoreSitesProvider instance, cannot be directly injected.
|
2018-01-25 16:28:47 +00:00
|
|
|
* @param {CoreEventsProvider} [eventsProvider] CoreEventsProvider instance, cannot be directly injected.
|
|
|
|
* If not set, no events will be fired.
|
2018-01-15 14:57:42 +00:00
|
|
|
*/
|
|
|
|
constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
|
2018-01-29 09:05:20 +00:00
|
|
|
protected eventsProvider?: CoreEventsProvider) {
|
2018-01-15 14:57:42 +00:00
|
|
|
this.logger = this.loggerProvider.getInstance(delegateName);
|
|
|
|
|
2018-01-25 16:28:47 +00:00
|
|
|
if (eventsProvider) {
|
|
|
|
// Update handlers on this cases.
|
|
|
|
eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this));
|
|
|
|
eventsProvider.on(CoreEventsProvider.SITE_UPDATED, this.updateHandlers.bind(this));
|
2018-03-09 13:36:30 +00:00
|
|
|
eventsProvider.on(CoreEventsProvider.SITE_PLUGINS_LOADED, this.updateHandlers.bind(this));
|
2018-01-25 16:28:47 +00:00
|
|
|
}
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
|
2018-01-18 15:38:41 +00:00
|
|
|
/**
|
|
|
|
* Execute a certain function in a enabled handler.
|
|
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
|
|
*
|
|
|
|
* @param {string} handlerName The handler name.
|
|
|
|
* @param {string} fnName Name of the function to execute.
|
|
|
|
* @param {any[]} params Parameters to pass to the function.
|
|
|
|
* @return {any} Function returned value or default value.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected executeFunctionOnEnabled(handlerName: string, fnName: string, params?: any[]): any {
|
2018-01-18 15:38:41 +00:00
|
|
|
return this.execute(this.enabledHandlers[handlerName], fnName, params);
|
|
|
|
}
|
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
/**
|
|
|
|
* Execute a certain function in a handler.
|
|
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
|
|
*
|
|
|
|
* @param {string} handlerName The handler name.
|
|
|
|
* @param {string} fnName Name of the function to execute.
|
|
|
|
* @param {any[]} params Parameters to pass to the function.
|
|
|
|
* @return {any} Function returned value or default value.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected executeFunction(handlerName: string, fnName: string, params?: any[]): any {
|
2018-01-18 15:38:41 +00:00
|
|
|
return this.execute(this.handlers[handlerName], fnName, params);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Execute a certain function in a handler.
|
|
|
|
* If the handler isn't found or function isn't defined, call the same function in the default handler.
|
|
|
|
*
|
|
|
|
* @param {any} handler The handler.
|
|
|
|
* @param {string} fnName Name of the function to execute.
|
|
|
|
* @param {any[]} params Parameters to pass to the function.
|
|
|
|
* @return {any} Function returned value or default value.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
private execute(handler: any, fnName: string, params?: any[]): any {
|
2018-01-15 14:57:42 +00:00
|
|
|
if (handler && handler[fnName]) {
|
|
|
|
return handler[fnName].apply(handler, params);
|
|
|
|
} else if (this.defaultHandler && this.defaultHandler[fnName]) {
|
2018-02-02 07:57:34 +00:00
|
|
|
return this.defaultHandler[fnName].apply(this.defaultHandler, params);
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-01-18 15:38:41 +00:00
|
|
|
/**
|
|
|
|
* Get a handler.
|
|
|
|
*
|
|
|
|
* @param {string} handlerName The handler name.
|
|
|
|
* @param {boolean} [enabled] Only enabled, or any.
|
2018-02-01 09:14:47 +00:00
|
|
|
* @return {CoreDelegateHandler} Handler.
|
2018-01-18 15:38:41 +00:00
|
|
|
*/
|
2018-02-01 09:14:47 +00:00
|
|
|
protected getHandler(handlerName: string, enabled: boolean = false): CoreDelegateHandler {
|
2018-01-18 15:38:41 +00:00
|
|
|
return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
|
|
|
}
|
|
|
|
|
2018-04-12 12:56:51 +00:00
|
|
|
/**
|
|
|
|
* Check if function exists on a handler.
|
|
|
|
*
|
|
|
|
* @param {string} handlerName The handler name.
|
|
|
|
* @param {string} fnName Name of the function to execute.
|
|
|
|
* @param {booealn} [onlyEnabled=true] If check only enabled handlers or all.
|
|
|
|
* @return {any} Function returned value or default value.
|
|
|
|
*/
|
|
|
|
protected hasFunction(handlerName: string, fnName: string, onlyEnabled: boolean = true): any {
|
|
|
|
const handler = onlyEnabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName];
|
|
|
|
|
|
|
|
return handler && handler[fnName];
|
|
|
|
}
|
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
/**
|
|
|
|
* Check if a handler name has a registered handler (not necessarily enabled).
|
|
|
|
*
|
2018-01-18 15:38:41 +00:00
|
|
|
* @param {string} name The handler name.
|
|
|
|
* @param {boolean} [enabled] Only enabled, or any.
|
2018-01-25 12:19:11 +00:00
|
|
|
* @return {boolean} If the handler is registered or not.
|
2018-01-15 14:57:42 +00:00
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
hasHandler(name: string, enabled: boolean = false): boolean {
|
2018-01-18 15:38:41 +00:00
|
|
|
return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined';
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if a time belongs to the last update handlers call.
|
|
|
|
* This is to handle the cases where updateHandlers don't finish in the same order as they're called.
|
|
|
|
*
|
|
|
|
* @param {number} time Time to check.
|
|
|
|
* @return {boolean} Whether it's the last call.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
isLastUpdateCall(time: number): boolean {
|
2018-01-15 14:57:42 +00:00
|
|
|
if (!this.lastUpdateHandlersStart) {
|
|
|
|
return true;
|
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
return time == this.lastUpdateHandlersStart;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
2018-01-25 12:19:11 +00:00
|
|
|
* Register a handler.
|
|
|
|
*
|
|
|
|
* @param {CoreDelegateHandler} handler The handler delegate object to register.
|
|
|
|
* @return {boolean} True when registered, false if already registered.
|
2018-01-15 14:57:42 +00:00
|
|
|
*/
|
2018-01-25 12:19:11 +00:00
|
|
|
registerHandler(handler: CoreDelegateHandler): boolean {
|
2018-03-23 12:24:31 +00:00
|
|
|
if (typeof this.handlers[handler[this.handlerNameProperty]] !== 'undefined') {
|
|
|
|
this.logger.log(`Handler '${handler[this.handlerNameProperty]}' already registered`);
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2018-03-23 12:24:31 +00:00
|
|
|
this.logger.log(`Registered handler '${handler[this.handlerNameProperty]}'`);
|
|
|
|
this.handlers[handler[this.handlerNameProperty]] = handler;
|
2018-01-29 09:05:20 +00:00
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the handler for the current site.
|
|
|
|
*
|
|
|
|
* @param {CoreDelegateHandler} handler The handler to check.
|
|
|
|
* @param {number} time Time this update process started.
|
|
|
|
* @return {Promise<void>} Resolved when done.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected updateHandler(handler: CoreDelegateHandler, time: number): Promise<void> {
|
2018-01-29 09:05:20 +00:00
|
|
|
const siteId = this.sitesProvider.getCurrentSiteId(),
|
2018-01-15 14:57:42 +00:00
|
|
|
currentSite = this.sitesProvider.getCurrentSite();
|
2018-01-29 09:05:20 +00:00
|
|
|
let promise;
|
2018-01-15 14:57:42 +00:00
|
|
|
|
2018-03-22 13:51:00 +00:00
|
|
|
if (this.updatePromises[siteId] && this.updatePromises[siteId][handler.name]) {
|
|
|
|
// There's already an update ongoing for this handler, return the promise.
|
|
|
|
return this.updatePromises[siteId][handler.name];
|
|
|
|
} else if (!this.updatePromises[siteId]) {
|
|
|
|
this.updatePromises[siteId] = {};
|
|
|
|
}
|
|
|
|
|
2018-01-15 14:57:42 +00:00
|
|
|
if (!this.sitesProvider.isLoggedIn()) {
|
|
|
|
promise = Promise.reject(null);
|
|
|
|
} else if (this.isFeatureDisabled(handler, currentSite)) {
|
|
|
|
promise = Promise.resolve(false);
|
|
|
|
} else {
|
|
|
|
promise = Promise.resolve(handler.isEnabled());
|
|
|
|
}
|
|
|
|
|
|
|
|
// Checks if the handler is enabled.
|
2018-03-22 13:51:00 +00:00
|
|
|
this.updatePromises[siteId][handler.name] = promise.catch(() => {
|
2018-01-15 14:57:42 +00:00
|
|
|
return false;
|
|
|
|
}).then((enabled: boolean) => {
|
|
|
|
// Check that site hasn't changed since the check started.
|
2018-07-12 07:43:04 +00:00
|
|
|
if (this.sitesProvider.getCurrentSiteId() === siteId) {
|
2018-01-15 14:57:42 +00:00
|
|
|
if (enabled) {
|
2018-03-23 12:24:31 +00:00
|
|
|
this.enabledHandlers[handler[this.handlerNameProperty]] = handler;
|
2018-01-15 14:57:42 +00:00
|
|
|
} else {
|
2018-03-23 12:24:31 +00:00
|
|
|
delete this.enabledHandlers[handler[this.handlerNameProperty]];
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
}
|
2018-03-22 13:51:00 +00:00
|
|
|
}).finally(() => {
|
|
|
|
// Update finished, delete the promise.
|
|
|
|
delete this.updatePromises[siteId][handler.name];
|
2018-01-15 14:57:42 +00:00
|
|
|
});
|
2018-03-22 13:51:00 +00:00
|
|
|
|
|
|
|
return this.updatePromises[siteId][handler.name];
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check if feature is enabled or disabled in the site, depending on the feature prefix and the handler name.
|
|
|
|
*
|
|
|
|
* @param {CoreDelegateHandler} handler Handler to check.
|
|
|
|
* @param {any} site Site to check.
|
|
|
|
* @return {boolean} Whether is enabled or disabled in site.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected isFeatureDisabled(handler: CoreDelegateHandler, site: any): boolean {
|
2018-01-29 09:05:20 +00:00
|
|
|
return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name);
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update the handlers for the current site.
|
|
|
|
*
|
|
|
|
* @return {Promise<void>} Resolved when done.
|
|
|
|
*/
|
2018-01-25 16:28:47 +00:00
|
|
|
protected updateHandlers(): Promise<void> {
|
2018-01-29 09:05:20 +00:00
|
|
|
const promises = [],
|
2018-01-15 14:57:42 +00:00
|
|
|
now = Date.now();
|
|
|
|
|
|
|
|
this.logger.debug('Updating handlers for current site.');
|
|
|
|
|
|
|
|
this.lastUpdateHandlersStart = now;
|
|
|
|
|
|
|
|
// Loop over all the handlers.
|
2018-01-29 09:05:20 +00:00
|
|
|
for (const name in this.handlers) {
|
2018-01-15 14:57:42 +00:00
|
|
|
promises.push(this.updateHandler(this.handlers[name], now));
|
|
|
|
}
|
|
|
|
|
|
|
|
return Promise.all(promises).then(() => {
|
|
|
|
return true;
|
|
|
|
}, () => {
|
|
|
|
// Never reject.
|
|
|
|
return true;
|
|
|
|
}).then(() => {
|
|
|
|
|
|
|
|
// Verify that this call is the last one that was started.
|
|
|
|
if (this.isLastUpdateCall(now)) {
|
|
|
|
this.updateData();
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update handlers Data.
|
|
|
|
* Override this function to update handlers data.
|
|
|
|
*/
|
2018-01-29 09:05:20 +00:00
|
|
|
updateData(): any {
|
|
|
|
// To be overridden.
|
2018-01-15 14:57:42 +00:00
|
|
|
}
|
2018-01-29 09:05:20 +00:00
|
|
|
}
|