HEX
Server: LiteSpeed
System: Linux premium127.web-hosting.com 4.18.0-553.44.1.lve.el8.x86_64 #1 SMP Thu Mar 13 14:29:12 UTC 2025 x86_64
User: luxbsolr (925)
PHP: 8.1.33
Disabled: NONE
Upload Files
File: /home/luxbsolr/cardsord.store/wp-content/plugins/woocommerce-shipping/client/utils/validators.ts
import { __ } from '@wordpress/i18n';
import {
	ACCEPTED_USPS_ORIGIN_COUNTRIES,
	createLocalErrors,
	createValidationResult,
	hasStates,
	US_MILITARY_STATES,
} from 'utils';
import {
	AddressValidationInput,
	CamelCaseType,
	LocationResponse,
	OriginAddress,
} from 'types';
import { isEmail } from '@wordpress/url';

/**
 * Validates required fields for an address, but leaves email and phone validation to other validators.
 */

export const validateRequiredFields =
	( isCrossBorderShipment: boolean ) =>
	< T extends CamelCaseType< LocationResponse > >( {
		values,
		errors,
	}: AddressValidationInput< T > ): AddressValidationInput< T > => {
		const localErrors = createLocalErrors();

		const errorMessages: Partial< {
			[ K in keyof AddressValidationInput< T >[ 'errors' ] ]: string;
		} > = {
			address: __(
				'Please provide a valid address.',
				'woocommerce-shipping'
			),
			city: __( 'Please provide a valid city.', 'woocommerce-shipping' ),
			postcode: __(
				'Please provide a valid postal code.',
				'woocommerce-shipping'
			),
			country: __(
				'Please provide a valid country.',
				'woocommerce-shipping'
			),
		};

		if ( isCrossBorderShipment ) {
			errorMessages.email = __(
				"Email address can't be empty for this address.",
				'woocommerce-shipping'
			);

			errorMessages.phone = __(
				"Phone number can't be empty for this address.",
				'woocommerce-shipping'
			);
		}

		Object.entries( values ).forEach( ( [ key, value ] ) => {
			const errorMessage = errorMessages[ key as keyof OriginAddress ];
			if ( ! value && errorMessage ) {
				localErrors[ key as keyof OriginAddress ] = errorMessage;
			}
		} );

		// Either name or company is required.
		if ( ! values.name && ! values.company ) {
			localErrors.name = localErrors.company = __(
				'Please provide a valid name or company name.',
				'woocommerce-shipping'
			);
		}

		return createValidationResult( values, errors, localErrors );
	};

export const validatePostalCode = ( {
	values,
	errors,
}: AddressValidationInput ) => {
	const localErrors = createLocalErrors();

	const { postcode, country } = values;
	if (
		ACCEPTED_USPS_ORIGIN_COUNTRIES.includes( country ) &&
		! /^\d{5}(?:-\d{4})?$/.test( postcode )
	) {
		localErrors.postcode = __(
			'Invalid postal code format',
			'woocommerce-shipping'
		);
	}
	return createValidationResult( values, errors, localErrors );
};

export const validateCountryAndState = ( {
	values,
	errors,
}: AddressValidationInput ) => {
	const localErrors = createLocalErrors();

	const { country, state } = values;
	if ( ! state && hasStates( country ) ) {
		localErrors.state = __(
			'Please provide a valid state.',
			'woocommerce-shipping'
		);
	}

	return createValidationResult( values, errors, localErrors );
};

/**
 * Validates phone number format with enhanced security and format checking
 * @param param0        Validation input with values and errors
 * @param param0.values Address values containing phone number
 * @param param0.errors Current validation errors
 * @return Updated validation result
 */
export const validatePhone = ( {
	values,
	errors,
}: AddressValidationInput ): AddressValidationInput => {
	const localErrors = createLocalErrors();

	const { phone } = values;

	if ( ! phone ) {
		localErrors.phone = __(
			'Please provide a valid phone number.',
			'woocommerce-shipping'
		);
	} else {
		// Input sanitization: trim whitespace and validate input
		const trimmedPhone = phone.trim();

		// Security enhancement: basic format check to prevent injection
		if ( trimmedPhone.length > 50 ) {
			localErrors.phone = __(
				'Please provide a valid phone number.',
				'woocommerce-shipping'
			);
			return createValidationResult( values, errors, localErrors );
		}

		// Only allow digits, spaces, dashes, parentheses, dots, and plus sign
		const allowedPattern = /^[\d\s\-\(\)\.\+]+$/;
		if ( ! allowedPattern.test( trimmedPhone ) ) {
			localErrors.phone = __(
				'Please provide a valid phone number.',
				'woocommerce-shipping'
			);
			return createValidationResult( values, errors, localErrors );
		}

		// Extract only digits from the phone number
		const digitsOnly = trimmedPhone.replace( /\D/g, '' );

		// Check if we have exactly 10 digits, or 11 digits starting with 1 (US country code)
		const isValid =
			digitsOnly.length === 10 ||
			( digitsOnly.length === 11 && digitsOnly.startsWith( '1' ) );

		if ( ! isValid ) {
			localErrors.phone = __(
				'Please provide a valid phone number.',
				'woocommerce-shipping'
			);
		}
	}

	return createValidationResult( values, errors, localErrors );
};

export const validateDestinationPhone =
	( originCountry: string ) =>
	( { values, errors }: AddressValidationInput ) => {
		const localErrors = createLocalErrors();
		const { phone, country, state } = values;

		// Check if this is a military address that requires special validation
		const isMilitaryAddress =
			originCountry === country &&
			country === 'US' &&
			US_MILITARY_STATES.includes( state );

		// For military addresses, use the specific military validation
		if ( isMilitaryAddress && ! phone ) {
			localErrors.phone = __(
				'A destination address phone number is required for this shipment.',
				'woocommerce-shipping'
			);
		} else if (
			isMilitaryAddress &&
			phone.split( /\D+/g ).join( '' ).replace( /^1/, '' ).length !== 10
		) {
			localErrors.phone = __(
				'Customs forms require a 10-digit phone number. ' +
					'Please edit your phone number so it has at most 10 digits.',
				'woocommerce-shipping'
			);
		} else {
			// For all other destination addresses, use standard phone validation
			return validatePhone( { values, errors } );
		}

		return createValidationResult( values, errors, localErrors );
	};

export const validateEmail = ( {
	values,
	errors,
}: AddressValidationInput ): AddressValidationInput => {
	const localErrors = createLocalErrors();
	if ( values.email && ! isEmail( values.email ) ) {
		localErrors.email = __(
			'Please, enter a valid email address.',
			'woocommerce-shipping'
		);
	}

	return createValidationResult( values, errors, localErrors );
};

export const hasInvalidChar = ( text: string ) => {
	// Convert to string to ensure we're working with a string.
	const textStr = String( text );

	// Array of regex patterns to test.
	const patterns = [
		/:([^\s:]+):/gi, // Emoji pattern like :smile:.
		/\p{Extended_Pictographic}/gu, // Unicode emojis.
	];

	// Loop through each pattern and return true if any match is found.
	for ( const pattern of patterns ) {
		if ( pattern.test( textStr ) ) {
			return true;
		}
	}

	// Return false if no pattern matches.
	return false;
};

export const validateEmojiString = ( {
	values,
	errors,
}: AddressValidationInput ): AddressValidationInput => {
	const localErrors = createLocalErrors();

	const errorMessages: Partial< {
		[ K in keyof AddressValidationInput[ 'errors' ] ]: string;
	} > = {
		address: __(
			'Address contains invalid characters.',
			'woocommerce-shipping'
		),
		city: __( 'City contains invalid characters.', 'woocommerce-shipping' ),
		postcode: __(
			'Postal code contains invalid characters.',
			'woocommerce-shipping'
		),
		state: __(
			'State contains invalid characters.',
			'woocommerce-shipping'
		),
	};

	Object.entries( values ).forEach( ( [ key, value ] ) => {
		const errorMessage =
			errorMessages[ key as keyof AddressValidationInput[ 'errors' ] ];
		if ( hasInvalidChar( value ) && errorMessage ) {
			localErrors[ key as keyof AddressValidationInput[ 'errors' ] ] =
				errorMessage;
		}
	} );

	return createValidationResult( values, errors, localErrors );
};