public static function linkFromText(string $text) : string
{
$patternGenericTld = '(?:tld|aero|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|asia|post|geo)';
$patternTld = '(?-i:' . $patternGenericTld . '|[a-z]{2})';
$patternDomain = '(?:(?:[a-z]|[a-z0-9](?:[\\-a-z0-9]{0,61}[a-z0-9]))[.])*(?:[a-z0-9](?:[\\-a-z0-9]{0,61}[a-z0-9])[.]' . $patternTld . ')';
$pattern8bit = '(?:25[0-5]|2[0-4][0-9]|[0-1]?[0-9]?[0-9])';
$patternIPv4 = '(?:' . $pattern8bit . '(?:[.]' . $pattern8bit . '){3})';
// a:b:c:d:e:f:g:h
$patternIpV6Variant8Hex = '(?:(?:[0-9a-f]{1,4}:){7}[0-9a-f]{1,4})';
// Compressed a::b
$patternIpV6VariantCompressedHex = '(?:(?:(?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4})*)?)::(?:(?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4})*)?))';
// IPv4 mapped to IPv6 a:b:c:d:e:f:w.x.y.z
$patternIpV6VariantHex4Dec = '(?:(?:(?:[0-9a-f]{1,4}:){6})' . $patternIPv4 . ')';
// Compressed IPv4 mapped to IPv6 a::b:w.x.y.z
$patternIpV6VariantCompressedHex4Dec = '(?:(?:(?:[0-9a-f]{1,4}(?::[0-9a-f]{1,4})*)?)::(?:(?:[0-9a-f]{1,4}:)*)' . $patternIPv4 . ')';
$patternIpV6 = '(?:' . $patternIpV6Variant8Hex . '|' . $patternIpV6VariantCompressedHex . '|' . $patternIpV6VariantHex4Dec . '|' . $patternIpV6VariantCompressedHex4Dec . ')';
// mailto:username
$patternEmail = '(?:mailto:)?(?:[\\-\\w!#\\$%&\'*+/=?^`{|}\\~]+(?:[.][\\-\\w!#\\$%&\'*+/=?^`{|}\\~]+)*)';
// @domain.tld
$patternEmail .= '(?:@' . $patternDomain . ')';
// protocol://user:password@
$patternUrl = '(?:(?:http|ftp)s?://(?:[\\S]+(?:[:][\\S]*)?@)?)?';
// domain.tld, IPv4 or IPv6
$patternUrl .= '(?:' . $patternDomain . '|' . $patternIPv4 . '|' . $patternIpV6 . ')';
// :port/path/file.extension
$patternUrl .= '(?::[0-9]+)?(?:(?:/[-\\w\\pL\\pN\\~.:!%]+)*(?:/|[.][a-z0-9]{2,4})?)?';
// ?query#hash
$patternUrl .= '(?:[?][\\]\\[\\-\\w\\pL\\pN.,?!\\~%#@&;:/\'\\=+]*)?(?:#[\\]\\[\\-\\w\\pL\\pN.,?!\\~%@&;:/\'\\=+]*)?';
return preg_replace_callback('~(^|[^\\pL\\pN])(?:(' . $patternEmail . ')|(' . $patternUrl . '))(?=$|\\W)~iu', function ($matches) {
// Url
if (isset($matches[3])) {
$url = $matches[3];
// Remove special chars at the end
if (preg_match('~(([.,:;?!>)\\]}]|(>))+)$~i', $url, $matches2)) {
$punctuation = $matches2[1];
// strlen is necessary because of >
$url = mb_substr($url, 0, -strlen($matches2[1]), 'utf-8');
} else {
$punctuation = '';
}
// Add missing http://
$linkUrl = !preg_match('~^(http|ftp)s?://~i', $url) ? 'http://' . $url : $url;
// Create a link
return $matches[1] . '<a href="' . $linkUrl . '">' . $url . '</a>' . $punctuation;
}
// Emails
if (isset($matches[2])) {
$email = $matches[2];
if (false !== stripos($email, 'mailto:')) {
$email = substr($matches[2], 7);
$protocol = 'mailto:';
} else {
$protocol = '';
}
return $matches[1] . '<a href="mailto:' . $email . '">' . $protocol . $email . '</a>';
}
}, $text);
}