Commit 143b1c5ea5d for woocommerce
commit 143b1c5ea5d1c2ff63a90e943783ffe815f8a04d
Author: Néstor Soriano <konamiman@konamiman.com>
Date: Mon Jul 6 17:03:47 2026 +0200
Add woocommerce_format_phone_number filter, decouple from is_phone (#66122)
diff --git a/plugins/woocommerce/changelog/add-woocommerce-format-phone-filter b/plugins/woocommerce/changelog/add-woocommerce-format-phone-filter
new file mode 100644
index 00000000000..8f7a75e762e
--- /dev/null
+++ b/plugins/woocommerce/changelog/add-woocommerce-format-phone-filter
@@ -0,0 +1,4 @@
+Significance: minor
+Type: add
+
+Add a woocommerce_format_phone_number filter and a WC_Validation::is_phone_format() method.
diff --git a/plugins/woocommerce/includes/class-wc-validation.php b/plugins/woocommerce/includes/class-wc-validation.php
index c5cad488180..9ab923e124e 100644
--- a/plugins/woocommerce/includes/class-wc-validation.php
+++ b/plugins/woocommerce/includes/class-wc-validation.php
@@ -23,6 +23,25 @@ class WC_Validation {
return is_email( $email );
}
+ /**
+ * Checks whether a string has the basic shape of a phone number, i.e. it contains
+ * only digits and characters commonly used in phone numbers (whitespace and the
+ * "# _ - + / ( ) ." characters).
+ *
+ * Unlike `is_phone`, this method doesn't apply the `woocommerce_validate_phone` filter,
+ * so its result always reflects the default validation rules regardless of any
+ * merchant-defined validation policy. It's intended for contexts that need a
+ * country-agnostic sanity check, such as phone number formatting.
+ *
+ * @since 11.0.0
+ *
+ * @param string $phone Phone number to check.
+ * @return bool
+ */
+ public static function is_phone_format( $phone ): bool {
+ return '' === trim( preg_replace( '/[\s\#0-9_\-\+\/\(\)\.]/', '', (string) $phone ) );
+ }
+
/**
* Validates a phone number using a regular expression.
*
@@ -31,7 +50,7 @@ class WC_Validation {
* @return bool
*/
public static function is_phone( $phone, $country = null ) {
- $valid = 0 === strlen( trim( preg_replace( '/[\s\#0-9_\-\+\/\(\)\.]/', '', $phone ) ) );
+ $valid = self::is_phone_format( $phone );
/**
* Filters whether a phone number is considered valid.
diff --git a/plugins/woocommerce/includes/wc-formatting-functions.php b/plugins/woocommerce/includes/wc-formatting-functions.php
index 710c184f53b..7488ae85731 100644
--- a/plugins/woocommerce/includes/wc-formatting-functions.php
+++ b/plugins/woocommerce/includes/wc-formatting-functions.php
@@ -1082,12 +1082,23 @@ function wc_normalize_postcode( $postcode ) {
* @return string
*/
function wc_format_phone_number( $phone ) {
- $phone = $phone ?? '';
+ $original = $phone ?? '';
- if ( ! WC_Validation::is_phone( $phone ) ) {
- return '';
- }
- return preg_replace( '/[^0-9\+\-\(\)\s]/', '-', preg_replace( '/[\x00-\x1F\x7F-\xFF]/', '', $phone ) );
+ $is_valid = WC_Validation::is_phone_format( $original );
+ $formatted = $is_valid
+ ? (string) preg_replace( '/[^0-9\+\-\(\)\s]/', '-', preg_replace( '/[\x00-\x1F\x7F-\xFF]/', '', $original ) )
+ : '';
+
+ /**
+ * Filters the formatted phone number.
+ *
+ * @since 11.0.0
+ *
+ * @param string $formatted The formatted phone number, or an empty string if $original isn't a valid phone number.
+ * @param string $original The phone number passed to the function.
+ * @param bool $is_valid Whether $original passed the default phone number validation.
+ */
+ return apply_filters( 'woocommerce_format_phone_number', $formatted, $original, $is_valid );
}
/**
diff --git a/plugins/woocommerce/phpstan-baseline.neon b/plugins/woocommerce/phpstan-baseline.neon
index 808f2739efb..af373fb9e2f 100644
--- a/plugins/woocommerce/phpstan-baseline.neon
+++ b/plugins/woocommerce/phpstan-baseline.neon
@@ -34377,12 +34377,6 @@ parameters:
count: 1
path: includes/wc-formatting-functions.php
- -
- message: '#^Function wc_format_phone_number\(\) should return string but returns string\|null\.$#'
- identifier: return.type
- count: 1
- path: includes/wc-formatting-functions.php
-
-
message: '#^Function wc_get_filename_from_url\(\) should return string but return statement is missing\.$#'
identifier: return.missing
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php b/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
index 1593ff747a1..bd46c90cdf9 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/formatting/functions.php
@@ -855,6 +855,49 @@ class WC_Tests_Formatting_Functions extends WC_Unit_Test_Case {
$this->assertEquals( '', wc_format_phone_number( '1-800-not a phone number' ) );
}
+ /**
+ * Test that wc_format_phone_number() isn't affected by the woocommerce_validate_phone filter.
+ *
+ * The filter is country-aware and used for validation; formatting has no country context,
+ * so it must not let the filter blank otherwise-formattable numbers.
+ */
+ public function test_wc_format_phone_number_is_not_affected_by_validate_phone_filter() {
+ add_filter( 'woocommerce_validate_phone', '__return_false' );
+
+ try {
+ $this->assertEquals( '1-610-385-0000', wc_format_phone_number( '1.610.385.0000' ) );
+ } finally {
+ remove_filter( 'woocommerce_validate_phone', '__return_false' );
+ }
+ }
+
+ /**
+ * Test that wc_format_phone_number() applies the woocommerce_format_phone_number filter.
+ */
+ public function test_wc_format_phone_number_applies_format_filter() {
+ $received = array();
+
+ $callback = function ( $formatted, $original, $is_valid ) use ( &$received ) {
+ $received = array(
+ 'formatted' => $formatted,
+ 'original' => $original,
+ 'is_valid' => $is_valid,
+ );
+ return 'filtered';
+ };
+
+ add_filter( 'woocommerce_format_phone_number', $callback, 10, 3 );
+
+ try {
+ $this->assertEquals( 'filtered', wc_format_phone_number( '1.610.385.0000' ) );
+ $this->assertEquals( '1-610-385-0000', $received['formatted'] );
+ $this->assertEquals( '1.610.385.0000', $received['original'] );
+ $this->assertTrue( $received['is_valid'] );
+ } finally {
+ remove_filter( 'woocommerce_format_phone_number', $callback, 10 );
+ }
+ }
+
/**
* Test wc_sanitize_phone_number().
*
diff --git a/plugins/woocommerce/tests/legacy/unit-tests/util/validation.php b/plugins/woocommerce/tests/legacy/unit-tests/util/validation.php
index f7592d60580..2e066528b97 100644
--- a/plugins/woocommerce/tests/legacy/unit-tests/util/validation.php
+++ b/plugins/woocommerce/tests/legacy/unit-tests/util/validation.php
@@ -230,4 +230,27 @@ class WC_Tests_Validation extends WC_Unit_Test_Case {
$this->assertEquals( '+00-000-00-00-000', WC_Validation::format_phone( '+00.000.00.00.000' ) );
$this->assertEquals( '+00 000 00 00 000', WC_Validation::format_phone( '+00 000 00 00 000' ) );
}
+
+ /**
+ * Test is_phone_format().
+ */
+ public function test_is_phone_format() {
+ $this->assertTrue( WC_Validation::is_phone_format( '+00 000 00 00 000' ) );
+ $this->assertTrue( WC_Validation::is_phone_format( '(000) 00 00 000' ) );
+ $this->assertFalse( WC_Validation::is_phone_format( '+00 aaa dd ee fff' ) );
+ }
+
+ /**
+ * Test that is_phone_format() ignores the woocommerce_validate_phone filter while is_phone() honors it.
+ */
+ public function test_is_phone_format_ignores_validate_phone_filter() {
+ add_filter( 'woocommerce_validate_phone', '__return_false' );
+
+ try {
+ $this->assertTrue( WC_Validation::is_phone_format( '+00 000 00 00 000' ) );
+ $this->assertFalse( WC_Validation::is_phone( '+00 000 00 00 000' ) );
+ } finally {
+ remove_filter( 'woocommerce_validate_phone', '__return_false' );
+ }
+ }
}