Немного о юзабилити обычных ссылок
Казалось бы, что может быть проще, чем вывести ссылку в HTML? Однако если создавать сайт, наполнением которого будут заниматься сами пользователи, обнаруживается множество тонкостей в плане юзабилити, которые нужно учитывать. Во-первых, ссылки, вставленные пользователями, могут быть слишком длинными из-за большого «хвоста» параметров (типичный пример — ссылка на страницы Интернет-магазинов с настроенными фильтрами) и переноситься на несколько строк, и в таких случаях их нужно визуально сокращать. Во-вторых, если в ссылке есть кириллица, она при копировании через буфер обмена преобразуется либо в punycode, либо в url encoding и становится нечитаемой. Пример подобного — ссылки на Wikipedia, глядя на которые, нельзя сказать до перехода, на какую статью они ведут. В-третьих, если ссылка ведёт на корневую страницу сайта, желательно показывать только доменное имя, без http или https в начале и / в конце.
Возникает необходимость сделать обработчик для ссылок без описаний (т.е. вида <a href="URL">URL</a>), который справлялся бы с перечисленными ситуациями. Я для себя написал следующую функцию:
<?php
function link_processor($matches) {
$url_parts = parse_url($matches[3]);
// Blocks unallowed schemes
if (!empty($url_parts['scheme']) && !in_array(strtolower($url_parts['scheme']),array('http','https','ftp','tg','magnet'))) return '*** Invalid or malicious link! ***';
$scheme = empty($url_parts['scheme']) ? '' : $url_parts['scheme'];
if ($matches[3]!==$matches[5]) return $matches[0]; // skipping links with user's description
// processing URL parts
$host = (!empty($url_parts['host'])) ? mb_strtoupper(mb_substr($url_parts['host'],0,1)).mb_substr($url_parts['host'],1) : '';
$path = (empty($url_parts['path']) || $url_parts['path']==='/') ? '' : $path = urldecode($url_parts['path']);
$query = (!empty($url_parts['query'])) ? $url_parts['query'] : '';
$fragment = (!empty($url_parts['fragment'])) ? '#'.urldecode($url_parts['fragment']) : '';
// Wikipedia
if (strtolower(substr($host,-14))==='.wikipedia.org' && mb_substr($path,0,6)==='/wiki/') $new_link = 'Wikipedia: '.str_replace('_',' ',mb_substr($path,6));
// Google
elseif (strtolower(substr($host,-10))==='google.com' && $path==='/search' && substr($query,0,2)==='q=') {
$pos = strpos($query,'&');
if ($pos!==false) $query=substr($query,0,$pos);
$new_link = "Google: ".urldecode(substr($query,2));
}
// Yandex
elseif ((strtolower(substr($host,-5))==='ya.ru' || strtolower(substr($host,-9))==='yandex.ru') && $path==='/search/' && substr($query,0,5)==='text=') {
$pos = strpos($query,'&');
if ($pos!==false) $query=substr($query,0,$pos);
$new_link = "Yandex: ".urldecode(substr($query,5));
}
// Telegram
elseif (strtolower(substr($host,-4))==='t.me' || $scheme=='tg') $new_link = 'Telegram: '.mb_substr($url_parts['path'],1);
// YouTube
elseif (strtolower(substr($host,-11))==='youtube.com' || strtolower(substr($host,-8))==='youtu.be') {
$new_link = 'YouTube: ';
if ($path==='/watchv') {
$pos = strpos($query,'&');
if ($pos!==false) $new_link.=substr($query,0,$pos);
else $new_link.=$query;
}
if ($path==='/watch') {
if (preg_match('|v=([\w\-]+)|',$query,$match)) $new_link.=$match[1];
else $new_link = $matches[5]; // if v parameter not found, fallbacks to initial link description
}
else $new_link.=mb_substr($path,1);
if (preg_match('|t=(\d+)|',$query,$match)) { // if time offset specified
$time = intval($match[1]);
$min = floor($time/60);
$sec = $time % 60;
$new_link.=" ($min:$sec)";
}
}
else {
if ($query && strpos($path,'search')===false && !preg_match('/search=|text=|query=|filter=/i',$query)) $query='?...';
elseif (strlen($query)>32) $query=substr($query,0,32).'…';
$host = idn_to_utf8($host);
if (empty($path)) $host=mb_strtoupper(mb_substr($host,0,1)).mb_substr($host,1);
if (mb_strtolower($host)==='vk.com' || mb_strtolower($host)==='m.vk.com') $host='VK.com';
if (mb_strlen($path)>48) $path = mb_substr($path,0,6).'…'.mb_substr($path,-6);
if (mb_strlen($fragment)>32) $path = mb_substr($path,0,32).'…';
$new_link = $host.$path.$query.$fragment;
}
return str_replace('>'.$matches[5].'<','>'.$new_link.'<',$matches[0]);
}
Эта функция сокращает пути длиннее 48 символов, строки параметров и anchors длиннее 32, скрывает префиксы и делает первую букву имени домена заглавной. Для её корректной работы необходим модуль mbstring.
Кроме перечисленного выше, эта функция умеет также распознавать поисковые ссылки на Яндекс и Google, и ссылки на YouTube, выделяя из них идентификатор ролика и время, с которого тот начнёт воспроизводиться, если оно указано.
Вызывать её нужно с помощью preg_replace_callback (предположим, что текст для обработки лежит в переменной $text):
<?php
$text = preg_replace_callback('|<a\s+([^>]*?)href=(["\']?)(\S+)\2([^>]*)>(.*?)</a\s*>|isu','link_processor',$text);
Посмотреть код в действии с тестовыми примерами можно на onlinephp.io/c/edbb8.