From 456426ac6d6629457ff1e474015717fd96f0c314 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 17 Sep 2019 13:28:15 +0200 Subject: [PATCH 01/12] MOBILE-2491 filter: Create filter provider --- src/app/app.module.ts | 2 + src/core/filter/filter.module.ts | 32 +++++ src/core/filter/providers/filter.ts | 184 ++++++++++++++++++++++++++++ 3 files changed, 218 insertions(+) create mode 100644 src/core/filter/filter.module.ts create mode 100644 src/core/filter/providers/filter.ts diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 4d55ba341..740759df7 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -82,6 +82,7 @@ import { CoreCommentsModule } from '@core/comments/comments.module'; import { CoreBlockModule } from '@core/block/block.module'; import { CoreRatingModule } from '@core/rating/rating.module'; import { CoreTagModule } from '@core/tag/tag.module'; +import { CoreFilterModule } from '@core/filter/filter.module'; // Addon modules. import { AddonBadgesModule } from '@addon/badges/badges.module'; @@ -227,6 +228,7 @@ export const WP_PROVIDER: any = null; CoreRatingModule, CorePushNotificationsModule, CoreTagModule, + CoreFilterModule, AddonBadgesModule, AddonBlogModule, AddonCalendarModule, diff --git a/src/core/filter/filter.module.ts b/src/core/filter/filter.module.ts new file mode 100644 index 000000000..f0762912f --- /dev/null +++ b/src/core/filter/filter.module.ts @@ -0,0 +1,32 @@ +// (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 { CoreFilterProvider } from './providers/filter'; + +// List of providers (without handlers). +export const CORE_FILTER_PROVIDERS: any[] = [ + CoreFilterProvider +]; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreFilterProvider + ] +}) +export class CoreFilterModule { } diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts new file mode 100644 index 000000000..bdb88578e --- /dev/null +++ b/src/core/filter/providers/filter.ts @@ -0,0 +1,184 @@ +// (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 { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreSite } from '@classes/site'; +import { CoreWSExternalWarning } from '@providers/ws'; + +/** + * Service to provide filter functionalities. + */ +@Injectable() +export class CoreFilterProvider { + + protected ROOT_CACHE_KEY = 'mmFilter:'; + + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { + this.logger = logger.getInstance('CoreFilterProvider'); + } + + /** + * Returns whether or not WS get available in context is avalaible. + * + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with true if ws is avalaible, false otherwise. + * @since 3.4 + */ + canGetAvailableInContext(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.wsAvailable('core_filters_get_available_in_context'); + }); + } + + /** + * Get cache key for available in contexts WS calls. + * + * @param contexts The contexts to check. + * @return Cache key. + */ + protected getAvailableInContextsCacheKey(contexts: {contextlevel: string, instanceid: number}[]): string { + return this.getAvailableInContextsPrefixCacheKey() + JSON.stringify(contexts); + } + + /** + * Get prefixed cache key for available in contexts WS calls. + * + * @return Cache key. + */ + protected getAvailableInContextsPrefixCacheKey(): string { + return this.ROOT_CACHE_KEY + 'availableInContexts:'; + } + + /** + * Get the filters available in several contexts. + * + * @param contexts The contexts to check. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters classified by context. + */ + getAvailableInContexts(contexts: {contextlevel: string, instanceid: number}[], siteId?: string) + : Promise<{[contextlevel: string]: {[instanceid: number]: CoreFilterFilter[]}}> { + + return this.sitesProvider.getSite(siteId).then((site) => { + + const data = { + contexts: contexts, + }, + preSets = { + cacheKey: this.getAvailableInContextsCacheKey(contexts), + updateFrequency: CoreSite.FREQUENCY_RARELY + }; + + return site.read('core_filters_get_available_in_context', data, preSets) + .then((result: CoreFilterGetAvailableInContextResult) => { + + const classified: {[contextlevel: string]: {[instanceid: number]: CoreFilterFilter[]}} = {}; + + // Initialize all contexts. + contexts.forEach((context) => { + classified[context.contextlevel] = {}; + classified[context.contextlevel][context.instanceid] = []; + }); + + if (contexts.length == 1) { + // Only 1 context, no need to iterate over the filters. + classified[contexts[0].contextlevel][contexts[0].instanceid] = result.filters; + + return classified; + } + + result.filters.forEach((filter) => { + classified[filter.contextlevel][filter.instanceid].push(filter); + }); + + return classified; + }); + }); + } + + /** + * Get the filters available in a certain context. + * + * @param contextLevel The context level to check. + * @param instanceId The instance ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters. + */ + getAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + return this.getAvailableInContexts([{contextlevel: contextLevel, instanceid: instanceId}], siteId).then((result) => { + return result[contextLevel][instanceId]; + }); + } + + /** + * Invalidates all available in context WS calls. + * + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + invalidateAllAvailableInContext(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKeyStartingWith(this.getAvailableInContextsPrefixCacheKey()); + }); + } + + /** + * Invalidates available in context WS call. + * + * @param contexts The contexts to check. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + invalidateAvailableInContexts(contexts: {contextlevel: string, instanceid: number}[], siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getAvailableInContextsCacheKey(contexts)); + }); + } + + /** + * Invalidates available in context WS call. + * + * @param contextLevel The context level to check. + * @param instanceId The instance ID. + * @param siteId Site ID (empty for current site). + * @return Promise resolved when the data is invalidated. + */ + invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + return this.invalidateAvailableInContexts([{contextlevel: contextLevel, instanceid: instanceId}], siteId); + } +} + +/** + * Filter object returned by core_filters_get_available_in_context. + */ +export type CoreFilterFilter = { + contextlevel: string; // The context level where the filters are: (coursecat, course, module). + instanceid: number; // The instance id of item associated with the context. + contextid: number; // The context id. + filter: string; // Filter plugin name. + localstate: number; // Filter state: 1 for on, -1 for off, 0 if inherit. + inheritedstate: number; // 1 or 0 to use when localstate is set to inherit. +}; + +/** + * Result of core_filters_get_available_in_context. + */ +export type CoreFilterGetAvailableInContextResult = { + filters: CoreFilterFilter[]; // Available filters. + warning: CoreWSExternalWarning[]; // List of warnings. +}; From 26a3b4a9de7266e9111fbb20f73a280a28399a97 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 20 Sep 2019 09:27:50 +0200 Subject: [PATCH 02/12] 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); + } +} From 88323319bc7287469c4c395b7fac441a31ee554c Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 20 Sep 2019 11:18:36 +0200 Subject: [PATCH 03/12] MOBILE-2491 filter: Implement basic filters --- .../activitynames/activitynames.module.ts | 34 +++++ .../filter/activitynames/providers/handler.ts | 32 ++++ src/addon/filter/censor/censor.module.ts | 34 +++++ src/addon/filter/censor/providers/handler.ts | 32 ++++ src/addon/filter/data/data.module.ts | 34 +++++ src/addon/filter/data/providers/handler.ts | 32 ++++ .../emailprotect/emailprotect.module.ts | 34 +++++ .../filter/emailprotect/providers/handler.ts | 32 ++++ src/addon/filter/emoticon/emoticon.module.ts | 34 +++++ .../filter/emoticon/providers/handler.ts | 32 ++++ src/addon/filter/filter.module.ts | 47 ++++++ src/addon/filter/glossary/glossary.module.ts | 34 +++++ .../filter/glossary/providers/handler.ts | 32 ++++ .../filter/mediaplugin/mediaplugin.module.ts | 34 +++++ .../filter/mediaplugin/providers/handler.ts | 137 ++++++++++++++++++ .../filter/multilang/multilang.module.ts | 34 +++++ .../filter/multilang/providers/handler.ts | 81 +++++++++++ src/addon/filter/tex/providers/handler.ts | 32 ++++ src/addon/filter/tex/tex.module.ts | 34 +++++ src/addon/filter/tidy/providers/handler.ts | 32 ++++ src/addon/filter/tidy/tidy.module.ts | 34 +++++ .../filter/urltolink/providers/handler.ts | 32 ++++ .../filter/urltolink/urltolink.module.ts | 34 +++++ src/app/app.module.ts | 4 +- src/core/filter/providers/delegate.ts | 4 +- src/directives/format-text.ts | 85 ----------- src/providers/utils/text.ts | 1 + 27 files changed, 933 insertions(+), 88 deletions(-) create mode 100644 src/addon/filter/activitynames/activitynames.module.ts create mode 100644 src/addon/filter/activitynames/providers/handler.ts create mode 100644 src/addon/filter/censor/censor.module.ts create mode 100644 src/addon/filter/censor/providers/handler.ts create mode 100644 src/addon/filter/data/data.module.ts create mode 100644 src/addon/filter/data/providers/handler.ts create mode 100644 src/addon/filter/emailprotect/emailprotect.module.ts create mode 100644 src/addon/filter/emailprotect/providers/handler.ts create mode 100644 src/addon/filter/emoticon/emoticon.module.ts create mode 100644 src/addon/filter/emoticon/providers/handler.ts create mode 100644 src/addon/filter/filter.module.ts create mode 100644 src/addon/filter/glossary/glossary.module.ts create mode 100644 src/addon/filter/glossary/providers/handler.ts create mode 100644 src/addon/filter/mediaplugin/mediaplugin.module.ts create mode 100644 src/addon/filter/mediaplugin/providers/handler.ts create mode 100644 src/addon/filter/multilang/multilang.module.ts create mode 100644 src/addon/filter/multilang/providers/handler.ts create mode 100644 src/addon/filter/tex/providers/handler.ts create mode 100644 src/addon/filter/tex/tex.module.ts create mode 100644 src/addon/filter/tidy/providers/handler.ts create mode 100644 src/addon/filter/tidy/tidy.module.ts create mode 100644 src/addon/filter/urltolink/providers/handler.ts create mode 100644 src/addon/filter/urltolink/urltolink.module.ts diff --git a/src/addon/filter/activitynames/activitynames.module.ts b/src/addon/filter/activitynames/activitynames.module.ts new file mode 100644 index 000000000..b3e5d5da7 --- /dev/null +++ b/src/addon/filter/activitynames/activitynames.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterActivityNamesHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterActivityNamesHandler + ] +}) +export class AddonFilterActivityNamesModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterActivityNamesHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/activitynames/providers/handler.ts b/src/addon/filter/activitynames/providers/handler.ts new file mode 100644 index 000000000..8e55846bb --- /dev/null +++ b/src/addon/filter/activitynames/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Activity names filter. + */ +@Injectable() +export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterActivityNamesHandler'; + filterName = 'activitynames'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/censor/censor.module.ts b/src/addon/filter/censor/censor.module.ts new file mode 100644 index 000000000..126a7bfb2 --- /dev/null +++ b/src/addon/filter/censor/censor.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterCensorHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterCensorHandler + ] +}) +export class AddonFilterCensorModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterCensorHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/censor/providers/handler.ts b/src/addon/filter/censor/providers/handler.ts new file mode 100644 index 000000000..874d86ef9 --- /dev/null +++ b/src/addon/filter/censor/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Word censorship filter. + */ +@Injectable() +export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterCensorHandler'; + filterName = 'censor'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/data/data.module.ts b/src/addon/filter/data/data.module.ts new file mode 100644 index 000000000..1272618d8 --- /dev/null +++ b/src/addon/filter/data/data.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterDataHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterDataHandler + ] +}) +export class AddonFilterDataModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterDataHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/data/providers/handler.ts b/src/addon/filter/data/providers/handler.ts new file mode 100644 index 000000000..f01cadb1d --- /dev/null +++ b/src/addon/filter/data/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Database auto-link filter. + */ +@Injectable() +export class AddonFilterDataHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterDataHandler'; + filterName = 'data'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/emailprotect/emailprotect.module.ts b/src/addon/filter/emailprotect/emailprotect.module.ts new file mode 100644 index 000000000..d746dfc37 --- /dev/null +++ b/src/addon/filter/emailprotect/emailprotect.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterEmailProtectHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterEmailProtectHandler + ] +}) +export class AddonFilterEmailProtectModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmailProtectHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/emailprotect/providers/handler.ts b/src/addon/filter/emailprotect/providers/handler.ts new file mode 100644 index 000000000..e7971386c --- /dev/null +++ b/src/addon/filter/emailprotect/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Email protection filter. + */ +@Injectable() +export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterEmailProtectHandler'; + filterName = 'emailprotect'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/emoticon/emoticon.module.ts b/src/addon/filter/emoticon/emoticon.module.ts new file mode 100644 index 000000000..5237286f9 --- /dev/null +++ b/src/addon/filter/emoticon/emoticon.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterEmoticonHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterEmoticonHandler + ] +}) +export class AddonFilterEmoticonModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterEmoticonHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/emoticon/providers/handler.ts b/src/addon/filter/emoticon/providers/handler.ts new file mode 100644 index 000000000..39c2ffe33 --- /dev/null +++ b/src/addon/filter/emoticon/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Emoticon filter. + */ +@Injectable() +export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterEmoticonHandler'; + filterName = 'emoticon'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/filter.module.ts b/src/addon/filter/filter.module.ts new file mode 100644 index 000000000..b20447e06 --- /dev/null +++ b/src/addon/filter/filter.module.ts @@ -0,0 +1,47 @@ +// (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 { AddonFilterActivityNamesModule } from './activitynames/activitynames.module'; +import { AddonFilterCensorModule } from './censor/censor.module'; +import { AddonFilterDataModule } from './data/data.module'; +import { AddonFilterEmailProtectModule } from './emailprotect/emailprotect.module'; +import { AddonFilterEmoticonModule } from './emoticon/emoticon.module'; +import { AddonFilterGlossaryModule } from './glossary/glossary.module'; +import { AddonFilterMediaPluginModule } from './mediaplugin/mediaplugin.module'; +import { AddonFilterMultilangModule } from './multilang/multilang.module'; +import { AddonFilterTexModule } from './tex/tex.module'; +import { AddonFilterTidyModule } from './tidy/tidy.module'; +import { AddonFilterUrlToLinkModule } from './urltolink/urltolink.module'; + +@NgModule({ + declarations: [], + imports: [ + AddonFilterActivityNamesModule, + AddonFilterCensorModule, + AddonFilterDataModule, + AddonFilterEmailProtectModule, + AddonFilterEmoticonModule, + AddonFilterGlossaryModule, + AddonFilterMediaPluginModule, + AddonFilterMultilangModule, + AddonFilterTexModule, + AddonFilterTidyModule, + AddonFilterUrlToLinkModule + ], + providers: [ + ], + exports: [] +}) +export class AddonFilterModule { } diff --git a/src/addon/filter/glossary/glossary.module.ts b/src/addon/filter/glossary/glossary.module.ts new file mode 100644 index 000000000..337bffbbc --- /dev/null +++ b/src/addon/filter/glossary/glossary.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterGlossaryHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterGlossaryHandler + ] +}) +export class AddonFilterGlossaryModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterGlossaryHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/glossary/providers/handler.ts b/src/addon/filter/glossary/providers/handler.ts new file mode 100644 index 000000000..f7e38d553 --- /dev/null +++ b/src/addon/filter/glossary/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the Glossary auto-link filter. + */ +@Injectable() +export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterGlossaryHandler'; + filterName = 'glossary'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/mediaplugin/mediaplugin.module.ts b/src/addon/filter/mediaplugin/mediaplugin.module.ts new file mode 100644 index 000000000..2ef135ddd --- /dev/null +++ b/src/addon/filter/mediaplugin/mediaplugin.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterMediaPluginHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterMediaPluginHandler + ] +}) +export class AddonFilterMediaPluginModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMediaPluginHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/mediaplugin/providers/handler.ts b/src/addon/filter/mediaplugin/providers/handler.ts new file mode 100644 index 000000000..29da3de94 --- /dev/null +++ b/src/addon/filter/mediaplugin/providers/handler.ts @@ -0,0 +1,137 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter } from '@core/filter/providers/filter'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; + +/** + * Handler to support the Multimedia filter. + */ +@Injectable() +export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterMediaPluginHandler'; + filterName = 'mediaplugin'; + + constructor(private textUtils: CoreTextUtilsProvider) { + super(); + } + + /** + * 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 { + + const div = document.createElement('div'); + div.innerHTML = text; + + const videos = Array.from(div.querySelectorAll('video')); + + videos.forEach((video) => { + this.treatVideoFilters(video); + }); + + return div.innerHTML; + } + + /** + * Treat video filters. Currently only treating youtube video using video JS. + * + * @param el Video element. + * @param navCtrl NavController to use. + */ + protected treatVideoFilters(video: HTMLElement): void { + // Treat Video JS Youtube video links and translate them to iframes. + if (!video.classList.contains('video-js')) { + return; + } + + const data = this.textUtils.parseJSON(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), + youtubeData = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' && + this.parseYoutubeUrl(data.sources && data.sources[0] && data.sources[0].src); + + if (!youtubeData || !youtubeData.videoId) { + return; + } + + const iframe = document.createElement('iframe'); + iframe.id = video.id; + iframe.src = 'https://www.youtube.com/embed/' + youtubeData.videoId; // Don't apply other params to align with Moodle web. + iframe.setAttribute('frameborder', '0'); + iframe.setAttribute('allowfullscreen', '1'); + iframe.width = '100%'; + iframe.height = '300'; + + // Replace video tag by the iframe. + video.parentNode.replaceChild(iframe, video); + } + + /** + * Parse a YouTube URL. + * Based on Youtube.parseUrl from Moodle media/player/videojs/amd/src/Youtube-lazy.js + * + * @param url URL of the video. + * @return Data of the video. + */ + protected parseYoutubeUrl(url: string): {videoId: string, listId?: string, start?: number} { + const result = { + videoId: null, + listId: null, + start: null + }; + + if (!url) { + return result; + } + + url = this.textUtils.decodeHTML(url); + + // Get the video ID. + let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/); + + if (match && match[2].length === 11) { + result.videoId = match[2]; + } + + // Now get the playlist (if any). + match = url.match(/[?&]list=([^#\&\?]+)/); + + if (match && match[1]) { + result.listId = match[1]; + } + + // Now get the start time (if any). + match = url.match(/[?&]start=(\d+)/); + + if (match && match[1]) { + result.start = parseInt(match[1], 10); + } else { + // No start param, but it could have a time param. + match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/); + if (match) { + result.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) + + (match[3] ? parseInt(match[3], 10) : 0); + } + } + + return result; + } +} diff --git a/src/addon/filter/multilang/multilang.module.ts b/src/addon/filter/multilang/multilang.module.ts new file mode 100644 index 000000000..149cdfd0c --- /dev/null +++ b/src/addon/filter/multilang/multilang.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterMultilangHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterMultilangHandler + ] +}) +export class AddonFilterMultilangModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterMultilangHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/multilang/providers/handler.ts b/src/addon/filter/multilang/providers/handler.ts new file mode 100644 index 000000000..e689d9769 --- /dev/null +++ b/src/addon/filter/multilang/providers/handler.ts @@ -0,0 +1,81 @@ + +// (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 { CoreSitesProvider } from '@providers/sites'; +import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFilter } from '@core/filter/providers/filter'; +import { CoreLangProvider } from '@providers/lang'; + +/** + * Handler to support the Multilang filter. + */ +@Injectable() +export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterMultilangHandler'; + filterName = 'multilang'; + + constructor(private langProvider: CoreLangProvider, + private sitesProvider: CoreSitesProvider) { + super(); + } + + /** + * 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 { + // In Moodle versions older than 3.7, some specific content can be received unfiltered. Filter it in the app. + const currentSite = this.sitesProvider.getCurrentSite(); + + return !currentSite.isVersionGreaterEqualThan('3.7'); + } + + /** + * 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 this.langProvider.getCurrentLanguage().then((language) => { + // Match the current language. + const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; + let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); + + if (!text.match(currentLangRegEx)) { + // Current lang not found. Try to find the first language. + const matches = text.match(anyLangRegEx); + if (matches && matches[0]) { + language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; + currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); + } else { + // No multi-lang tag found, stop. + return text; + } + } + // Extract contents of current language. + text = text.replace(currentLangRegEx, '$1'); + // Delete the rest of languages + text = text.replace(anyLangRegEx, ''); + + return text; + }); + } +} diff --git a/src/addon/filter/tex/providers/handler.ts b/src/addon/filter/tex/providers/handler.ts new file mode 100644 index 000000000..a02e8e7c7 --- /dev/null +++ b/src/addon/filter/tex/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the TeX notation filter. + */ +@Injectable() +export class AddonFilterTexHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterTexHandler'; + filterName = 'tex'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/tex/tex.module.ts b/src/addon/filter/tex/tex.module.ts new file mode 100644 index 000000000..21084238b --- /dev/null +++ b/src/addon/filter/tex/tex.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterTexHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterTexHandler + ] +}) +export class AddonFilterTexModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTexHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/tidy/providers/handler.ts b/src/addon/filter/tidy/providers/handler.ts new file mode 100644 index 000000000..e0fff232d --- /dev/null +++ b/src/addon/filter/tidy/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the HTML tidy filter. + */ +@Injectable() +export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterTidyHandler'; + filterName = 'tidy'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/tidy/tidy.module.ts b/src/addon/filter/tidy/tidy.module.ts new file mode 100644 index 000000000..ab0b43098 --- /dev/null +++ b/src/addon/filter/tidy/tidy.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterTidyHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterTidyHandler + ] +}) +export class AddonFilterTidyModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterTidyHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/addon/filter/urltolink/providers/handler.ts b/src/addon/filter/urltolink/providers/handler.ts new file mode 100644 index 000000000..d23c392b3 --- /dev/null +++ b/src/addon/filter/urltolink/providers/handler.ts @@ -0,0 +1,32 @@ + +// (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 { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; + +/** + * Handler to support the URL to link and images filter. + */ +@Injectable() +export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { + name = 'AddonFilterUrlToLinkHandler'; + filterName = 'urltolink'; + + constructor() { + super(); + + // This filter is handled by Moodle, nothing to do in the app. + } +} diff --git a/src/addon/filter/urltolink/urltolink.module.ts b/src/addon/filter/urltolink/urltolink.module.ts new file mode 100644 index 000000000..afbdbf15c --- /dev/null +++ b/src/addon/filter/urltolink/urltolink.module.ts @@ -0,0 +1,34 @@ +// (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 { IonicModule } from 'ionic-angular'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; +import { AddonFilterUrlToLinkHandler } from './providers/handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + IonicModule + ], + providers: [ + AddonFilterUrlToLinkHandler + ] +}) +export class AddonFilterUrlToLinkModule { + constructor(filterDelegate: CoreFilterDelegate, handler: AddonFilterUrlToLinkHandler) { + filterDelegate.registerHandler(handler); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 740759df7..6f9966fa8 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -148,6 +148,7 @@ import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module import { AddonQbehaviourModule } from '@addon/qbehaviour/qbehaviour.module'; import { AddonQtypeModule } from '@addon/qtype/qtype.module'; import { AddonStorageManagerModule } from '@addon/storagemanager/storagemanager.module'; +import { AddonFilterModule } from '@addon/filter/filter.module'; // For translate loader. AoT requires an exported function for factories. export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { @@ -290,7 +291,8 @@ export const WP_PROVIDER: any = null; AddonRemoteThemesModule, AddonQbehaviourModule, AddonQtypeModule, - AddonStorageManagerModule + AddonStorageManagerModule, + AddonFilterModule ], bootstrap: [IonicApp], entryComponents: [ diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 4d5913c6a..4fd742262 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -74,8 +74,8 @@ export class CoreFilterDelegate extends CoreDelegate { * @return Promise resolved with the filtered text. */ filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[]): Promise { - if (!text) { - return Promise.resolve(text); + if (!text || typeof text != 'string') { + return Promise.resolve(''); } // Wait for filters to be initialized. diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index a723f2116..88fb67c20 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -425,7 +425,6 @@ export class CoreFormatTextDirective implements OnChanges { }); videos.forEach((video) => { - this.treatVideoFilters(video, navCtrl); this.treatMedia(video); }); @@ -549,40 +548,6 @@ export class CoreFormatTextDirective implements OnChanges { this.showMoreDisplayed = false; } - /** - * Treat video filters. Currently only treating youtube video using video JS. - * - * @param el Video element. - * @param navCtrl NavController to use. - */ - protected treatVideoFilters(video: HTMLElement, navCtrl: NavController): void { - // Treat Video JS Youtube video links and translate them to iframes. - if (!video.classList.contains('video-js')) { - return; - } - - const data = this.textUtils.parseJSON(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), - youtubeData = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' && - this.parseYoutubeUrl(data.sources && data.sources[0] && data.sources[0].src); - - if (!youtubeData || !youtubeData.videoId) { - return; - } - - const iframe = document.createElement('iframe'); - iframe.id = video.id; - iframe.src = 'https://www.youtube.com/embed/' + youtubeData.videoId; // Don't apply other params to align with Moodle web. - iframe.setAttribute('frameborder', '0'); - iframe.setAttribute('allowfullscreen', '1'); - iframe.width = '100%'; - iframe.height = '300'; - - // Replace video tag by the iframe. - video.parentNode.replaceChild(iframe, video); - - this.iframeUtils.treatFrame(iframe, false, navCtrl); - } - /** * Add media adapt class and apply CoreExternalContentDirective to the media element and its sources and tracks. * @@ -693,54 +658,4 @@ export class CoreFormatTextDirective implements OnChanges { this.iframeUtils.treatFrame(iframe, false, navCtrl); } - - /** - * Parse a YouTube URL. - * Based on Youtube.parseUrl from Moodle media/player/videojs/amd/src/Youtube-lazy.js - * - * @param url URL of the video. - */ - protected parseYoutubeUrl(url: string): {videoId: string, listId?: string, start?: number} { - const result = { - videoId: null, - listId: null, - start: null - }; - - if (!url) { - return result; - } - - url = this.textUtils.decodeHTML(url); - - // Get the video ID. - let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/); - - if (match && match[2].length === 11) { - result.videoId = match[2]; - } - - // Now get the playlist (if any). - match = url.match(/[?&]list=([^#\&\?]+)/); - - if (match && match[1]) { - result.listId = match[1]; - } - - // Now get the start time (if any). - match = url.match(/[?&]start=(\d+)/); - - if (match && match[1]) { - result.start = parseInt(match[1], 10); - } else { - // No start param, but it could have a time param. - match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/); - if (match) { - result.start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) + (match[2] ? parseInt(match[2], 10) * 60 : 0) + - (match[3] ? parseInt(match[3], 10) : 0); - } - } - - return result; - } } diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 9a6b7f005..262662da6 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -732,6 +732,7 @@ export class CoreTextUtilsProvider { * * @param text The text to be treated. * @return Promise resolved with the formatted text. + * @deprecated since 3.8.0. Now this is handled by AddonFilterMultilangHandler. */ treatMultilangTags(text: string): Promise { if (!text || typeof text != 'string') { From 952ce4939b5d4b9ce659c6b2af02610f79034825 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 30 Sep 2019 09:05:49 +0200 Subject: [PATCH 04/12] MOBILE-2491 filter: Apply filters to all views that need it --- .../pages/issued-badge/issued-badge.html | 36 ++- .../badges/pages/user-badges/user-badges.html | 2 +- .../addon-block-activitymodules.html | 2 +- .../addon-block-recentlyaccesseditems.html | 4 +- .../addon-block-sitemainmenu.html | 2 +- .../components/sitemainmenu/sitemainmenu.ts | 1 + .../events/addon-block-timeline-events.html | 4 +- .../entries/addon-blog-entries.html | 4 +- src/addon/blog/components/entries/entries.ts | 30 +++ .../addon-calendar-upcoming-events.html | 4 +- src/addon/calendar/pages/day/day.html | 4 +- .../calendar/pages/edit-event/edit-event.ts | 7 +- src/addon/calendar/pages/event/event.html | 14 +- src/addon/calendar/pages/event/event.ts | 3 + src/addon/calendar/pages/list/list.html | 2 +- src/addon/calendar/providers/helper.ts | 19 +- .../course/addon-competency-course.html | 12 +- .../competency/components/course/course.ts | 6 +- .../pages/competency/competency.html | 8 +- .../competency/pages/competency/competency.ts | 17 +- .../competencysummary/competencysummary.html | 2 +- .../competencysummary/competencysummary.ts | 20 +- src/addon/competency/pages/plan/plan.html | 4 +- src/addon/competency/providers/competency.ts | 4 +- .../addon-course-completion-report.html | 12 +- src/addon/files/pages/list/list.html | 2 +- .../addon-messages-confirmed-contacts.html | 2 +- .../addon-messages-contact-requests.html | 2 +- .../contacts/addon-messages-contacts.html | 2 +- .../addon-messages-discussions.html | 10 +- .../components/discussions/discussions.ts | 2 + .../conversation-info/conversation-info.html | 6 +- .../conversation-info/conversation-info.ts | 5 +- .../messages/pages/discussion/discussion.html | 4 +- .../messages/pages/discussion/discussion.ts | 2 + .../group-conversations.html | 8 +- .../group-conversations.ts | 2 + src/addon/messages/pages/search/search.html | 4 +- src/addon/messages/pages/search/search.ts | 3 + .../messages/providers/mainmenu-handler.ts | 7 +- .../addon-mod-assign-feedback-plugin.html | 2 +- .../index/addon-mod-assign-index.html | 2 +- .../mod/assign/components/index/index.ts | 2 +- .../addon-mod-assign-submission-plugin.html | 2 +- .../addon-mod-assign-submission.html | 8 +- .../addon-mod-assign-feedback-comments.html | 2 +- .../feedback/comments/component/comments.ts | 3 +- .../edit-feedback-modal.html | 2 +- src/addon/mod/assign/pages/edit/edit.html | 4 +- src/addon/mod/assign/pages/index/index.html | 2 +- .../submission-list/submission-list.html | 2 +- .../submission-review/submission-review.html | 2 +- ...ddon-mod-assign-submission-onlinetext.html | 2 +- .../onlinetext/component/onlinetext.ts | 3 +- .../index/addon-mod-book-index.html | 4 +- src/addon/mod/book/components/index/index.ts | 1 + src/addon/mod/book/pages/index/index.html | 2 +- src/addon/mod/book/pages/toc/toc.html | 2 +- src/addon/mod/book/pages/toc/toc.ts | 2 + .../index/addon-mod-chat-index.html | 2 +- src/addon/mod/chat/components/index/index.ts | 7 +- src/addon/mod/chat/pages/chat/chat.html | 6 +- src/addon/mod/chat/pages/chat/chat.ts | 3 + src/addon/mod/chat/pages/index/index.html | 2 +- .../session-messages/session-messages.html | 4 +- .../session-messages/session-messages.ts | 6 +- src/addon/mod/chat/pages/sessions/sessions.ts | 3 +- src/addon/mod/chat/pages/users/users.html | 2 +- .../index/addon-mod-choice-index.html | 10 +- src/addon/mod/choice/pages/index/index.html | 2 +- .../index/addon-mod-data-index.html | 2 +- .../addon-mod-data-field-checkbox.html | 2 +- .../component/addon-mod-data-field-date.html | 2 +- .../component/addon-mod-data-field-menu.html | 2 +- .../addon-mod-data-field-multimenu.html | 2 +- .../addon-mod-data-field-number.html | 2 +- .../addon-mod-data-field-radiobutton.html | 2 +- .../component/addon-mod-data-field-text.html | 2 +- .../addon-mod-data-field-textarea.html | 2 +- src/addon/mod/data/pages/edit/edit.html | 2 +- src/addon/mod/data/pages/entry/entry.html | 2 +- src/addon/mod/data/pages/index/index.html | 2 +- .../index/addon-mod-feedback-index.html | 12 +- .../mod/feedback/pages/attempt/attempt.html | 9 +- src/addon/mod/feedback/pages/form/form.html | 14 +- src/addon/mod/feedback/pages/index/index.html | 2 +- .../pages/nonrespondents/nonrespondents.html | 2 +- .../pages/respondents/respondents.html | 2 +- .../index/addon-mod-folder-index.html | 2 +- src/addon/mod/folder/pages/index/index.html | 2 +- .../index/addon-mod-forum-index.html | 6 +- .../components/post/addon-mod-forum-post.html | 4 +- .../forum/pages/discussion/discussion.html | 2 +- src/addon/mod/forum/pages/index/index.html | 2 +- .../sort-order-selector.html | 2 +- .../index/addon-mod-glossary-index.html | 2 +- .../mod/glossary/components/index/index.ts | 2 + src/addon/mod/glossary/pages/edit/edit.html | 2 +- src/addon/mod/glossary/pages/entry/entry.html | 10 +- src/addon/mod/glossary/pages/index/index.html | 2 +- .../index/addon-mod-imscp-index.html | 3 +- src/addon/mod/imscp/pages/index/index.html | 2 +- .../index/addon-mod-lesson-index.html | 4 +- src/addon/mod/lesson/pages/index/index.html | 2 +- .../lesson/pages/menu-modal/menu-modal.html | 2 +- src/addon/mod/lesson/pages/player/player.html | 50 ++-- .../lesson/pages/user-retake/user-retake.html | 22 +- .../components/index/addon-mod-lti-index.html | 2 +- src/addon/mod/lti/pages/index/index.html | 2 +- .../index/addon-mod-page-index.html | 4 +- src/addon/mod/page/pages/index/index.html | 2 +- .../index/addon-mod-quiz-index.html | 6 +- src/addon/mod/quiz/pages/attempt/attempt.html | 4 +- src/addon/mod/quiz/pages/index/index.html | 2 +- src/addon/mod/quiz/pages/player/player.html | 6 +- src/addon/mod/quiz/pages/review/review.html | 8 +- .../index/addon-mod-resource-index.html | 4 +- src/addon/mod/resource/pages/index/index.html | 2 +- .../index/addon-mod-scorm-index.html | 6 +- src/addon/mod/scorm/pages/index/index.html | 2 +- src/addon/mod/scorm/pages/player/player.html | 2 +- src/addon/mod/scorm/pages/player/player.ts | 3 +- src/addon/mod/scorm/pages/toc/toc.html | 2 +- src/addon/mod/scorm/pages/toc/toc.ts | 2 + .../index/addon-mod-survey-index.html | 4 +- src/addon/mod/survey/pages/index/index.html | 2 +- .../components/index/addon-mod-url-index.html | 2 +- src/addon/mod/url/pages/index/index.html | 2 +- .../index/addon-mod-wiki-index.html | 4 +- src/addon/mod/wiki/components/index/index.ts | 3 +- src/addon/mod/wiki/pages/edit/edit.html | 2 +- src/addon/mod/wiki/pages/index/index.html | 2 +- src/addon/mod/wiki/pages/map/map.html | 2 +- src/addon/mod/wiki/pages/map/map.ts | 2 + ...shop-assessment-strategy-accumulative.html | 4 +- ...workshop-assessment-strategy-comments.html | 4 +- ...orkshop-assessment-strategy-numerrors.html | 8 +- ...d-workshop-assessment-strategy-rubric.html | 4 +- .../classes/assessment-strategy-component.ts | 1 + ...ddon-mod-workshop-assessment-strategy.html | 2 +- .../assessment-strategy.ts | 4 +- .../index/addon-mod-workshop-index.html | 14 +- .../addon-mod-workshop-submission.html | 8 +- .../workshop/pages/assessment/assessment.html | 4 +- .../edit-submission/edit-submission.html | 2 +- src/addon/mod/workshop/pages/index/index.html | 2 +- src/addon/mod/workshop/pages/phase/phase.html | 2 +- .../workshop/pages/submission/submission.html | 6 +- .../components/list/addon-notes-list.html | 4 +- src/addon/notes/components/list/list.ts | 4 + src/addon/notifications/pages/list/list.html | 6 +- src/addon/notifications/pages/list/list.ts | 3 + .../addon-qbehaviour-deferredcbm.html | 4 +- .../deferredcbm/component/deferredcbm.ts | 2 + .../component/informationitem.ts | 2 + .../component/addon-qtype-calculated.html | 6 +- .../component/addon-qtype-ddimageortext.html | 4 +- .../component/addon-qtype-ddmarker.html | 4 +- .../ddwtos/component/addon-qtype-ddwtos.html | 4 +- src/addon/qtype/ddwtos/component/ddwtos.ts | 3 +- .../component/addon-qtype-description.html | 2 +- .../essay/component/addon-qtype-essay.html | 6 +- .../component/addon-qtype-gapselect.html | 2 +- .../qtype/gapselect/component/gapselect.ts | 3 +- .../match/component/addon-qtype-match.html | 4 +- .../component/addon-qtype-multianswer.html | 2 +- .../multianswer/component/multianswer.ts | 3 +- .../component/addon-qtype-multichoice.html | 12 +- .../component/addon-qtype-shortanswer.html | 2 +- .../checkbox/component/checkbox.ts | 2 + .../datetime/component/datetime.ts | 2 + .../addon-user-profile-field-menu.html | 2 +- .../userprofilefield/menu/component/menu.ts | 2 + .../addon-user-profile-field-text.html | 2 +- .../userprofilefield/text/component/text.ts | 2 + .../addon-user-profile-field-textarea.html | 2 +- .../textarea/component/textarea.ts | 2 + .../core-context-menu-popover.html | 2 +- .../core-course-picker-menu-popover.html | 2 +- .../navigation-bar/navigation-bar.ts | 5 +- src/components/site-picker/site-picker.ts | 7 +- .../core-block-only-title.html | 2 +- .../core-block-pre-rendered.html | 6 +- src/core/comments/pages/viewer/viewer.html | 6 +- .../pages/choose-site/choose-site.html | 2 +- .../course/classes/main-resource-component.ts | 3 +- .../components/format/core-course-format.html | 8 +- .../module-completion/module-completion.ts | 13 +- .../core-course-module-description.html | 2 +- .../module-description/module-description.ts | 2 + .../components/module/core-course-module.html | 10 +- .../core-course-unsupported-module.html | 2 +- .../pages/list-mod-type/list-mod-type.html | 2 +- .../section-selector/section-selector.html | 4 +- .../section-selector/section-selector.ts | 3 + src/core/course/pages/section/section.html | 2 +- src/core/course/pages/section/section.ts | 7 +- .../unsupported-module.html | 2 +- .../unsupported-module/unsupported-module.ts | 3 +- .../core-courses-course-list-item.html | 2 +- .../core-courses-course-progress.html | 4 +- .../courses/pages/categories/categories.html | 8 +- .../pages/course-preview/course-preview.html | 12 +- .../courses/pages/dashboard/dashboard.html | 2 +- src/core/courses/pages/dashboard/dashboard.ts | 2 + src/core/filter/providers/filter.ts | 223 +++++++++++++++++- src/core/grades/pages/courses/courses.html | 2 +- src/core/grades/pages/grade/grade.html | 22 +- .../login/pages/credentials/credentials.html | 4 +- .../pages/email-signup/email-signup.html | 4 +- src/core/login/pages/reconnect/reconnect.html | 2 +- .../login/pages/site-error/site-error.html | 2 +- src/core/login/pages/site-help/site-help.html | 2 +- src/core/login/pages/sites/sites.html | 2 +- src/core/login/pages/sites/sites.ts | 8 +- src/core/mainmenu/pages/more/more.html | 2 +- src/core/mainmenu/pages/more/more.ts | 2 + .../providers/pushnotifications.ts | 14 +- .../classes/base-question-component.ts | 2 + .../components/question/core-question.html | 4 +- .../question/components/question/question.ts | 4 + src/core/question/providers/helper.ts | 9 +- src/core/rating/pages/ratings/ratings.html | 2 +- .../pages/space-usage/space-usage.html | 2 +- .../settings/pages/space-usage/space-usage.ts | 8 +- .../synchronization/synchronization.html | 2 +- .../pages/choose-site/choose-site.html | 2 +- src/core/sharedfiles/pages/list/list.html | 2 +- .../components/index/core-sitehome-index.html | 2 +- .../components/module-index/module-index.ts | 3 +- .../participants/core-user-participants.html | 2 +- .../user-profile-field/user-profile-field.ts | 4 + src/core/user/pages/about/about.html | 25 +- src/core/user/pages/profile/profile.html | 8 +- src/core/viewer/pages/text/text.html | 2 +- src/core/viewer/pages/text/text.ts | 6 + src/directives/format-text.ts | 38 ++- src/providers/sites.ts | 8 +- src/providers/utils/text.ts | 12 +- 239 files changed, 901 insertions(+), 475 deletions(-) diff --git a/src/addon/badges/pages/issued-badge/issued-badge.html b/src/addon/badges/pages/issued-badge/issued-badge.html index 0dcc1e810..75ee12adf 100644 --- a/src/addon/badges/pages/issued-badge/issued-badge.html +++ b/src/addon/badges/pages/issued-badge/issued-badge.html @@ -24,9 +24,7 @@

{{ 'core.name' | translate}}

-

- -

+

{{ user.fullname }}

@@ -36,14 +34,12 @@

{{ 'addon.badges.issuername' | translate}}

-

- -

+

{{ badge.issuername }}

{{ 'addon.badges.contact' | translate}}

- + {{ badge.issuercontact }}

@@ -66,9 +62,7 @@

{{ 'core.description' | translate}}

-

- -

+

{{ badge.description }}

{{ 'addon.badges.imageauthorname' | translate}}

@@ -77,23 +71,23 @@

{{ 'addon.badges.imageauthoremail' | translate}}

- + {{ badge.imageauthoremail }}

{{ 'addon.badges.imageauthorurl' | translate}}

- + {{ badge.imageauthorurl }}

{{ 'addon.badges.imagecaption' | translate}}

-

+

{{ badge.imagecaption }}

{{ 'core.course' | translate}}

- +

@@ -131,13 +125,13 @@

{{ 'addon.badges.issueremail' | translate}}

- + {{ badge.endorsement.issueremail }}

{{ 'addon.badges.issuerurl' | translate}}

- + {{ badge.endorsement.issuerurl }}

@@ -147,14 +141,12 @@

{{ 'addon.badges.claimid' | translate}}

- + {{ badge.endorsement.claimid }}

{{ 'addon.badges.claimcomment' | translate}}

-

- -

+

{{ badge.endorsement.claimcomment }}

@@ -164,7 +156,7 @@

{{ 'addon.badges.relatedbages' | translate}}

-

+

<{{ relatedBadge.name }}

{{ 'addon.badges.norelated' | translate}}

@@ -177,7 +169,7 @@

{{ 'addon.badges.alignment' | translate}}

-

+

{{ alignment.targetname }}

{{ 'addon.badges.noalignment' | translate}}

diff --git a/src/addon/badges/pages/user-badges/user-badges.html b/src/addon/badges/pages/user-badges/user-badges.html index 6a2a33a24..315e9af08 100644 --- a/src/addon/badges/pages/user-badges/user-badges.html +++ b/src/addon/badges/pages/user-badges/user-badges.html @@ -17,7 +17,7 @@ -

+

{{ badge.name }}

{{ badge.dateissued * 1000 | coreFormatDate :'strftimedatetimeshort' }}

{{ 'addon.badges.expired' | translate }} diff --git a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html index 7711b65f6..342e38924 100644 --- a/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html +++ b/src/addon/block/activitymodules/components/activitymodules/addon-block-activitymodules.html @@ -4,6 +4,6 @@ - + {{ entry.name }} diff --git a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html index f6e60ba65..dbf92f587 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html @@ -7,8 +7,8 @@ -

-

+

+

diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html index f9a5b7f82..49dfb8748 100644 --- a/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/addon-block-sitemainmenu.html @@ -4,7 +4,7 @@ - + diff --git a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts index 8568c8225..20651772a 100644 --- a/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts +++ b/src/addon/block/sitemainmenu/components/sitemainmenu/sitemainmenu.ts @@ -30,6 +30,7 @@ import { CoreBlockBaseComponent } from '@core/block/classes/base-block-component export class AddonBlockSiteMainMenuComponent extends CoreBlockBaseComponent implements OnInit { @Input() downloadEnabled: boolean; + component = 'AddonBlockSiteMainMenu'; mainMenuBlock: any; siteHomeId: number; diff --git a/src/addon/block/timeline/components/events/addon-block-timeline-events.html b/src/addon/block/timeline/components/events/addon-block-timeline-events.html index 46af070cc..22e5445f7 100644 --- a/src/addon/block/timeline/components/events/addon-block-timeline-events.html +++ b/src/addon/block/timeline/components/events/addon-block-timeline-events.html @@ -5,9 +5,9 @@ -

+

- +

diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index 5954398ff..2c9fc51e0 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -1,6 +1,6 @@ - + @@ -12,16 +12,16 @@ -

+

{{ entry.timemodified | coreDateDayOrTime }} -

+

{{ entry.userfullname }}

-

+

{{ entry.timemodified | coreDateDayOrTime }}
- +
diff --git a/src/addon/mod/glossary/pages/index/index.html b/src/addon/mod/glossary/pages/index/index.html index f33735512..35ce44b11 100644 --- a/src/addon/mod/glossary/pages/index/index.html +++ b/src/addon/mod/glossary/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index e8c542dfa..435cf919a 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -16,8 +16,7 @@
- - +
diff --git a/src/addon/mod/imscp/pages/index/index.html b/src/addon/mod/imscp/pages/index/index.html index 8d8662c3d..f31c63425 100644 --- a/src/addon/mod/imscp/pages/index/index.html +++ b/src/addon/mod/imscp/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html index 655de6301..98c3eaf5d 100644 --- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html @@ -18,12 +18,12 @@ - +
- +
diff --git a/src/addon/mod/lesson/pages/index/index.html b/src/addon/mod/lesson/pages/index/index.html index e09f2c477..d90832113 100644 --- a/src/addon/mod/lesson/pages/index/index.html +++ b/src/addon/mod/lesson/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html index 5744f07bd..deeeaca2c 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html @@ -26,7 +26,7 @@ diff --git a/src/addon/mod/lesson/pages/player/player.html b/src/addon/mod/lesson/pages/player/player.html index 6c83c1f4b..5e351352b 100644 --- a/src/addon/mod/lesson/pages/player/player.html +++ b/src/addon/mod/lesson/pages/player/player.html @@ -1,6 +1,6 @@ - +
diff --git a/src/addon/mod/quiz/pages/review/review.html b/src/addon/mod/quiz/pages/review/review.html index 4d398ed27..15bafe825 100644 --- a/src/addon/mod/quiz/pages/review/review.html +++ b/src/addon/mod/quiz/pages/review/review.html @@ -46,7 +46,7 @@

{{ 'addon.mod_quiz.marks' | translate }}

-

+

<{{ attempt.readableMark }}

{{ 'addon.mod_quiz.grade' | translate }}

@@ -54,7 +54,7 @@

{{ data.title }}

- +
@@ -73,11 +73,11 @@

{{ 'core.question.information' | translate }}

{{question.status}}

-

+

{{question.readableMark}}

- + diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index 43e4c906f..d8172c08b 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -13,14 +13,14 @@ - +
- +
diff --git a/src/addon/mod/resource/pages/index/index.html b/src/addon/mod/resource/pages/index/index.html index bae9e1278..59b962225 100644 --- a/src/addon/mod/resource/pages/index/index.html +++ b/src/addon/mod/resource/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html index 959fd819f..8d86b0309 100644 --- a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html @@ -14,7 +14,7 @@ - +
@@ -98,8 +98,8 @@

- {{ sco.title }} - {{ sco.title }} + +

diff --git a/src/addon/mod/scorm/pages/index/index.html b/src/addon/mod/scorm/pages/index/index.html index f8a5f7d42..1bd6f15a8 100644 --- a/src/addon/mod/scorm/pages/index/index.html +++ b/src/addon/mod/scorm/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/scorm/pages/player/player.html b/src/addon/mod/scorm/pages/player/player.html index bdf47bd53..848a18bc9 100644 --- a/src/addon/mod/scorm/pages/player/player.html +++ b/src/addon/mod/scorm/pages/player/player.html @@ -1,6 +1,6 @@ - + - - - + {{ note.content }} diff --git a/src/addon/notes/components/list/list.ts b/src/addon/notes/components/list/list.ts index 57a9de314..843ff1c39 100644 --- a/src/addon/notes/components/list/list.ts +++ b/src/addon/notes/components/list/list.ts @@ -103,6 +103,10 @@ export class AddonNotesListComponent implements OnInit, OnDestroy { return this.notesProvider.getNotes(this.courseId, this.userId).then((notes) => { const notesList: AddonNotesNoteFormatted[] = notes[this.type + 'notes'] || []; + notesList.forEach((note) => { + note.content = this.textUtils.decodeHTML(note.content); + }); + return this.notesProvider.setOfflineDeletedNotes(notesList, this.courseId).then((notesList) => { this.hasOffline = notesList.some((note) => note.offline || note.deleted); diff --git a/src/addon/notifications/pages/list/list.html b/src/addon/notifications/pages/list/list.html index bd523d6ba..72b4cdce1 100644 --- a/src/addon/notifications/pages/list/list.html +++ b/src/addon/notifications/pages/list/list.html @@ -21,16 +21,16 @@ -

+

{{ notification.subject }}

{{notification.timecreated | coreDateDayOrTime}}

-

+

{{ notification.userfromfullname }}

-

+

diff --git a/src/addon/notifications/pages/list/list.ts b/src/addon/notifications/pages/list/list.ts index 2046b00bc..70342f5e3 100644 --- a/src/addon/notifications/pages/list/list.ts +++ b/src/addon/notifications/pages/list/list.ts @@ -40,6 +40,7 @@ export class AddonNotificationsListPage { loadMoreError = false; canMarkAllNotificationsAsRead = false; loadingMarkAllNotificationsAsRead = false; + siteHomeId: number; protected isCurrentView: boolean; protected cronObserver: CoreEventObserver; @@ -51,6 +52,8 @@ export class AddonNotificationsListPage { private utils: CoreUtilsProvider, private notificationsProvider: AddonNotificationsProvider, private pushNotificationsDelegate: CorePushNotificationsDelegate, private notificationsHelper: AddonNotificationsHelperProvider) { + + this.siteHomeId = sitesProvider.getCurrentSite().getSiteHomeId(); } /** diff --git a/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html b/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html index 0975635c1..a141d0e03 100644 --- a/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html +++ b/src/addon/qbehaviour/deferredcbm/component/addon-qbehaviour-deferredcbm.html @@ -4,9 +4,7 @@
- - - + {{ option.text }}
diff --git a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts index d85eccf09..dcb4c1310 100644 --- a/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts +++ b/src/addon/qbehaviour/deferredcbm/component/deferredcbm.ts @@ -27,6 +27,8 @@ export class AddonQbehaviourDeferredCBMComponent { @Input() componentId: number; // ID of the component the question belongs to. @Input() attemptId: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Should emit an event if the question should be aborted. diff --git a/src/addon/qbehaviour/informationitem/component/informationitem.ts b/src/addon/qbehaviour/informationitem/component/informationitem.ts index 3842fca29..aaa44569e 100644 --- a/src/addon/qbehaviour/informationitem/component/informationitem.ts +++ b/src/addon/qbehaviour/informationitem/component/informationitem.ts @@ -27,6 +27,8 @@ export class AddonQbehaviourInformationItemComponent { @Input() componentId: number; // ID of the component the question belongs to. @Input() attemptId: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Should emit an event if the question should be aborted. diff --git a/src/addon/qtype/calculated/component/addon-qtype-calculated.html b/src/addon/qtype/calculated/component/addon-qtype-calculated.html index e2ea63c89..beb823202 100644 --- a/src/addon/qtype/calculated/component/addon-qtype-calculated.html +++ b/src/addon/qtype/calculated/component/addon-qtype-calculated.html @@ -1,6 +1,6 @@
-

+

@@ -55,9 +55,7 @@
- - - + {{ option.text }} diff --git a/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html b/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html index 218b25b24..e1e79fdc3 100644 --- a/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html +++ b/src/addon/qtype/ddimageortext/component/addon-qtype-ddimageortext.html @@ -7,7 +7,7 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+
diff --git a/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html b/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html index 199abc85d..58763a33e 100644 --- a/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html +++ b/src/addon/qtype/ddmarker/component/addon-qtype-ddmarker.html @@ -7,7 +7,7 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+ diff --git a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html index 67c15fdfd..ae2f63831 100644 --- a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html +++ b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html @@ -7,8 +7,8 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

- +

+
diff --git a/src/addon/qtype/ddwtos/component/ddwtos.ts b/src/addon/qtype/ddwtos/component/ddwtos.ts index b144a4013..83190e29e 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.ts +++ b/src/addon/qtype/ddwtos/component/ddwtos.ts @@ -95,7 +95,8 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme this.questionInstance = new AddonQtypeDdwtosQuestion(this.loggerProvider, this.domUtils, this.element, this.question, this.question.readOnly, this.inputIds, this.textUtils); - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId); this.question.loaded = true; }); diff --git a/src/addon/qtype/description/component/addon-qtype-description.html b/src/addon/qtype/description/component/addon-qtype-description.html index 74d4f190d..3b54888e9 100644 --- a/src/addon/qtype/description/component/addon-qtype-description.html +++ b/src/addon/qtype/description/component/addon-qtype-description.html @@ -2,6 +2,6 @@ -

+

diff --git a/src/addon/qtype/essay/component/addon-qtype-essay.html b/src/addon/qtype/essay/component/addon-qtype-essay.html index ec4293251..b8df1320e 100644 --- a/src/addon/qtype/essay/component/addon-qtype-essay.html +++ b/src/addon/qtype/essay/component/addon-qtype-essay.html @@ -1,7 +1,7 @@
-

+

@@ -20,7 +20,7 @@

{{ 'core.question.errorinlinefilesnotsupported' | translate }}

-

+

@@ -31,7 +31,7 @@ -

+

diff --git a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html index 70efb95b7..099c6d926 100644 --- a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html +++ b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html @@ -1,5 +1,5 @@
-

+

diff --git a/src/addon/qtype/gapselect/component/gapselect.ts b/src/addon/qtype/gapselect/component/gapselect.ts index 34de237db..2faa3ada4 100644 --- a/src/addon/qtype/gapselect/component/gapselect.ts +++ b/src/addon/qtype/gapselect/component/gapselect.ts @@ -44,6 +44,7 @@ export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent impl * The question has been rendered. */ questionRendered(): void { - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId); } } diff --git a/src/addon/qtype/match/component/addon-qtype-match.html b/src/addon/qtype/match/component/addon-qtype-match.html index 95bed5ddc..2f48f297d 100644 --- a/src/addon/qtype/match/component/addon-qtype-match.html +++ b/src/addon/qtype/match/component/addon-qtype-match.html @@ -1,12 +1,12 @@
- + -

+

diff --git a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html index 74bc91da4..68b989b11 100644 --- a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html +++ b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html @@ -1,5 +1,5 @@
- +
diff --git a/src/addon/qtype/multianswer/component/multianswer.ts b/src/addon/qtype/multianswer/component/multianswer.ts index 5fa7ce6ca..d19750434 100644 --- a/src/addon/qtype/multianswer/component/multianswer.ts +++ b/src/addon/qtype/multianswer/component/multianswer.ts @@ -44,6 +44,7 @@ export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent im * The question has been rendered. */ questionRendered(): void { - this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId); + this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, + this.contextInstanceId); } } diff --git a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html index 56ac05cc3..2914b227a 100644 --- a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html +++ b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html @@ -1,16 +1,16 @@
- - + + {{ question.prompt }} - -
+ +
@@ -25,8 +25,8 @@
- -
+ +
diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index 42febebf0..ee269de08 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -1,6 +1,6 @@
- + {{ 'addon.mod_quiz.answercolon' | translate }} diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts index 0212563d4..80a8143ab 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.ts +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -28,6 +28,8 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index e1b287627..a9b66bf1f 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -30,6 +30,8 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider, private translate: TranslateService) { } diff --git a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html index dcde5daa8..d46a4dbd6 100644 --- a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html +++ b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index 59642861a..89f82faa0 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -27,6 +27,8 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder) { } diff --git a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html index 1ca1a727d..5c9a460eb 100644 --- a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html +++ b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index 089a58bb0..c25ff38d9 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -28,6 +28,8 @@ export class AddonUserProfileFieldTextComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } diff --git a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html index 4a993015b..a9e60d41d 100644 --- a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html +++ b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index 79919bd35..475e1e717 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -27,6 +27,8 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. control: FormControl; diff --git a/src/components/context-menu/core-context-menu-popover.html b/src/components/context-menu/core-context-menu-popover.html index 69ffdadf7..76a6b96d1 100644 --- a/src/components/context-menu/core-context-menu-popover.html +++ b/src/components/context-menu/core-context-menu-popover.html @@ -2,7 +2,7 @@ {{title}} - + {{item.badge}} diff --git a/src/components/course-picker-menu/core-course-picker-menu-popover.html b/src/components/course-picker-menu/core-course-picker-menu-popover.html index 6eea11d5e..ebfa0f52a 100644 --- a/src/components/course-picker-menu/core-course-picker-menu-popover.html +++ b/src/components/course-picker-menu/core-course-picker-menu-popover.html @@ -1,6 +1,6 @@ - + \ No newline at end of file diff --git a/src/components/navigation-bar/navigation-bar.ts b/src/components/navigation-bar/navigation-bar.ts index b5c2b0b42..5a38d49a0 100644 --- a/src/components/navigation-bar/navigation-bar.ts +++ b/src/components/navigation-bar/navigation-bar.ts @@ -35,6 +35,8 @@ export class CoreNavigationBarComponent { @Input() title?: string; // Title to show when seeing the info (new page). @Input() component?: string; // Component the bar belongs to. @Input() componentId?: number; // Component ID. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() action?: EventEmitter; // Function to call when an arrow is clicked. Will receive as a param the item to load. constructor(private textUtils: CoreTextUtilsProvider) { @@ -42,6 +44,7 @@ export class CoreNavigationBarComponent { } showInfo(): void { - this.textUtils.expandText(this.title, this.info, this.component, this.componentId); + this.textUtils.expandText(this.title, this.info, this.component, this.componentId, [], true, this.contextLevel, + this.contextInstanceId); } } diff --git a/src/components/site-picker/site-picker.ts b/src/components/site-picker/site-picker.ts index e58dfda86..c0ca1d6e1 100644 --- a/src/components/site-picker/site-picker.ts +++ b/src/components/site-picker/site-picker.ts @@ -15,7 +15,7 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; /** * Component to display a site selector. It will display a select with the list of sites. If the selected site changes, @@ -36,7 +36,7 @@ export class CoreSitePickerComponent implements OnInit { sites: any[]; constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider) { + private filterProvider: CoreFilterProvider) { this.siteSelected = new EventEmitter(); } @@ -49,7 +49,8 @@ export class CoreSitePickerComponent implements OnInit { sites.forEach((site: any) => { // Format the site name. - promises.push(this.textUtils.formatText(site.siteName, true, true).catch(() => { + promises.push(this.filterProvider.getFiltersAndFormatText(site.siteName, 'system', site.siteHomeId, + {clean: true, singleLine: true}, site.getId()).catch(() => { return site.siteName; }).then((formatted) => { site.fullNameAndSiteName = this.translate.instant('core.fullnameandsitename', diff --git a/src/core/block/components/only-title-block/core-block-only-title.html b/src/core/block/components/only-title-block/core-block-only-title.html index 358fbde44..287592371 100644 --- a/src/core/block/components/only-title-block/core-block-only-title.html +++ b/src/core/block/components/only-title-block/core-block-only-title.html @@ -1,3 +1,3 @@ -

+

{{ title | translate }}

\ No newline at end of file diff --git a/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html b/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html index 84cf2ac52..4b3f46315 100644 --- a/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html +++ b/src/core/block/components/pre-rendered-block/core-block-pre-rendered.html @@ -1,11 +1,11 @@ -

+

- + - + diff --git a/src/core/comments/pages/viewer/viewer.html b/src/core/comments/pages/viewer/viewer.html index 2a023a419..dc24b1acc 100644 --- a/src/core/comments/pages/viewer/viewer.html +++ b/src/core/comments/pages/viewer/viewer.html @@ -1,6 +1,6 @@ - +
- + @@ -56,7 +56,7 @@
- + diff --git a/src/core/contentlinks/pages/choose-site/choose-site.html b/src/core/contentlinks/pages/choose-site/choose-site.html index c6a401175..198304584 100644 --- a/src/core/contentlinks/pages/choose-site/choose-site.html +++ b/src/core/contentlinks/pages/choose-site/choose-site.html @@ -15,7 +15,7 @@ {{ 'core.pictureof' | translate:{$a: site.fullname} }}

{{site.fullName}}

-

+

{{site.siteUrl}}

diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index 3ffae46fe..2233f893a 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -236,7 +236,8 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, * Expand the description. */ expandDescription(): void { - this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id); + this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id, + [], true, 'module', this.module.id); } /** diff --git a/src/core/course/components/format/core-course-format.html b/src/core/course/components/format/core-course-format.html index 7013b69f5..07b21f6db 100644 --- a/src/core/course/components/format/core-course-format.html +++ b/src/core/course/components/format/core-course-format.html @@ -58,10 +58,10 @@ @@ -74,13 +74,13 @@
- + - + diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index df4f4ceb2..ff3f265d1 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -15,9 +15,9 @@ import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCourseProvider } from '../../providers/course'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; /** * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing @@ -34,13 +34,14 @@ import { CoreCourseProvider } from '../../providers/course'; }) export class CoreCourseModuleCompletionComponent implements OnChanges { @Input() completion: any; // The completion status. + @Input() moduleId?: number; // The name of the module this completion affects. @Input() moduleName?: string; // The name of the module this completion affects. @Output() completionChanged?: EventEmitter; // Will emit an event when the completion changes. completionImage: string; completionDescription: string; - constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, + constructor(private filterProvider: CoreFilterProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private courseProvider: CoreCourseProvider, private userProvider: CoreUserProvider) { this.completionChanged = new EventEmitter(); @@ -137,7 +138,9 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { } if (moduleName) { - this.textUtils.formatText(moduleName, true, true, 50).then((modNameFormatted) => { + this.filterProvider.getFiltersAndFormatText(moduleName, 'module', this.moduleId, + {clean: true, singleLine: true, shortenLength: 50}).then((modName) => { + let promise; if (this.completion.overrideby > 0) { @@ -147,11 +150,11 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { (profile) => { return { overrideuser: profile.fullname, - modname: modNameFormatted + modname: modName }; }); } else { - promise = Promise.resolve(modNameFormatted); + promise = Promise.resolve(modName); } return promise.then((translateParams) => { diff --git a/src/core/course/components/module-description/core-course-module-description.html b/src/core/course/components/module-description/core-course-module-description.html index 412d5bf3b..af4e525b3 100644 --- a/src/core/course/components/module-description/core-course-module-description.html +++ b/src/core/course/components/module-description/core-course-module-description.html @@ -1,6 +1,6 @@ - + {{ note }} diff --git a/src/core/course/components/module-description/module-description.ts b/src/core/course/components/module-description/module-description.ts index 0e19d86b1..eaa08a1b0 100644 --- a/src/core/course/components/module-description/module-description.ts +++ b/src/core/course/components/module-description/module-description.ts @@ -40,6 +40,8 @@ export class CoreCourseModuleDescriptionComponent { @Input() component?: string; // Component for format text directive. @Input() componentId?: string | number; // Component ID to use in conjunction with the component. @Input() showFull?: string | boolean; // Whether to always display the full description. + @Input() contextLevel?: string; // The context level. + @Input() contextInstanceId?: number; // The instance ID related to the context. constructor() { // Nothing to do. diff --git a/src/core/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index 0b0239685..4d9a4021d 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -2,12 +2,12 @@
- +
- +
@@ -21,15 +21,15 @@
- + {{ 'core.course.hiddenfromstudents' | translate }} {{ 'core.course.hiddenoncoursepage' | translate }}
{{ 'core.restricted' | translate }} - +
{{ 'core.course.manualcompletionnotsynced' | translate }}
- + \ No newline at end of file diff --git a/src/core/course/components/unsupported-module/core-course-unsupported-module.html b/src/core/course/components/unsupported-module/core-course-unsupported-module.html index 3738ade00..f1796de2e 100644 --- a/src/core/course/components/unsupported-module/core-course-unsupported-module.html +++ b/src/core/course/components/unsupported-module/core-course-unsupported-module.html @@ -1,5 +1,5 @@
- +

{{ 'core.whoops' | translate }}

{{ 'core.uhoh' | translate }}

diff --git a/src/core/course/pages/list-mod-type/list-mod-type.html b/src/core/course/pages/list-mod-type/list-mod-type.html index 7315af183..39678efed 100644 --- a/src/core/course/pages/list-mod-type/list-mod-type.html +++ b/src/core/course/pages/list-mod-type/list-mod-type.html @@ -1,6 +1,6 @@ - + {{ title }} diff --git a/src/core/course/pages/section-selector/section-selector.html b/src/core/course/pages/section-selector/section-selector.html index e160fa1f6..e8af2bc87 100644 --- a/src/core/course/pages/section-selector/section-selector.html +++ b/src/core/course/pages/section-selector/section-selector.html @@ -13,10 +13,10 @@ -

+

{{ 'core.course.hiddenfromstudents' | translate }} - +
diff --git a/src/core/course/pages/section-selector/section-selector.ts b/src/core/course/pages/section-selector/section-selector.ts index c86cf171c..d3ce687c3 100644 --- a/src/core/course/pages/section-selector/section-selector.ts +++ b/src/core/course/pages/section-selector/section-selector.ts @@ -30,12 +30,15 @@ export class CoreCourseSectionSelectorPage { stealthModulesSectionId = CoreCourseProvider.STEALTH_MODULES_SECTION_ID; sections: any; selected: number; + courseId: number; constructor(navParams: NavParams, courseHelper: CoreCourseHelperProvider, private viewCtrl: ViewController) { this.sections = navParams.get('sections'); this.selected = navParams.get('selected'); const course = navParams.get('course'); + this.courseId = course && course.id; + if (course && course.enablecompletion && course.courseformatoptions && course.courseformatoptions.coursedisplay == 1 && course.completionusertracked !== false) { this.sections.forEach((section) => { diff --git a/src/core/course/pages/section/section.html b/src/core/course/pages/section/section.html index ea12ab46f..6d2411153 100644 --- a/src/core/course/pages/section/section.html +++ b/src/core/course/pages/section/section.html @@ -1,6 +1,6 @@ - + diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 907e88518..5590b1c27 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -18,7 +18,6 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '@providers/events'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; -import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreTabsComponent } from '@components/tabs/tabs'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; @@ -30,6 +29,7 @@ import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay, CoreCourseOptionsMenuHandlerToDisplay } from '../../providers/options-delegate'; import { CoreCourseSyncProvider } from '../../providers/sync'; import { CoreCourseFormatComponent } from '../../components/format/format'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; /** * Page that displays the list of courses the user is enrolled in. @@ -75,7 +75,7 @@ export class CoreCourseSectionPage implements OnDestroy { constructor(navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider, - private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider, + private coursesProvider: CoreCoursesProvider, private filterProvider: CoreFilterProvider, sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private syncProvider: CoreCourseSyncProvider, private utils: CoreUtilsProvider) { @@ -263,7 +263,8 @@ export class CoreCourseSectionPage implements OnDestroy { // Format the name of each section and check if it has content. this.sections = sections.map((section) => { - this.textUtils.formatText(section.name.trim(), true, true).then((name) => { + this.filterProvider.getFiltersAndFormatText(section.name.trim(), 'course', this.course.id, + {clean: true, singleLine: true}).then((name) => { section.formattedName = name; }); section.hasContent = this.courseHelper.sectionHasContent(section); diff --git a/src/core/course/pages/unsupported-module/unsupported-module.html b/src/core/course/pages/unsupported-module/unsupported-module.html index 13c933949..c0ae5285a 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.html +++ b/src/core/course/pages/unsupported-module/unsupported-module.html @@ -1,6 +1,6 @@ - + diff --git a/src/core/course/pages/unsupported-module/unsupported-module.ts b/src/core/course/pages/unsupported-module/unsupported-module.ts index 8ecd58f00..6e4c87008 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.ts @@ -36,6 +36,7 @@ export class CoreCourseUnsupportedModulePage { * Expand the description. */ expandDescription(): void { - this.textUtils.expandText(this.translate.instant('core.description'), this.module.description); + this.textUtils.expandText(this.translate.instant('core.description'), this.module.description, undefined, undefined, + [], true, 'module', this.module.id); } } diff --git a/src/core/courses/components/course-list-item/core-courses-course-list-item.html b/src/core/courses/components/course-list-item/core-courses-course-list-item.html index a56a02b9b..e594ff06b 100644 --- a/src/core/courses/components/course-list-item/core-courses-course-list-item.html +++ b/src/core/courses/components/course-list-item/core-courses-course-list-item.html @@ -1,6 +1,6 @@ -

+

diff --git a/src/core/courses/components/course-progress/core-courses-course-progress.html b/src/core/courses/components/course-progress/core-courses-course-progress.html index 985d92008..ed72a738e 100644 --- a/src/core/courses/components/course-progress/core-courses-course-progress.html +++ b/src/core/courses/components/course-progress/core-courses-course-progress.html @@ -4,10 +4,10 @@
-

+

- +

diff --git a/src/core/courses/pages/categories/categories.html b/src/core/courses/pages/categories/categories.html index e24c372bb..0952bb4ef 100644 --- a/src/core/courses/pages/categories/categories.html +++ b/src/core/courses/pages/categories/categories.html @@ -1,6 +1,6 @@ - + @@ -10,10 +10,10 @@ -

+

- +
@@ -21,7 +21,7 @@
-

+

{{category.coursecount}}
diff --git a/src/core/courses/pages/course-preview/course-preview.html b/src/core/courses/pages/course-preview/course-preview.html index e379c0b21..f84814ab6 100644 --- a/src/core/courses/pages/course-preview/course-preview.html +++ b/src/core/courses/pages/course-preview/course-preview.html @@ -1,6 +1,6 @@ - + @@ -15,13 +15,13 @@
-

-

+

+

{{course.startdate * 1000 | coreFormatDate:"strftimedatefullshort" }} - {{course.enddate * 1000 | coreFormatDate:"strftimedatefullshort" }}

- + @@ -36,8 +36,8 @@
- : - + : +
diff --git a/src/core/courses/pages/dashboard/dashboard.html b/src/core/courses/pages/dashboard/dashboard.html index 4f030b26f..0030c0b0f 100644 --- a/src/core/courses/pages/dashboard/dashboard.html +++ b/src/core/courses/pages/dashboard/dashboard.html @@ -1,6 +1,6 @@ - +
diff --git a/src/core/user/pages/profile/profile.html b/src/core/user/pages/profile/profile.html index fae5a9edd..fcda7fd09 100644 --- a/src/core/user/pages/profile/profile.html +++ b/src/core/user/pages/profile/profile.html @@ -1,6 +1,6 @@ - + {{ title }} @@ -13,11 +13,11 @@ -

-

+

{{ user.fullname }}

+

{{ user.address }}

{{ 'core.user.roles' | translate}}{{'core.labelsep' | translate}} - + {{ user.roles }}

diff --git a/src/core/viewer/pages/text/text.html b/src/core/viewer/pages/text/text.html index 56fc902a9..f73c58612 100644 --- a/src/core/viewer/pages/text/text.html +++ b/src/core/viewer/pages/text/text.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/viewer/pages/text/text.ts b/src/core/viewer/pages/text/text.ts index 6ab1e7bf3..912c9ccff 100644 --- a/src/core/viewer/pages/text/text.ts +++ b/src/core/viewer/pages/text/text.ts @@ -30,6 +30,9 @@ export class CoreViewerTextPage { component: string; // Component to use in format-text. componentId: string | number; // Component ID to use in format-text. files: any[]; // List of files. + filter: boolean; // Whether to filter the text. + contextLevel: string; // The context level. + instanceId: number; // The instance ID related to the context. constructor(private viewCtrl: ViewController, params: NavParams, textUtils: CoreTextUtilsProvider) { this.title = params.get('title'); @@ -37,6 +40,9 @@ export class CoreViewerTextPage { this.component = params.get('component'); this.componentId = params.get('componentId'); this.files = params.get('files'); + this.filter = params.get('filter'); + this.contextLevel = params.get('contextLevel'); + this.instanceId = params.get('instanceId'); } /** diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 88fb67c20..e9c78ec04 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -30,10 +30,13 @@ import { CoreLinkDirective } from '../directives/link'; import { CoreExternalContentDirective } from '../directives/external-content'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective - * and CoreExternalContentDirective. + * and CoreExternalContentDirective. It also applies filters if needed. + * + * Please use this directive if your text needs to be filtered or it can contain links or media (images, audio, video). * * Example usage: * @@ -55,6 +58,9 @@ export class CoreFormatTextDirective implements OnChanges { @Input() fullOnClick?: boolean | string; // Whether it should open a new page with the full contents on click. @Input() fullTitle?: string; // Title to use in full view. Defaults to "Description". @Input() highlight?: string; // Text to highlight. + @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set. + @Input() contextLevel?: string; // The context level of the text. + @Input() contextInstanceId?: number; // The instance ID related to the context. @Output() afterRender?: EventEmitter; // Called when the data is rendered. protected element: HTMLElement; @@ -67,7 +73,8 @@ export class CoreFormatTextDirective implements OnChanges { private filepoolProvider: CoreFilepoolProvider, private appProvider: CoreAppProvider, private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, - private iframeUtils: CoreIframeUtilsProvider, private eventsProvider: CoreEventsProvider) { + private iframeUtils: CoreIframeUtilsProvider, private eventsProvider: CoreEventsProvider, + private filterProvider: CoreFilterProvider) { this.element = element.nativeElement; this.element.classList.add('opacity-hide'); // Hide contents until they're treated. this.afterRender = new EventEmitter(); @@ -275,8 +282,10 @@ export class CoreFormatTextDirective implements OnChanges { return; } else { // Open a new state with the contents. + const filter = this.utils.isTrueOrOne(this.filter); + this.textUtils.expandText(this.fullTitle || this.translate.instant('core.description'), this.text, - this.component, this.componentId); + this.component, this.componentId, undefined, filter, this.contextLevel, this.contextInstanceId); } } @@ -363,9 +372,26 @@ export class CoreFormatTextDirective implements OnChanges { }).then((siteInstance: CoreSite) => { site = siteInstance; - // Apply format text function. - return this.textUtils.formatText(this.text, this.utils.isTrueOrOne(this.clean), - this.utils.isTrueOrOne(this.singleLine), undefined, this.highlight); + if (this.contextLevel == 'course' && this.contextInstanceId <= 0) { + this.contextInstanceId = site.getSiteHomeId(); + } + + this.filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && this.contextInstanceId) : !!this.filter; + + const options = { + clean: this.utils.isTrueOrOne(this.clean), + singleLine: this.utils.isTrueOrOne(this.singleLine), + highlight: this.highlight, + filter: this.filter + }; + + if (this.filter) { + return this.filterProvider.getFiltersAndFormatText(this.text, this.contextLevel, this.contextInstanceId, options, + site.getId()); + } else { + return this.filterProvider.formatText(this.text, options); + } + }).then((formatted) => { const div = document.createElement('div'), canTreatVimeo = site && site.isVersionGreaterEqualThan(['3.3.4', '3.4']), diff --git a/src/providers/sites.ts b/src/providers/sites.ts index 5616f3dd2..d3e1ecba8 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -113,6 +113,11 @@ export interface CoreSiteBasicInfo { * Badge to display in the site. */ badge?: number; + + /** + * Site home ID. + */ + siteHomeId?: number; } /** @@ -1130,7 +1135,8 @@ export class CoreSitesProvider { siteUrl: site.siteUrl, fullName: siteInfo && siteInfo.fullname, siteName: CoreConfigConstants.sitename ? CoreConfigConstants.sitename : siteInfo && siteInfo.sitename, - avatar: siteInfo && siteInfo.userpictureurl + avatar: siteInfo && siteInfo.userpictureurl, + siteHomeId: siteInfo && siteInfo.siteid || 1 }; formattedSites.push(basicInfo); } diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 262662da6..50593fc93 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -349,15 +349,22 @@ export class CoreTextUtilsProvider { * @param component Component to link the embedded files to. * @param componentId An ID to use in conjunction with the component. * @param files List of files to display along with the text. + * @param filter Whether the text should be filtered. + * @param contextLevel The context level. + * @param instanceId The instance ID related to the context. */ - expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[]): void { + expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], + filter?: boolean, contextLevel?: string, instanceId?: number): void { if (text.length > 0) { const params: any = { title: title, content: text, component: component, componentId: componentId, - files: files + files: files, + filter: filter, + contextLevel: contextLevel, + instanceId: instanceId }; // Open a modal with the contents. @@ -398,6 +405,7 @@ export class CoreTextUtilsProvider { * @param shortenLength Number of characters to shorten the text. * @param highlight Text to highlight. * @return Promise resolved with the formatted text. + * @deprecated since 3.8.0. Please use CoreFilterProvider.formatText instead. */ formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number, highlight?: string): Promise { return this.treatMultilangTags(text).then((formatted) => { From 31caccc39632491b196d99aac0bdedc7039154a9 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 Oct 2019 11:25:58 +0200 Subject: [PATCH 05/12] MOBILE-2491 filter: Support wsNotFiltered option and filter in chart --- .../filter/activitynames/providers/handler.ts | 13 +++ src/addon/filter/censor/providers/handler.ts | 13 +++ src/addon/filter/data/providers/handler.ts | 13 +++ .../filter/emailprotect/providers/handler.ts | 13 +++ .../filter/emoticon/providers/handler.ts | 13 +++ .../filter/glossary/providers/handler.ts | 13 +++ .../filter/mediaplugin/providers/handler.ts | 6 +- .../filter/multilang/providers/handler.ts | 78 ++++++++++-------- src/addon/filter/tex/providers/handler.ts | 13 +++ src/addon/filter/tidy/providers/handler.ts | 13 +++ .../filter/urltolink/providers/handler.ts | 13 +++ .../index/addon-mod-choice-index.html | 2 +- .../index/addon-mod-feedback-index.html | 2 +- src/addon/mod/feedback/pages/form/form.html | 8 +- src/components/chart/chart.ts | 82 ++++++++++++++----- src/core/filter/providers/default-filter.ts | 21 ++--- src/core/filter/providers/delegate.ts | 72 +++++++++++----- src/core/filter/providers/filter.ts | 64 ++++++++------- src/directives/format-text.ts | 3 +- 19 files changed, 332 insertions(+), 123 deletions(-) diff --git a/src/addon/filter/activitynames/providers/handler.ts b/src/addon/filter/activitynames/providers/handler.ts index 8e55846bb..025121803 100644 --- a/src/addon/filter/activitynames/providers/handler.ts +++ b/src/addon/filter/activitynames/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Activity names filter. @@ -29,4 +31,15 @@ export class AddonFilterActivityNamesHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/censor/providers/handler.ts b/src/addon/filter/censor/providers/handler.ts index 874d86ef9..018eb368b 100644 --- a/src/addon/filter/censor/providers/handler.ts +++ b/src/addon/filter/censor/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Word censorship filter. @@ -29,4 +31,15 @@ export class AddonFilterCensorHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/data/providers/handler.ts b/src/addon/filter/data/providers/handler.ts index f01cadb1d..e07e2ea4b 100644 --- a/src/addon/filter/data/providers/handler.ts +++ b/src/addon/filter/data/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Database auto-link filter. @@ -29,4 +31,15 @@ export class AddonFilterDataHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/emailprotect/providers/handler.ts b/src/addon/filter/emailprotect/providers/handler.ts index e7971386c..d339f6d33 100644 --- a/src/addon/filter/emailprotect/providers/handler.ts +++ b/src/addon/filter/emailprotect/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Email protection filter. @@ -29,4 +31,15 @@ export class AddonFilterEmailProtectHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/emoticon/providers/handler.ts b/src/addon/filter/emoticon/providers/handler.ts index 39c2ffe33..5523b0780 100644 --- a/src/addon/filter/emoticon/providers/handler.ts +++ b/src/addon/filter/emoticon/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Emoticon filter. @@ -29,4 +31,15 @@ export class AddonFilterEmoticonHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/glossary/providers/handler.ts b/src/addon/filter/glossary/providers/handler.ts index f7e38d553..9ffd6fec4 100644 --- a/src/addon/filter/glossary/providers/handler.ts +++ b/src/addon/filter/glossary/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Glossary auto-link filter. @@ -29,4 +31,15 @@ export class AddonFilterGlossaryHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/mediaplugin/providers/handler.ts b/src/addon/filter/mediaplugin/providers/handler.ts index 29da3de94..d7b91f2be 100644 --- a/src/addon/filter/mediaplugin/providers/handler.ts +++ b/src/addon/filter/mediaplugin/providers/handler.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; -import { CoreFilterFilter } from '@core/filter/providers/filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; import { CoreTextUtilsProvider } from '@providers/utils/text'; /** @@ -36,9 +36,11 @@ export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler { * @param text The text to filter. * @param filter The filter. * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. * @return Filtered text (or promise resolved with the filtered text). */ - filter(text: string, filter: CoreFilterFilter, options: any): string | Promise { + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { const div = document.createElement('div'); div.innerHTML = text; diff --git a/src/addon/filter/multilang/providers/handler.ts b/src/addon/filter/multilang/providers/handler.ts index e689d9769..0b8d25d35 100644 --- a/src/addon/filter/multilang/providers/handler.ts +++ b/src/addon/filter/multilang/providers/handler.ts @@ -16,8 +16,9 @@ import { Injectable } from '@angular/core'; import { CoreSitesProvider } from '@providers/sites'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; -import { CoreFilterFilter } from '@core/filter/providers/filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; import { CoreLangProvider } from '@providers/lang'; +import { CoreSite } from '@classes/site'; /** * Handler to support the Multilang filter. @@ -32,50 +33,61 @@ export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler { super(); } - /** - * 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 { - // In Moodle versions older than 3.7, some specific content can be received unfiltered. Filter it in the app. - const currentSite = this.sitesProvider.getCurrentSite(); - - return !currentSite.isVersionGreaterEqualThan('3.7'); - } - /** * Filter some text. * * @param text The text to filter. * @param filter The filter. * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. * @return Filtered text (or promise resolved with the filtered text). */ - filter(text: string, filter: CoreFilterFilter, options: any): string | Promise { + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { - return this.langProvider.getCurrentLanguage().then((language) => { - // Match the current language. - const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; - let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); + return this.sitesProvider.getSite(siteId).then((site) => { - if (!text.match(currentLangRegEx)) { - // Current lang not found. Try to find the first language. - const matches = text.match(anyLangRegEx); - if (matches && matches[0]) { - language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; - currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); - } else { - // No multi-lang tag found, stop. - return text; - } + // Don't apply this filter if Moodle is 3.7 or higher and the WS already filtered the content. + if (!this.shouldBeApplied(options, site)) { + return text; } - // Extract contents of current language. - text = text.replace(currentLangRegEx, '$1'); - // Delete the rest of languages - text = text.replace(anyLangRegEx, ''); - return text; + return this.langProvider.getCurrentLanguage().then((language) => { + // Match the current language. + const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; + let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); + + if (!text.match(currentLangRegEx)) { + // Current lang not found. Try to find the first language. + const matches = text.match(anyLangRegEx); + if (matches && matches[0]) { + language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; + currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', + 'g'); + } else { + // No multi-lang tag found, stop. + return text; + } + } + // Extract contents of current language. + text = text.replace(currentLangRegEx, '$1'); + // Delete the rest of languages + text = text.replace(anyLangRegEx, ''); + + return text; + }); }); } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + // The filter should be applied if site is older than 3.7 or the WS didn't filter the text. + return options.wsNotFiltered || !site.isVersionGreaterEqualThan('3.7'); + } } diff --git a/src/addon/filter/tex/providers/handler.ts b/src/addon/filter/tex/providers/handler.ts index a02e8e7c7..d29302a0d 100644 --- a/src/addon/filter/tex/providers/handler.ts +++ b/src/addon/filter/tex/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the TeX notation filter. @@ -29,4 +31,15 @@ export class AddonFilterTexHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/tidy/providers/handler.ts b/src/addon/filter/tidy/providers/handler.ts index e0fff232d..6ce6a6287 100644 --- a/src/addon/filter/tidy/providers/handler.ts +++ b/src/addon/filter/tidy/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the HTML tidy filter. @@ -29,4 +31,15 @@ export class AddonFilterTidyHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/filter/urltolink/providers/handler.ts b/src/addon/filter/urltolink/providers/handler.ts index d23c392b3..311a0c2c5 100644 --- a/src/addon/filter/urltolink/providers/handler.ts +++ b/src/addon/filter/urltolink/providers/handler.ts @@ -15,6 +15,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; +import { CoreFilterFormatTextOptions } from '@core/filter/providers/filter'; +import { CoreSite } from '@classes/site'; /** * Handler to support the URL to link and images filter. @@ -29,4 +31,15 @@ export class AddonFilterUrlToLinkHandler extends CoreFilterDefaultHandler { // This filter is handled by Moodle, nothing to do in the app. } + + /** + * Check if the filter should be applied in a certain site based on some filter options. + * + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. + */ + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return false; + } } diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html index cf16a7b02..f4c0d5ac2 100644 --- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html @@ -73,7 +73,7 @@ {{ 'addon.mod_choice.resultsnotsynced' | translate }} - + diff --git a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html index f57fb1633..f1713c694 100644 --- a/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html +++ b/src/addon/mod/feedback/components/index/addon-mod-feedback-index.html @@ -162,7 +162,7 @@ - +

{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}

diff --git a/src/addon/mod/feedback/pages/form/form.html b/src/addon/mod/feedback/pages/form/form.html index 0339e939a..a1c3a8a63 100644 --- a/src/addon/mod/feedback/pages/form/form.html +++ b/src/addon/mod/feedback/pages/form/form.html @@ -23,7 +23,7 @@
-

+

@@ -38,20 +38,20 @@ - + - + - + diff --git a/src/components/chart/chart.ts b/src/components/chart/chart.ts index 8d67b8853..cd310d47e 100644 --- a/src/components/chart/chart.ts +++ b/src/components/chart/chart.ts @@ -14,6 +14,8 @@ import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild } from '@angular/core'; import { Chart } from 'chart.js'; +import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreUtilsProvider } from '@providers/utils/utils'; /** * This component shows a chart using chart.js. @@ -44,13 +46,15 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { @Input() type: string; // Type of chart. @Input() legend: any; // Legend options. @Input() height = 300; // Height of the chart element. + @Input() filter?: boolean | string; // Whether to filter labels. If not defined, true if contextLevel and instanceId are set. + @Input() contextLevel?: string; // The context level of the text. + @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the labels for some reason. @ViewChild('canvas') canvas: ElementRef; chart: any; - constructor() { - // Nothing to do. - } + constructor(protected filterProvider: CoreFilterProvider, private utils: CoreUtilsProvider) { } /** * Component being initialized. @@ -86,17 +90,21 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { this.type = 'horizontalBar'; } - const context = this.canvas.nativeElement.getContext('2d'); - this.chart = new Chart(context, { - type: this.type, - data: { - labels: this.labels, - datasets: [{ - data: this.data, - backgroundColor: this.getRandomColors(this.data.length) - }] - }, - options: {legend: legend} + // Format labels if needed. + this.formatLabels().then(() => { + + const context = this.canvas.nativeElement.getContext('2d'); + this.chart = new Chart(context, { + type: this.type, + data: { + labels: this.labels, + datasets: [{ + data: this.data, + backgroundColor: this.getRandomColors(this.data.length) + }] + }, + options: {legend: legend} + }); }); } @@ -105,15 +113,49 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { */ ngOnChanges(): void { if (this.chart) { - this.chart.data.datasets[0] = { - data: this.data, - backgroundColor: this.getRandomColors(this.data.length) - }; - this.chart.data.labels = this.labels; - this.chart.update(); + // Format labels if needed. + this.formatLabels().then(() => { + this.chart.data.datasets[0] = { + data: this.data, + backgroundColor: this.getRandomColors(this.data.length) + }; + this.chart.data.labels = this.labels; + this.chart.update(); + }); } } + /** + * Format labels if needed. + * + * @return Promise resolved when done. + */ + protected formatLabels(): Promise { + this.filter = typeof this.filter == 'undefined' ? !!(this.contextLevel && this.contextInstanceId) : !!this.filter; + + if (!this.filter) { + return Promise.resolve(); + } + + const options = { + clean: true, + singleLine: true, + wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) + }; + + return this.filterProvider.getFilters(this.contextLevel, this.contextInstanceId, options).then((filters) => { + const promises = []; + + this.labels.forEach((label, i) => { + promises.push(this.filterProvider.formatText(label, options, filters).then((text) => { + this.labels[i] = text; + })); + }); + + return Promise.all(promises); + }); + } + /** * Generate random colors if needed. * diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts index d339a4c8d..77abe1443 100644 --- a/src/core/filter/providers/default-filter.ts +++ b/src/core/filter/providers/default-filter.ts @@ -14,7 +14,8 @@ import { Injectable } from '@angular/core'; import { CoreFilterHandler } from './delegate'; -import { CoreFilterFilter } from './filter'; +import { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; +import { CoreSite } from '@classes/site'; /** * Default handler used when the module doesn't have a specific implementation. @@ -34,9 +35,11 @@ export class CoreFilterDefaultHandler implements CoreFilterHandler { * @param text The text to filter. * @param filter The filter. * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. * @return Filtered text (or promise resolved with the filtered text). */ - filter(text: string, filter: CoreFilterFilter, options: any): string | Promise { + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string) + : string | Promise { return text; } @@ -50,15 +53,13 @@ export class CoreFilterDefaultHandler implements CoreFilterHandler { } /** - * Setup the filter to be used. + * Check if the filter should be applied in a certain site based on some filter options. * - * 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. + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. */ - setup(filter: CoreFilterFilter): void | Promise { - // Nothing to do. + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { + return true; } } diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 4fd742262..3fa27c298 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -16,9 +16,10 @@ 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 { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; import { CoreFilterDefaultHandler } from './default-filter'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; +import { CoreSite } from '@classes/site'; /** * Interface that all filter handlers must implement. @@ -35,20 +36,19 @@ export interface CoreFilterHandler extends CoreDelegateHandler { * @param text The text to filter. * @param filter The filter. * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. * @return Filtered text (or promise resolved with the filtered text). */ - filter(text: string, filter: CoreFilterFilter, options: any): string | Promise; + filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string): string | Promise; /** - * Setup the filter to be used. + * Check if the filter should be applied in a certain site based on some filter options. * - * 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. + * @param options Options. + * @param site Site. + * @return Whether filter should be applied. */ - setup(filter: CoreFilterFilter): void | Promise; + shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean; } /** @@ -71,12 +71,10 @@ export class CoreFilterDelegate extends CoreDelegate { * @param filters Filters to apply. * @param options Options passed to the filters. * @param skipFilters Names of filters that shouldn't be applied. + * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the filtered text. */ - filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[]): Promise { - if (!text || typeof text != 'string') { - return Promise.resolve(''); - } + filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[], siteId?: string): Promise { // Wait for filters to be initialized. return this.handlersInitPromise.then(() => { @@ -98,7 +96,7 @@ export class CoreFilterDelegate extends CoreDelegate { } promise = promise.then((text) => { - return this.executeFunctionOnEnabled(filter.filter, 'filter', [text, filter, options]); + return this.executeFunctionOnEnabled(filter.filter, 'filter', [text, filter, options, siteId]); }); }); @@ -138,18 +136,48 @@ export class CoreFilterDelegate extends CoreDelegate { } /** - * Setup filters to be applied to some content. + * Check if at least 1 filter should be applied in a certain site and with certain options. * - * @param filters Filters to apply. - * @return Promise resolved when done. + * @param filter Filter to check. + * @param options Options passed to the filters. + * @param site Site. If not defined, current site. + * @return {Promise} Promise resolved with true: whether the filter should be applied. */ - setupFilters(filters: CoreFilterFilter[]): Promise { - const promises: Promise[] = []; + shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise { + // Wait for filters to be initialized. + return this.handlersInitPromise.then(() => { + const promises = []; + let shouldBeApplied = false; - filters.forEach((filter) => { - promises.push(this.executeFunctionOnEnabled(filter.filter, 'setup', [filter])); + filters.forEach((filter) => { + promises.push(this.shouldFilterBeApplied(filter, options, site).then((applied) => { + if (applied) { + shouldBeApplied = applied; + } + })); + }); + + return Promise.all(promises).then(() => { + return shouldBeApplied; + }); }); + } - return Promise.all(promises); + /** + * Check whether a filter should be applied in a certain site and with certain options. + * + * @param filter Filter to check. + * @param options Options passed to the filters. + * @param site Site. If not defined, current site. + * @return {Promise} Promise resolved with true: whether the filter should be applied. + */ + protected shouldFilterBeApplied(filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, site?: CoreSite) + : Promise { + + if (!this.hasHandler(filter.filter, true)) { + return Promise.resolve(false); + } + + return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'shouldBeApplied', [options, site])); } } diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts index 9e6752d12..ca8e77078 100644 --- a/src/core/filter/providers/filter.ts +++ b/src/core/filter/providers/filter.ts @@ -29,8 +29,6 @@ export class CoreFilterProvider { protected ROOT_CACHE_KEY = 'mmFilter:'; protected logger; - protected FILTERS_NOT_TREATED = ['activitynames', 'censor', 'data', 'emailprotect', 'emoticon', 'glossary', 'tex', 'tidy', - 'urltolink']; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, @@ -71,9 +69,11 @@ export class CoreFilterProvider { * @param text The text to be formatted. * @param options Formatting options. * @param filters The filters to apply. Required if filter is set to true. + * @param siteId Site ID. If not defined, current site. * @return Promise resolved with the formatted text. */ - formatText(text: string, options?: CoreFilterFormatTextOptions, filters?: CoreFilterFilter[]): Promise { + formatText(text: string, options?: CoreFilterFormatTextOptions, filters?: CoreFilterFilter[], siteId?: string) + : Promise { if (!text || typeof text != 'string') { // No need to do any filters and cleaning. @@ -95,12 +95,10 @@ export class CoreFilterProvider { options.filter = false; } - // @todo: Setup? - let promise: Promise; if (options.filter) { - promise = this.filterDelegate.filterText(text, filters, options); + promise = this.filterDelegate.filterText(text, filters, options, [], siteId); } else { promise = Promise.resolve(text); } @@ -250,17 +248,18 @@ export class CoreFilterProvider { } /** - * Get filters and format text. + * Get the filters in a certain context, performing some checks like the site version. + * It's recommended to use this function instead of canGetAvailableInContext because this function will check if + * it's really needed to call the WS. * - * @param text Text to filter. * @param contextLevel The context level. * @param instanceId Instance ID related to the context. * @param options Options for format text. * @param siteId Site ID. If not defined, current site. - * @return Promise resolved with the formatted text. + * @return Promise resolved with the filters. */ - getFiltersAndFormatText(text: string, contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, - siteId?: string): Promise { + getFilters(contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string) + : Promise { options.contextLevel = contextLevel; options.instanceId = instanceId; @@ -275,7 +274,7 @@ export class CoreFilterProvider { } // Check if site has any filter to treat. - return this.siteHasFiltersToTreat(siteId).then((hasFilters) => { + return this.siteHasFiltersToTreat(options, siteId).then((hasFilters) => { if (hasFilters) { options.filter = true; @@ -286,8 +285,24 @@ export class CoreFilterProvider { }).catch(() => { return []; }); - }).then((filters) => { - return this.formatText(text, options, filters); + }); + } + + /** + * Get filters and format text. + * + * @param text Text to filter. + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the formatted text. + */ + getFiltersAndFormatText(text: string, contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, + siteId?: string): Promise { + + return this.getFilters(contextLevel, instanceId, options, siteId).then((filters) => { + return this.formatText(text, options, filters, siteId); }); } @@ -331,29 +346,19 @@ export class CoreFilterProvider { /** * Check if site has available any filter that should be treated by the app. * + * @param options Options passed to the filters. * @param siteId Site ID. If not defined, current site. * @return Promise resolved with boolean: whether it has filters to treat. */ - siteHasFiltersToTreat(siteId?: string): Promise { + siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise { + options = options || {}; + return this.sitesProvider.getSite(siteId).then((site) => { // Get filters at site level. return this.getAvailableInContext('system', site.getSiteHomeId(), site.getId()).then((filters) => { - for (let i = 0; i < filters.length; i++) { - const filter = filters[i]; - - if (this.FILTERS_NOT_TREATED.indexOf(filter.filter) != -1) { - continue; - } - - if (this.filterDelegate.hasHandler(filter.filter, true)) { - // There is a filter to treat and is enabled. - return true; - } - } - - return false; + return this.filterDelegate.shouldBeApplied(filters, options, site); }); }); } @@ -390,4 +395,5 @@ export type CoreFilterFormatTextOptions = { singleLine?: boolean; // If true then new lines will be removed (all the text in a single line). shortenLength?: number; // Number of characters to shorten the text. highlight?: string; // Text to highlight. + wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason. }; diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index e9c78ec04..644812107 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -61,6 +61,7 @@ export class CoreFormatTextDirective implements OnChanges { @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set. @Input() contextLevel?: string; // The context level of the text. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason. @Output() afterRender?: EventEmitter; // Called when the data is rendered. protected element: HTMLElement; @@ -382,7 +383,7 @@ export class CoreFormatTextDirective implements OnChanges { clean: this.utils.isTrueOrOne(this.clean), singleLine: this.utils.isTrueOrOne(this.singleLine), highlight: this.highlight, - filter: this.filter + wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) }; if (this.filter) { From 30a5e83056d6ccca8df6216693a7ea46bfec5a9d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 3 Oct 2019 17:37:51 +0200 Subject: [PATCH 06/12] MOBILE-2491 filter: Minimize WS calls for module context --- .../addon-block-recentlyaccesseditems.html | 2 +- .../events/addon-block-timeline-events.html | 2 +- .../calendar/pages/edit-event/edit-event.ts | 6 +- .../course/addon-competency-course.html | 2 +- .../pages/competency/competency.html | 2 +- .../messages/providers/mainmenu-handler.ts | 6 +- .../addon-mod-assign-feedback-plugin.html | 2 +- .../index/addon-mod-assign-index.html | 2 +- .../mod/assign/components/index/index.ts | 2 +- .../addon-mod-assign-submission-plugin.html | 2 +- .../addon-mod-assign-feedback-comments.html | 2 +- .../feedback/comments/component/comments.ts | 2 +- src/addon/mod/assign/pages/edit/edit.html | 2 +- src/addon/mod/assign/pages/index/index.html | 2 +- .../submission-list/submission-list.html | 2 +- .../submission-review/submission-review.html | 2 +- .../addon-mod-assign-submission-comments.html | 2 +- ...ddon-mod-assign-submission-onlinetext.html | 2 +- .../onlinetext/component/onlinetext.ts | 2 +- .../index/addon-mod-book-index.html | 4 +- src/addon/mod/book/components/index/index.ts | 3 +- src/addon/mod/book/pages/index/index.html | 2 +- src/addon/mod/book/pages/toc/toc.html | 2 +- src/addon/mod/book/pages/toc/toc.ts | 2 + .../index/addon-mod-chat-index.html | 2 +- src/addon/mod/chat/pages/chat/chat.html | 6 +- src/addon/mod/chat/pages/index/index.html | 2 +- .../session-messages/session-messages.html | 4 +- .../index/addon-mod-choice-index.html | 12 +- src/addon/mod/choice/pages/index/index.html | 2 +- .../action/addon-mod-data-action.html | 6 +- .../index/addon-mod-data-index.html | 2 +- .../addon-mod-data-field-textarea.html | 2 +- src/addon/mod/data/pages/edit/edit.html | 2 +- src/addon/mod/data/pages/entry/entry.html | 4 +- src/addon/mod/data/pages/index/index.html | 2 +- .../index/addon-mod-feedback-index.html | 10 +- .../mod/feedback/pages/attempt/attempt.html | 4 +- src/addon/mod/feedback/pages/form/form.html | 14 +- src/addon/mod/feedback/pages/index/index.html | 2 +- .../index/addon-mod-folder-index.html | 2 +- src/addon/mod/folder/pages/index/index.html | 2 +- .../index/addon-mod-forum-index.html | 6 +- .../components/post/addon-mod-forum-post.html | 4 +- .../forum/pages/discussion/discussion.html | 2 +- .../mod/forum/pages/discussion/discussion.ts | 1 + src/addon/mod/forum/pages/index/index.html | 2 +- .../index/addon-mod-glossary-index.html | 2 +- src/addon/mod/glossary/pages/edit/edit.html | 2 +- src/addon/mod/glossary/pages/entry/entry.html | 6 +- src/addon/mod/glossary/pages/index/index.html | 2 +- .../index/addon-mod-imscp-index.html | 2 +- src/addon/mod/imscp/pages/index/index.html | 2 +- .../index/addon-mod-lesson-index.html | 2 +- src/addon/mod/lesson/pages/index/index.html | 2 +- .../lesson/pages/menu-modal/menu-modal.html | 2 +- src/addon/mod/lesson/pages/player/player.html | 20 +- .../lesson/pages/user-retake/user-retake.html | 22 +-- .../components/index/addon-mod-lti-index.html | 2 +- src/addon/mod/lti/pages/index/index.html | 2 +- .../index/addon-mod-page-index.html | 4 +- src/addon/mod/page/pages/index/index.html | 2 +- .../index/addon-mod-quiz-index.html | 6 +- src/addon/mod/quiz/pages/attempt/attempt.html | 4 +- src/addon/mod/quiz/pages/index/index.html | 2 +- src/addon/mod/quiz/pages/player/player.html | 4 +- src/addon/mod/quiz/pages/review/review.html | 4 +- .../index/addon-mod-resource-index.html | 2 +- src/addon/mod/resource/pages/index/index.html | 2 +- .../index/addon-mod-scorm-index.html | 6 +- src/addon/mod/scorm/pages/index/index.html | 2 +- src/addon/mod/scorm/pages/player/player.html | 2 +- src/addon/mod/scorm/pages/player/player.ts | 3 +- src/addon/mod/scorm/pages/toc/toc.html | 2 +- src/addon/mod/scorm/pages/toc/toc.ts | 2 + .../index/addon-mod-survey-index.html | 2 +- .../components/index/addon-mod-url-index.html | 2 +- .../index/addon-mod-wiki-index.html | 4 +- src/addon/mod/wiki/components/index/index.ts | 3 +- src/addon/mod/wiki/pages/edit/edit.html | 2 +- src/addon/mod/wiki/pages/index/index.html | 2 +- src/addon/mod/wiki/pages/map/map.html | 2 +- src/addon/mod/wiki/pages/map/map.ts | 2 + ...shop-assessment-strategy-accumulative.html | 4 +- ...workshop-assessment-strategy-comments.html | 4 +- ...orkshop-assessment-strategy-numerrors.html | 4 +- ...d-workshop-assessment-strategy-rubric.html | 4 +- .../classes/assessment-strategy-component.ts | 1 + ...ddon-mod-workshop-assessment-strategy.html | 2 +- .../assessment-strategy.ts | 4 +- .../index/addon-mod-workshop-index.html | 8 +- .../addon-mod-workshop-submission.html | 8 +- .../workshop/pages/assessment/assessment.html | 4 +- src/addon/mod/workshop/pages/index/index.html | 2 +- .../workshop/pages/submission/submission.html | 6 +- .../component/addon-qtype-calculated.html | 2 +- .../component/addon-qtype-ddimageortext.html | 2 +- .../component/addon-qtype-ddmarker.html | 2 +- .../ddwtos/component/addon-qtype-ddwtos.html | 2 +- src/addon/qtype/ddwtos/component/ddwtos.ts | 2 +- .../component/addon-qtype-description.html | 2 +- .../essay/component/addon-qtype-essay.html | 6 +- .../component/addon-qtype-gapselect.html | 2 +- .../qtype/gapselect/component/gapselect.ts | 2 +- .../match/component/addon-qtype-match.html | 4 +- .../component/addon-qtype-multianswer.html | 2 +- .../multianswer/component/multianswer.ts | 2 +- .../component/addon-qtype-multichoice.html | 10 +- .../component/addon-qtype-shortanswer.html | 2 +- .../addon-user-profile-field-menu.html | 2 +- .../userprofilefield/menu/component/menu.ts | 1 + .../addon-user-profile-field-text.html | 2 +- .../userprofilefield/text/component/text.ts | 1 + .../addon-user-profile-field-textarea.html | 2 +- .../textarea/component/textarea.ts | 1 + src/components/chart/chart.ts | 8 +- .../navigation-bar/navigation-bar.ts | 3 +- src/components/site-picker/site-picker.ts | 6 +- .../comments/components/comments/comments.ts | 2 + src/core/comments/pages/viewer/viewer.html | 6 +- src/core/comments/pages/viewer/viewer.ts | 2 + .../course/classes/main-resource-component.ts | 2 +- .../module-completion/module-completion.ts | 8 +- .../core-course-module-description.html | 2 +- .../module-description/module-description.ts | 1 + .../components/module/core-course-module.html | 6 +- .../core-course-unsupported-module.html | 2 +- .../unsupported-module/unsupported-module.ts | 2 +- src/core/course/pages/section/section.ts | 6 +- .../unsupported-module.html | 4 +- .../unsupported-module/unsupported-module.ts | 4 +- src/core/course/providers/default-module.ts | 2 +- src/core/filter/filter.module.ts | 7 +- src/core/filter/providers/filter.ts | 80 +------- src/core/filter/providers/helper.ts | 171 ++++++++++++++++++ src/core/login/pages/sites/sites.ts | 6 +- .../providers/pushnotifications.ts | 92 ++++++---- .../classes/base-question-component.ts | 1 + .../components/question/core-question.html | 4 +- .../question/components/question/question.ts | 2 + src/core/question/providers/helper.ts | 5 +- .../settings/pages/space-usage/space-usage.ts | 6 +- .../components/module-index/module-index.ts | 2 +- .../user-profile-field/user-profile-field.ts | 2 + src/core/user/pages/about/about.html | 2 +- src/core/viewer/pages/text/text.html | 2 +- src/core/viewer/pages/text/text.ts | 2 + src/directives/format-text.ts | 9 +- src/providers/utils/text.ts | 6 +- 149 files changed, 507 insertions(+), 348 deletions(-) create mode 100644 src/core/filter/providers/helper.ts diff --git a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html index dbf92f587..cfa2892d6 100644 --- a/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html +++ b/src/addon/block/recentlyaccesseditems/components/recentlyaccesseditems/addon-block-recentlyaccesseditems.html @@ -7,7 +7,7 @@ -

+

diff --git a/src/addon/block/timeline/components/events/addon-block-timeline-events.html b/src/addon/block/timeline/components/events/addon-block-timeline-events.html index 22e5445f7..832c4902d 100644 --- a/src/addon/block/timeline/components/events/addon-block-timeline-events.html +++ b/src/addon/block/timeline/components/events/addon-block-timeline-events.html @@ -5,7 +5,7 @@ -

+

diff --git a/src/addon/calendar/pages/edit-event/edit-event.ts b/src/addon/calendar/pages/edit-event/edit-event.ts index 18f0e5feb..04027cae0 100644 --- a/src/addon/calendar/pages/edit-event/edit-event.ts +++ b/src/addon/calendar/pages/edit-event/edit-event.ts @@ -31,7 +31,7 @@ import { AddonCalendarOfflineProvider } from '../../providers/calendar-offline'; import { AddonCalendarHelperProvider } from '../../providers/helper'; import { AddonCalendarSyncProvider } from '../../providers/calendar-sync'; import { CoreSite } from '@classes/site'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays a form to create/edit an event. @@ -93,7 +93,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { private calendarSync: AddonCalendarSyncProvider, private fb: FormBuilder, private syncProvider: CoreSyncProvider, - private filterProvider: CoreFilterProvider, + private filterHelper: CoreFilterHelperProvider, @Optional() private svComponent: CoreSplitViewComponent) { this.eventId = navParams.get('eventId'); @@ -244,7 +244,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy { // Format the name of the courses. const subPromises = []; courses.forEach((course) => { - subPromises.push(this.filterProvider.getFiltersAndFormatText(course.fullname, 'course', course.id) + subPromises.push(this.filterHelper.getFiltersAndFormatText(course.fullname, 'course', course.id) .then((text) => { course.fullname = text; }).catch(() => { diff --git a/src/addon/competency/components/course/addon-competency-course.html b/src/addon/competency/components/course/addon-competency-course.html index 4ccccd2bc..74722c707 100644 --- a/src/addon/competency/components/course/addon-competency-course.html +++ b/src/addon/competency/components/course/addon-competency-course.html @@ -69,7 +69,7 @@

- +
diff --git a/src/addon/competency/pages/competency/competency.html b/src/addon/competency/pages/competency/competency.html index 1835fb1bc..664ca9393 100644 --- a/src/addon/competency/pages/competency/competency.html +++ b/src/addon/competency/pages/competency/competency.html @@ -48,7 +48,7 @@

- + diff --git a/src/addon/messages/providers/mainmenu-handler.ts b/src/addon/messages/providers/mainmenu-handler.ts index 84cec9984..1544ce7a1 100644 --- a/src/addon/messages/providers/mainmenu-handler.ts +++ b/src/addon/messages/providers/mainmenu-handler.ts @@ -24,7 +24,7 @@ import { CoreLocalNotificationsProvider } from '@providers/local-notifications'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; import { CorePushNotificationsDelegate } from '@core/pushnotifications/providers/delegate'; import { CoreEmulatorHelperProvider } from '@core/emulator/providers/helper'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Handler to inject an option into main menu. @@ -49,7 +49,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr constructor(private messagesProvider: AddonMessagesProvider, private sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, - private localNotificationsProvider: CoreLocalNotificationsProvider, private filterProvider: CoreFilterProvider, + private localNotificationsProvider: CoreLocalNotificationsProvider, private filterHelper: CoreFilterHelperProvider, private pushNotificationsProvider: CorePushNotificationsProvider, utils: CoreUtilsProvider, pushNotificationsDelegate: CorePushNotificationsDelegate, private emulatorHelper: CoreEmulatorHelperProvider) { @@ -297,7 +297,7 @@ export class AddonMessagesMainMenuHandler implements CoreMainMenuHandler, CoreCr title: message.name || message.userfromfullname, }; - return this.filterProvider.getFiltersAndFormatText(message.text, 'system', this.sitesProvider.getCurrentSiteHomeId(), + return this.filterHelper.getFiltersAndFormatText(message.text, 'system', this.sitesProvider.getCurrentSiteHomeId(), {clean: true, singleLine: true}).catch(() => { return message.text; }).then((formattedText) => { diff --git a/src/addon/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html b/src/addon/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html index 131ee1bd6..daa72e7d8 100644 --- a/src/addon/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html +++ b/src/addon/mod/assign/components/feedback-plugin/addon-mod-assign-feedback-plugin.html @@ -8,7 +8,7 @@ {{ 'addon.mod_assign.feedbacknotsupported' | translate }}

- +

diff --git a/src/addon/mod/assign/components/index/addon-mod-assign-index.html b/src/addon/mod/assign/components/index/addon-mod-assign-index.html index a13be748d..eb716eebb 100644 --- a/src/addon/mod/assign/components/index/addon-mod-assign-index.html +++ b/src/addon/mod/assign/components/index/addon-mod-assign-index.html @@ -17,7 +17,7 @@ - + diff --git a/src/addon/mod/assign/components/index/index.ts b/src/addon/mod/assign/components/index/index.ts index 23be9881f..1d37fe6ac 100644 --- a/src/addon/mod/assign/components/index/index.ts +++ b/src/addon/mod/assign/components/index/index.ts @@ -134,7 +134,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo if (this.assign && (this.description || this.assign.introattachments)) { this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, - this.module.id, this.assign.introattachments, true, 'module', this.module.id); + this.module.id, this.assign.introattachments, true, 'module', this.module.id, this.courseId); } } diff --git a/src/addon/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html b/src/addon/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html index 6a6a9dfe8..d7223bfc7 100644 --- a/src/addon/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html +++ b/src/addon/mod/assign/components/submission-plugin/addon-mod-assign-submission-plugin.html @@ -8,7 +8,7 @@ {{ 'addon.mod_assign.submissionnotsupported' | translate }}

- +

diff --git a/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html b/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html index bb23e20a9..6a2fff370 100644 --- a/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html +++ b/src/addon/mod/assign/feedback/comments/component/addon-mod-assign-feedback-comments.html @@ -2,7 +2,7 @@

{{ plugin.name }}

- +

diff --git a/src/addon/mod/assign/feedback/comments/component/comments.ts b/src/addon/mod/assign/feedback/comments/component/comments.ts index 4da125a41..27eb0baa1 100644 --- a/src/addon/mod/assign/feedback/comments/component/comments.ts +++ b/src/addon/mod/assign/feedback/comments/component/comments.ts @@ -66,7 +66,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb if (this.text) { // Open a new state with the text. this.textUtils.expandText(this.plugin.name, this.text, this.component, this.assign.cmid, undefined, true, - 'module', this.assign.cmid); + 'module', this.assign.cmid, this.assign.course); } }); } else if (this.edit) { diff --git a/src/addon/mod/assign/pages/edit/edit.html b/src/addon/mod/assign/pages/edit/edit.html index 8549419ff..c85acfcf9 100644 --- a/src/addon/mod/assign/pages/edit/edit.html +++ b/src/addon/mod/assign/pages/edit/edit.html @@ -1,6 +1,6 @@ - +
@@ -56,7 +56,7 @@

- +

diff --git a/src/addon/mod/chat/pages/index/index.html b/src/addon/mod/chat/pages/index/index.html index 234abae0e..52c0b1f3c 100644 --- a/src/addon/mod/chat/pages/index/index.html +++ b/src/addon/mod/chat/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/chat/pages/session-messages/session-messages.html b/src/addon/mod/chat/pages/session-messages/session-messages.html index f75385d59..0b662db80 100644 --- a/src/addon/mod/chat/pages/session-messages/session-messages.html +++ b/src/addon/mod/chat/pages/session-messages/session-messages.html @@ -40,7 +40,7 @@ {{ message.timestamp * 1000 | coreFormatDate:"strftimetime" }} - {{ message.userfullname }} + {{ message.userfullname }}
@@ -53,7 +53,7 @@

- +

diff --git a/src/addon/mod/choice/components/index/addon-mod-choice-index.html b/src/addon/mod/choice/components/index/addon-mod-choice-index.html index f4c0d5ac2..5857d522a 100644 --- a/src/addon/mod/choice/components/index/addon-mod-choice-index.html +++ b/src/addon/mod/choice/components/index/addon-mod-choice-index.html @@ -14,7 +14,7 @@ - + @@ -24,7 +24,7 @@ -

{{ 'addon.mod_choice.yourselection' | translate }}

+

{{ 'addon.mod_choice.yourselection' | translate }}

{{ 'addon.mod_choice.expired' | translate:{$a: closeTimeReadable} }}

@@ -43,13 +43,13 @@ - {{ 'addon.mod_choice.full' | translate }} + {{ 'addon.mod_choice.full' | translate }} - {{ 'addon.mod_choice.full' | translate }} + {{ 'addon.mod_choice.full' | translate }} @@ -73,13 +73,13 @@ {{ 'addon.mod_choice.resultsnotsynced' | translate }} - + -

+

{{ 'addon.mod_choice.numberofuser' | translate }}: {{ result.numberofuser }} ({{ 'core.percentagenumber' | translate: {$a: result.percentageamountfixed} }})

diff --git a/src/addon/mod/choice/pages/index/index.html b/src/addon/mod/choice/pages/index/index.html index a9b19d1e4..3feab085c 100644 --- a/src/addon/mod/choice/pages/index/index.html +++ b/src/addon/mod/choice/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/data/components/action/addon-mod-data-action.html b/src/addon/mod/data/components/action/addon-mod-data-action.html index b6c9e9924..74d2afd56 100644 --- a/src/addon/mod/data/components/action/addon-mod-data-action.html +++ b/src/addon/mod/data/components/action/addon-mod-data-action.html @@ -22,15 +22,15 @@ - + {{ entry.timecreated * 1000 | coreFormatDate }} {{ entry.timemodified * 1000 | coreFormatDate }} - + -{{entry.fullname}} +{{entry.fullname}} diff --git a/src/addon/mod/data/components/index/addon-mod-data-index.html b/src/addon/mod/data/components/index/addon-mod-data-index.html index c06e0cc8e..1376b8249 100644 --- a/src/addon/mod/data/components/index/addon-mod-data-index.html +++ b/src/addon/mod/data/components/index/addon-mod-data-index.html @@ -19,7 +19,7 @@ - +
diff --git a/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html b/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html index 709a96669..02ce6f367 100644 --- a/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html +++ b/src/addon/mod/data/fields/textarea/component/addon-mod-data-field-textarea.html @@ -6,4 +6,4 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/addon/mod/data/pages/edit/edit.html b/src/addon/mod/data/pages/edit/edit.html index 92b0571aa..d5865e7e3 100644 --- a/src/addon/mod/data/pages/edit/edit.html +++ b/src/addon/mod/data/pages/edit/edit.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index 2c9fc51e0..7a9741482 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -1,6 +1,6 @@ - + @@ -12,7 +12,7 @@ -

+

{{ entry.timemodified | coreDateDayOrTime }}

{{ entry.userfullname }}

@@ -21,7 +21,7 @@ {{ entry.timemodified | coreDateDayOrTime }} - +
diff --git a/src/addon/mod/glossary/pages/index/index.html b/src/addon/mod/glossary/pages/index/index.html index 35ce44b11..bd651f76a 100644 --- a/src/addon/mod/glossary/pages/index/index.html +++ b/src/addon/mod/glossary/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html index 435cf919a..4e431dbfd 100644 --- a/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html +++ b/src/addon/mod/imscp/components/index/addon-mod-imscp-index.html @@ -16,7 +16,7 @@
- +
diff --git a/src/addon/mod/imscp/pages/index/index.html b/src/addon/mod/imscp/pages/index/index.html index f31c63425..b99777991 100644 --- a/src/addon/mod/imscp/pages/index/index.html +++ b/src/addon/mod/imscp/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html index 98c3eaf5d..d7549ccb4 100644 --- a/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html +++ b/src/addon/mod/lesson/components/index/addon-mod-lesson-index.html @@ -18,7 +18,7 @@ - +
diff --git a/src/addon/mod/lesson/pages/index/index.html b/src/addon/mod/lesson/pages/index/index.html index d90832113..691bb7d19 100644 --- a/src/addon/mod/lesson/pages/index/index.html +++ b/src/addon/mod/lesson/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html index deeeaca2c..a3fe189bc 100644 --- a/src/addon/mod/lesson/pages/menu-modal/menu-modal.html +++ b/src/addon/mod/lesson/pages/menu-modal/menu-modal.html @@ -26,7 +26,7 @@ diff --git a/src/addon/mod/lesson/pages/player/player.html b/src/addon/mod/lesson/pages/player/player.html index 5e351352b..13c7e7f0b 100644 --- a/src/addon/mod/lesson/pages/player/player.html +++ b/src/addon/mod/lesson/pages/player/player.html @@ -1,6 +1,6 @@ - +
diff --git a/src/addon/mod/quiz/pages/review/review.html b/src/addon/mod/quiz/pages/review/review.html index 15bafe825..21491de5d 100644 --- a/src/addon/mod/quiz/pages/review/review.html +++ b/src/addon/mod/quiz/pages/review/review.html @@ -54,7 +54,7 @@

{{ data.title }}

- +
@@ -77,7 +77,7 @@ - +
diff --git a/src/addon/mod/resource/components/index/addon-mod-resource-index.html b/src/addon/mod/resource/components/index/addon-mod-resource-index.html index d8172c08b..57c634f15 100644 --- a/src/addon/mod/resource/components/index/addon-mod-resource-index.html +++ b/src/addon/mod/resource/components/index/addon-mod-resource-index.html @@ -13,7 +13,7 @@ - + diff --git a/src/addon/mod/resource/pages/index/index.html b/src/addon/mod/resource/pages/index/index.html index 59b962225..d52cc3f30 100644 --- a/src/addon/mod/resource/pages/index/index.html +++ b/src/addon/mod/resource/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html index 8d86b0309..27e83c61e 100644 --- a/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html +++ b/src/addon/mod/scorm/components/index/addon-mod-scorm-index.html @@ -14,7 +14,7 @@ - +
@@ -98,8 +98,8 @@

- - + +

diff --git a/src/addon/mod/scorm/pages/index/index.html b/src/addon/mod/scorm/pages/index/index.html index 1bd6f15a8..9a0b2d855 100644 --- a/src/addon/mod/scorm/pages/index/index.html +++ b/src/addon/mod/scorm/pages/index/index.html @@ -1,6 +1,6 @@ - + diff --git a/src/addon/mod/scorm/pages/player/player.html b/src/addon/mod/scorm/pages/player/player.html index 848a18bc9..6d7874bad 100644 --- a/src/addon/mod/scorm/pages/player/player.html +++ b/src/addon/mod/scorm/pages/player/player.html @@ -1,6 +1,6 @@ - +
diff --git a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html index ae2f63831..e70dcfd69 100644 --- a/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html +++ b/src/addon/qtype/ddwtos/component/addon-qtype-ddwtos.html @@ -7,7 +7,7 @@ {{ 'core.question.howtodraganddrop' | translate }}

-

+

diff --git a/src/addon/qtype/ddwtos/component/ddwtos.ts b/src/addon/qtype/ddwtos/component/ddwtos.ts index 83190e29e..2ad23fcc6 100644 --- a/src/addon/qtype/ddwtos/component/ddwtos.ts +++ b/src/addon/qtype/ddwtos/component/ddwtos.ts @@ -96,7 +96,7 @@ export class AddonQtypeDdwtosComponent extends CoreQuestionBaseComponent impleme this.question, this.question.readOnly, this.inputIds, this.textUtils); this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, - this.contextInstanceId); + this.contextInstanceId, this.courseId); this.question.loaded = true; }); diff --git a/src/addon/qtype/description/component/addon-qtype-description.html b/src/addon/qtype/description/component/addon-qtype-description.html index 3b54888e9..1264d7c70 100644 --- a/src/addon/qtype/description/component/addon-qtype-description.html +++ b/src/addon/qtype/description/component/addon-qtype-description.html @@ -2,6 +2,6 @@ -

+

diff --git a/src/addon/qtype/essay/component/addon-qtype-essay.html b/src/addon/qtype/essay/component/addon-qtype-essay.html index b8df1320e..5c171fedc 100644 --- a/src/addon/qtype/essay/component/addon-qtype-essay.html +++ b/src/addon/qtype/essay/component/addon-qtype-essay.html @@ -1,7 +1,7 @@
-

+

@@ -20,7 +20,7 @@

{{ 'core.question.errorinlinefilesnotsupported' | translate }}

-

+

@@ -31,7 +31,7 @@ -

+

diff --git a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html index 099c6d926..88b29f7fc 100644 --- a/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html +++ b/src/addon/qtype/gapselect/component/addon-qtype-gapselect.html @@ -1,5 +1,5 @@
-

+

diff --git a/src/addon/qtype/gapselect/component/gapselect.ts b/src/addon/qtype/gapselect/component/gapselect.ts index 2faa3ada4..7dd56017f 100644 --- a/src/addon/qtype/gapselect/component/gapselect.ts +++ b/src/addon/qtype/gapselect/component/gapselect.ts @@ -45,6 +45,6 @@ export class AddonQtypeGapSelectComponent extends CoreQuestionBaseComponent impl */ questionRendered(): void { this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, - this.contextInstanceId); + this.contextInstanceId, this.courseId); } } diff --git a/src/addon/qtype/match/component/addon-qtype-match.html b/src/addon/qtype/match/component/addon-qtype-match.html index 2f48f297d..08bbe1274 100644 --- a/src/addon/qtype/match/component/addon-qtype-match.html +++ b/src/addon/qtype/match/component/addon-qtype-match.html @@ -1,12 +1,12 @@
- + -

+

diff --git a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html index 68b989b11..919da0149 100644 --- a/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html +++ b/src/addon/qtype/multianswer/component/addon-qtype-multianswer.html @@ -1,5 +1,5 @@
- +
diff --git a/src/addon/qtype/multianswer/component/multianswer.ts b/src/addon/qtype/multianswer/component/multianswer.ts index d19750434..7c2257113 100644 --- a/src/addon/qtype/multianswer/component/multianswer.ts +++ b/src/addon/qtype/multianswer/component/multianswer.ts @@ -45,6 +45,6 @@ export class AddonQtypeMultiAnswerComponent extends CoreQuestionBaseComponent im */ questionRendered(): void { this.questionHelper.treatCorrectnessIconsClicks(this.element, this.component, this.componentId, this.contextLevel, - this.contextInstanceId); + this.contextInstanceId, this.courseId); } } diff --git a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html index 2914b227a..32fc617f0 100644 --- a/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html +++ b/src/addon/qtype/multichoice/component/addon-qtype-multichoice.html @@ -1,7 +1,7 @@
- + {{ question.prompt }} @@ -9,8 +9,8 @@ - -
+ +
@@ -25,8 +25,8 @@
- -
+ +
diff --git a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html index ee269de08..5cfaf0139 100644 --- a/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html +++ b/src/addon/qtype/shortanswer/component/addon-qtype-shortanswer.html @@ -1,6 +1,6 @@
- + {{ 'addon.mod_quiz.answercolon' | translate }} diff --git a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html index d46a4dbd6..ac8f11b04 100644 --- a/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html +++ b/src/addon/userprofilefield/menu/component/addon-user-profile-field-menu.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index 89f82faa0..c4365a44f 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -29,6 +29,7 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { @Input() form?: FormGroup; // Form where to add the form control. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). constructor(private fb: FormBuilder) { } diff --git a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html index 5c9a460eb..977c7cd49 100644 --- a/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html +++ b/src/addon/userprofilefield/text/component/addon-user-profile-field-text.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index c25ff38d9..1eda97aca 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -30,6 +30,7 @@ export class AddonUserProfileFieldTextComponent implements OnInit { @Input() form?: FormGroup; // Form where to add the form control. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } diff --git a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html index a9e60d41d..1d149a8ed 100644 --- a/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html +++ b/src/addon/userprofilefield/textarea/component/addon-user-profile-field-textarea.html @@ -1,7 +1,7 @@

{{ field.name }}

-

+

diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index 475e1e717..43c9701b2 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -29,6 +29,7 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { @Input() form?: FormGroup; // Form where to add the form control. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the field belongs to (if any). control: FormControl; diff --git a/src/components/chart/chart.ts b/src/components/chart/chart.ts index cd310d47e..b3715ad43 100644 --- a/src/components/chart/chart.ts +++ b/src/components/chart/chart.ts @@ -15,6 +15,7 @@ import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild } from '@angular/core'; import { Chart } from 'chart.js'; import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; import { CoreUtilsProvider } from '@providers/utils/utils'; /** @@ -49,12 +50,14 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { @Input() filter?: boolean | string; // Whether to filter labels. If not defined, true if contextLevel and instanceId are set. @Input() contextLevel?: string; // The context level of the text. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the labels for some reason. @ViewChild('canvas') canvas: ElementRef; chart: any; - constructor(protected filterProvider: CoreFilterProvider, private utils: CoreUtilsProvider) { } + constructor(protected filterProvider: CoreFilterProvider, private utils: CoreUtilsProvider, + private filterHelper: CoreFilterHelperProvider) { } /** * Component being initialized. @@ -140,10 +143,11 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { const options = { clean: true, singleLine: true, + courseId: this.courseId, wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) }; - return this.filterProvider.getFilters(this.contextLevel, this.contextInstanceId, options).then((filters) => { + return this.filterHelper.getFilters(this.contextLevel, this.contextInstanceId, options).then((filters) => { const promises = []; this.labels.forEach((label, i) => { diff --git a/src/components/navigation-bar/navigation-bar.ts b/src/components/navigation-bar/navigation-bar.ts index 5a38d49a0..2942e5c2d 100644 --- a/src/components/navigation-bar/navigation-bar.ts +++ b/src/components/navigation-bar/navigation-bar.ts @@ -37,6 +37,7 @@ export class CoreNavigationBarComponent { @Input() componentId?: number; // Component ID. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Output() action?: EventEmitter; // Function to call when an arrow is clicked. Will receive as a param the item to load. constructor(private textUtils: CoreTextUtilsProvider) { @@ -45,6 +46,6 @@ export class CoreNavigationBarComponent { showInfo(): void { this.textUtils.expandText(this.title, this.info, this.component, this.componentId, [], true, this.contextLevel, - this.contextInstanceId); + this.contextInstanceId, this.courseId); } } diff --git a/src/components/site-picker/site-picker.ts b/src/components/site-picker/site-picker.ts index c0ca1d6e1..e3b9c62ae 100644 --- a/src/components/site-picker/site-picker.ts +++ b/src/components/site-picker/site-picker.ts @@ -15,7 +15,7 @@ import { Component, Input, Output, EventEmitter, OnInit } from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreSitesProvider } from '@providers/sites'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Component to display a site selector. It will display a select with the list of sites. If the selected site changes, @@ -36,7 +36,7 @@ export class CoreSitePickerComponent implements OnInit { sites: any[]; constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, - private filterProvider: CoreFilterProvider) { + private filterHelper: CoreFilterHelperProvider) { this.siteSelected = new EventEmitter(); } @@ -49,7 +49,7 @@ export class CoreSitePickerComponent implements OnInit { sites.forEach((site: any) => { // Format the site name. - promises.push(this.filterProvider.getFiltersAndFormatText(site.siteName, 'system', site.siteHomeId, + promises.push(this.filterHelper.getFiltersAndFormatText(site.siteName, 'system', site.siteHomeId, {clean: true, singleLine: true}, site.getId()).catch(() => { return site.siteName; }).then((formatted) => { diff --git a/src/core/comments/components/comments/comments.ts b/src/core/comments/components/comments/comments.ts index ae310eae8..0f23b3353 100644 --- a/src/core/comments/components/comments/comments.ts +++ b/src/core/comments/components/comments/comments.ts @@ -35,6 +35,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { @Input() title?: string; @Input() displaySpinner = true; // Whether to display the loading spinner. @Output() onLoading: EventEmitter; // Eevent that indicates whether the component is loading data. + @Input() courseId?: number; // Course ID the comments belong to. It can be used to improve performance with filters. commentsLoaded = false; commentsCount: string; @@ -155,6 +156,7 @@ export class CoreCommentsCommentsComponent implements OnChanges, OnDestroy { itemId: this.itemId, area: this.area, title: this.title, + courseId: this.courseId }); } } diff --git a/src/core/comments/pages/viewer/viewer.html b/src/core/comments/pages/viewer/viewer.html index dc24b1acc..40eadf01b 100644 --- a/src/core/comments/pages/viewer/viewer.html +++ b/src/core/comments/pages/viewer/viewer.html @@ -1,6 +1,6 @@ - + - + @@ -56,7 +56,7 @@
- + diff --git a/src/core/comments/pages/viewer/viewer.ts b/src/core/comments/pages/viewer/viewer.ts index 63f88471b..b17d39f17 100644 --- a/src/core/comments/pages/viewer/viewer.ts +++ b/src/core/comments/pages/viewer/viewer.ts @@ -47,6 +47,7 @@ export class CoreCommentsViewerPage implements OnDestroy { area: string; page: number; title: string; + courseId: number; canLoadMore = false; loadMoreError = false; canAddComments = false; @@ -74,6 +75,7 @@ export class CoreCommentsViewerPage implements OnDestroy { this.itemId = navParams.get('itemId'); this.area = navParams.get('area') || ''; this.title = navParams.get('title') || this.translate.instant('core.comments.comments'); + this.courseId = navParams.get('courseId'); this.page = 0; // Refresh data if comments are synchronized automatically. diff --git a/src/core/course/classes/main-resource-component.ts b/src/core/course/classes/main-resource-component.ts index 2233f893a..87691f7d8 100644 --- a/src/core/course/classes/main-resource-component.ts +++ b/src/core/course/classes/main-resource-component.ts @@ -237,7 +237,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy, */ expandDescription(): void { this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id, - [], true, 'module', this.module.id); + [], true, 'module', this.module.id, this.courseId); } /** diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index ff3f265d1..a2db476ad 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -17,7 +17,7 @@ import { TranslateService } from '@ngx-translate/core'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreUserProvider } from '@core/user/providers/user'; import { CoreCourseProvider } from '../../providers/course'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing @@ -41,7 +41,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { completionImage: string; completionDescription: string; - constructor(private filterProvider: CoreFilterProvider, private domUtils: CoreDomUtilsProvider, + constructor(private filterHelper: CoreFilterHelperProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private courseProvider: CoreCourseProvider, private userProvider: CoreUserProvider) { this.completionChanged = new EventEmitter(); @@ -138,8 +138,8 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { } if (moduleName) { - this.filterProvider.getFiltersAndFormatText(moduleName, 'module', this.moduleId, - {clean: true, singleLine: true, shortenLength: 50}).then((modName) => { + this.filterHelper.getFiltersAndFormatText(moduleName, 'module', this.moduleId, + {clean: true, singleLine: true, shortenLength: 50, courseId: this.completion.courseId}).then((modName) => { let promise; diff --git a/src/core/course/components/module-description/core-course-module-description.html b/src/core/course/components/module-description/core-course-module-description.html index af4e525b3..7cb12666b 100644 --- a/src/core/course/components/module-description/core-course-module-description.html +++ b/src/core/course/components/module-description/core-course-module-description.html @@ -1,6 +1,6 @@ - + {{ note }} diff --git a/src/core/course/components/module-description/module-description.ts b/src/core/course/components/module-description/module-description.ts index eaa08a1b0..c17f51609 100644 --- a/src/core/course/components/module-description/module-description.ts +++ b/src/core/course/components/module-description/module-description.ts @@ -42,6 +42,7 @@ export class CoreCourseModuleDescriptionComponent { @Input() showFull?: string | boolean; // Whether to always display the full description. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. constructor() { // Nothing to do. diff --git a/src/core/course/components/module/core-course-module.html b/src/core/course/components/module/core-course-module.html index 4d9a4021d..3c57e9abb 100644 --- a/src/core/course/components/module/core-course-module.html +++ b/src/core/course/components/module/core-course-module.html @@ -2,7 +2,7 @@
- +
@@ -27,9 +27,9 @@ {{ 'core.course.hiddenoncoursepage' | translate }}
{{ 'core.restricted' | translate }} - +
{{ 'core.course.manualcompletionnotsynced' | translate }}
- + \ No newline at end of file diff --git a/src/core/course/components/unsupported-module/core-course-unsupported-module.html b/src/core/course/components/unsupported-module/core-course-unsupported-module.html index f1796de2e..e28fe05c8 100644 --- a/src/core/course/components/unsupported-module/core-course-unsupported-module.html +++ b/src/core/course/components/unsupported-module/core-course-unsupported-module.html @@ -1,5 +1,5 @@
- +

{{ 'core.whoops' | translate }}

{{ 'core.uhoh' | translate }}

diff --git a/src/core/course/components/unsupported-module/unsupported-module.ts b/src/core/course/components/unsupported-module/unsupported-module.ts index c606d4658..6a0cfdd2c 100644 --- a/src/core/course/components/unsupported-module/unsupported-module.ts +++ b/src/core/course/components/unsupported-module/unsupported-module.ts @@ -24,7 +24,7 @@ import { CoreCourseModuleDelegate } from '../../providers/module-delegate'; templateUrl: 'core-course-unsupported-module.html', }) export class CoreCourseUnsupportedModuleComponent implements OnInit { - @Input() course: any; // The course to module belongs to. + @Input() courseId: number; // The course to module belongs to. @Input() module: any; // The module to render. isDisabledInSite: boolean; diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 5590b1c27..de9e1cdf2 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -29,7 +29,7 @@ import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay, CoreCourseOptionsMenuHandlerToDisplay } from '../../providers/options-delegate'; import { CoreCourseSyncProvider } from '../../providers/sync'; import { CoreCourseFormatComponent } from '../../components/format/format'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays the list of courses the user is enrolled in. @@ -75,7 +75,7 @@ export class CoreCourseSectionPage implements OnDestroy { constructor(navParams: NavParams, private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider, - private coursesProvider: CoreCoursesProvider, private filterProvider: CoreFilterProvider, + private coursesProvider: CoreCoursesProvider, private filterHelper: CoreFilterHelperProvider, sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector, private prefetchDelegate: CoreCourseModulePrefetchDelegate, private syncProvider: CoreCourseSyncProvider, private utils: CoreUtilsProvider) { @@ -263,7 +263,7 @@ export class CoreCourseSectionPage implements OnDestroy { // Format the name of each section and check if it has content. this.sections = sections.map((section) => { - this.filterProvider.getFiltersAndFormatText(section.name.trim(), 'course', this.course.id, + this.filterHelper.getFiltersAndFormatText(section.name.trim(), 'course', this.course.id, {clean: true, singleLine: true}).then((name) => { section.formattedName = name; }); diff --git a/src/core/course/pages/unsupported-module/unsupported-module.html b/src/core/course/pages/unsupported-module/unsupported-module.html index c0ae5285a..21a839390 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.html +++ b/src/core/course/pages/unsupported-module/unsupported-module.html @@ -1,6 +1,6 @@ - + @@ -11,5 +11,5 @@ - + diff --git a/src/core/course/pages/unsupported-module/unsupported-module.ts b/src/core/course/pages/unsupported-module/unsupported-module.ts index 6e4c87008..a28a2dde6 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.ts @@ -27,9 +27,11 @@ import { CoreTextUtilsProvider } from '@providers/utils/text'; }) export class CoreCourseUnsupportedModulePage { module: any; + courseId: number; constructor(navParams: NavParams, private translate: TranslateService, private textUtils: CoreTextUtilsProvider) { this.module = navParams.get('module') || {}; + this.courseId = navParams.get('courseId'); } /** @@ -37,6 +39,6 @@ export class CoreCourseUnsupportedModulePage { */ expandDescription(): void { this.textUtils.expandText(this.translate.instant('core.description'), this.module.description, undefined, undefined, - [], true, 'module', this.module.id); + [], true, 'module', this.module.id, this.courseId); } } diff --git a/src/core/course/providers/default-module.ts b/src/core/course/providers/default-module.ts index c69e280a6..bfa297f11 100644 --- a/src/core/course/providers/default-module.ts +++ b/src/core/course/providers/default-module.ts @@ -55,7 +55,7 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler { event.preventDefault(); event.stopPropagation(); - navCtrl.push('CoreCourseUnsupportedModulePage', { module: module }, options); + navCtrl.push('CoreCourseUnsupportedModulePage', { module: module, courseId: courseId }, options); } }; diff --git a/src/core/filter/filter.module.ts b/src/core/filter/filter.module.ts index 20b0073c0..81c898f55 100644 --- a/src/core/filter/filter.module.ts +++ b/src/core/filter/filter.module.ts @@ -16,11 +16,13 @@ import { NgModule } from '@angular/core'; import { CoreFilterProvider } from './providers/filter'; import { CoreFilterDelegate } from './providers/delegate'; import { CoreFilterDefaultHandler } from './providers/default-filter'; +import { CoreFilterHelperProvider } from './providers/helper'; // List of providers (without handlers). export const CORE_FILTER_PROVIDERS: any[] = [ CoreFilterProvider, - CoreFilterDelegate + CoreFilterDelegate, + CoreFilterHelperProvider ]; @NgModule({ @@ -31,7 +33,8 @@ export const CORE_FILTER_PROVIDERS: any[] = [ providers: [ CoreFilterProvider, CoreFilterDelegate, - CoreFilterDefaultHandler + CoreFilterHelperProvider, + CoreFilterDefaultHandler, ] }) export class CoreFilterModule { } diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts index ca8e77078..8092d01c4 100644 --- a/src/core/filter/providers/filter.ts +++ b/src/core/filter/providers/filter.ts @@ -247,65 +247,6 @@ export class CoreFilterProvider { }); } - /** - * Get the filters in a certain context, performing some checks like the site version. - * It's recommended to use this function instead of canGetAvailableInContext because this function will check if - * it's really needed to call the WS. - * - * @param contextLevel The context level. - * @param instanceId Instance ID related to the context. - * @param options Options for format text. - * @param siteId Site ID. If not defined, current site. - * @return Promise resolved with the filters. - */ - getFilters(contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string) - : Promise { - - options.contextLevel = contextLevel; - options.instanceId = instanceId; - options.filter = false; - - return this.canGetAvailableInContext(siteId).then((canGet) => { - if (!canGet) { - options.filter = true; - - // We cannot check which filters are available, apply them all. - return this.filterDelegate.getEnabledFilters(contextLevel, instanceId); - } - - // Check if site has any filter to treat. - return this.siteHasFiltersToTreat(options, siteId).then((hasFilters) => { - if (hasFilters) { - options.filter = true; - - return this.getAvailableInContext(contextLevel, instanceId, siteId); - } - - return []; - }).catch(() => { - return []; - }); - }); - } - - /** - * Get filters and format text. - * - * @param text Text to filter. - * @param contextLevel The context level. - * @param instanceId Instance ID related to the context. - * @param options Options for format text. - * @param siteId Site ID. If not defined, current site. - * @return Promise resolved with the formatted text. - */ - getFiltersAndFormatText(text: string, contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, - siteId?: string): Promise { - - return this.getFilters(contextLevel, instanceId, options, siteId).then((filters) => { - return this.formatText(text, options, filters, siteId); - }); - } - /** * Invalidates all available in context WS calls. * @@ -342,26 +283,6 @@ export class CoreFilterProvider { invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { return this.invalidateAvailableInContexts([{contextlevel: contextLevel, instanceid: instanceId}], siteId); } - - /** - * Check if site has available any filter that should be treated by the app. - * - * @param options Options passed to the filters. - * @param siteId Site ID. If not defined, current site. - * @return Promise resolved with boolean: whether it has filters to treat. - */ - siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise { - options = options || {}; - - return this.sitesProvider.getSite(siteId).then((site) => { - - // Get filters at site level. - return this.getAvailableInContext('system', site.getSiteHomeId(), site.getId()).then((filters) => { - - return this.filterDelegate.shouldBeApplied(filters, options, site); - }); - }); - } } /** @@ -396,4 +317,5 @@ export type CoreFilterFormatTextOptions = { shortenLength?: number; // Number of characters to shorten the text. highlight?: string; // Text to highlight. wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason. + courseId?: number; // Course ID the text belongs to. It can be used to improve performance. }; diff --git a/src/core/filter/providers/helper.ts b/src/core/filter/providers/helper.ts new file mode 100644 index 000000000..06c4a7b83 --- /dev/null +++ b/src/core/filter/providers/helper.ts @@ -0,0 +1,171 @@ +// (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 { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreFilterDelegate } from './delegate'; +import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; +import { CoreCourseProvider } from '@core/course/providers/course'; +import { CoreSite } from '@classes/site'; + +/** + * Helper service to provide filter functionalities. + */ +@Injectable() +export class CoreFilterHelperProvider { + + protected logger; + + constructor(logger: CoreLoggerProvider, + private sitesProvider: CoreSitesProvider, + private filterDelegate: CoreFilterDelegate, + private courseProvider: CoreCourseProvider, + private filterProvider: CoreFilterProvider) { + this.logger = logger.getInstance('CoreFilterHelperProvider'); + } + + /** + * Get the contexts of all course modules in a course. + * + * @param courseId Course ID. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the contexts. + */ + getCourseModulesContexts(courseId: number, siteId?: string): Promise<{contextlevel: string, instanceid: number}[]> { + + return this.courseProvider.getSections(courseId, false, true, {omitExpires: true}, siteId).then((sections) => { + const contexts: {contextlevel: string, instanceid: number}[] = []; + + sections.forEach((section) => { + if (section.modules) { + section.modules.forEach((module) => { + if (module.uservisible) { + contexts.push({ + contextlevel: 'module', + instanceid: module.id + }); + } + }); + } + }); + + return contexts; + }); + } + + /** + * Get the filters in a certain context, performing some checks like the site version. + * It's recommended to use this function instead of canGetAvailableInContext because this function will check if + * it's really needed to call the WS. + * + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the filters. + */ + getFilters(contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string) + : Promise { + + let site: CoreSite; + + options.contextLevel = contextLevel; + options.instanceId = instanceId; + options.filter = false; + + return this.sitesProvider.getSite(siteId).then((s) => { + site = s; + + return this.filterProvider.canGetAvailableInContext(siteId); + }).then((canGet) => { + if (!canGet) { + options.filter = true; + + // We cannot check which filters are available, apply them all. + return this.filterDelegate.getEnabledFilters(contextLevel, instanceId); + } + + let promise: Promise; + + if (instanceId == site.getSiteHomeId() && (contextLevel == 'system' || contextLevel == 'course')) { + // No need to check the site filters because we're requesting the same context, so we'd do the same twice. + promise = Promise.resolve(true); + } else { + // Check if site has any filter to treat. + promise = this.siteHasFiltersToTreat(options, siteId); + } + + return promise.then((hasFilters) => { + if (hasFilters) { + options.filter = true; + + if (contextLevel == 'module' && options.courseId) { + // Get all the modules filters with a single call to decrease the number of WS calls. + return this.getCourseModulesContexts(options.courseId, site.getId()).then((contexts) => { + + return this.filterProvider.getAvailableInContexts(contexts, site.getId()).then((filters) => { + return filters[contextLevel][instanceId] || []; + }); + }); + } + + return this.filterProvider.getAvailableInContext(contextLevel, instanceId, siteId); + } + + return []; + }).catch(() => { + return []; + }); + }); + } + + /** + * Get filters and format text. + * + * @param text Text to filter. + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options for format text. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with the formatted text. + */ + getFiltersAndFormatText(text: string, contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, + siteId?: string): Promise { + + return this.getFilters(contextLevel, instanceId, options, siteId).then((filters) => { + return this.filterProvider.formatText(text, options, filters, siteId); + }); + } + + /** + * Check if site has available any filter that should be treated by the app. + * + * @param options Options passed to the filters. + * @param siteId Site ID. If not defined, current site. + * @return Promise resolved with boolean: whether it has filters to treat. + */ + siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise { + options = options || {}; + + return this.sitesProvider.getSite(siteId).then((site) => { + + // Get filters at site level. + return this.filterProvider.getAvailableInContext('system', site.getSiteHomeId(), site.getId()).then((filters) => { + + return this.filterDelegate.shouldBeApplied(filters, options, site); + }); + }); + } +} diff --git a/src/core/login/pages/sites/sites.ts b/src/core/login/pages/sites/sites.ts index 1ed6b49c2..8283f15be 100644 --- a/src/core/login/pages/sites/sites.ts +++ b/src/core/login/pages/sites/sites.ts @@ -20,7 +20,7 @@ import { CoreSitesProvider, CoreSiteBasicInfo } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CorePushNotificationsProvider } from '@core/pushnotifications/providers/pushnotifications'; import { CoreLoginHelperProvider } from '../../providers/helper'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays the list of stored sites. @@ -35,7 +35,7 @@ export class CoreLoginSitesPage { showDelete: boolean; protected logger; - constructor(private domUtils: CoreDomUtilsProvider, private filterProvider: CoreFilterProvider, + constructor(private domUtils: CoreDomUtilsProvider, private filterHelper: CoreFilterHelperProvider, private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, logger: CoreLoggerProvider, private translate: TranslateService, private pushNotificationsProvider: CorePushNotificationsProvider) { this.logger = logger.getInstance('CoreLoginSitesPage'); @@ -86,7 +86,7 @@ export class CoreLoginSitesPage { const site = this.sites[index], siteName = site.siteName; - this.filterProvider.getFiltersAndFormatText(siteName, 'system', site.siteHomeId, {clean: true, singleLine: true}, site.id) + this.filterHelper.getFiltersAndFormatText(siteName, 'system', site.siteHomeId, {clean: true, singleLine: true}, site.id) .then((siteName) => { this.domUtils.showConfirm(this.translate.instant('core.login.confirmdeletesite', { sitename: siteName })).then(() => { diff --git a/src/core/pushnotifications/providers/pushnotifications.ts b/src/core/pushnotifications/providers/pushnotifications.ts index 652647f3e..982041ac2 100644 --- a/src/core/pushnotifications/providers/pushnotifications.ts +++ b/src/core/pushnotifications/providers/pushnotifications.ts @@ -34,6 +34,7 @@ import { ILocalNotification } from '@ionic-native/local-notifications'; import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb'; import { CoreSite } from '@classes/site'; import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterDelegate } from '@core/filter/providers/delegate'; /** * Data needed to register a device in a Moodle site. @@ -178,7 +179,7 @@ export class CorePushNotificationsProvider { private utils: CoreUtilsProvider, private textUtils: CoreTextUtilsProvider, private push: Push, private configProvider: CoreConfigProvider, private device: Device, private zone: NgZone, private translate: TranslateService, private platform: Platform, private sitesFactory: CoreSitesFactoryProvider, - private filterProvider: CoreFilterProvider) { + private filterProvider: CoreFilterProvider, private filterDelegate: CoreFilterDelegate) { this.logger = logger.getInstance('CorePushNotificationsProvider'); this.appDB = appProvider.getDB(); this.appDB.createTablesFromSchema(this.appTablesSchema); @@ -448,46 +449,67 @@ export class CorePushNotificationsProvider { text: '', channel: 'PushPluginChannel' }, - promises = [], + options = { + clean: true, + singleLine: true, + contextLevel: 'system', + instanceId: site.getSiteHomeId(), + filter: true + }, isAndroid = this.platform.is('android'), extraFeatures = this.utils.isTrueOrOne(data.extrafeatures); - // Apply formatText to title and message. - promises.push(this.filterProvider.getFiltersAndFormatText(notification.title, 'system', site.getSiteHomeId(), - {clean: true, singleLine: true}, site.getId()).then((title) => { - localNotif.title = title; - }).catch(() => { - localNotif.title = notification.title; - })); - - promises.push(this.filterProvider.getFiltersAndFormatText(notification.message, 'system', site.getSiteHomeId(), - {clean: true, singleLine: true}, site.getId()).catch(() => { - // Error formatting, use the original message. - return notification.message; - }).then((formattedMessage) => { - if (extraFeatures && isAndroid && this.utils.isFalseOrZero(data.notif)) { - // It's a message, use messaging style. Ionic Native doesn't specify this option. - ( localNotif).text = [ - { - message: formattedMessage, - person: data.conversationtype == 2 ? data.userfromfullname : '' - } - ]; - } else { - localNotif.text = formattedMessage; + // Get the filters to apply to text and message. Don't use FIlterHelper to prevent circular dependencies. + this.filterProvider.canGetAvailableInContext(site.getId()).then((canGet) => { + if (!canGet) { + // We cannot check which filters are available, apply them all. + return this.filterDelegate.getEnabledFilters(options.contextLevel, options.instanceId); } - })); - if (extraFeatures && isAndroid) { - // Use a different icon if needed. - localNotif.icon = notification.image; - // This feature isn't supported by the official plugin, we use a fork. - ( localNotif).iconType = data['image-type']; - } + return this.filterProvider.getAvailableInContext(options.contextLevel, options.instanceId, site.getId()); + }).catch(() => { + return []; + }).then((filters) => { + const promises = []; + + // Apply formatText to title and message. + promises.push(this.filterProvider.formatText(notification.title, options, filters, site.getId()) + .then((title) => { + localNotif.title = title; + }).catch(() => { + localNotif.title = notification.title; + })); + + promises.push(this.filterProvider.formatText(notification.message, options, filters, site.getId()) + .catch(() => { + // Error formatting, use the original message. + return notification.message; + }).then((formattedMessage) => { + if (extraFeatures && isAndroid && this.utils.isFalseOrZero(data.notif)) { + // It's a message, use messaging style. Ionic Native doesn't specify this option. + ( localNotif).text = [ + { + message: formattedMessage, + person: data.conversationtype == 2 ? data.userfromfullname : '' + } + ]; + } else { + localNotif.text = formattedMessage; + } + })); + + if (extraFeatures && isAndroid) { + // Use a different icon if needed. + localNotif.icon = notification.image; + // This feature isn't supported by the official plugin, we use a fork. + ( localNotif).iconType = data['image-type']; + } + + Promise.all(promises).then(() => { + this.localNotificationsProvider.schedule(localNotif, CorePushNotificationsProvider.COMPONENT, data.site, + true); + }); - Promise.all(promises).then(() => { - this.localNotificationsProvider.schedule(localNotif, CorePushNotificationsProvider.COMPONENT, data.site, - true); }); } diff --git a/src/core/question/classes/base-question-component.ts b/src/core/question/classes/base-question-component.ts index 9f6f51be1..bb0ca438a 100644 --- a/src/core/question/classes/base-question-component.ts +++ b/src/core/question/classes/base-question-component.ts @@ -29,6 +29,7 @@ export class CoreQuestionBaseComponent { @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // The course the question belongs to (if any). @Output() buttonClicked: EventEmitter; // Should emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Should emit an event if the question should be aborted. diff --git a/src/core/question/components/question/core-question.html b/src/core/question/components/question/core-question.html index 2124c94d4..005a3ce7d 100644 --- a/src/core/question/components/question/core-question.html +++ b/src/core/question/components/question/core-question.html @@ -23,10 +23,10 @@ -

+

-

+

diff --git a/src/core/question/components/question/question.ts b/src/core/question/components/question/question.ts index 5890dc9d5..ce43dbf13 100644 --- a/src/core/question/components/question/question.ts +++ b/src/core/question/components/question/question.ts @@ -38,6 +38,7 @@ export class CoreQuestionComponent implements OnInit { @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters. @Output() buttonClicked: EventEmitter; // Will emit an event when a behaviour button is clicked. @Output() onAbort: EventEmitter; // Will emit an event if the question should be aborted. @@ -86,6 +87,7 @@ export class CoreQuestionComponent implements OnInit { offlineEnabled: this.offlineEnabled, contextLevel: this.contextLevel, contextInstanceId: this.contextInstanceId, + courseId: this.courseId, buttonClicked: this.buttonClicked, onAbort: this.onAbort }; diff --git a/src/core/question/providers/helper.ts b/src/core/question/providers/helper.ts index 6a2a257b4..4b7da5ea3 100644 --- a/src/core/question/providers/helper.ts +++ b/src/core/question/providers/helper.ts @@ -693,9 +693,10 @@ export class CoreQuestionHelperProvider { * @param componentId An ID to use in conjunction with the component. * @param contextLevel The context level. * @param contextInstanceId Instance ID related to the context. + * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. */ treatCorrectnessIconsClicks(element: HTMLElement, component?: string, componentId?: number, contextLevel?: string, - contextInstanceId?: number): void { + contextInstanceId?: number, courseId?: number): void { const icons = Array.from(element.querySelectorAll('i.icon.questioncorrectnessicon[tappable]')), title = this.translate.instant('core.question.feedback'); @@ -708,7 +709,7 @@ export class CoreQuestionHelperProvider { // There's a hidden feedback, show it when the icon is clicked. icon.addEventListener('click', (event) => { this.textUtils.expandText(title, span.innerHTML, component, componentId, [], true, contextLevel, - contextInstanceId); + contextInstanceId, courseId); }); } }); diff --git a/src/core/settings/pages/space-usage/space-usage.ts b/src/core/settings/pages/space-usage/space-usage.ts index 61b75b041..8ce241df8 100644 --- a/src/core/settings/pages/space-usage/space-usage.ts +++ b/src/core/settings/pages/space-usage/space-usage.ts @@ -20,7 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; import { CoreCourseProvider } from '@core/course/providers/course'; -import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Page that displays the space usage settings. @@ -39,7 +39,7 @@ export class CoreSettingsSpaceUsagePage { totalEntries = 0; constructor(private filePoolProvider: CoreFilepoolProvider, - private sitesProvider: CoreSitesProvider, private filterProvider: CoreFilterProvider, + private sitesProvider: CoreSitesProvider, private filterHelper: CoreFilterHelperProvider, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, appProvider: CoreAppProvider, private courseProvider: CoreCourseProvider) { this.currentSiteId = this.sitesProvider.getCurrentSiteId(); @@ -162,7 +162,7 @@ export class CoreSettingsSpaceUsagePage { * @param siteData Site object with space usage. */ deleteSiteStorage(siteData: any): void { - this.filterProvider.getFiltersAndFormatText(siteData.siteName, 'system', siteData.siteHomeId, + this.filterHelper.getFiltersAndFormatText(siteData.siteName, 'system', siteData.siteHomeId, {clean: true, singleLine: true}, siteData.id).then((siteName) => { const title = this.translate.instant('core.settings.deletesitefilestitle'); diff --git a/src/core/siteplugins/components/module-index/module-index.ts b/src/core/siteplugins/components/module-index/module-index.ts index d65486a00..a9f9dfeed 100644 --- a/src/core/siteplugins/components/module-index/module-index.ts +++ b/src/core/siteplugins/components/module-index/module-index.ts @@ -146,7 +146,7 @@ export class CoreSitePluginsModuleIndexComponent implements OnInit, OnDestroy, C */ expandDescription(): void { this.textUtils.expandText(this.translate.instant('core.description'), this.description, this.component, this.module.id, - [], true, 'module', this.module.id); + [], true, 'module', this.module.id, this.courseId); } /** diff --git a/src/core/user/components/user-profile-field/user-profile-field.ts b/src/core/user/components/user-profile-field/user-profile-field.ts index 41c4a1e64..b00acfb20 100644 --- a/src/core/user/components/user-profile-field/user-profile-field.ts +++ b/src/core/user/components/user-profile-field/user-profile-field.ts @@ -31,6 +31,7 @@ export class CoreUserProfileFieldComponent implements OnInit { @Input() registerAuth?: string; // Register auth method. E.g. 'email'. @Input() contextLevel?: string; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters. componentClass: any; // The class of the component to render. data: any = {}; // Data to pass to the component. @@ -55,6 +56,7 @@ export class CoreUserProfileFieldComponent implements OnInit { this.data.registerAuth = this.registerAuth; this.data.contextLevel = this.contextLevel; this.data.contextInstanceId = this.contextInstanceId; + this.data.courseId = this.courseId; } } } diff --git a/src/core/user/pages/about/about.html b/src/core/user/pages/about/about.html index fd73bfdc8..42fa04ce2 100644 --- a/src/core/user/pages/about/about.html +++ b/src/core/user/pages/about/about.html @@ -56,7 +56,7 @@

{{ 'core.user.interests' | translate}}

{{ user.interests }}

- + {{ 'core.user.description' | translate}} diff --git a/src/core/viewer/pages/text/text.html b/src/core/viewer/pages/text/text.html index f73c58612..410063705 100644 --- a/src/core/viewer/pages/text/text.html +++ b/src/core/viewer/pages/text/text.html @@ -10,7 +10,7 @@ - + diff --git a/src/core/viewer/pages/text/text.ts b/src/core/viewer/pages/text/text.ts index 912c9ccff..6c8c1b6f8 100644 --- a/src/core/viewer/pages/text/text.ts +++ b/src/core/viewer/pages/text/text.ts @@ -33,6 +33,7 @@ export class CoreViewerTextPage { filter: boolean; // Whether to filter the text. contextLevel: string; // The context level. instanceId: number; // The instance ID related to the context. + courseId: number; // Course ID the text belongs to. It can be used to improve performance with filters. constructor(private viewCtrl: ViewController, params: NavParams, textUtils: CoreTextUtilsProvider) { this.title = params.get('title'); @@ -43,6 +44,7 @@ export class CoreViewerTextPage { this.filter = params.get('filter'); this.contextLevel = params.get('contextLevel'); this.instanceId = params.get('instanceId'); + this.courseId = params.get('courseId'); } /** diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index 644812107..cffef5187 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -31,6 +31,7 @@ import { CoreExternalContentDirective } from '../directives/external-content'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreFilterProvider } from '@core/filter/providers/filter'; +import { CoreFilterHelperProvider } from '@core/filter/providers/helper'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -61,6 +62,7 @@ export class CoreFormatTextDirective implements OnChanges { @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set. @Input() contextLevel?: string; // The context level of the text. @Input() contextInstanceId?: number; // The instance ID related to the context. + @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason. @Output() afterRender?: EventEmitter; // Called when the data is rendered. @@ -75,7 +77,7 @@ export class CoreFormatTextDirective implements OnChanges { private contentLinksHelper: CoreContentLinksHelperProvider, @Optional() private navCtrl: NavController, @Optional() private content: Content, @Optional() private svComponent: CoreSplitViewComponent, private iframeUtils: CoreIframeUtilsProvider, private eventsProvider: CoreEventsProvider, - private filterProvider: CoreFilterProvider) { + private filterProvider: CoreFilterProvider, private filterHelper: CoreFilterHelperProvider) { this.element = element.nativeElement; this.element.classList.add('opacity-hide'); // Hide contents until they're treated. this.afterRender = new EventEmitter(); @@ -286,7 +288,7 @@ export class CoreFormatTextDirective implements OnChanges { const filter = this.utils.isTrueOrOne(this.filter); this.textUtils.expandText(this.fullTitle || this.translate.instant('core.description'), this.text, - this.component, this.componentId, undefined, filter, this.contextLevel, this.contextInstanceId); + this.component, this.componentId, undefined, filter, this.contextLevel, this.contextInstanceId, this.courseId); } } @@ -383,11 +385,12 @@ export class CoreFormatTextDirective implements OnChanges { clean: this.utils.isTrueOrOne(this.clean), singleLine: this.utils.isTrueOrOne(this.singleLine), highlight: this.highlight, + courseId: this.courseId, wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered) }; if (this.filter) { - return this.filterProvider.getFiltersAndFormatText(this.text, this.contextLevel, this.contextInstanceId, options, + return this.filterHelper.getFiltersAndFormatText(this.text, this.contextLevel, this.contextInstanceId, options, site.getId()); } else { return this.filterProvider.formatText(this.text, options); diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index 50593fc93..9a244b6f7 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -352,9 +352,10 @@ export class CoreTextUtilsProvider { * @param filter Whether the text should be filtered. * @param contextLevel The context level. * @param instanceId The instance ID related to the context. + * @param courseId Course ID the text belongs to. It can be used to improve performance with filters. */ expandText(title: string, text: string, component?: string, componentId?: string | number, files?: any[], - filter?: boolean, contextLevel?: string, instanceId?: number): void { + filter?: boolean, contextLevel?: string, instanceId?: number, courseId?: number): void { if (text.length > 0) { const params: any = { title: title, @@ -364,7 +365,8 @@ export class CoreTextUtilsProvider { files: files, filter: filter, contextLevel: contextLevel, - instanceId: instanceId + instanceId: instanceId, + courseId: courseId }; // Open a modal with the contents. From 08c8487646e0c55cc5f4d52aeff2054433eb28ab Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 4 Oct 2019 16:02:03 +0200 Subject: [PATCH 07/12] MOBILE-2491 filter: Add memory caches to speed up filter --- src/classes/site.ts | 31 ++-- src/core/filter/providers/delegate.ts | 7 +- src/core/filter/providers/filter.ts | 114 ++++++++++++- src/core/filter/providers/helper.ts | 155 +++++++++++++----- .../settings/pages/space-usage/space-usage.ts | 11 +- src/providers/events.ts | 2 + 6 files changed, 264 insertions(+), 56 deletions(-) diff --git a/src/classes/site.ts b/src/classes/site.ts index 84e7bdaaf..d4988f66a 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -1036,15 +1036,7 @@ export class CoreSite { preSets.omitExpires = preSets.omitExpires || preSets.forceOffline || !this.appProvider.isOnline(); if (!preSets.omitExpires) { - let expirationDelay = this.UPDATE_FREQUENCIES[preSets.updateFrequency] || - this.UPDATE_FREQUENCIES[CoreSite.FREQUENCY_USUALLY]; - - if (this.appProvider.isNetworkAccessLimited()) { - // Not WiFi, increase the expiration delay a 50% to decrease the data usage in this case. - expirationDelay *= 1.5; - } - - expirationTime = entry.expirationTime + expirationDelay; + expirationTime = entry.expirationTime + this.getExpirationDelay(preSets.updateFrequency); if (now > expirationTime) { this.logger.debug('Cached element found, but it is expired'); @@ -1165,7 +1157,9 @@ export class CoreSite { this.logger.debug('Invalidate all the cache for site: ' + this.id); - return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }); + return this.db.updateRecords(CoreSite.WS_CACHE_TABLE, { expirationTime: 0 }).finally(() => { + this.eventsProvider.trigger(CoreEventsProvider.WS_CACHE_INVALIDATED, {}, this.getId()); + }); } /** @@ -1875,4 +1869,21 @@ export class CoreSite { setLocalSiteConfig(name: string, value: number | string): Promise { return this.db.insertRecord(CoreSite.CONFIG_TABLE, { name: name, value: value }); } + + /** + * Get a certain cache expiration delay. + * + * @param updateFrequency The update frequency of the entry. + * @return {number} Expiration delay. + */ + getExpirationDelay(updateFrequency?: number): number { + let expirationDelay = this.UPDATE_FREQUENCIES[updateFrequency] || this.UPDATE_FREQUENCIES[CoreSite.FREQUENCY_USUALLY]; + + if (this.appProvider.isNetworkAccessLimited()) { + // Not WiFi, increase the expiration delay a 50% to decrease the data usage in this case. + expirationDelay *= 1.5; + } + + return expirationDelay; + } } diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 3fa27c298..11e081c0b 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -96,7 +96,12 @@ export class CoreFilterDelegate extends CoreDelegate { } promise = promise.then((text) => { - return this.executeFunctionOnEnabled(filter.filter, 'filter', [text, filter, options, siteId]); + return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'filter', [text, filter, options, siteId])) + .catch((error) => { + this.logger.error('Error applying filter' + filter.filter, error); + + return text; + }); }); }); diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts index 8092d01c4..24d01f83c 100644 --- a/src/core/filter/providers/filter.ts +++ b/src/core/filter/providers/filter.ts @@ -13,6 +13,8 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreSite } from '@classes/site'; @@ -30,11 +32,36 @@ export class CoreFilterProvider { protected logger; + /** + * Store the contexts in memory to speed up the process, it can take a lot of time otherwise. + */ + protected contextsCache: { + [siteId: string]: { + [contextlevel: string]: { + [instanceid: number]: { + filters: CoreFilterFilter[], + time: number + } + } + } + } = {}; + constructor(logger: CoreLoggerProvider, + eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private textUtils: CoreTextUtilsProvider, - private filterDelegate: CoreFilterDelegate) { + private filterDelegate: CoreFilterDelegate, + private appProvider: CoreAppProvider) { + this.logger = logger.getInstance('CoreFilterProvider'); + + eventsProvider.on(CoreEventsProvider.WS_CACHE_INVALIDATED, (data) => { + delete this.contextsCache[data.siteId]; + }); + + eventsProvider.on(CoreEventsProvider.SITE_STORAGE_DELETED, (data) => { + delete this.contextsCache[data.siteId]; + }); } /** @@ -148,9 +175,19 @@ export class CoreFilterProvider { * @return Promise resolved with the filters classified by context. */ getAvailableInContexts(contexts: {contextlevel: string, instanceid: number}[], siteId?: string) - : Promise<{[contextlevel: string]: {[instanceid: number]: CoreFilterFilter[]}}> { + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { + siteId = site.getId(); + + const result = this.getFromMemoryCache(contexts, site); + + if (result) { + return result; + } + + this.contextsCache[siteId] = this.contextsCache[siteId] || {}; + let hasSystemContext = false, hasSiteHomeContext = false; @@ -194,7 +231,7 @@ export class CoreFilterProvider { return site.read('core_filters_get_available_in_context', data, preSets) .then((result: CoreFilterGetAvailableInContextResult) => { - const classified: {[contextlevel: string]: {[instanceid: number]: CoreFilterFilter[]}} = {}; + const classified: CoreFilterClassifiedFilters = {}; // Initialize all contexts. contexts.forEach((context) => { @@ -205,6 +242,7 @@ export class CoreFilterProvider { if (contexts.length == 1 && !hasSystemContext) { // Only 1 context, no need to iterate over the filters. classified[contexts[0].contextlevel][contexts[0].instanceid] = result.filters; + this.storeInMemoryCache(classified, siteId); return classified; } @@ -228,6 +266,8 @@ export class CoreFilterProvider { classified[filter.contextlevel][filter.instanceid].push(filter); }); + this.storeInMemoryCache(classified, siteId); + return classified; }); }); @@ -247,6 +287,45 @@ export class CoreFilterProvider { }); } + /** + * Get contexts filters from the memory cache. + * + * @param contexts Contexts to get. + * @param site Site. + * @return The filters classified by context and instance. + */ + protected getFromMemoryCache(contexts: {contextlevel: string, instanceid: number}[], site: CoreSite) + : CoreFilterClassifiedFilters { + + if (this.contextsCache[site.getId()]) { + // Check if we have the contexts in the memory cache. + const siteContexts = this.contextsCache[site.getId()], + isOnline = this.appProvider.isOnline(), + result: CoreFilterClassifiedFilters = {}; + let allFound = true; + + for (let i = 0; i < contexts.length; i++) { + const context = contexts[i], + cachedCtxt = siteContexts[context.contextlevel] && siteContexts[context.contextlevel][context.instanceid]; + + // Check the context isn't "expired". The time stored in this cache will not match the one in the site cache. + if (cachedCtxt && (!isOnline || + Date.now() <= cachedCtxt.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY))) { + + result[context.contextlevel] = result[context.contextlevel] || {}; + result[context.contextlevel][context.instanceid] = cachedCtxt.filters; + } else { + allFound = false; + break; + } + } + + if (allFound) { + return result; + } + } + } + /** * Invalidates all available in context WS calls. * @@ -283,6 +362,26 @@ export class CoreFilterProvider { invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { return this.invalidateAvailableInContexts([{contextlevel: contextLevel, instanceid: instanceId}], siteId); } + + /** + * Store filters in the memory cache. + * + * @param filters Filters to store, classified by contextlevel and instanceid + * @param siteId Site ID. + */ + protected storeInMemoryCache(filters: CoreFilterClassifiedFilters, siteId: string): void { + + for (const contextLevel in filters) { + this.contextsCache[siteId][contextLevel] = this.contextsCache[siteId][contextLevel] || {}; + + for (const instanceId in filters[contextLevel]) { + this.contextsCache[siteId][contextLevel][instanceId] = { + filters: filters[contextLevel][instanceId], + time: Date.now() + }; + } + } + } } /** @@ -319,3 +418,12 @@ export type CoreFilterFormatTextOptions = { wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason. courseId?: number; // Course ID the text belongs to. It can be used to improve performance. }; + +/** + * Filters classified by context and instance. + */ +export type CoreFilterClassifiedFilters = { + [contextlevel: string]: { + [instanceid: number]: CoreFilterFilter[] + } +}; diff --git a/src/core/filter/providers/helper.ts b/src/core/filter/providers/helper.ts index 06c4a7b83..bb01bdbaf 100644 --- a/src/core/filter/providers/helper.ts +++ b/src/core/filter/providers/helper.ts @@ -13,10 +13,12 @@ // limitations under the License. import { Injectable } from '@angular/core'; +import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; import { CoreLoggerProvider } from '@providers/logger'; import { CoreSitesProvider } from '@providers/sites'; import { CoreFilterDelegate } from './delegate'; -import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; +import { CoreFilterProvider, CoreFilterFilter, CoreFilterFormatTextOptions, CoreFilterClassifiedFilters } from './filter'; import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreSite } from '@classes/site'; @@ -28,12 +30,36 @@ export class CoreFilterHelperProvider { protected logger; + /** + * When a module context is requested, we request all the modules in a course to decrease WS calls. If there are a lot of + * modules, checking the cache of all contexts can be really slow, so we use this memory cache to speed up the process. + */ + protected moduleContextsCache: { + [siteId: string]: { + [courseId: number]: { + contexts: CoreFilterClassifiedFilters, + time: number + } + } + } = {}; + constructor(logger: CoreLoggerProvider, + eventsProvider: CoreEventsProvider, + private appProvider: CoreAppProvider, private sitesProvider: CoreSitesProvider, private filterDelegate: CoreFilterDelegate, private courseProvider: CoreCourseProvider, private filterProvider: CoreFilterProvider) { + this.logger = logger.getInstance('CoreFilterHelperProvider'); + + eventsProvider.on(CoreEventsProvider.WS_CACHE_INVALIDATED, (data) => { + delete this.moduleContextsCache[data.siteId]; + }); + + eventsProvider.on(CoreEventsProvider.SITE_STORAGE_DELETED, (data) => { + delete this.moduleContextsCache[data.siteId]; + }); } /** @@ -79,55 +105,64 @@ export class CoreFilterHelperProvider { getFilters(contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string) : Promise { - let site: CoreSite; - options.contextLevel = contextLevel; options.instanceId = instanceId; options.filter = false; - return this.sitesProvider.getSite(siteId).then((s) => { - site = s; + return this.sitesProvider.getSite(siteId).then((site) => { + siteId = site.getId(); - return this.filterProvider.canGetAvailableInContext(siteId); - }).then((canGet) => { - if (!canGet) { - options.filter = true; - - // We cannot check which filters are available, apply them all. - return this.filterDelegate.getEnabledFilters(contextLevel, instanceId); - } - - let promise: Promise; - - if (instanceId == site.getSiteHomeId() && (contextLevel == 'system' || contextLevel == 'course')) { - // No need to check the site filters because we're requesting the same context, so we'd do the same twice. - promise = Promise.resolve(true); - } else { - // Check if site has any filter to treat. - promise = this.siteHasFiltersToTreat(options, siteId); - } - - return promise.then((hasFilters) => { - if (hasFilters) { + return this.filterProvider.canGetAvailableInContext(siteId).then((canGet) => { + if (!canGet) { options.filter = true; - if (contextLevel == 'module' && options.courseId) { - // Get all the modules filters with a single call to decrease the number of WS calls. - return this.getCourseModulesContexts(options.courseId, site.getId()).then((contexts) => { - - return this.filterProvider.getAvailableInContexts(contexts, site.getId()).then((filters) => { - return filters[contextLevel][instanceId] || []; - }); - }); - } - - return this.filterProvider.getAvailableInContext(contextLevel, instanceId, siteId); + // We cannot check which filters are available, apply them all. + return this.filterDelegate.getEnabledFilters(contextLevel, instanceId); } - return []; - }).catch(() => { - return []; + let promise: Promise; + + if (instanceId == site.getSiteHomeId() && (contextLevel == 'system' || contextLevel == 'course')) { + // No need to check the site filters because we're requesting the same context, so we'd do the same twice. + promise = Promise.resolve(true); + } else { + // Check if site has any filter to treat. + promise = this.siteHasFiltersToTreat(options, siteId); + } + + return promise.then((hasFilters) => { + if (hasFilters) { + options.filter = true; + + if (contextLevel == 'module' && options.courseId) { + // Get all the modules filters with a single call to decrease the number of WS calls. + // Check the memory cache first to speed up the process. + + const result = this.getFromMemoryCache(options.courseId, contextLevel, instanceId, site); + if (result) { + return result; + } + + return this.getCourseModulesContexts(options.courseId, siteId).then((contexts) => { + + return this.filterProvider.getAvailableInContexts(contexts, siteId).then((filters) => { + this.storeInMemoryCache(options.courseId, filters, siteId); + + return filters[contextLevel][instanceId] || []; + }); + }); + } + + return this.filterProvider.getAvailableInContext(contextLevel, instanceId, siteId); + } + + return []; + }); }); + }).catch((error) => { + this.logger.error('Error getting filters, return an empty array', error, contextLevel, instanceId); + + return []; }); } @@ -149,6 +184,32 @@ export class CoreFilterHelperProvider { }); } + /** + * Get module context filters from the memory cache. + * + * @param courseId Course the module belongs to. + * @param contextLevel Context level. + * @param instanceId Instance ID. + * @param site Site. + * @return The filters, undefined if not found. + */ + protected getFromMemoryCache(courseId: number, contextLevel: string, instanceId: number, site: CoreSite): CoreFilterFilter[] { + + const siteId = site.getId(); + + // Check if we have the context in the memory cache. + if (this.moduleContextsCache[siteId] && this.moduleContextsCache[siteId][courseId]) { + const cachedCourse = this.moduleContextsCache[siteId][courseId]; + + if (!this.appProvider.isOnline() || + Date.now() <= cachedCourse.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY)) { + + // We can use cache, return the filters if found. + return cachedCourse.contexts[contextLevel] && cachedCourse.contexts[contextLevel][instanceId]; + } + } + } + /** * Check if site has available any filter that should be treated by the app. * @@ -168,4 +229,18 @@ export class CoreFilterHelperProvider { }); }); } + + /** + * Store filters in the memory cache. + * + * @param contexts Filters to store, classified by contextlevel and instanceid + * @param siteId Site ID. + */ + protected storeInMemoryCache(courseId: number, contexts: CoreFilterClassifiedFilters, siteId: string): void { + this.moduleContextsCache[siteId] = this.moduleContextsCache[siteId] || {}; + this.moduleContextsCache[siteId][courseId] = { + contexts: contexts, + time: Date.now() + }; + } } diff --git a/src/core/settings/pages/space-usage/space-usage.ts b/src/core/settings/pages/space-usage/space-usage.ts index 8ce241df8..44a24784f 100644 --- a/src/core/settings/pages/space-usage/space-usage.ts +++ b/src/core/settings/pages/space-usage/space-usage.ts @@ -16,6 +16,7 @@ import { Component, } from '@angular/core'; import { IonicPage } from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreAppProvider } from '@providers/app'; +import { CoreEventsProvider } from '@providers/events'; import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreSitesProvider } from '@providers/sites'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; @@ -39,8 +40,12 @@ export class CoreSettingsSpaceUsagePage { totalEntries = 0; constructor(private filePoolProvider: CoreFilepoolProvider, - private sitesProvider: CoreSitesProvider, private filterHelper: CoreFilterHelperProvider, - private translate: TranslateService, private domUtils: CoreDomUtilsProvider, appProvider: CoreAppProvider, + private eventsProvider: CoreEventsProvider, + private sitesProvider: CoreSitesProvider, + private filterHelper: CoreFilterHelperProvider, + private translate: TranslateService, + private domUtils: CoreDomUtilsProvider, + appProvider: CoreAppProvider, private courseProvider: CoreCourseProvider) { this.currentSiteId = this.sitesProvider.getCurrentSiteId(); } @@ -196,6 +201,8 @@ export class CoreSettingsSpaceUsagePage { }); } }).finally(() => { + this.eventsProvider.trigger(CoreEventsProvider.SITE_STORAGE_DELETED, {}, site.getId()); + this.calcSiteClearRows(site).then((rows) => { siteData.cacheEntries = rows; }); diff --git a/src/providers/events.ts b/src/providers/events.ts index 2371b7fc5..5d1557ed8 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -62,6 +62,8 @@ export class CoreEventsProvider { static SEND_ON_ENTER_CHANGED = 'send_on_enter_changed'; static MAIN_MENU_OPEN = 'main_menu_open'; static SELECT_COURSE_TAB = 'select_course_tab'; + static WS_CACHE_INVALIDATED = 'ws_cache_invalidated'; + static SITE_STORAGE_DELETED = 'site_storage_deleted'; protected logger; protected observables: { [s: string]: Subject } = {}; From 6c219b52465417528bca21ca20ed9a81c52c7ec2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 8 Oct 2019 08:11:10 +0200 Subject: [PATCH 08/12] MOBILE-2491 filter: Change copyright of filter files --- src/addon/filter/activitynames/activitynames.module.ts | 2 +- src/addon/filter/activitynames/providers/handler.ts | 2 +- src/addon/filter/censor/censor.module.ts | 2 +- src/addon/filter/censor/providers/handler.ts | 2 +- src/addon/filter/data/data.module.ts | 2 +- src/addon/filter/data/providers/handler.ts | 2 +- src/addon/filter/emailprotect/emailprotect.module.ts | 2 +- src/addon/filter/emailprotect/providers/handler.ts | 2 +- src/addon/filter/emoticon/emoticon.module.ts | 2 +- src/addon/filter/emoticon/providers/handler.ts | 2 +- src/addon/filter/filter.module.ts | 2 +- src/addon/filter/glossary/glossary.module.ts | 2 +- src/addon/filter/glossary/providers/handler.ts | 2 +- src/addon/filter/mediaplugin/mediaplugin.module.ts | 2 +- src/addon/filter/mediaplugin/providers/handler.ts | 2 +- src/addon/filter/multilang/multilang.module.ts | 2 +- src/addon/filter/multilang/providers/handler.ts | 2 +- src/addon/filter/tex/providers/handler.ts | 2 +- src/addon/filter/tex/tex.module.ts | 2 +- src/addon/filter/tidy/providers/handler.ts | 2 +- src/addon/filter/tidy/tidy.module.ts | 2 +- src/addon/filter/urltolink/providers/handler.ts | 2 +- src/addon/filter/urltolink/urltolink.module.ts | 2 +- src/core/filter/filter.module.ts | 2 +- src/core/filter/providers/default-filter.ts | 2 +- src/core/filter/providers/delegate.ts | 2 +- src/core/filter/providers/filter.ts | 2 +- src/core/filter/providers/helper.ts | 2 +- 28 files changed, 28 insertions(+), 28 deletions(-) diff --git a/src/addon/filter/activitynames/activitynames.module.ts b/src/addon/filter/activitynames/activitynames.module.ts index b3e5d5da7..91d9e6f40 100644 --- a/src/addon/filter/activitynames/activitynames.module.ts +++ b/src/addon/filter/activitynames/activitynames.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/activitynames/providers/handler.ts b/src/addon/filter/activitynames/providers/handler.ts index 025121803..e82644bc1 100644 --- a/src/addon/filter/activitynames/providers/handler.ts +++ b/src/addon/filter/activitynames/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/censor/censor.module.ts b/src/addon/filter/censor/censor.module.ts index 126a7bfb2..6d6fc5521 100644 --- a/src/addon/filter/censor/censor.module.ts +++ b/src/addon/filter/censor/censor.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/censor/providers/handler.ts b/src/addon/filter/censor/providers/handler.ts index 018eb368b..b9ce9649f 100644 --- a/src/addon/filter/censor/providers/handler.ts +++ b/src/addon/filter/censor/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/data/data.module.ts b/src/addon/filter/data/data.module.ts index 1272618d8..e76812085 100644 --- a/src/addon/filter/data/data.module.ts +++ b/src/addon/filter/data/data.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/data/providers/handler.ts b/src/addon/filter/data/providers/handler.ts index e07e2ea4b..b0dfc9c7a 100644 --- a/src/addon/filter/data/providers/handler.ts +++ b/src/addon/filter/data/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/emailprotect/emailprotect.module.ts b/src/addon/filter/emailprotect/emailprotect.module.ts index d746dfc37..a722980be 100644 --- a/src/addon/filter/emailprotect/emailprotect.module.ts +++ b/src/addon/filter/emailprotect/emailprotect.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/emailprotect/providers/handler.ts b/src/addon/filter/emailprotect/providers/handler.ts index d339f6d33..77b431bde 100644 --- a/src/addon/filter/emailprotect/providers/handler.ts +++ b/src/addon/filter/emailprotect/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/emoticon/emoticon.module.ts b/src/addon/filter/emoticon/emoticon.module.ts index 5237286f9..9b975cea4 100644 --- a/src/addon/filter/emoticon/emoticon.module.ts +++ b/src/addon/filter/emoticon/emoticon.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/emoticon/providers/handler.ts b/src/addon/filter/emoticon/providers/handler.ts index 5523b0780..7b403231f 100644 --- a/src/addon/filter/emoticon/providers/handler.ts +++ b/src/addon/filter/emoticon/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/filter.module.ts b/src/addon/filter/filter.module.ts index b20447e06..617f623c6 100644 --- a/src/addon/filter/filter.module.ts +++ b/src/addon/filter/filter.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/glossary/glossary.module.ts b/src/addon/filter/glossary/glossary.module.ts index 337bffbbc..a5bae30c8 100644 --- a/src/addon/filter/glossary/glossary.module.ts +++ b/src/addon/filter/glossary/glossary.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/glossary/providers/handler.ts b/src/addon/filter/glossary/providers/handler.ts index 9ffd6fec4..c89489b12 100644 --- a/src/addon/filter/glossary/providers/handler.ts +++ b/src/addon/filter/glossary/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/mediaplugin/mediaplugin.module.ts b/src/addon/filter/mediaplugin/mediaplugin.module.ts index 2ef135ddd..c29931c91 100644 --- a/src/addon/filter/mediaplugin/mediaplugin.module.ts +++ b/src/addon/filter/mediaplugin/mediaplugin.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/mediaplugin/providers/handler.ts b/src/addon/filter/mediaplugin/providers/handler.ts index d7b91f2be..405ba0672 100644 --- a/src/addon/filter/mediaplugin/providers/handler.ts +++ b/src/addon/filter/mediaplugin/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/multilang/multilang.module.ts b/src/addon/filter/multilang/multilang.module.ts index 149cdfd0c..db8328d2b 100644 --- a/src/addon/filter/multilang/multilang.module.ts +++ b/src/addon/filter/multilang/multilang.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/multilang/providers/handler.ts b/src/addon/filter/multilang/providers/handler.ts index 0b8d25d35..93db110d0 100644 --- a/src/addon/filter/multilang/providers/handler.ts +++ b/src/addon/filter/multilang/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/tex/providers/handler.ts b/src/addon/filter/tex/providers/handler.ts index d29302a0d..313f13a9f 100644 --- a/src/addon/filter/tex/providers/handler.ts +++ b/src/addon/filter/tex/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/tex/tex.module.ts b/src/addon/filter/tex/tex.module.ts index 21084238b..2fecf37d7 100644 --- a/src/addon/filter/tex/tex.module.ts +++ b/src/addon/filter/tex/tex.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/tidy/providers/handler.ts b/src/addon/filter/tidy/providers/handler.ts index 6ce6a6287..d1a3f608a 100644 --- a/src/addon/filter/tidy/providers/handler.ts +++ b/src/addon/filter/tidy/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/tidy/tidy.module.ts b/src/addon/filter/tidy/tidy.module.ts index ab0b43098..54782656c 100644 --- a/src/addon/filter/tidy/tidy.module.ts +++ b/src/addon/filter/tidy/tidy.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/urltolink/providers/handler.ts b/src/addon/filter/urltolink/providers/handler.ts index 311a0c2c5..cc1aa05cc 100644 --- a/src/addon/filter/urltolink/providers/handler.ts +++ b/src/addon/filter/urltolink/providers/handler.ts @@ -1,5 +1,5 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/addon/filter/urltolink/urltolink.module.ts b/src/addon/filter/urltolink/urltolink.module.ts index afbdbf15c..7316a849a 100644 --- a/src/addon/filter/urltolink/urltolink.module.ts +++ b/src/addon/filter/urltolink/urltolink.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/core/filter/filter.module.ts b/src/core/filter/filter.module.ts index 81c898f55..9211ecd98 100644 --- a/src/core/filter/filter.module.ts +++ b/src/core/filter/filter.module.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/core/filter/providers/default-filter.ts b/src/core/filter/providers/default-filter.ts index 77abe1443..d381e580f 100644 --- a/src/core/filter/providers/default-filter.ts +++ b/src/core/filter/providers/default-filter.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/core/filter/providers/delegate.ts b/src/core/filter/providers/delegate.ts index 11e081c0b..dcea780cd 100644 --- a/src/core/filter/providers/delegate.ts +++ b/src/core/filter/providers/delegate.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/core/filter/providers/filter.ts b/src/core/filter/providers/filter.ts index 24d01f83c..e521861eb 100644 --- a/src/core/filter/providers/filter.ts +++ b/src/core/filter/providers/filter.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. diff --git a/src/core/filter/providers/helper.ts b/src/core/filter/providers/helper.ts index bb01bdbaf..15982e9a5 100644 --- a/src/core/filter/providers/helper.ts +++ b/src/core/filter/providers/helper.ts @@ -1,4 +1,4 @@ -// (C) Copyright 2015 Martin Dougiamas +// (C) Copyright 2015 Moodle Pty Ltd. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. From b99d31ca61f397794251058a415551dac36d23ee Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Tue, 8 Oct 2019 08:42:15 +0200 Subject: [PATCH 09/12] MOBILE-2491 filter: Use instanceid 0 for system context --- src/addon/blog/components/entries/entries.ts | 2 +- .../components/discussions/addon-messages-discussions.html | 4 ++-- src/addon/messages/components/discussions/discussions.ts | 2 -- .../messages/pages/conversation-info/conversation-info.html | 4 ++-- .../messages/pages/conversation-info/conversation-info.ts | 2 -- src/addon/messages/pages/discussion/discussion.html | 4 ++-- src/addon/messages/pages/discussion/discussion.ts | 2 -- .../pages/group-conversations/group-conversations.html | 6 +++--- .../pages/group-conversations/group-conversations.ts | 2 -- src/addon/messages/pages/search/search.html | 2 +- src/addon/messages/pages/search/search.ts | 3 --- src/addon/messages/providers/mainmenu-handler.ts | 3 +-- src/addon/notifications/pages/list/list.html | 2 +- src/addon/notifications/pages/list/list.ts | 3 --- src/components/site-picker/site-picker.ts | 2 +- src/core/contentlinks/pages/choose-site/choose-site.html | 2 +- src/core/courses/pages/dashboard/dashboard.html | 2 +- src/core/courses/pages/dashboard/dashboard.ts | 2 -- src/core/filter/providers/filter.ts | 5 +++-- src/core/filter/providers/helper.ts | 4 ++-- src/core/login/pages/sites/sites.html | 2 +- src/core/login/pages/sites/sites.ts | 2 +- src/core/mainmenu/pages/more/more.html | 2 +- src/core/mainmenu/pages/more/more.ts | 2 -- src/core/pushnotifications/providers/pushnotifications.ts | 2 +- src/core/settings/pages/space-usage/space-usage.html | 2 +- src/core/settings/pages/space-usage/space-usage.ts | 2 +- .../settings/pages/synchronization/synchronization.html | 2 +- src/core/sharedfiles/pages/choose-site/choose-site.html | 2 +- 29 files changed, 29 insertions(+), 47 deletions(-) diff --git a/src/addon/blog/components/entries/entries.ts b/src/addon/blog/components/entries/entries.ts index 29b225c09..8853d8842 100644 --- a/src/addon/blog/components/entries/entries.ts +++ b/src/addon/blog/components/entries/entries.ts @@ -104,7 +104,7 @@ export class AddonBlogEntriesComponent implements OnInit { this.contextInstanceId = this.courseId; } else { this.contextLevel = 'system'; - this.contextInstanceId = this.siteHomeId; + this.contextInstanceId = 0; } this.commentsEnabled = !this.commentsProvider.areCommentsDisabledInSite(); diff --git a/src/addon/messages/components/discussions/addon-messages-discussions.html b/src/addon/messages/components/discussions/addon-messages-discussions.html index 8d21aaba8..0094d0dc5 100644 --- a/src/addon/messages/components/discussions/addon-messages-discussions.html +++ b/src/addon/messages/components/discussions/addon-messages-discussions.html @@ -15,7 +15,7 @@

{{ result.fullname }}

-

+

@@ -27,7 +27,7 @@ {{discussion.message.timecreated / 1000 | coreDateDayOrTime}} -

+

diff --git a/src/addon/messages/components/discussions/discussions.ts b/src/addon/messages/components/discussions/discussions.ts index 0a37c5ec3..629446b1a 100644 --- a/src/addon/messages/components/discussions/discussions.ts +++ b/src/addon/messages/components/discussions/discussions.ts @@ -50,7 +50,6 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { loading: '', text: '' }; - siteHomeId: number; constructor(private eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider, translate: TranslateService, private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, @@ -60,7 +59,6 @@ export class AddonMessagesDiscussionsComponent implements OnDestroy { this.search.loading = translate.instant('core.searching'); this.loadingMessages = translate.instant('core.loading'); this.siteId = sitesProvider.getCurrentSiteId(); - this.siteHomeId = sitesProvider.getCurrentSiteHomeId(); // Update discussions when new message is received. this.newMessagesObserver = eventsProvider.on(AddonMessagesProvider.NEW_MESSAGE_EVENT, (data) => { diff --git a/src/addon/messages/pages/conversation-info/conversation-info.html b/src/addon/messages/pages/conversation-info/conversation-info.html index 66dd220f2..b5885ddd1 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.html +++ b/src/addon/messages/pages/conversation-info/conversation-info.html @@ -18,8 +18,8 @@
-

-

+

+

{{ 'addon.messages.numparticipants' | translate:{$a: conversation.membercount} }}

diff --git a/src/addon/messages/pages/conversation-info/conversation-info.ts b/src/addon/messages/pages/conversation-info/conversation-info.ts index dcd2597dd..5dfeb450d 100644 --- a/src/addon/messages/pages/conversation-info/conversation-info.ts +++ b/src/addon/messages/pages/conversation-info/conversation-info.ts @@ -35,14 +35,12 @@ export class AddonMessagesConversationInfoPage implements OnInit { members: AddonMessagesConversationMember[] = []; canLoadMore = false; loadMoreError = false; - siteHomeId: number; protected conversationId: number; constructor(private messagesProvider: AddonMessagesProvider, private domUtils: CoreDomUtilsProvider, navParams: NavParams, protected viewCtrl: ViewController, sitesProvider: CoreSitesProvider) { this.conversationId = navParams.get('conversationId'); - this.siteHomeId = sitesProvider.getCurrentSiteHomeId(); } /** diff --git a/src/addon/messages/pages/discussion/discussion.html b/src/addon/messages/pages/discussion/discussion.html index 5c7ea16ad..a869dad74 100644 --- a/src/addon/messages/pages/discussion/discussion.html +++ b/src/addon/messages/pages/discussion/discussion.html @@ -3,7 +3,7 @@ - + @@ -58,7 +58,7 @@

- +