Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 24 additions & 24 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"build:prod": "npx webpack --config config/webpack.prod.js",
"clean": "rm -rf lib && rm -rf dist && rm -rf docs",
"build": "yarn clean && gulp templates && yarn docs && yarn lib && yarn alias && yarn build:dev && yarn build:prod",
"prepublish": "yarn lint && yarn format && yarn build && yarn test",
"prepublish": "yarn format && yarn build && yarn test",
"show-coverage": "open coverage/lcov-report/index.html",
"lint": "eslint src",
"format": "prettier --write .",
Expand Down Expand Up @@ -57,31 +57,31 @@
],
"homepage": "https://github.com/formio/core#readme",
"devDependencies": {
"@eslint/js": "^9.12.0",
"@eslint/js": "^9.38.0",
"@types/chai": "^4.3.16",
"@types/chance": "^1.1.6",
"@types/dompurify": "^3.0.5",
"@types/chance": "^1.1.7",
"@types/dompurify": "^3.2.0",
"@types/fetch-mock": "^7.3.8",
"@types/flatpickr": "^3.1.2",
"@types/flatpickr": "^3.1.4",
"@types/inputmask": "^5.0.7",
"@types/json-logic-js": "^2.0.7",
"@types/lodash": "^4.17.4",
"@types/json-logic-js": "^2.0.8",
"@types/lodash": "^4.17.20",
"@types/lodash.template": "^4.5.3",
"@types/mocha": "^10.0.4",
"@types/mocha": "^10.0.10",
"@types/power-assert": "^1.5.11",
"@types/sinon": "^17.0.3",
"@types/sinon": "^17.0.4",
"@types/uuid": "^9.0.8",
"chai": "4.4.1",
"chance": "^1.1.8",
"eslint": "^9.12.0",
"chance": "^1.1.13",
"eslint": "9.12.0",
"eslint-plugin-mocha": "^10.5.0",
"fetch-mock": "^9.11.0",
"globals": "^15.11.0",
"gulp": "^5.0.0",
"gulp": "^5.0.1",
"gulp-insert": "^0.5.0",
"gulp-rename": "^2.0.0",
"gulp-rename": "^2.1.0",
"gulp-template": "^5.0.0",
"husky": ">=6",
"husky": ">=9.1.7",
"jsdom": "22.1.0",
"jsdom-global": "^3.0.2",
"lint-staged": ">=10",
Expand All @@ -90,24 +90,24 @@
"mock-local-storage": "^1.1.20",
"nyc": "^15.1.0",
"power-assert": "^1.6.1",
"prettier": "^3.3.3",
"prettier": "^3.6.2",
"sinon": "^17.0.2",
"ts-loader": "^9.5.0",
"ts-loader": "^9.5.4",
"ts-node": "^10.5.0",
"tsc-alias": "^1.8.10",
"tsc-alias": "^1.8.16",
"tsconfig-paths": "^4.1.2",
"tsconfig-paths-webpack-plugin": "^4.1.0",
"tsconfig-paths-webpack-plugin": "^4.2.0",
"typedoc": "^0.25.13",
"typescript": "^5.4.5",
"typescript-eslint": "^8.8.1",
"webpack": "^5.91.0",
"typescript": "^5.9.3",
"typescript-eslint": "^8.46.2",
"webpack": "^5.102.1",
"webpack-cli": "^5.1.4"
},
"dependencies": {
"browser-cookies": "^1.2.0",
"core-js": "^3.39.0",
"dayjs": "^1.11.12",
"dompurify": "^3.2.4",
"core-js": "^3.46.0",
"dayjs": "^1.11.19",
"dompurify": "3.2.4",
"eventemitter3": "^5.0.0",
"fast-json-patch": "^3.1.1",
"fetch-ponyfill": "^7.1.0",
Expand Down
4 changes: 2 additions & 2 deletions src/process/validation/rules/validateAvailableItems.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isEmpty, isUndefined, difference } from 'lodash';
import { isEmpty, isUndefined, difference, isObject } from 'lodash';
import { FieldError, ProcessorError } from 'error';
import { evaluate } from 'utils';
import {
Expand All @@ -10,9 +10,9 @@ import {
FetchFn,
SelectBoxesComponent,
} from 'types';
import { isObject, isPromise } from '../util';
import { ProcessorInfo } from 'types/process/ProcessorInfo';
import { getErrorMessage } from 'utils/error';
import { isPromise } from '../util';

function isValidatableRadioComponent(component: any): component is RadioComponent {
return component && component.type === 'radio' && !!component.validate?.onlyAvailableItems;
Expand Down
87 changes: 3 additions & 84 deletions src/process/validation/rules/validateRequired.ts
Original file line number Diff line number Diff line change
@@ -1,76 +1,8 @@
import { FieldError } from 'error';
import {
AddressComponentDataObject,
RuleFn,
RuleFnSync,
ValidationContext,
AddressComponent,
DayComponent,
} from 'types';
import { isEmptyObject, doesArrayDataHaveValue } from '../util';
import { isComponentNestedDataType } from 'utils/formUtil';
import { RuleFn, RuleFnSync, ValidationContext } from 'types';
import { componentHasValue } from 'utils';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

const isAddressComponent = (component: any): component is AddressComponent => {
return component.type === 'address';
};

const isDayComponent = (component: any): component is DayComponent => {
return component.type === 'day';
};

const isAddressComponentDataObject = (value: any): value is AddressComponentDataObject => {
return (
value !== null &&
typeof value === 'object' &&
value.mode &&
value.address &&
typeof value.address === 'object'
);
};

// Checkboxes and selectboxes consider false to be falsy, whereas other components with
// settable values (e.g. radio, select, datamap, container, etc.) consider it truthy
const isComponentThatCannotHaveFalseValue = (component: any): boolean => {
return component.type === 'checkbox' || component.type === 'selectboxes';
};

const valueIsPresent = (
value: any,
considerFalseTruthy: boolean,
isNestedDatatype?: boolean,
): boolean => {
// Evaluate for 3 out of 6 falsy values ("", null, undefined), don't check for 0
// and only check for false under certain conditions
if (
value === null ||
value === undefined ||
value === '' ||
(!considerFalseTruthy && value === false)
) {
return false;
}
// Evaluate for empty object
else if (isEmptyObject(value)) {
return false;
}
// Evaluate for empty array
else if (Array.isArray(value) && value.length === 0) {
return false;
}
// Recursively evaluate
else if (typeof value === 'object' && !isNestedDatatype) {
return Object.values(value).some((val) =>
valueIsPresent(val, considerFalseTruthy, isNestedDatatype),
);
}
// If value is an array, check it's children have value
else if (Array.isArray(value) && value.length) {
return doesArrayDataHaveValue(value);
}
return true;
};

export const shouldValidate = (context: ValidationContext): boolean => {
const { component } = context;
return !!component.validate?.required;
Expand All @@ -86,20 +18,7 @@ export const validateRequiredSync: RuleFnSync = (context: ValidationContext) =>
if (!shouldValidate(context)) {
return null;
}
if (isAddressComponent(component) && isAddressComponentDataObject(value)) {
return isEmptyObject(value.address)
? error
: Object.entries(value.address)
.filter(([key]) => !['address2'].includes(key))
.every(([, val]) => !!val)
? null
: error;
} else if (isDayComponent(component) && value === '00/00/0000') {
return error;
} else if (isComponentThatCannotHaveFalseValue(component)) {
return !valueIsPresent(value, false, isComponentNestedDataType(component)) ? error : null;
}
return !valueIsPresent(value, true, isComponentNestedDataType(component)) ? error : null;
return componentHasValue(component, value) ? null : error;
};

export const validateRequiredInfo: ProcessorInfo<ValidationContext, FieldError | null> = {
Expand Down
3 changes: 2 additions & 1 deletion src/process/validation/rules/validateResourceSelectValue.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { FieldError, ProcessorError } from 'error';
import { SelectComponent, RuleFn, ValidationContext } from 'types';
import { isEmptyObject, toBoolean } from '../util';
import { toBoolean } from '../util';
import { ProcessorInfo } from 'types/process/ProcessorInfo';
import { isEmptyObject } from 'utils';

const isValidatableSelectComponent = (component: any): component is SelectComponent => {
return (
Expand Down
2 changes: 1 addition & 1 deletion src/process/validation/rules/validateUnique.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isEmptyObject } from 'utils';
import { FieldError } from '../../../error/FieldError';
import { RuleFn, ValidationContext } from '../../../types/index';
import { isEmptyObject } from '../util';
import { ProcessorError } from 'error';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

Expand Down
4 changes: 2 additions & 2 deletions src/process/validation/rules/validateUrlSelectValue.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FieldError, ProcessorError } from 'error';
import { SelectComponent, RuleFn, ValidationContext, FetchFn } from 'types';
import { Evaluator } from 'utils';
import { isEmptyObject, toBoolean } from '../util';
import { Evaluator, isEmptyObject } from 'utils';
import { toBoolean } from '../util';
import { getErrorMessage } from 'utils/error';
import { ProcessorInfo } from 'types/process/ProcessorInfo';

Expand Down
46 changes: 0 additions & 46 deletions src/process/validation/util.ts
Original file line number Diff line number Diff line change
@@ -1,22 +1,6 @@
import { FieldError } from 'error';
import { Component } from 'types';
import { Evaluator, unescapeHTML } from 'utils';
import { VALIDATION_ERRORS } from './i18n';
import _isEmpty from 'lodash/isEmpty';
import _isObject from 'lodash/isObject';
import _isPlainObject from 'lodash/isPlainObject';

export function isComponentPersistent(component: Component) {
return component.persistent ? component.persistent : true;
}

export function isComponentProtected(component: Component) {
return component.protected ? component.protected : false;
}

export function isEmptyObject(obj: any) {
return !!obj && Object.keys(obj).length === 0 && obj.constructor === Object;
}

export function toBoolean(value: any) {
switch (typeof value) {
Expand Down Expand Up @@ -44,10 +28,6 @@ export function isPromise(value: any): value is Promise<any> {
);
}

export function isObject(obj: any): obj is object {
return obj != null && (typeof obj === 'object' || typeof obj === 'function');
}

const getCustomErrorMessage = ({ errorKeyOrMessage, context }: FieldError): string =>
context.component?.errors?.[errorKeyOrMessage] || '';

Expand Down Expand Up @@ -90,29 +70,3 @@ export const interpolateErrors = (errors: FieldError[], lang: string = 'en') =>
};
});
};

export const hasValue = (value: any) => {
if (_isObject(value)) {
return !_isEmpty(value);
}

return (typeof value === 'number' && !Number.isNaN(value)) || !!value;
};

export const doesArrayDataHaveValue = (dataValue: any[] = []): boolean => {
if (!Array.isArray(dataValue)) {
return !!dataValue;
}

if (!dataValue.length) {
return false;
}

const isArrayDataComponent = dataValue.every(_isPlainObject);

if (isArrayDataComponent) {
return dataValue.some((value) => Object.values(value).some(hasValue));
}

return dataValue.some(hasValue);
};
5 changes: 4 additions & 1 deletion src/types/Access.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,13 @@ import { RoleId } from 'types/Role';

export type Access = {
type: AccessType;
roles: RoleId[];
roles?: RoleId[];
resources?: string[];
};

export type BasicAccessType = 'read' | 'write' | 'create' | 'admin';
export type AccessType =
| 'self'
| 'create_own'
| 'create_all'
| 'read_own'
Expand Down
2 changes: 1 addition & 1 deletion src/types/Action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ export type FormAction = {
method: Array<string>;
condition?: any;
priority: number;
settings: ActionSettings;
settings: ActionSettings | Record<string, unknown>;
form: FormId;
// Database timestamps
deleted: Date | string;
Expand Down
8 changes: 4 additions & 4 deletions src/types/AdvancedLogic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,22 @@ import { SimpleConditional } from './BaseComponent';

export type LogicTriggerSimple = {
type: 'simple';
simple: SimpleConditional;
simple?: SimpleConditional;
};

export type LogicTriggerJavascript = {
type: 'javascript';
javascript: string;
javascript?: string;
};

export type LogicTriggerJson = {
type: 'json';
json: RulesLogic;
json?: RulesLogic;
};

export type LogicTriggerEvent = {
type: 'event';
event: string;
event?: string;
};

export type LogicTrigger =
Expand Down
3 changes: 3 additions & 0 deletions src/types/BaseComponent.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { RulesLogic } from 'json-logic-js';
import { AdvancedLogic } from './AdvancedLogic';
import { getModelType } from 'utils/formUtil';
import { Access, BasicAccessType } from './Access';
export type JSONConditional = { json: RulesLogic };
export type LegacyConditional = {
show: boolean | string | null;
Expand Down Expand Up @@ -83,6 +84,8 @@ export type BaseComponent = {
height: string;
};
allowCalculateOverride?: boolean;
submissionAccess?: Access[];
defaultPermission?: BasicAccessType;
encrypted?: boolean;
showCharCount?: boolean;
showWordCount?: boolean;
Expand Down
3 changes: 1 addition & 2 deletions src/types/Component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,7 @@ export type PanelComponent = NestedComponent & {
};

export type PasswordComponent = TextFieldComponent;

export type PhoneNumberComponent = NumberComponent & { inputMode: 'decimal' };
export type PhoneNumberComponent = TextFieldComponent;

export type ListComponent = BaseComponent & {
values?: { label: string; value: string; shortcut?: string }[];
Expand Down
Loading