PluginProbe
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management / 1.1.0
SureDonation – Donation Forms, Fundraising Campaigns & Donor Management v1.1.0
1.6.0 1.5.1 1.5.0 1.4.0 1.3.0 trunk 0.0.1 1.0.0 1.1.0 1.1.1 1.1.2 1.2.0
← All changes | inc/field-validation.php +8 -447 1.4.0 → 1.1.0 View file →
@@ -43,11 +43,8 @@
43 43 public const VALIDATABLE_BLOCKS = [
44 44 'suredonation/input',
45 45 'suredonation/email',
46 46 'suredonation/number',
47 - 'suredonation/dropdown',
48 - 'suredonation/phone',
49 - 'suredonation/url',
50 47 ];
51 48
52 49 /**
53 50 * Add block configuration for form fields.
@@ -170,17 +167,8 @@
170 167 break;
171 168 case 'suredonation/email':
172 169 $processed_config = self::process_email_block( $block['attrs'] );
173 170 break;
174 - case 'suredonation/dropdown':
175 - $processed_config = self::process_dropdown_block( $block['attrs'] );
176 - break;
177 - case 'suredonation/phone':
178 - $processed_config = self::process_phone_block( $block['attrs'] );
179 - break;
180 - case 'suredonation/url':
181 - $processed_config = self::process_url_block( $block['attrs'] );
182 - break;
183 171 }
184 172
185 173 /**
186 174 * Filter the stored validation config for a field block.
@@ -301,186 +289,8 @@
301 289 return null;
302 290 }
303 291
304 292 /**
305 - * Resolve the slugs of the core donor fields (name, email, variable amount
306 - * and the optional mapped phone) from a form's saved payment block.
307 - *
308 - * These fields are surfaced as first-class donation data and persisted in
309 - * their own columns, so the stored "additional" field set omits them.
310 - * Deriving the slugs from the saved form here (instead of trusting a
311 - * client-supplied list) keeps the exclusion authoritative — a tampered
312 - * submission cannot smuggle a core field into the additional set.
313 - *
314 - * @since 1.1.1
315 - * @param int $form_id The donation form post ID.
316 - * @return array<int, string> List of core field slugs (empty when none/invalid).
317 - */
318 - public static function get_core_field_slugs( $form_id ) {
319 - if ( ! is_int( $form_id ) || $form_id <= 0 || ! function_exists( 'parse_blocks' ) ) {
320 - return [];
321 - }
322 -
323 - $post = get_post( $form_id );
324 - if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
325 - return [];
326 - }
327 -
328 - $payment_attrs = self::find_payment_block_attrs( parse_blocks( $post->post_content ) );
329 - if ( empty( $payment_attrs ) ) {
330 - return [];
331 - }
332 -
333 - $slugs = [];
334 - foreach ( [ 'customerNameField', 'customerEmailField', 'customerPhoneField', 'variableAmountField' ] as $attr ) {
335 - if ( isset( $payment_attrs[ $attr ] ) && is_string( $payment_attrs[ $attr ] ) ) {
336 - $slug = sanitize_text_field( $payment_attrs[ $attr ] );
337 - if ( '' !== $slug ) {
338 - $slugs[] = $slug;
339 - }
340 - }
341 - }
342 -
343 - return array_values( array_unique( $slugs ) );
344 - }
345 -
346 - /**
347 - * Find the suredonation/payment block's attributes recursively.
348 - *
349 - * @since 1.1.1
350 - * @param array<mixed> $blocks Array of parsed blocks.
351 - * @return array<string, mixed>|null The payment block attributes, or null when absent.
352 - */
353 - private static function find_payment_block_attrs( $blocks ) {
354 - if ( ! is_array( $blocks ) ) {
355 - return null;
356 - }
357 -
358 - foreach ( $blocks as $block ) {
359 - if ( ! is_array( $block ) ) {
360 - continue;
361 - }
362 -
363 - if ( isset( $block['blockName'] ) && 'suredonation/payment' === $block['blockName'] && isset( $block['attrs'] ) && is_array( $block['attrs'] ) ) {
364 - return $block['attrs'];
365 - }
366 -
367 - if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
368 - $found = self::find_payment_block_attrs( $block['innerBlocks'] );
369 - if ( null !== $found ) {
370 - return $found;
371 - }
372 - }
373 - }
374 -
375 - return null;
376 - }
377 -
378 - /**
379 - * Resolve the slug of the field mapped to the donor phone on the payment block.
380 - *
381 - * The mapping is optional: when an author maps a Phone field via the payment
382 - * block's "Customer Phone Field" picker, its value is stored in the dedicated
383 - * donor_phone column. Returning the slug lets the submission handlers read the
384 - * already-validated value from the submitted fields rather than trusting a
385 - * separate client-supplied donor_phone field.
386 - *
387 - * @since 1.1.1
388 - * @param int $form_id The donation form post ID.
389 - * @return string The mapped phone field slug, or '' when unset/invalid.
390 - */
391 - public static function get_mapped_phone_slug( $form_id ) {
392 - if ( ! is_int( $form_id ) || $form_id <= 0 || ! function_exists( 'parse_blocks' ) ) {
393 - return '';
394 - }
395 -
396 - $post = get_post( $form_id );
397 - if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
398 - return '';
399 - }
400 -
401 - $payment_attrs = self::find_payment_block_attrs( parse_blocks( $post->post_content ) );
402 - if ( empty( $payment_attrs ) || ! isset( $payment_attrs['customerPhoneField'] ) || ! is_string( $payment_attrs['customerPhoneField'] ) ) {
403 - return '';
404 - }
405 -
406 - return sanitize_text_field( $payment_attrs['customerPhoneField'] );
407 - }
408 -
409 - /**
410 - * Build a map of field slug => label from a form's saved blocks.
411 - *
412 - * The label persisted with each submitted field is resolved from the saved
413 - * form (the authoritative source) rather than scraped from the rendered
414 - * page and trusted from the request — mirroring how SureForms recovers a
415 - * field's label server-side instead of from client-supplied text. Gutenberg
416 - * omits attributes left at their default, so a slug missing from this map
417 - * simply has no customized label and the caller falls back to the label
418 - * sent with the submission.
419 - *
420 - * @since 1.1.1
421 - * @param int $form_id The donation form post ID.
422 - * @return array<string, string> Map of field slug => label (empty when none/invalid).
423 - */
424 - public static function get_field_labels_map( $form_id ) {
425 - if ( ! is_int( $form_id ) || $form_id <= 0 || ! function_exists( 'parse_blocks' ) ) {
426 - return [];
427 - }
428 -
429 - $post = get_post( $form_id );
430 - if ( ! ( $post instanceof \WP_Post ) || empty( $post->post_content ) ) {
431 - return [];
432 - }
433 -
434 - $labels = [];
435 - self::collect_field_labels( parse_blocks( $post->post_content ), $labels );
436 -
437 - return $labels;
438 - }
439 -
440 - /**
441 - * Recursively collect slug => label pairs from parsed blocks.
442 - *
443 - * Inner blocks are walked first so nested sub-fields are captured before
444 - * their container; the first label seen for a slug wins.
445 - *
446 - * @since 1.1.1
447 - * @param array<mixed> $blocks Parsed blocks.
448 - * @param array<string, string> $labels Accumulator passed by reference.
449 - * @return void
450 - */
451 - private static function collect_field_labels( $blocks, &$labels ) {
452 - if ( ! is_array( $blocks ) ) {
453 - return;
454 - }
455 -
456 - foreach ( $blocks as $block ) {
457 - if ( ! is_array( $block ) ) {
458 - continue;
459 - }
460 -
461 - if ( ! empty( $block['innerBlocks'] ) && is_array( $block['innerBlocks'] ) ) {
462 - self::collect_field_labels( $block['innerBlocks'], $labels );
463 - }
464 -
465 - if ( ! isset( $block['attrs'] ) || ! is_array( $block['attrs'] ) ) {
466 - continue;
467 - }
468 -
469 - $slug = isset( $block['attrs']['slug'] ) && is_string( $block['attrs']['slug'] )
470 - ? sanitize_text_field( $block['attrs']['slug'] )
471 - : '';
472 - if ( '' === $slug || isset( $labels[ $slug ] ) ) {
473 - continue;
474 - }
475 -
476 - if ( isset( $block['attrs']['label'] ) && is_string( $block['attrs']['label'] ) ) {
477 - $labels[ $slug ] = sanitize_text_field( $block['attrs']['label'] );
478 - }
479 - }
480 - }
481 -
482 - /**
483 293 * Process donation-amount block configuration.
484 294 *
485 295 * @param array<mixed> $attrs Block attributes.
486 296 * @return array<string, mixed> Processed donation-amount configuration.
@@ -652,98 +462,8 @@
652 462 return $email_config;
653 463 }
654 464
655 465 /**
656 - * Process dropdown block configuration.
657 - *
658 - * Stores required state, multi-select bounds and the allowed option labels so
659 - * the server can enforce required/min/max selections and reject tampered values.
660 - *
661 - * @param array<mixed> $attrs Block attributes.
662 - * @return array<string, mixed> Processed dropdown block configuration.
663 - * @since 1.1.1
664 - */
665 - private static function process_dropdown_block( $attrs ) {
666 - $dropdown_config = [
667 - 'required' => ! empty( $attrs['required'] ),
668 - 'multi_select' => ! empty( $attrs['multiSelect'] ),
669 - 'min_selection' => isset( $attrs['minSelection'] ) ? absint( Helper::get_string_value( $attrs['minSelection'] ) ) : 0,
670 - 'max_selection' => isset( $attrs['maxSelection'] ) ? absint( Helper::get_string_value( $attrs['maxSelection'] ) ) : 0,
671 - ];
672 -
673 - // Allowed option labels (the submitted value(s) must match one of these).
674 - $options = [];
675 - if ( isset( $attrs['options'] ) && is_array( $attrs['options'] ) ) {
676 - foreach ( $attrs['options'] as $option ) {
677 - if ( is_array( $option ) && isset( $option['label'] ) && '' !== $option['label'] ) {
678 - $options[] = sanitize_text_field( Helper::get_string_value( $option['label'] ) );
679 - }
680 - }
681 - }
682 - $dropdown_config['options'] = $options;
683 -
684 - $error_msg = isset( $attrs['errorMsg'] ) ? sanitize_text_field( Helper::get_string_value( $attrs['errorMsg'] ) ) : '';
685 - if ( '' !== $error_msg ) {
686 - $dropdown_config['error_msg'] = $error_msg;
687 - }
688 -
689 - return $dropdown_config;
690 - }
691 -
692 - /**
693 - * Process phone block configuration.
694 - *
695 - * Stores required state and the optional per-field custom required message for
696 - * server-side enforcement. Phone-number format is validated loosely (see
697 - * validate_field_value) because the submitted value is the E.164-style number
698 - * produced by intl-tel-input.
699 - *
700 - * @param array<mixed> $attrs Block attributes.
701 - * @return array<string, mixed> Processed phone block configuration.
702 - * @since 1.1.1
703 - */
704 - private static function process_phone_block( $attrs ) {
705 - $phone_config = [
706 - 'required' => ! empty( $attrs['required'] ),
707 - ];
708 -
709 - $error_msg = isset( $attrs['errorMsg'] ) ? sanitize_text_field( Helper::get_string_value( $attrs['errorMsg'] ) ) : '';
710 - if ( '' !== $error_msg ) {
711 - $phone_config['error_msg'] = $error_msg;
712 - }
713 -
714 - return $phone_config;
715 - }
716 -
717 - /**
718 - * Process url block configuration.
719 - *
720 - * Stores required state, the optional per-field custom required message and
721 - * the per-field invalid-URL message for server-side enforcement.
722 - *
723 - * @param array<mixed> $attrs Block attributes.
724 - * @return array<string, mixed> Processed url block configuration.
725 - * @since 1.1.1
726 - */
727 - private static function process_url_block( $attrs ) {
728 - $url_config = [
729 - 'required' => ! empty( $attrs['required'] ),
730 - ];
731 -
732 - $error_msg = isset( $attrs['errorMsg'] ) ? sanitize_text_field( Helper::get_string_value( $attrs['errorMsg'] ) ) : '';
733 - if ( '' !== $error_msg ) {
734 - $url_config['error_msg'] = $error_msg;
735 - }
736 -
737 - $invalid_url_msg = isset( $attrs['invalidUrlMsg'] ) ? sanitize_text_field( Helper::get_string_value( $attrs['invalidUrlMsg'] ) ) : '';
738 - if ( '' !== $invalid_url_msg ) {
739 - $url_config['invalid_url_msg'] = $invalid_url_msg;
740 - }
741 -
742 - return $url_config;
743 - }
744 -
745 - /**
746 466 * Get the block types that participate in field validation.
747 467 *
748 468 * Extensions register new validatable field blocks (e.g. phone, address,
749 469 * url) via the filter so their values run through validate_form_data().
@@ -866,35 +586,19 @@
866 586 * @since 1.1.0
867 587 */
868 588 public static function default_validation_messages() {
869 589 $messages = [
870 - 'suredonation_input_block_required_text' => __( 'This field is required.', 'suredonation' ),
871 - 'suredonation_email_block_required_text' => __( 'This field is required.', 'suredonation' ),
872 - 'suredonation_number_block_required_text' => __( 'This field is required.', 'suredonation' ),
873 - 'suredonation_dropdown_block_required_text' => __( 'This field is required.', 'suredonation' ),
874 - 'suredonation_phone_block_required_text' => __( 'This field is required.', 'suredonation' ),
875 - 'suredonation_url_block_required_text' => __( 'This field is required.', 'suredonation' ),
876 - 'suredonation_valid_email' => __( 'Please enter a valid email address.', 'suredonation' ),
877 - 'suredonation_valid_number' => __( 'Please enter a valid number.', 'suredonation' ),
878 - 'suredonation_valid_phone' => __( 'Please enter a valid phone number.', 'suredonation' ),
879 - 'suredonation_valid_url' => __( 'Please enter a valid URL.', 'suredonation' ),
880 - 'suredonation_dropdown_invalid_option' => __( 'Please select a valid option.', 'suredonation' ),
881 - /* translators: %s: minimum number of selections required. */
882 - 'suredonation_dropdown_min_selection' => __( 'Please select at least %s option(s).', 'suredonation' ),
883 - /* translators: %s: maximum number of selections allowed. */
884 - 'suredonation_dropdown_max_selection' => __( 'Please select no more than %s option(s).', 'suredonation' ),
590 + 'suredonation_input_block_required_text' => __( 'This field is required.', 'suredonation' ),
591 + 'suredonation_email_block_required_text' => __( 'This field is required.', 'suredonation' ),
592 + 'suredonation_number_block_required_text' => __( 'This field is required.', 'suredonation' ),
593 + 'suredonation_valid_email' => __( 'Please enter a valid email address.', 'suredonation' ),
594 + 'suredonation_valid_number' => __( 'Please enter a valid number.', 'suredonation' ),
885 595 /* translators: %s: maximum number of characters allowed. */
886 - 'suredonation_input_max_length' => __( 'Maximum length is %s characters.', 'suredonation' ),
887 - /* translators: %s: maximum characters allowed before the @ symbol. */
888 - 'suredonation_email_local_max_length' => __( 'The part before @ may not exceed %s characters.', 'suredonation' ),
889 - /* translators: %s: maximum characters allowed after the @ symbol. */
890 - 'suredonation_email_domain_max_length' => __( 'The part after @ may not exceed %s characters.', 'suredonation' ),
891 - /* translators: %s: maximum total characters allowed in an email address. */
892 - 'suredonation_email_max_length' => __( 'The email address may not exceed %s characters.', 'suredonation' ),
596 + 'suredonation_input_max_length' => __( 'Maximum length is %s characters.', 'suredonation' ),
893 597 /* translators: %s: minimum allowed value. */
894 - 'suredonation_input_min_value' => __( 'Minimum value is %s.', 'suredonation' ),
598 + 'suredonation_input_min_value' => __( 'Minimum value is %s.', 'suredonation' ),
895 599 /* translators: %s: maximum allowed value. */
896 - 'suredonation_input_max_value' => __( 'Maximum value is %s.', 'suredonation' ),
600 + 'suredonation_input_max_value' => __( 'Maximum value is %s.', 'suredonation' ),
897 601 ];
898 602
899 603 /**
900 604 * Filter the default validation messages.
@@ -973,41 +677,10 @@
973 677 return $config['invalid_email_msg'];
974 678 }
975 679 return self::get_validation_message( 'suredonation_valid_email' );
976 680 }
977 -
978 - $email_length_error = self::validate_email_length( $value );
979 - if ( '' !== $email_length_error ) {
980 - return $email_length_error;
981 - }
982 681 break;
983 682
984 - case 'suredonation/url':
985 - // Intentional dotted-host-only restriction (same as SureForms): the
986 - // value must be a domain with a TLD or an IPv4 host, with an optional
987 - // scheme, port, path, query and fragment. Bare single-label hosts
988 - // (localhost, intranet names, typos like "abcdef") are deliberately
989 - // rejected for a public "website" field. The 2048-byte cap
990 - // short-circuits before the regex on overlong, public, unauthenticated
991 - // input so its host-label sub-pattern cannot backtrack (ReDoS guard).
992 - // Kept in sync with the client check in src/form-frontend/validation.js.
993 - if ( strlen( $value ) > 2048 || ! preg_match( '#^(https?://)?((([a-z\d]([a-z\d-]*[a-z\d])*)\.)+[a-z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(/[-a-z\d%_.~+]*)*(\?[;&a-z\d%_.~+=-]*)?(\#[-a-z\d_]*)?$#i', $value ) ) {
994 - if ( ! empty( $config['invalid_url_msg'] ) && is_string( $config['invalid_url_msg'] ) ) {
995 - return $config['invalid_url_msg'];
996 - }
997 - return self::get_validation_message( 'suredonation_valid_url' );
998 - }
999 - break;
1000 -
1001 - case 'suredonation/phone':
1002 - // Loose format check: digits plus the common phone punctuation,
1003 - // 6–20 characters. The strict country-aware check happens client-side
1004 - // via intl-tel-input; this guards against obviously bad submissions.
1005 - if ( ! preg_match( '/^[\d\s()+.\-]{6,20}$/', $value ) ) {
1006 - return self::get_validation_message( 'suredonation_valid_phone' );
1007 - }
1008 - break;
1009 -
1010 683 case 'suredonation/number':
1011 684 if ( ! is_numeric( $value ) ) {
1012 685 return self::get_validation_message( 'suredonation_valid_number' );
1013 686 }
@@ -1022,120 +695,8 @@
1022 695 if ( $validation_max > 0 && $number > $validation_max ) {
1023 696 return str_replace( '%s', self::format_number( $validation_max ), self::get_validation_message( 'suredonation_input_max_value' ) );
1024 697 }
1025 698 break;
1026 -
1027 - case 'suredonation/dropdown':
1028 - $multi_select = ! empty( $config['multi_select'] );
1029 - // Deduplicate before the min/max count check — the server is the
1030 - // trust boundary, and a crafted "A|A|A" must not pass max_selection
1031 - // (or min_selection) with a single distinct value.
1032 - $selections = $multi_select
1033 - ? array_values( array_unique( array_filter( array_map( 'trim', explode( '|', $value ) ), 'strlen' ) ) )
1034 - : [ $value ];
1035 -
1036 - // Reject values that are not among the configured options.
1037 - $allowed = isset( $config['options'] ) && is_array( $config['options'] ) ? $config['options'] : [];
1038 - if ( ! empty( $allowed ) ) {
1039 - foreach ( $selections as $selection ) {
1040 - if ( ! in_array( $selection, $allowed, true ) ) {
1041 - return self::get_validation_message( 'suredonation_dropdown_invalid_option' );
1042 - }
1043 - }
1044 - }
1045 -
1046 - // Min/max apply to multi-select only.
1047 - if ( $multi_select ) {
1048 - $count = count( $selections );
1049 - $min = isset( $config['min_selection'] ) ? (int) $config['min_selection'] : 0;
1050 - $max = isset( $config['max_selection'] ) ? (int) $config['max_selection'] : 0;
1051 -
1052 - if ( $min > 0 && $count < $min ) {
1053 - return str_replace( '%s', number_format_i18n( $min ), self::get_validation_message( 'suredonation_dropdown_min_selection' ) );
1054 - }
1055 - if ( $max > 0 && $count > $max ) {
1056 - return str_replace( '%s', number_format_i18n( $max ), self::get_validation_message( 'suredonation_dropdown_max_selection' ) );
1057 - }
1058 - }
1059 - break;
1060 - }
1061 -
1062 - return '';
1063 - }
1064 -
1065 - /**
1066 - * Enforce RFC 5321 length limits on an email value.
1067 - *
1068 - * The value is split on the last @ so the local part (before @, max 64) and
1069 - * domain part (after @, max 255) are bounded separately. Limits are
1070 - * overridable via the suredonation_email_field_char_limits filter.
1071 - *
1072 - * Public so the payment layer can length-cap the persisted donor_email
1073 - * (which is separate from the validation-only fields[] copy this class
1074 - * normally inspects). A value with no @ — possible when the caller has not
1075 - * already run is_email() — is bounded by the local-part limit so oversized
1076 - * junk still cannot be stored.
1077 - *
1078 - * @param string $value Submitted, trimmed email value.
1079 - * @return string Error message, or '' when the value passes.
1080 - * @since 1.1.1
1081 - */
1082 - public static function validate_email_length( $value ) {
1083 - $defaults = [
1084 - 'local' => 64,
1085 - 'domain' => 255,
1086 - ];
1087 -
1088 - /**
1089 - * Filter the RFC 5321 character limits enforced on the Email field.
1090 - *
1091 - * @since 1.1.1
1092 - * @param array{local:int,domain:int} $limits Max characters for the local and domain parts.
1093 - */
1094 - $limits = apply_filters( 'suredonation_email_field_char_limits', $defaults );
1095 -
1096 - // Fall back to defaults if the filter returns junk or non-positive values.
1097 - $local_limit = is_array( $limits ) && isset( $limits['local'] ) && (int) $limits['local'] > 0 ? (int) $limits['local'] : $defaults['local'];
1098 - $domain_limit = is_array( $limits ) && isset( $limits['domain'] ) && (int) $limits['domain'] > 0 ? (int) $limits['domain'] : $defaults['domain'];
1099 -
1100 - $at = strrpos( $value, '@' );
1101 - if ( false === $at ) {
1102 - // No @ (caller did not run is_email first): bound the whole value by
1103 - // the local-part limit so oversized junk cannot be persisted.
1104 - $length = function_exists( 'mb_strlen' ) ? mb_strlen( $value ) : strlen( $value );
1105 - if ( $length > $local_limit ) {
1106 - return str_replace( '%s', number_format_i18n( $local_limit ), self::get_validation_message( 'suredonation_email_local_max_length' ) );
1107 - }
1108 - // Defensive total cap (see below): only reachable if a filter raised the
1109 - // local limit past 254; keeps a no-@ value within the VARCHAR(255) column.
1110 - if ( $length > 254 ) {
1111 - return str_replace( '%s', number_format_i18n( 254 ), self::get_validation_message( 'suredonation_email_max_length' ) );
1112 - }
1113 - return '';
1114 - }
1115 -
1116 - $local_part = substr( $value, 0, $at );
1117 - $domain_part = substr( $value, $at + 1 );
1118 -
1119 - $local_length = function_exists( 'mb_strlen' ) ? mb_strlen( $local_part ) : strlen( $local_part );
1120 - $domain_length = function_exists( 'mb_strlen' ) ? mb_strlen( $domain_part ) : strlen( $domain_part );
1121 -
1122 - // str_replace (not sprintf) because the message is admin/translator
1123 - // editable; a stray literal % would make sprintf throw on PHP 8.
1124 - if ( $local_length > $local_limit ) {
1125 - return str_replace( '%s', number_format_i18n( $local_limit ), self::get_validation_message( 'suredonation_email_local_max_length' ) );
1126 - }
1127 -
1128 - if ( $domain_length > $domain_limit ) {
1129 - return str_replace( '%s', number_format_i18n( $domain_limit ), self::get_validation_message( 'suredonation_email_domain_max_length' ) );
1130 - }
1131 -
1132 - // RFC 5321 §4.5.3.1.3: the whole address may not exceed 254 chars. This is a
1133 - // fixed cap (independent of the per-part filter) because it also guarantees
1134 - // the value fits the VARCHAR(255) donor_email/email columns, which the
1135 - // per-part caps alone do not — they sum to 320.
1136 - if ( ( $local_length + 1 + $domain_length ) > 254 ) {
1137 - return str_replace( '%s', number_format_i18n( 254 ), self::get_validation_message( 'suredonation_email_max_length' ) );
1138 699 }
1139 700
1140 701 return '';
1141 702 }