From 26a3b4a9de7266e9111fbb20f73a280a28399a97 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 20 Sep 2019 09:27:50 +0200 Subject: [PATCH] MOBILE-2491 filter: Create filter delegate --- src/classes/delegate.ts | 22 +++ src/core/filter/filter.module.ts | 9 +- src/core/filter/providers/default-filter.ts | 64 ++++++++ src/core/filter/providers/delegate.ts | 155 ++++++++++++++++++++ 4 files changed, 248 insertions(+), 2 deletions(-) create mode 100644 src/core/filter/providers/default-filter.ts create mode 100644 src/core/filter/providers/delegate.ts diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index ca6cea0e5..db31ff907 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -79,6 +79,21 @@ export class CoreDelegate { */ protected updatePromises: {[siteId: string]: {[name: string]: Promise}} = {}; + /** + * Whether handlers have been initialized. + */ + protected handlersInitialized = false; + + /** + * Promise to wait for handlers to be initialized. + */ + protected handlersInitPromise: Promise; + + /** + * Function to resolve the handlers init promise. + */ + protected handlersInitResolve: (value?: any) => void; + /** * Constructor of the Delegate. * @@ -92,6 +107,10 @@ export class CoreDelegate { protected eventsProvider?: CoreEventsProvider) { this.logger = this.loggerProvider.getInstance(delegateName); + this.handlersInitPromise = new Promise((resolve): void => { + this.handlersInitResolve = resolve; + }); + if (eventsProvider) { // Update handlers on this cases. eventsProvider.on(CoreEventsProvider.LOGIN, this.updateHandlers.bind(this)); @@ -315,6 +334,9 @@ export class CoreDelegate { // Verify that this call is the last one that was started. if (this.isLastUpdateCall(now)) { + this.handlersInitialized = true; + this.handlersInitResolve(); + this.updateData(); } }); diff --git a/src/core/filter/filter.module.ts b/src/core/filter/filter.module.ts index f0762912f..20b0073c0 100644 --- a/src/core/filter/filter.module.ts +++ b/src/core/filter/filter.module.ts @@ -14,10 +14,13 @@ import { NgModule } from '@angular/core'; import { CoreFilterProvider } from './providers/filter'; +import { CoreFilterDelegate } from './providers/delegate'; +import { CoreFilterDefaultHandler } from './providers/default-filter'; // List of providers (without handlers). export const CORE_FILTER_PROVIDERS: any[] = [ - CoreFilterProvider + CoreFilterProvider, + CoreFilterDelegate ]; @NgModule({ @@ -26,7 +29,9 @@ export const CORE_FILTER_PROVIDERS: any[] = [ imports: [ ], providers: [ - CoreFilterProvider + CoreFilterProvider, + CoreFilterDelegate, + CoreFilterDefaultHandler ] }) export class CoreFilterModule { } diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts new file mode 100644 index 000000000..d339a4c8d --- /dev/null +++ b/src/core/filter/providers/default-filter.ts @@ -0,0 +1,64 @@ +// (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 { CoreFilterHandler } from './delegate'; +import { CoreFilterFilter } from './filter'; + +/** + * Default handler used when the module doesn't have a specific implementation. + */ +@Injectable() +export class CoreFilterDefaultHandler implements CoreFilterHandler { + name = 'CoreFilterDefaultHandler'; + filterName = 'default'; + + constructor() { + // Nothing to do. + } + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: any): string | Promise { + return text; + } + + /** + * Whether or not the handler is enabled on a site level. + * + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return true; + } + + /** + * Setup the filter to be used. + * + * Please notice this method iwill be called for each piece of text being filtered, so it is responsible + * for controlling its own execution cardinality. + * + * @param filter The filter. + * @return Promise resolved when done, or nothing if it's synchronous. + */ + setup(filter: CoreFilterFilter): void | Promise { + // Nothing to do. + } +} diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts new file mode 100644 index 000000000..4d5913c6a --- /dev/null +++ b/src/core/filter/providers/delegate.ts @@ -0,0 +1,155 @@ +// (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 '@providers/events'; +import { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreFilterFilter } from './filter'; +import { CoreFilterDefaultHandler } from './default-filter'; +import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; + +/** + * Interface that all filter handlers must implement. + */ +export interface CoreFilterHandler extends CoreDelegateHandler { + /** + * Name of the filter. It should match the "filter" field returned in core_filters_get_available_in_context. + */ + filterName: string; + + /** + * Filter some text. + * + * @param text The text to filter. + * @param filter The filter. + * @param options Options passed to the filters. + * @return Filtered text (or promise resolved with the filtered text). + */ + filter(text: string, filter: CoreFilterFilter, options: any): string | Promise; + + /** + * Setup the filter to be used. + * + * Please notice this method iwill be called for each piece of text being filtered, so it is responsible + * for controlling its own execution cardinality. + * + * @param filter The filter. + * @return Promise resolved when done, or nothing if it's synchronous. + */ + setup(filter: CoreFilterFilter): void | Promise; +} + +/** + * Delegate to register filters. + */ +@Injectable() +export class CoreFilterDelegate extends CoreDelegate { + protected featurePrefix = 'CoreFilterDelegate_'; + protected handlerNameProperty = 'filterName'; + + constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, + protected defaultHandler: CoreFilterDefaultHandler) { + super('CoreFilterDelegate', loggerProvider, sitesProvider, eventsProvider); + } + + /** + * Apply a list of filters to some content. + * + * @param text The text to filter. + * @param filters Filters to apply. + * @param options Options passed to the filters. + * @param skipFilters Names of filters that shouldn't be applied. + * @return Promise resolved with the filtered text. + */ + filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[]): Promise { + if (!text) { + return Promise.resolve(text); + } + + // Wait for filters to be initialized. + return this.handlersInitPromise.then(() => { + + let promise: Promise = Promise.resolve(text); + + filters = filters || []; + options = options || {}; + + filters.forEach((filter) => { + if (skipFilters && skipFilters.indexOf(filter.filter) != -1) { + // Skip this filter. + return; + } + + if (filter.localstate == -1 || (filter.localstate == 0 && filter.inheritedstate == -1)) { + // Filter is disabled, ignore it. + return; + } + + promise = promise.then((text) => { + return this.executeFunctionOnEnabled(filter.filter, 'filter', [text, filter, options]); + }); + }); + + return promise.then((text) => { + // Remove tags for XHTML compatibility. + text = text.replace(/<\/?nolink>/gi, ''); + + return text; + }); + }); + } + + /** + * Get filters that have an enabled handler. + * + * @param contextLevel Context level of the filters. + * @param instanceId Instance ID. + * @return Filters. + */ + getEnabledFilters(contextLevel: string, instanceId: number): CoreFilterFilter[] { + const filters: CoreFilterFilter[] = []; + + for (const name in this.enabledHandlers) { + const handler = this.enabledHandlers[name]; + + filters.push({ + contextid: -1, + contextlevel: contextLevel, + filter: handler.filterName, + inheritedstate: 1, + instanceid: instanceId, + localstate: 1 + }); + } + + return filters; + } + + /** + * Setup filters to be applied to some content. + * + * @param filters Filters to apply. + * @return Promise resolved when done. + */ + setupFilters(filters: CoreFilterFilter[]): Promise { + const promises: Promise[] = []; + + filters.forEach((filter) => { + promises.push(this.executeFunctionOnEnabled(filter.filter, 'setup', [filter])); + }); + + return Promise.all(promises); + } +}