MOBILE-2491 filter: Support wsNotFiltered option and filter in chart

main
Dani Palou 2019-10-03 11:25:58 +02:00
parent 952ce4939b
commit 31caccc396
19 changed files with 332 additions and 123 deletions

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,7 +15,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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'; import { CoreTextUtilsProvider } from '@providers/utils/text';
/** /**
@ -36,9 +36,11 @@ export class AddonFilterMediaPluginHandler extends CoreFilterDefaultHandler {
* @param text The text to filter. * @param text The text to filter.
* @param filter The filter. * @param filter The filter.
* @param options Options passed to the filters. * @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). * @return Filtered text (or promise resolved with the filtered text).
*/ */
filter(text: string, filter: CoreFilterFilter, options: any): string | Promise<string> { filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string)
: string | Promise<string> {
const div = document.createElement('div'); const div = document.createElement('div');
div.innerHTML = text; div.innerHTML = text;

View File

@ -16,8 +16,9 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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 { CoreLangProvider } from '@providers/lang';
import { CoreSite } from '@classes/site';
/** /**
* Handler to support the Multilang filter. * Handler to support the Multilang filter.
@ -32,50 +33,61 @@ export class AddonFilterMultilangHandler extends CoreFilterDefaultHandler {
super(); super();
} }
/**
* Whether or not the handler is enabled on a site level.
*
* @return {boolean|Promise<boolean>} Whether or not the handler is enabled on a site level.
*/
isEnabled(): boolean | Promise<boolean> {
// 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. * Filter some text.
* *
* @param text The text to filter. * @param text The text to filter.
* @param filter The filter. * @param filter The filter.
* @param options Options passed to the filters. * @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). * @return Filtered text (or promise resolved with the filtered text).
*/ */
filter(text: string, filter: CoreFilterFilter, options: any): string | Promise<string> { filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string)
: string | Promise<string> {
return this.langProvider.getCurrentLanguage().then((language) => { return this.sitesProvider.getSite(siteId).then((site) => {
// 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)) { // Don't apply this filter if Moodle is 3.7 or higher and the WS already filtered the content.
// Current lang not found. Try to find the first language. if (!this.shouldBeApplied(options, site)) {
const matches = text.match(anyLangRegEx); return text;
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; 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');
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -15,6 +15,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterDefaultHandler } from '@core/filter/providers/default-filter'; 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. * 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. // 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;
}
} }

View File

@ -73,7 +73,7 @@
<ion-icon item-start name="warning" color="warning"></ion-icon> {{ 'addon.mod_choice.resultsnotsynced' | translate }} <ion-icon item-start name="warning" color="warning"></ion-icon> {{ 'addon.mod_choice.resultsnotsynced' | translate }}
</ion-item> </ion-item>
<ion-item> <ion-item>
<core-chart type="pie" [data]="data" [labels]="labels" height="300"></core-chart> <core-chart type="pie" [data]="data" [labels]="labels" height="300" contextLevel="module" [contextInstanceId]="module.id"></core-chart>
</ion-item> </ion-item>
</ion-col> </ion-col>
<ion-col *ngIf="choice.publish && results" col-12 col-lg-7> <ion-col *ngIf="choice.publish && results" col-12 col-lg-7>

View File

@ -162,7 +162,7 @@
</ul> </ul>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'chart'"> <ng-container *ngSwitchCase="'chart'">
<core-chart [type]="item.chartType" [data]="item.chartData" [labels]="item.labels" height="300"></core-chart> <core-chart [type]="item.chartType" [data]="item.chartData" [labels]="item.labels" height="300" contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"></core-chart>
<p *ngIf="item.average">{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}</p> <p *ngIf="item.average">{{ 'addon.mod_feedback.average' | translate }}: {{item.average | number : '1.2-2'}}</p>
</ng-container> </ng-container>
</ng-container> </ng-container>

View File

@ -23,7 +23,7 @@
<div item-content class="addon-mod_feedback-form-content" *ngIf="item.template"> <div item-content class="addon-mod_feedback-form-content" *ngIf="item.template">
<ng-container [ngSwitch]="item.template"> <ng-container [ngSwitch]="item.template">
<ng-container *ngSwitchCase="'label'"> <ng-container *ngSwitchCase="'label'">
<p><core-format-text [component]="component" [componentId]="componentId" [text]="item.presentation" contextLevel="module" [contextInstanceId]="module.id"></core-format-text></p> <p><core-format-text [component]="component" [componentId]="componentId" [text]="item.presentation" contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"></core-format-text></p>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'textfield'"> <ng-container *ngSwitchCase="'textfield'">
<ion-input type="text" [(ngModel)]="item.value" autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.maxlength}}" [required]="item.required"></ion-input> <ion-input type="text" [(ngModel)]="item.value" autocorrect="off" name="{{item.typ}}_{{item.id}}" maxlength="{{item.maxlength}}" [required]="item.required"></ion-input>
@ -38,20 +38,20 @@
<ng-container *ngSwitchCase="'multichoice-r'"> <ng-container *ngSwitchCase="'multichoice-r'">
<ion-list radio-group [(ngModel)]="item.value" [required]="item.required" name="{{item.typ}}_{{item.id}}"> <ion-list radio-group [(ngModel)]="item.value" [required]="item.required" name="{{item.typ}}_{{item.id}}">
<ion-item *ngFor="let option of item.choices"> <ion-item *ngFor="let option of item.choices">
<ion-label><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id"></core-format-text></ion-label> <ion-label><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"></core-format-text></ion-label>
<ion-radio [value]="option.value"></ion-radio> <ion-radio [value]="option.value"></ion-radio>
</ion-item> </ion-item>
</ion-list> </ion-list>
</ng-container> </ng-container>
<ion-list *ngSwitchCase="'multichoice-c'"> <ion-list *ngSwitchCase="'multichoice-c'">
<ion-item *ngFor="let option of item.choices"> <ion-item *ngFor="let option of item.choices">
<ion-label><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id"></core-format-text></ion-label> <ion-label><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"></core-format-text></ion-label>
<ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked" value="option.value"></ion-checkbox> <ion-checkbox [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="option.checked" value="option.value"></ion-checkbox>
</ion-item> </ion-item>
</ion-list> </ion-list>
<ng-container *ngSwitchCase="'multichoice-d'"> <ng-container *ngSwitchCase="'multichoice-d'">
<ion-select [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" interface="action-sheet"> <ion-select [required]="item.required" name="{{item.typ}}_{{item.id}}" [(ngModel)]="item.value" interface="action-sheet">
<ion-option *ngFor="let option of item.choices" [value]="option.value"><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id"></core-format-text></ion-option> <ion-option *ngFor="let option of item.choices" [value]="option.value"><core-format-text [component]="component" [componentId]="componentId" [text]="option.label" contextLevel="module" [contextInstanceId]="module.id" [wsNotFiltered]="true"></core-format-text></ion-option>
</ion-select> </ion-select>
</ng-container> </ng-container>
<ng-container *ngSwitchCase="'captcha'"> <ng-container *ngSwitchCase="'captcha'">

View File

@ -14,6 +14,8 @@
import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild } from '@angular/core'; import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild } from '@angular/core';
import { Chart } from 'chart.js'; 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. * 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() type: string; // Type of chart.
@Input() legend: any; // Legend options. @Input() legend: any; // Legend options.
@Input() height = 300; // Height of the chart element. @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; @ViewChild('canvas') canvas: ElementRef;
chart: any; chart: any;
constructor() { constructor(protected filterProvider: CoreFilterProvider, private utils: CoreUtilsProvider) { }
// Nothing to do.
}
/** /**
* Component being initialized. * Component being initialized.
@ -86,17 +90,21 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
this.type = 'horizontalBar'; this.type = 'horizontalBar';
} }
const context = this.canvas.nativeElement.getContext('2d'); // Format labels if needed.
this.chart = new Chart(context, { this.formatLabels().then(() => {
type: this.type,
data: { const context = this.canvas.nativeElement.getContext('2d');
labels: this.labels, this.chart = new Chart(context, {
datasets: [{ type: this.type,
data: this.data, data: {
backgroundColor: this.getRandomColors(this.data.length) labels: this.labels,
}] datasets: [{
}, data: this.data,
options: {legend: legend} backgroundColor: this.getRandomColors(this.data.length)
}]
},
options: {legend: legend}
});
}); });
} }
@ -105,15 +113,49 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges {
*/ */
ngOnChanges(): void { ngOnChanges(): void {
if (this.chart) { if (this.chart) {
this.chart.data.datasets[0] = { // Format labels if needed.
data: this.data, this.formatLabels().then(() => {
backgroundColor: this.getRandomColors(this.data.length) this.chart.data.datasets[0] = {
}; data: this.data,
this.chart.data.labels = this.labels; backgroundColor: this.getRandomColors(this.data.length)
this.chart.update(); };
this.chart.data.labels = this.labels;
this.chart.update();
});
} }
} }
/**
* Format labels if needed.
*
* @return Promise resolved when done.
*/
protected formatLabels(): Promise<any> {
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. * Generate random colors if needed.
* *

View File

@ -14,7 +14,8 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreFilterHandler } from './delegate'; 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. * 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 text The text to filter.
* @param filter The filter. * @param filter The filter.
* @param options Options passed to the filters. * @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). * @return Filtered text (or promise resolved with the filtered text).
*/ */
filter(text: string, filter: CoreFilterFilter, options: any): string | Promise<string> { filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string)
: string | Promise<string> {
return text; 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 * @param options Options.
* for controlling its own execution cardinality. * @param site Site.
* * @return Whether filter should be applied.
* @param filter The filter.
* @return Promise resolved when done, or nothing if it's synchronous.
*/ */
setup(filter: CoreFilterFilter): void | Promise<any> { shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean {
// Nothing to do. return true;
} }
} }

View File

@ -16,9 +16,10 @@ import { Injectable } from '@angular/core';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreFilterFilter } from './filter'; import { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter';
import { CoreFilterDefaultHandler } from './default-filter'; import { CoreFilterDefaultHandler } from './default-filter';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
import { CoreSite } from '@classes/site';
/** /**
* Interface that all filter handlers must implement. * Interface that all filter handlers must implement.
@ -35,20 +36,19 @@ export interface CoreFilterHandler extends CoreDelegateHandler {
* @param text The text to filter. * @param text The text to filter.
* @param filter The filter. * @param filter The filter.
* @param options Options passed to the filters. * @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). * @return Filtered text (or promise resolved with the filtered text).
*/ */
filter(text: string, filter: CoreFilterFilter, options: any): string | Promise<string>; filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string): string | Promise<string>;
/** /**
* 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 * @param options Options.
* for controlling its own execution cardinality. * @param site Site.
* * @return Whether filter should be applied.
* @param filter The filter.
* @return Promise resolved when done, or nothing if it's synchronous.
*/ */
setup(filter: CoreFilterFilter): void | Promise<any>; shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean;
} }
/** /**
@ -71,12 +71,10 @@ export class CoreFilterDelegate extends CoreDelegate {
* @param filters Filters to apply. * @param filters Filters to apply.
* @param options Options passed to the filters. * @param options Options passed to the filters.
* @param skipFilters Names of filters that shouldn't be applied. * @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. * @return Promise resolved with the filtered text.
*/ */
filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[]): Promise<string> { filterText(text: string, filters: CoreFilterFilter[], options?: any, skipFilters?: string[], siteId?: string): Promise<string> {
if (!text || typeof text != 'string') {
return Promise.resolve('');
}
// Wait for filters to be initialized. // Wait for filters to be initialized.
return this.handlersInitPromise.then(() => { return this.handlersInitPromise.then(() => {
@ -98,7 +96,7 @@ export class CoreFilterDelegate extends CoreDelegate {
} }
promise = promise.then((text) => { 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. * @param filter Filter to check.
* @return Promise resolved when done. * @param options Options passed to the filters.
* @param site Site. If not defined, current site.
* @return {Promise<boolean>} Promise resolved with true: whether the filter should be applied.
*/ */
setupFilters(filters: CoreFilterFilter[]): Promise<any> { shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise<boolean> {
const promises: Promise<any>[] = []; // Wait for filters to be initialized.
return this.handlersInitPromise.then(() => {
const promises = [];
let shouldBeApplied = false;
filters.forEach((filter) => { filters.forEach((filter) => {
promises.push(this.executeFunctionOnEnabled(filter.filter, 'setup', [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<boolean>} Promise resolved with true: whether the filter should be applied.
*/
protected shouldFilterBeApplied(filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, site?: CoreSite)
: Promise<boolean> {
if (!this.hasHandler(filter.filter, true)) {
return Promise.resolve(false);
}
return Promise.resolve(this.executeFunctionOnEnabled(filter.filter, 'shouldBeApplied', [options, site]));
} }
} }

View File

@ -29,8 +29,6 @@ export class CoreFilterProvider {
protected ROOT_CACHE_KEY = 'mmFilter:'; protected ROOT_CACHE_KEY = 'mmFilter:';
protected logger; protected logger;
protected FILTERS_NOT_TREATED = ['activitynames', 'censor', 'data', 'emailprotect', 'emoticon', 'glossary', 'tex', 'tidy',
'urltolink'];
constructor(logger: CoreLoggerProvider, constructor(logger: CoreLoggerProvider,
private sitesProvider: CoreSitesProvider, private sitesProvider: CoreSitesProvider,
@ -71,9 +69,11 @@ export class CoreFilterProvider {
* @param text The text to be formatted. * @param text The text to be formatted.
* @param options Formatting options. * @param options Formatting options.
* @param filters The filters to apply. Required if filter is set to true. * @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. * @return Promise resolved with the formatted text.
*/ */
formatText(text: string, options?: CoreFilterFormatTextOptions, filters?: CoreFilterFilter[]): Promise<string> { formatText(text: string, options?: CoreFilterFormatTextOptions, filters?: CoreFilterFilter[], siteId?: string)
: Promise<string> {
if (!text || typeof text != 'string') { if (!text || typeof text != 'string') {
// No need to do any filters and cleaning. // No need to do any filters and cleaning.
@ -95,12 +95,10 @@ export class CoreFilterProvider {
options.filter = false; options.filter = false;
} }
// @todo: Setup?
let promise: Promise<string>; let promise: Promise<string>;
if (options.filter) { if (options.filter) {
promise = this.filterDelegate.filterText(text, filters, options); promise = this.filterDelegate.filterText(text, filters, options, [], siteId);
} else { } else {
promise = Promise.resolve(text); 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 contextLevel The context level.
* @param instanceId Instance ID related to the context. * @param instanceId Instance ID related to the context.
* @param options Options for format text. * @param options Options for format text.
* @param siteId Site ID. If not defined, current site. * @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, getFilters(contextLevel: string, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string)
siteId?: string): Promise<string> { : Promise<CoreFilterFilter[]> {
options.contextLevel = contextLevel; options.contextLevel = contextLevel;
options.instanceId = instanceId; options.instanceId = instanceId;
@ -275,7 +274,7 @@ export class CoreFilterProvider {
} }
// Check if site has any filter to treat. // Check if site has any filter to treat.
return this.siteHasFiltersToTreat(siteId).then((hasFilters) => { return this.siteHasFiltersToTreat(options, siteId).then((hasFilters) => {
if (hasFilters) { if (hasFilters) {
options.filter = true; options.filter = true;
@ -286,8 +285,24 @@ export class CoreFilterProvider {
}).catch(() => { }).catch(() => {
return []; 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<string> {
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. * 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. * @param siteId Site ID. If not defined, current site.
* @return Promise resolved with boolean: whether it has filters to treat. * @return Promise resolved with boolean: whether it has filters to treat.
*/ */
siteHasFiltersToTreat(siteId?: string): Promise<boolean> { siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise<boolean> {
options = options || {};
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
// Get filters at site level. // Get filters at site level.
return this.getAvailableInContext('system', site.getSiteHomeId(), site.getId()).then((filters) => { return this.getAvailableInContext('system', site.getSiteHomeId(), site.getId()).then((filters) => {
for (let i = 0; i < filters.length; i++) { return this.filterDelegate.shouldBeApplied(filters, options, site);
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;
}); });
}); });
} }
@ -390,4 +395,5 @@ export type CoreFilterFormatTextOptions = {
singleLine?: boolean; // If true then new lines will be removed (all the text in a single line). 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. shortenLength?: number; // Number of characters to shorten the text.
highlight?: string; // Text to highlight. highlight?: string; // Text to highlight.
wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason.
}; };

View File

@ -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() 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() contextLevel?: string; // The context level of the text.
@Input() contextInstanceId?: number; // The instance ID related to the context. @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<any>; // Called when the data is rendered. @Output() afterRender?: EventEmitter<any>; // Called when the data is rendered.
protected element: HTMLElement; protected element: HTMLElement;
@ -382,7 +383,7 @@ export class CoreFormatTextDirective implements OnChanges {
clean: this.utils.isTrueOrOne(this.clean), clean: this.utils.isTrueOrOne(this.clean),
singleLine: this.utils.isTrueOrOne(this.singleLine), singleLine: this.utils.isTrueOrOne(this.singleLine),
highlight: this.highlight, highlight: this.highlight,
filter: this.filter wsNotFiltered: this.utils.isTrueOrOne(this.wsNotFiltered)
}; };
if (this.filter) { if (this.filter) {