PluginProbe
GiveWP – Donation Plugin and Fundraising Platform / 4.17.0
GiveWP – Donation Plugin and Fundraising Platform v4.17.0
4.17.0 4.16.9 4.16.8.1 4.16.8 4.16.7.2 4.16.7.1 4.16.7 4.16.6.1 4.16.6 4.16.5.1 4.16.5 4.16.4 4.16.3 4.16.2 4.16.1 4.16.0 4.15.5 4.15.4 4.15.3 4.15.2 4.15.1 4.15.0 2.3.0 2.3.1 2.3.2 All 256 releases
give / src / DonationForms / resources / app / utilities / ConvertFieldAPIRulesToJoi.ts

ConvertFieldAPIRulesToJoi.ts in GiveWP – Donation Plugin and Fundraising Platform 4.17.0, at src/DonationForms/resources/app/utilities/ConvertFieldAPIRulesToJoi.ts

222 lines 6.8 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 import Joi, {AnySchema, ObjectSchema} from 'joi';
2 import {AmountField, BasicCondition, Field, Form, isField} from '@givewp/forms/types';
3 import {__, sprintf} from '@wordpress/i18n';
4 import conditionOperatorFunctions from '@givewp/forms/app/utilities/conditionOperatorFunctions';
5
6 /**
7 * @since 3.0.0
8 */
9 const requiredMessage = sprintf(
10 /* translators: base error message */
11 __('This is a required field', 'give'),
12 `{#label}`
13 );
14
15 /**
16 * @since 3.0.0
17 */
18 export default function getJoiRulesForForm(form: Form): ObjectSchema {
19 const joiRules = form.reduceNodes(
20 (rules, field: Field) => {
21 rules[field.name] = getJoiRulesForField(field);
22
23 return rules;
24 },
25 {},
26 isField
27 );
28
29 return Joi.object(joiRules).messages({
30 'string.base': requiredMessage,
31 'string.empty': requiredMessage,
32 'any.required': requiredMessage,
33 'number.base': requiredMessage,
34 'object.base': requiredMessage,
35 });
36 }
37
38 /**
39 * @since 4.16.8 collect the amounts exempt from the minimum and maximum
40 * @since 3.0.0
41 */
42 function getJoiRulesForField(field: Field): AnySchema {
43 let rules: AnySchema = convertFieldAPIRulesToJoi(field.validationRules, getExemptAmounts(field));
44
45 if (field.label) {
46 rules = rules.label(field.label);
47 }
48
49 return rules;
50 }
51
52 /**
53 * The amounts the admin configured on the donation amount field. The custom amount minimum and maximum only
54 * constrain what a donor types into the custom amount input, so these are always accepted.
55 *
56 * @since 4.16.8
57 */
58 function getExemptAmounts(field: Field): number[] {
59 if (field.type !== 'amount') {
60 return [];
61 }
62
63 const {levels, allowLevels, fixedAmountValue} = field as AmountField;
64
65 // An unset level or fixed amount reads as 0, which must never be exempt from the minimum.
66 if (allowLevels) {
67 return (levels ?? []).map((level) => Number(level.value)).filter((amount) => amount > 0);
68 }
69
70 return fixedAmountValue > 0 ? [Number(fixedAmountValue)] : [];
71 }
72
73 /**
74 * @since 4.16.8 exempt admin-defined amounts from the minimum and maximum
75 * @since 4.13.1 account for custom rules with only excludeUnless property
76 * @since 4.13.0 add support for optional false values
77 * @since 3.0.0
78 */
79 function convertFieldAPIRulesToJoi(rules, exemptAmounts: number[] = []): AnySchema {
80 let joiRules;
81 const ruleKeys = Object.keys(rules);
82
83 if (ruleKeys.length === 0 || (ruleKeys.length === 1 && 'excludeUnless' in rules)) {
84 return Joi.any();
85 }
86
87 if (rules.hasOwnProperty('numeric') || rules.hasOwnProperty('integer')) {
88 joiRules = Joi.number();
89
90 if (rules.hasOwnProperty('integer')) {
91 joiRules = joiRules.integer();
92 }
93 } else if (rules.hasOwnProperty('boolean')) {
94 joiRules = Joi.boolean();
95 } else if (rules.hasOwnProperty('array')) {
96 joiRules = Joi.array();
97 } else if (rules.hasOwnProperty('file')) {
98 joiRules = Joi.object();
99 } else if (rules.hasOwnProperty('dateTime')) {
100 joiRules = Joi.date();
101 } else {
102 joiRules = Joi.string();
103
104 if (rules.hasOwnProperty('email')) {
105 joiRules = joiRules.email({tlds: false});
106 }
107
108 if (rules.hasOwnProperty('alpha')) {
109 joiRules = joiRules.alpha();
110 }
111
112 if (rules.hasOwnProperty('alphanum')) {
113 joiRules = joiRules.alphanum();
114 }
115 }
116
117 if (rules.hasOwnProperty('number') || !rules.hasOwnProperty('boolean')) {
118 const hasMin = rules.hasOwnProperty('min');
119 const hasMax = rules.hasOwnProperty('max');
120
121 if (exemptAmounts.length > 0 && (hasMin || hasMax)) {
122 joiRules = joiRules.custom((value, helpers) => {
123 const amount = Number(value);
124
125 if (exemptAmounts.includes(amount)) {
126 return value;
127 }
128
129 if (hasMin && amount < rules.min) {
130 return helpers.error('number.min', {limit: rules.min});
131 }
132
133 if (hasMax && amount > rules.max) {
134 return helpers.error('number.max', {limit: rules.max});
135 }
136
137 return value;
138 }, 'exempt amount range');
139 } else {
140 if (hasMin) {
141 joiRules = joiRules.min(rules.min);
142 }
143
144 if (hasMax) {
145 joiRules = joiRules.max(rules.max);
146 }
147 }
148 }
149
150 if (rules.required) {
151 if (rules.hasOwnProperty('excludeUnless')) {
152 /**
153 * This applies requirements to a field if the field is not being excluded by its conditions. It only
154 * supports basic conditions at this time, but could be expanded to support more complex conditions.
155 *
156 * Note that this unsets the value if the field conditions are not met.
157 */
158 joiRules = joiRules.custom((value, helpers) => {
159 const formValues = helpers.state.ancestors[0];
160
161 const passesConditions = rules.excludeUnless.every((condition: BasicCondition) => {
162 return conditionOperatorFunctions[condition.comparisonOperator](
163 formValues[condition.field],
164 condition.value
165 );
166 });
167
168 if (passesConditions && (value === '' || value === null)) {
169 return helpers.error('required');
170 } else if (!passesConditions) {
171 return undefined;
172 }
173
174 return value;
175 }, 'exclude unless validation');
176 } else {
177 joiRules = joiRules.required();
178 }
179 } else {
180 joiRules = joiRules.optional().allow('', null, false);
181 }
182
183 joiRules = getJoiRulesForAmountField(rules, joiRules);
184
185 return joiRules;
186 }
187
188 /**
189 * @since 3.0.0
190 */
191 function getJoiRulesForAmountField(rules, joiRules): AnySchema {
192 if (rules.hasOwnProperty('donationType')) {
193 joiRules = Joi.allow('single', 'subscription').only().required();
194 }
195
196 if (rules.hasOwnProperty('subscriptionPeriod')) {
197 joiRules = Joi.when('donationType', {
198 is: 'subscription',
199 then: Joi.allow('day', 'week', 'quarter', 'month', 'year').only().required(),
200 otherwise: Joi.optional(),
201 });
202 }
203
204 if (rules.hasOwnProperty('subscriptionFrequency')) {
205 joiRules = Joi.when('donationType', {
206 is: 'subscription',
207 then: Joi.number().integer().required(),
208 otherwise: Joi.optional(),
209 });
210 }
211
212 if (rules.hasOwnProperty('subscriptionInstallments')) {
213 joiRules = Joi.when('donationType', {
214 is: 'subscription',
215 then: Joi.number().integer().required(),
216 otherwise: Joi.optional(),
217 });
218 }
219
220 return joiRules;
221 }
222