(float) $rules['max'] ) { return self::message( $rules, sprintf( /* translators: %s: maximum value */ __( 'Must be at most %s.', 'chargeforms' ), $rules['max'] ) ); } // the reference product parity: "Digits" — an exact digit-COUNT constraint // (e.g. a 4-digit PIN, a 10-digit phone-as-number field), distinct // from min/max which constrain the numeric VALUE. `validation_msg_digits` // (Global Settings > Validation Messages) already existed — set up // alongside required/email/numeric/min/max/url when that screen was // built — but had zero field-level rule to actually apply it to // until now; every other one of that group already had a working // enforcement path. if ( ! empty( $rules['digits'] ) ) { $digit_count = strlen( preg_replace( '/\D/', '', $val ) ); if ( $digit_count !== (int) $rules['digits'] ) { return self::message( $rules, Settings::get( 'validation_msg_digits', sprintf( /* translators: %d: required digit count */ __( 'Must be exactly %d digits.', 'chargeforms' ), (int) $rules['digits'] ) ) ); } } } return null; } private static function message( array $rules, $default ) { return ! empty( $rules['customMessage'] ) ? (string) $rules['customMessage'] : $default; } /** Same heuristic Form_Validator/Builtin_Operators already apply to conditional-logic regex conditions — a validation-rule regex runs against every real visitor submission just as often, so it needs the same ReDoS guard, not a weaker one just because it lives in a different settings section. */ public static function is_safe_regex_pattern( $pattern ) { $pattern = (string) $pattern; if ( '' === $pattern || strlen( $pattern ) > self::MAX_REGEX_LENGTH ) { return false; } return ! preg_match( '/\([^)]*[+*][^)]*\)\s*[+*{]/', $pattern ); } }