public static function fromText(string $text, bool $convertLinks = true) : string
{
// Trimming whitespace (except spaces)
$text = trim($text, "\r\n");
// Two empty lines max
$text = preg_replace("~\n\\s+\n~", "\n\n", $text);
// Special chars
$html = htmlspecialchars($text, ENT_QUOTES, 'utf-8', false);
// Two spaces mean an indent, convert to non-breaking spaces
$html = str_replace(' ', ' ', $html);
// Convert tabs to four non-breaking spaces
$html = str_replace("\t", ' ', $html);
// Paragraph
$html = '<p>' . preg_replace("~\n\n[^\\n]?~", '</p><p>\\0', $html) . '</p>';
$html = str_replace("\n", "<br />\n", $html);
$html = str_ireplace('<p><br />', "<p>\n", $html);
// Citation
preg_match_all('~(?:(^(?:<p>)?\\s*>(?:>|\\s)*)(.*)$)|(?:.+)~im', $html, $matches);
$html = '';
$offset = 0;
for ($i = 0; $i < count($matches[0]); $i++) {
$currentOffset = substr_count($matches[1][$i], '>');
if ($currentOffset > 0) {
if ($currentOffset > $offset) {
$html .= str_repeat('<blockquote type="cite">', $currentOffset - $offset) . '<p>';
$offset = $currentOffset;
} elseif ($currentOffset < $offset) {
$html .= '</p>' . str_repeat('</blockquote>', $offset - $currentOffset) . '<p>';
$offset = $currentOffset;
}
$html .= $matches[2][$i];
} else {
if ($offset > 0) {
$html .= '</p>' . str_repeat('</blockquote>', $offset) . '<p>';
$offset = 0;
}
$html .= $matches[0][$i];
}
}
if ($offset > 0) {
$html .= '</p>' . str_repeat('</blockquote>', $offset);
}
// Removes empty lines that were created during previous processing
$html = preg_replace('~(?:<br />)+</p></blockquote>~i', '</p></blockquote>', $html);
$html = str_ireplace('<p><br /></p>', '', $html);
$html = str_ireplace('<p><br />', '<p>', $html);
// Emails and urls
if ($convertLinks) {
$html = self::linkFromText($html);
}
return $html;
}