diff --git a/core/app/core/src/lib/services/formatters/data-type.formatter.service.ts b/core/app/core/src/lib/services/formatters/data-type.formatter.service.ts index 0d2b07e937..2f419302e4 100644 --- a/core/app/core/src/lib/services/formatters/data-type.formatter.service.ts +++ b/core/app/core/src/lib/services/formatters/data-type.formatter.service.ts @@ -31,6 +31,7 @@ import {FormatOptions, Formatter} from './formatter.model'; import {CurrencyFormatter} from './currency/currency-formatter.service'; import {DateFormatter} from './datetime/date-formatter.service'; import {PhoneFormatter} from './phone/phone-formatter.service'; +import {TextFormatter} from "./text/text-formatter.service"; export interface TypeFormatterMap { [key: string]: Formatter; @@ -49,6 +50,7 @@ export class DataTypeFormatter { protected dateFormatter: DateFormatter, protected datetimeFormatter: DatetimeFormatter, protected phoneFormatter: PhoneFormatter, + protected text: TextFormatter, ) { this.map.int = numberFormatter; this.map.float = numberFormatter; @@ -56,6 +58,7 @@ export class DataTypeFormatter { this.map.datetime = datetimeFormatter; this.map.currency = currencyFormatter; this.map.phone = phoneFormatter; + this.map.text = text; } toUserFormat(dataType: string, value: string, options?: FormatOptions): string { diff --git a/core/app/core/src/lib/services/formatters/data-type.formatter.spec.mock.ts b/core/app/core/src/lib/services/formatters/data-type.formatter.spec.mock.ts index 522d65deae..76d44e9fb4 100644 --- a/core/app/core/src/lib/services/formatters/data-type.formatter.spec.mock.ts +++ b/core/app/core/src/lib/services/formatters/data-type.formatter.spec.mock.ts @@ -30,11 +30,13 @@ import {numberFormatterMock} from './number/number-formatter.spec.mock'; import {datetimeFormatterMock} from './datetime/datetime-formatter.service.spec.mock'; import {phoneFormatterMock} from './phone/phone-formatter.spec.mock'; import {dateFormatterMock} from './datetime/date-formatter.service.spec.mock'; +import {textFormatterMock} from "./text/text-formatter.service.spec.mock"; export const dataTypeFormatterMock = new DataTypeFormatter( currencyFormatterMock, numberFormatterMock, dateFormatterMock, datetimeFormatterMock, - phoneFormatterMock + phoneFormatterMock, + textFormatterMock ); diff --git a/core/app/core/src/lib/services/formatters/text/text-formatter.service.spec.mock.ts b/core/app/core/src/lib/services/formatters/text/text-formatter.service.spec.mock.ts new file mode 100755 index 0000000000..e764ce5570 --- /dev/null +++ b/core/app/core/src/lib/services/formatters/text/text-formatter.service.spec.mock.ts @@ -0,0 +1,31 @@ +/** + * SuiteCRM is a customer relationship management program developed by SalesAgility Ltd. + * Copyright (C) 2021 SalesAgility Ltd. + * + * This program is free software; you can redistribute it and/or modify it under + * the terms of the GNU Affero General Public License version 3 as published by the + * Free Software Foundation with the addition of the following permission added + * to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED WORK + * IN WHICH THE COPYRIGHT IS OWNED BY SALESAGILITY, SALESAGILITY DISCLAIMS THE + * WARRANTY OF NON INFRINGEMENT OF THIRD PARTY RIGHTS. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS + * FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more + * details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * + * In accordance with Section 7(b) of the GNU Affero General Public License + * version 3, these Appropriate Legal Notices must retain the display of the + * "Supercharged by SuiteCRM" logo. If the display of the logos is not reasonably + * feasible for technical reasons, the Appropriate Legal Notices must display + * the words "Supercharged by SuiteCRM". + */ + +import { TextFormatter } from './text-formatter.service'; +import {FormControlUtils} from '../../../services/record/field/form-control.utils'; +import {userPreferenceStoreMock} from '../../../store/user-preference/user-preference.store.spec.mock'; + +export const textFormatterMock = new TextFormatter(userPreferenceStoreMock,new FormControlUtils(),'en_US'); diff --git a/core/app/core/src/lib/services/formatters/text/text-formatter.service.ts b/core/app/core/src/lib/services/formatters/text/text-formatter.service.ts new file mode 100755 index 0000000000..6c01f632fc --- /dev/null +++ b/core/app/core/src/lib/services/formatters/text/text-formatter.service.ts @@ -0,0 +1,85 @@ +import {Inject, Injectable, LOCALE_ID} from '@angular/core'; +import {FormatOptions, Formatter} from '../formatter.model'; +import { FormControlUtils } from '../../record/field/form-control.utils'; +import {UserPreferenceStore} from "../../../store/user-preference/user-preference.store"; + +@Injectable({ + providedIn: 'root' +}) +export class TextFormatter implements Formatter { + + private defaultMap: { [key: string]: string } = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + 'à': 'à', 'á': 'á', 'â': 'â', 'ã': 'ã', 'ä': 'ä', + 'è': 'è', 'é': 'é', 'ê': 'ê', 'ë': 'ë', + 'ì': 'ì', 'í': 'í', 'î': 'î', 'ï': 'ï', + 'ò': 'ò', 'ó': 'ó', 'ô': 'ô', 'õ': 'õ', 'ö': 'ö', + 'ù': 'ù', 'ú': 'ú', 'û': 'û', 'ü': 'ü', + 'ñ': 'ñ', 'Ñ': 'Ñ', + 'ç': 'ç', 'Ç': 'Ç', + '€': '€', + '¥': '¥', + '£': '£', + '¢': '¢', + '©': '©', + '®': '®', + '™': '™', + '§': '§', + '¶': '¶', + '•': '•', + '…': '…', + '–': '–', + '—': '—', + // ... add more characters as needed + }; + + constructor( + protected preferences: UserPreferenceStore, + protected formUtils: FormControlUtils, + @Inject(LOCALE_ID) public locale: string + ) {} + + + /** + * Converts the value to a format suitable for display to the user. + * Escapes HTML special characters. + */ + toUserFormat(value: string, options?: FormatOptions): string { + // Reverse the map for decoding + const decodeMap: { [key: string]: string } = Object.entries(this.defaultMap).reduce((acc, [char, entity]) => { + acc[entity] = char; + return acc; + }, {}); + + // Create a regex to match all entities in the text + const entityRegex = new RegExp(Object.keys(decodeMap).join('|'), 'g'); + + // Recursively decode until no more encoded entities remain + let decodedValue = value; + let previousValue; + + do { + previousValue = decodedValue; + decodedValue = decodedValue?.replace(entityRegex, (match) => decodeMap[match] || match); + } while (decodedValue !== previousValue); // Repeat until no changes + + return decodedValue; + } + + /** + * Converts the user input back to the internal format. + * Decodes HTML special characters. + */ + toInternalFormat(value): string { + const formmatedValue = value?.replace(/[<>&"'\u00A0-\uFFFF]/g, (match) => { + const encoded = this.defaultMap[match]; + return encoded !== undefined ? encoded : match; + }); + return formmatedValue; + } + +} diff --git a/core/app/core/src/lib/services/record/field/field.builder.ts b/core/app/core/src/lib/services/record/field/field.builder.ts index ce4ba1a1d7..5b06d7da93 100644 --- a/core/app/core/src/lib/services/record/field/field.builder.ts +++ b/core/app/core/src/lib/services/record/field/field.builder.ts @@ -211,6 +211,10 @@ export class FieldBuilder { if (defaultValue) { field.default = defaultValue; } + // issue #542 formmattedValue applied to value (only for text fields?) + if(field.type == 'text'){ + field.value = formattedValue; + } field.defaultValueModes = viewField?.defaultValueModes ?? definition?.defaultValueModes ?? [];