Commit a08bfc792eb for woocommerce

commit a08bfc792ebbda7cc5be51ac46a410278d2f72a6
Author: Parsa Kafi <parselearn@gmail.com>
Date:   Tue Jun 30 16:10:57 2026 +0330

    Add woocommerce_validate_phone filter for customizing of is_phone values (#65817)

diff --git a/plugins/woocommerce/changelog/add-woocommerce_validate_phone-filter-phone-validation b/plugins/woocommerce/changelog/add-woocommerce_validate_phone-filter-phone-validation
new file mode 100644
index 00000000000..99d3864fc1c
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-woocommerce_validate_phone-filter-phone-validation
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add woocommerce_validate_phone filter to allow customizing phone number validation
diff --git a/plugins/woocommerce/includes/class-wc-checkout.php b/plugins/woocommerce/includes/class-wc-checkout.php
index 3fd0f65557c..15c50e5ee71 100644
--- a/plugins/woocommerce/includes/class-wc-checkout.php
+++ b/plugins/woocommerce/includes/class-wc-checkout.php
@@ -917,10 +917,11 @@ class WC_Checkout {
 				}

 				if ( in_array( 'phone', $format, true ) ) {
+					$country = $data[ $fieldset_key . '_country' ] ?? WC()->customer->{"get_{$fieldset_key}_country"}();
 					// This is a safe sanitize to prevent copy-paste issues with invisible chars. Won't ensure validation.
 					$data[ $key ] = wc_remove_non_displayable_chars( $data[ $key ] );

-					if ( $validate_fieldset && '' !== $data[ $key ] && ! WC_Validation::is_phone( $data[ $key ] ) ) {
+					if ( $validate_fieldset && '' !== $data[ $key ] && ! WC_Validation::is_phone( $data[ $key ], $country ) ) {
 						/* translators: %s: phone number */
 						$errors->add( $key . '_validation', sprintf( __( '%s is not a valid phone number.', 'woocommerce' ), '<strong>' . esc_html( $field_label ) . '</strong>' ), array( 'id' => $key ) );
 					}
diff --git a/plugins/woocommerce/includes/class-wc-form-handler.php b/plugins/woocommerce/includes/class-wc-form-handler.php
index 72169baf096..daea46ee9ca 100644
--- a/plugins/woocommerce/includes/class-wc-form-handler.php
+++ b/plugins/woocommerce/includes/class-wc-form-handler.php
@@ -157,7 +157,9 @@ class WC_Form_Handler {
 								}
 								break;
 							case 'phone':
-								if ( '' !== $value && ! WC_Validation::is_phone( $value ) ) {
+								$country = wc_clean( wp_unslash( $_POST[ $address_type . '_country' ] ) );
+								$country = is_string( $country ) ? $country : '';
+								if ( '' !== $value && ! WC_Validation::is_phone( $value, $country ) ) {
 									/* translators: %s: Phone number. */
 									wc_add_notice( sprintf( __( '%s is not a valid phone number.', 'woocommerce' ), '<strong>' . $field['label'] . '</strong>' ), 'error' );
 								}
diff --git a/plugins/woocommerce/includes/class-wc-validation.php b/plugins/woocommerce/includes/class-wc-validation.php
index 576f97ac53a..c5cad488180 100644
--- a/plugins/woocommerce/includes/class-wc-validation.php
+++ b/plugins/woocommerce/includes/class-wc-validation.php
@@ -26,15 +26,23 @@ class WC_Validation {
 	/**
 	 * Validates a phone number using a regular expression.
 	 *
-	 * @param  string $phone Phone number to validate.
+	 * @param  string      $phone   Phone number to validate.
+	 * @param  string|null $country The country code the phone is being validated for, or null if unknown.
 	 * @return bool
 	 */
-	public static function is_phone( $phone ) {
-		if ( 0 < strlen( trim( preg_replace( '/[\s\#0-9_\-\+\/\(\)\.]/', '', $phone ) ) ) ) {
-			return false;
-		}
-
-		return true;
+	public static function is_phone( $phone, $country = null ) {
+		$valid = 0 === strlen( trim( preg_replace( '/[\s\#0-9_\-\+\/\(\)\.]/', '', $phone ) ) );
+
+		/**
+		 * Filters whether a phone number is considered valid.
+		 *
+		 * @since 11.0.0
+		 *
+		 * @param bool        $valid   Whether the phone number passed the default validation.
+		 * @param string      $phone   The phone number being validated.
+		 * @param string|null $country The country code the phone is being validated for, or null if unknown.
+		 */
+		return (bool) apply_filters( 'woocommerce_validate_phone', $valid, $phone, $country );
 	}

 	/**
diff --git a/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractAddressSchema.php b/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractAddressSchema.php
index 419e2c0a8a5..16ae7ce1d58 100644
--- a/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractAddressSchema.php
+++ b/plugins/woocommerce/src/StoreApi/Schemas/V1/AbstractAddressSchema.php
@@ -236,7 +236,7 @@ abstract class AbstractAddressSchema extends AbstractSchema {
 			// This is a safe sanitize to prevent copy-paste issues with invisible chars. Won't ensure validation.
 			$address['phone'] = wc_remove_non_displayable_chars( $address['phone'] );

-			if ( ! \WC_Validation::is_phone( $address['phone'] ) ) {
+			if ( ! \WC_Validation::is_phone( $address['phone'], $address['country'] ?? null ) ) {
 				$errors->add(
 					'invalid_phone',
 					__( 'The provided phone number is not valid', 'woocommerce' )
diff --git a/plugins/woocommerce/tests/php/includes/class-wc-validation-test.php b/plugins/woocommerce/tests/php/includes/class-wc-validation-test.php
index a72350997f5..5f8798a56e1 100644
--- a/plugins/woocommerce/tests/php/includes/class-wc-validation-test.php
+++ b/plugins/woocommerce/tests/php/includes/class-wc-validation-test.php
@@ -9,6 +9,60 @@
  * Class WC_Validation_Test.
  */
 class WC_Validation_Test extends \WC_Unit_Test_Case {
+	/**
+	 * Data provider for test_is_phone().
+	 */
+	public function data_provider_test_is_phone(): array {
+		return array(
+			array( true, '+00 000 00 00 000', null ),
+			array( true, '+00-000-00-00-000', null ),
+			array( true, '(000) 00 00 000', null ),
+			array( true, '+00.000.00.00.000', null ),
+			array( false, '+00 aaa dd ee fff', null ),
+		);
+	}
+
+	/**
+	 * Test phone validation (default behaviour).
+	 *
+	 * @dataProvider data_provider_test_is_phone
+	 *
+	 * @param bool        $expected Expected result.
+	 * @param string      $phone    Phone number to validate.
+	 * @param string|null $country  Country code.
+	 */
+	public function test_is_phone( bool $expected, string $phone, ?string $country ): void {
+		$this->assertSame( $expected, WC_Validation::is_phone( $phone, $country ) );
+	}
+
+	/**
+	 * The woocommerce_validate_phone filter can override the validation result.
+	 */
+	public function test_is_phone_filter_can_override_result(): void {
+		$callback = function ( $valid, $phone, $country ) {
+			if ( 'IR' === $country ) {
+				$phone = str_replace(
+					array( '۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹' ),
+					range( 0, 9 ),
+					$phone
+				);
+
+				return (bool) preg_match( '/^(0|0098|\+98)?(9\d{9}|[1-8]\d{9,10})$/', $phone );
+			}
+
+			return $valid;
+		};
+
+		add_filter( 'woocommerce_validate_phone', $callback, 10, 3 );
+		try {
+			$this->assertTrue( WC_Validation::is_phone( '+۹۸۹۱۵۱۱۱۲۲۳۳', 'IR' ) );
+			$this->assertTrue( WC_Validation::is_phone( '۰۰۹۸۹۱۵۱۱۱۲۲۳۳', 'IR' ) );
+			$this->assertTrue( WC_Validation::is_phone( '۰۹۱۵۱۱۱۲۲۳۳', 'IR' ) );
+		} finally {
+			remove_filter( 'woocommerce_validate_phone', $callback, 10 );
+		}
+	}
+
 	/**
 	 * Data provider for test_is_postcode().
 	 */