Add unified string utilities to PHP and JS

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
root
2026-01-28 18:37:22 +00:00
parent 6dde3bc4e7
commit ed9bf6f23c
2 changed files with 267 additions and 0 deletions

View File

@@ -1551,3 +1551,122 @@ function validate_short_url(?string $url): bool
return true;
}
/**
* Escape HTML and convert newlines to <br>
*
* Combines htmlspecialchars() and nl2br() for displaying user-generated
* plain text as HTML with preserved line breaks.
*
* @param string|null $str String to process
* @return string HTML-escaped string with line breaks
*/
function htmlbr(?string $str): string
{
if ($str === null || $str === '') {
return '';
}
return nl2br(htmlspecialchars($str, ENT_QUOTES | ENT_HTML5, 'UTF-8'));
}
/**
* Common TLDs for domain detection in linkify functions
*/
define('LINKIFY_TLDS', 'com|org|net|edu|gov|io|co|me|info|biz|us|uk|ca|au|de|fr|es|it|nl|ru|jp|cn|in|br|mx|app|dev|xyz|online|site|tech|store|blog|shop');
/**
* Convert plain text to HTML with URLs converted to hyperlinks
*
* First escapes the text to HTML, then converts URLs (with protocols) and
* domain-like text (with known TLDs) into clickable hyperlinks.
*
* @param string|null $content Plain text content
* @param bool $new_window Whether to add target="_blank" to links
* @return string HTML with clickable links
*/
function linkify_text(?string $content, bool $new_window = true): string
{
if ($content === null || $content === '') {
return '';
}
// First escape HTML
$html = htmlspecialchars($content, ENT_QUOTES | ENT_HTML5, 'UTF-8');
return _linkify_content($html, $new_window);
}
/**
* Convert URLs in HTML to hyperlinks, preserving existing links
*
* Converts URLs (with protocols) and domain-like text (with known TLDs)
* into clickable hyperlinks, but only for text not already inside <a> tags.
*
* @param string|null $content HTML content
* @param bool $new_window Whether to add target="_blank" to links
* @return string HTML with clickable links
*/
function linkify_html(?string $content, bool $new_window = true): string
{
if ($content === null || $content === '') {
return '';
}
// Split content into segments: inside <a> tags and outside
// Pattern matches <a ...>...</a> including nested content
$pattern = '/(<a\s[^>]*>.*?<\/a>)/is';
$segments = preg_split($pattern, $content, -1, PREG_SPLIT_DELIM_CAPTURE);
$result = '';
foreach ($segments as $segment) {
// Check if this segment is an <a> tag (starts with <a and contains </a>)
if (preg_match('/^<a\s/i', $segment)) {
// Already a link, keep as-is
$result .= $segment;
} else {
// Not inside a link, linkify it
$result .= _linkify_content($segment, $new_window);
}
}
return $result;
}
/**
* Internal helper to convert URLs/domains to links in content
*
* @param string $content Content to process (should not contain <a> tags to linkify)
* @param bool $new_window Whether to add target="_blank"
* @return string Content with URLs converted to links
*/
function _linkify_content(string $content, bool $new_window): string
{
$target = $new_window ? ' target="_blank" rel="noopener noreferrer"' : '';
$tlds = LINKIFY_TLDS;
// Pattern for URLs with protocol
$url_pattern = '/(https?:\/\/[^\s<>\[\]()]+)/i';
// Pattern for domain-like text (domain.tld or subdomain.domain.tld with optional path)
$domain_pattern = '/\b((?:[a-zA-Z0-9](?:[a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+(' . $tlds . ')(?:\/[^\s<>\[\]()]*)?)\b/i';
// First, replace URLs with protocol
$content = preg_replace_callback($url_pattern, function ($matches) use ($target) {
$url = $matches[1];
// Clean trailing punctuation that's likely not part of URL
$url = rtrim($url, '.,;:!?)\'\"');
return '<a href="' . $url . '"' . $target . '>' . $url . '</a>';
}, $content);
// Then, replace domain-like text (but not if already inside an href)
$content = preg_replace_callback($domain_pattern, function ($matches) use ($target) {
$domain = $matches[1];
// Clean trailing punctuation
$domain = rtrim($domain, '.,;:!?)\'\"');
// Don't linkify if it looks like it's already in an href attribute
return '<a href="https://' . $domain . '"' . $target . '>' . $domain . '</a>';
}, $content);
return $content;
}