Suitecrm V8 Fields Custom Validation

Hey there,

SuiteCRM V8.7.0

After digging the forum & internet, Im still looking for the upgrade safe way on how to do custom validation (via any sort of regex, php code etc) on any field.

Tried the new way with adding a custom field definition mapper in the defaultEtx folder. Not really successful as I miss how to get the bean value & stop the save process.

Tried the old V7 way with a before_save hook, everything was successful until displaying a custom error message to the user. Most promising way to do it for now as beans work, I succeed at stopping the save process, only the custom error message is the issue.

So both option have an issue I sadly cant find a fix for now.

This doc could have help (Save Handlers :: SuiteCRM Documentation) but sadly without example I have no clue how to make it work.

Will update this topic if I find any solution to a must have feature in a CRM IMO.

Have a wonderfull day

1 Like

I think field validations are performed before the saving the records. If all the validations are passed then the record is saved. We have different types of validators like date,int,float,phone,email etc. We can add a custom validator in Angular in Typescript language. We have to register them in validation manager.
Please refer to this existing validator (core\app\core\src\lib\services\record\validation\validators\email.validator.ts). ‘before-save’ or ‘after-save’ modes should be used for applying business rules or logic before or after the record is saved in the database table.These save handlers are available in 8.8. We might need to create a new logic key for the custom validator and in the process handler we can write the logic that needs more control in PHP code. Can you please help understand what validations you want to have in your requirement.

Hey @Harshad, thanks for answering and try to help.

Didn’t knew about those TS file, this is maybe the way !

For the validation, it’s pretty classic stuff… :

  • Need to validate a siren (french ID number) so siren_c need to be exactly 9 numbers
  • Same for siret_c need to be 14 number
  • Got some custom website ID that need to start with XXXX-00999, so like 4 characters and then a dash and then number.

Those fields also needs to be unique, so Im checking the database to see if it’s a fully unique value or not. If not, Im stopping the save process and display a custom error to the user.

That’s just a few examples, I made all of this to work in suitecrm V7. And Im trying to make them work in V8

Also made those fields read only for people that are not from a certain role, made this in a custom file (/defaultExt/modules/Leads/ViewDefinitions/Mappers/VerificationLead.php) and this work well so that step is OK.

Thanks for the making me understand the example. I tried something similar to your field at my end in 8.8 version. You can change according to 8.7 to see if it helps. Sharing the steps here based on my learning.

  1. Copy config/services/ui/ui.yaml at extensions\defaultExt\config\services\ui\ui.yaml and add a new regex (siren).
validations:
      regex:
        phone: "^([\\+]?|00)((([(]{0,1}\\s*[0-9]{1,4}\\s*[)]{0,1})\\s*)*|([\\-\\s\\./#x0-9])*)+$"
        email: '^(?:[\.\-\+&#!\$\*=\?\^_`\{\}~\/\w]+)@(?:(?:\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})|\w+(?:[\.-]*\w+)*(?:\.[\w-]{2,})+)$'
        siren: '^[0-9]{3}-[0-9]{2}$'

Please note that this is not a good way to add new config values, you can handle it in PHP code later.

  1. Create a new service at extensions\defaultExt\app\src\services\formatters\idnumber\idnumber-formatter.service.ts with the following code. Please create the directories if not exist
import {Injectable} from '@angular/core';
import {FormControlUtils,SystemConfigStore,Formatter} from 'core';


@Injectable({
    providedIn: 'root'
})
export class IdNumberFormatter implements Formatter {

    constructor(
        protected formUtils: FormControlUtils,
        protected systemConfigStore: SystemConfigStore
    ) {}

    toUserFormat(value: string): string {
        return value;
    }

    toInternalFormat(value: string): string {
        return value;
    }

    getDefaultFormatPattern(): string {
        const validations = this.systemConfigStore.getUi('validations');
        const defaultRegex = validations?.regex?.siren || '';
        return defaultRegex;
    }

    validateUserFormat(inputValue: any, regexPattern: string): boolean {

        const trimmedInputValue = this.formUtils.getTrimmedInputValue(inputValue);
        if (this.formUtils.isEmptyInputValue(trimmedInputValue)) {
            return false;
        }
        const regex = new RegExp(regexPattern);
        return !regex.test(trimmedInputValue);

    }

}


Please note that we have used our regex value in getDefaultFormatPattern()

  1. Create validator file at extensions\defaultExt\app\src\services\record\validation\validators\id_number.validator.ts with following code. Create the directories if not exist.
import {AbstractControl} from '@angular/forms';
import {Injectable} from '@angular/core';
import {IdNumberFormatter} from '../../../formatters/idnumber/idnumber-formatter.service';
import {Record,StandardValidatorFn,StandardValidationErrors,ViewFieldDefinition,ValidatorInterface} from 'core';


export const idnumberValidator = (formatter: IdNumberFormatter, customValidationRegex?: string): StandardValidatorFn => (
    (control: AbstractControl): StandardValidationErrors | null => {

        const validationRegex = customValidationRegex || formatter.getDefaultFormatPattern();
        const invalid = formatter.validateUserFormat(control.value, validationRegex);
        return invalid ? {
            emailValidator: {
                valid: false,
                format: new RegExp(validationRegex),
                message: {
                    labelKey: 'ERR_INVALID_VALUE',
                    context: {
                        value: control.value,
                        expected: '000-00',
                    }
                }
            },
        } : null;
    }
);


@Injectable({
    providedIn: 'root'
})
export class IdNumberValidator implements ValidatorInterface {

    constructor(protected formatter: IdNumberFormatter) {
		console.log('idnumber validator service 1');
    }

    applies(record: Record, viewField: ViewFieldDefinition): boolean {
        if (!viewField || !viewField.fieldDefinition) {
            return false;
        }

        return viewField.type === 'varchar' && viewField.name == 'siren_c';
    }

    getValidator(viewField: ViewFieldDefinition): StandardValidatorFn[] {

        if (!viewField || !viewField.fieldDefinition) {
            return [];
        }
        const customValidationRegex = viewField?.fieldDefinition?.validation?.regex.toString() ?? null;

        return [idnumberValidator(this.formatter, customValidationRegex)];
    }
}

Please note that we have used our formatted service here to check the input again the regex. Also note the label key ‘ERR_INVALID_VALUE’ you can add your own in public/legacy/custom/include/language/en_us.lang.php to display the validation message.

  1. Add references of these services in the extension.module.ts file at extensions\defaultExt\app\src\extension.module.ts
    as
import {NgModule} from '@angular/core';
import {CommonModule} from '@angular/common';
import {provideHttpClient, withInterceptorsFromDi} from '@angular/common/http';
import {IdNumberValidator} from './services/record/validation/validators/id_number.validator';
import {IdNumberFormatter} from './services/formatters/idnumber/idnumber-formatter.service';
import{ValidationManager,DataTypeFormatter} from 'core';
@NgModule({ declarations: [], imports: [CommonModule], providers: [provideHttpClient(withInterceptorsFromDi())] })
export class ExtensionModule {
    constructor(private dataTypeFormatter:DataTypeFormatter,
	idnumValidator:IdNumberValidator,validationManager:ValidationManager,idnumberFormatter:IdNumberFormatter) {
		console.log('extension module enabled');
		dataTypeFormatter.map.idnumber = idnumberFormatter;
		validationManager.registerFieldSaveValidator('accounts','varchar','siren_c',idnumValidator);
    }
    init(): void {
    }
}

  1. Enable the extension at extensions\defaultExt\config\extension.php as
    'enabled' => true, after remoteName property

  2. Run php bin/console cache:clear to build the symfony package

  3. Run the command yarn run build-dev:extension defaultExt to build nodejs angular package

  4. Reload the application to test the effect of code changes.

image

Please note that this is just an example of one field you can try making changes for similar fields in the validator file or as separate validators depending upon your requirements. Please take care of imports statements especially common module for your 8.7 version as the above example is working fine for 8.8 version. Thanks!

2 Likes