Skip to content

Instantly share code, notes, and snippets.

@RadGH
Last active May 31, 2023 08:00
Show Gist options
  • Save RadGH/7580023f71e8d4fbf356 to your computer and use it in GitHub Desktop.
Save RadGH/7580023f71e8d4fbf356 to your computer and use it in GitHub Desktop.
Convert user-inputted phone number to consistently formatted HTML link, with extension support
<?php
// improved version: https://gist.github.com/RadGH/31f16cd4705a2d8076021a9ad528f34f
// ----------
// Example #1: HTML
$phone_number = '555.123.4567';
echo format_phone( $phone_number );
// Output:
// <span class="tel"><a href="tel:+15551234567" class="tel-link">(555) 123-4567</a></span>
// Example #2: Plain text
$phone_number = '555.123.4567';
echo format_phone( $phone_number, false );
// Output:
// (555) 123-4567
// Structure as HTML. Ignore the line breaks. "tel-ext" is only included when an extension is provided.
// <span class="tel">
// <a href="tel:+15411234567" class="tel-link">
// 541-123-4567
// </a>
// <span class="tel-ext">
// <span class="tel-ext-prefix">ext.</span> 999
// <span>ext.</span> 999
// </span>
// </span>
// Output as text:
// 541-123-4567 ext. 999
/**
* Format a phone number as a string or html link.
* Supports most extensions. If provided, the extension will be displayed after the link.
* Supports most 10+ digit phone numbers. Unsupported phone numbers return the original $phone (with no link).
* You can customize the $display_format. The default format is: "1 (555) 123-4567"
*
* @param string $phone US/Canada phone number: 5551234567 or 555-123-4567 or (555) 123-4567 or +15551234567 etc.
* @param bool $html Default true. Returns an HTML "tel:" link.
* @param string $extension_prefix If an extension is provided, the text before the extension.
* @param string $display_format The format used to display the phone number. Does not affect the link's href property.
* @param bool $hide_us_code If true, the country code for US phone numbers (1) will not be displayed. (The display format must contain "%1$d")
*
* @return string
*/
function format_phone( $phone, $html = true, $extension_prefix = 'ext. ', $display_format = '%1$d (%2$d) %3$d-%4$d', $hide_us_code = true ) {
// Regex pattern to split parts of a phone number, and optional extension.
// Supports some country codes, but not all international phone number formats.
// https://regex101.com/r/4coVfU/1
$pattern = '/^\+?([0-9]{0,3})?[^0-9]*([0-9]{3,3})[^0-9]*([0-9]{3,3})[^0-9]*([0-9]{4,4})[^0-9]*([^0-9]*(-|e?xt?\.?)[^0-9\-]*([0-9\-]{1,}))?[^0-9]*$/i';
$found = preg_match($pattern, $phone, $matches);
// If the phone number could not be parsed, keep the original format
if ( ! $found ) return $phone;
// Result from regex match with input: "+1 (541) 123-4567 x999"
// [1] => 1
// [2] => 541
// [3] => 123
// [4] => 4567
// [5] => (ignore)
// [6] => (ignore)
// [7] => 999
$country_code = $matches[1] ?: 1;
$extension = $matches[7] ?? '';
// Should US country code be hidden?
if ( $hide_us_code && $country_code == '1' ) {
$display_format = str_replace( '%1$d', '', $display_format );
$display_format = trim( $display_format );
}
$output = '';
if ( $html ) {
$output .= '<span class="tel">';
}
// Start html link
if ( $html ) {
$tel_href = sprintf('tel:+%d%d%d%d', $country_code, $matches[2], $matches[3], $matches[4]);
$output .= '<a href="'. esc_attr($tel_href) .'" class="tel-link">';
}
// The actual phone number to display
$output .= sprintf('%d (%d) %d-%d', $country_code, $matches[2], $matches[3], $matches[4]);
// End html link
if ( $html ) {
$output .= '</a>';
}
// Add the extension
// Links do not (reliably) support an extension, so it appears after the link.
if ( $extension ) {
if ( $html ) {
$output .= '<span class="ext">';
$output .= '<span class="ext-prefix">'. esc_html($extension_prefix) .'</span>';
$output .= '<span class="ext-value">'. esc_html($extension) .'</span>';
$output .= '</span>';
}else{
$output .= $extension_prefix . $extension;
}
}
if ( $html ) {
$output .= '</span>';
}
return $output;
}
@quinns
Copy link

quinns commented Dec 8, 2017

Thank you for this, it's just what I needed. It should probably be noted that esc_html() is a WordPress-specific function and will return an error if you're using this code outside WordPress.

@ekazda
Copy link

ekazda commented Oct 1, 2020

In case anyone shows up here wanting this function for Javascript...

/***************************
    PROCESS PHONE NUMBERS FOR TEL LINKS
    ***************************/
	window.formatPhone = function formatPhone( string, html = true, classes = null ) {
		// Pattern to collect digits from a phone number, and optional extension
		// Extensions can be identified using: + - x ex ex. ext ext. extension extension.
		// Now (should) function with country codes!
		var matches = string.match(/\+?([0-9]{0,3})?[^0-9]*([0-9]{3,3})[^0-9]*([0-9]{3,3})[^0-9]*([0-9]{4,4})[^0-9]*([^0-9]*(-|e?xt?\.?)[^0-9]*([0-9]{1,}))?[^0-9]*$/i);
		if (matches) {
			// Input: "1 (541) 123-4567 x999"
			// 1 => 1
			// 2 => 541
			// 3 => 123
			// 4 => 4567
			// 7 => 999

			var result = [];
			if (typeof matches[7] !== 'undefined') var ext = matches[7];
			else var ext = '';

			var countrycode = matches[1] ? matches[1] : 1;

			// Output (HTML):
			// <span class="tel"><a href="tel+15411234567" class="tel-link">541-123-4567</a><span class="tel-ext"><span> x</span>999</span></span>
			// Output (Raw):
			// 541-123-4567 x999
			if ( html ) result.push('<span class="tel">');
			if ( html ) result.push(`<a href="tel:+${countrycode}${matches[2]}${matches[3]}${matches[4]}" class="tel-link ${classes}">`);
			result.push(`${countrycode} (${matches[2]}) ${matches[3]}-${matches[4]}`);
			if ( html ) result.push('</a>');

			// Note: tel: links cannot *reliably* include an extension, so it comes after the link.
			if ( ext && html ) result.push(`<span class="tel-ext"><span> x</span>${ext}</span>`);
			else if ( ext )    result.push(` x${ext}`);
			if ( html ) result.push('</span>');

			return result.join('');
		}

		// Pattern not found
		return string; // The phone number isn't valid, but that's ok. Keep the original.
	}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment