Merge pull request #14090 from annando/bbcode

The BBCode conversion is split into several smaller functions
This commit is contained in:
Hypolite Petovan 2024-04-14 21:59:46 -04:00 committed by GitHub
commit 49a0b0fc3c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 711 additions and 590 deletions

View file

@ -1317,10 +1317,8 @@ class BBCode
Hook::callAll('bbcode', $text); Hook::callAll('bbcode', $text);
$a = DI::app(); $text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid) {
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $uriid) {
$text = self::performWithEscapedTags($text, ['code'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a, $uriid) {
$text = self::performWithEscapedTags($text, ['noparse', 'nobb', 'pre'], function ($text) use ($try_oembed, $simple_html, $for_plaintext, $a, $uriid) {
/* /*
* preg_match_callback function to replace potential Oembed tags with Oembed content * preg_match_callback function to replace potential Oembed tags with Oembed content
* *
@ -1341,6 +1339,117 @@ class BBCode
return $return; return $return;
}; };
// Extract the private images which use data urls since preg has issues with
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
// in after we've done all the regex matching. We cannot use any preg functions to do this.
$extracted = self::extractImagesFromItemBody($text);
$saved_image = $extracted['images'];
// General clean up of the content, for example unneeded blanks and new lines
$text = self::normaliseInput($extracted['body']);
// Now the structural elements are converted
$text = self::convertHeaderToHtml($text, $simple_html);
$text = self::convertStylesToHtml($text, $simple_html);
$text = self::convertListsToHtml($text);
$text = self::convertTablesToHtml($text);
$text = self::convertSpoilersToHtml($text);
$text = self::convertStructuresToHtml($text);
// We add URL without a surrounding URL at this time, since at a earlier stage it would had been too early,
// since the used regular expression won't touch URL inside of BBCode elements, but with the structural ones it should.
// At a later stage we won't be able to exclude certain parts of the code.
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark', 'map', 'oembed'], function ($text) use ($simple_html, $for_plaintext) {
if (!$for_plaintext) {
$text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
}
return self::convertSmileysToHtml($text, $simple_html, $for_plaintext);
});
// Now for some more complex BBCode elements (mostly non standard ones)
$text = self::convertAttachmentsToHtml($text, $simple_html, $try_oembed, $uriid);
$text = self::convertMapsToHtml($text, $simple_html);
$text = self::convertQuotesToHtml($text);
$text = self::convertVideoPlatformsToHtml($text, $try_oembed);
$text = self::convertOEmbedToHtml($text, $uriid);
$text = self::convertEventsToHtml($text, $simple_html, $uriid);
// Some simpler non standard elements
$text = self::convertEmojisToHtml($text, $simple_html);
$text = self::convertCryptToHtml($text);
$text = self::convertIFramesToHtml($text);
$text = self::convertMailToHtml($text);
$text = self::convertAudioVideoToHtml($text, $simple_html, $try_oembed, $try_oembed_callback);
// At last, some standard elements. URL has to go last,
// since some previous conversions use URL elements.
$text = self::convertImagesToHtml($text, $simple_html, $uriid);
$text = self::convertUrlToHtml($text, $simple_html, $for_plaintext, $try_oembed, $try_oembed_callback);
// If the post only consists of an emoji, we display it larger than normal.
if (!$for_plaintext && DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA) && Smilies::isEmojiPost($text)) {
$text = '<span style="font-size: xx-large; line-height: normal;">' . $text . '</span>';
}
// Sanitize the created HTML.
$text = self::cleanupHtml($text);
// This needs to be called after the cleanup, since otherwise some links are invalidated
$text = self::convertSharesToHtml($text, $simple_html, $try_oembed, $uriid);
// Insert the previously extracted embedded image again.
return self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image);
}); // Escaped noparse, nobb, pre
// Remove escaping tags and replace new lines that remain
$text = preg_replace_callback('/\[(noparse|nobb)](.*?)\[\/\1]/ism', function ($match) {
return str_replace("\n", "<br>", $match[2]);
}, $text);
// Additionally, [pre] tags preserve spaces
$text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", function ($match) {
return str_replace([' ', "\n"], ['&nbsp;', "<br>"], htmlentities($match[1], ENT_NOQUOTES, 'UTF-8'));
}, $text);
return $text;
}); // Escaped code
$text = preg_replace_callback(
"#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism",
function ($matches) {
if (strpos($matches[2], "\n") !== false) {
$return = '<pre><code class="language-' . trim($matches[1]) . '">' . htmlentities(trim($matches[2], "\n\r"), ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
} else {
$return = '<code>' . htmlentities($matches[2], ENT_NOQUOTES, 'UTF-8') . '</code>';
}
return $return;
},
$text
);
// Default iframe allowed domains/path
$allowedIframeDomains = DI::config()->get('system', 'no_oembed_rich_content') ? [] : ['www.youtube.com/embed/', 'player.vimeo.com/video/'];
$allowedIframeDomains = array_merge(
$allowedIframeDomains,
DI::config()->get('system', 'allowed_oembed') ?
explode(',', DI::config()->get('system', 'allowed_oembed'))
: []
);
if (strpos($text, '<p>') !== false || strpos($text, '</p>') !== false) {
$text = '<p>' . $text . '</p>';
}
$text = HTML::purify($text, $allowedIframeDomains);
DI::profiler()->stopRecording();
return trim($text);
}
private static function normaliseInput(string $text): string
{
// Remove the abstract element. It is a non visible element. // Remove the abstract element. It is a non visible element.
$text = self::stripAbstract($text); $text = self::stripAbstract($text);
@ -1351,20 +1460,6 @@ class BBCode
$text = preg_replace("#\[(\w*)](\n*)#ism", '$2[$1]', $text); $text = preg_replace("#\[(\w*)](\n*)#ism", '$2[$1]', $text);
$text = preg_replace("#(\n*)\[/(\w*)]#ism", '[/$2]$1', $text); $text = preg_replace("#(\n*)\[/(\w*)]#ism", '[/$2]$1', $text);
// Extract the private images which use data urls since preg has issues with
// large data sizes. Stash them away while we do bbcode conversion, and then put them back
// in after we've done all the regex matching. We cannot use any preg functions to do this.
$extracted = self::extractImagesFromItemBody($text);
$text = $extracted['body'];
$saved_image = $extracted['images'];
// If we find any event code, turn it into an event.
// After we're finished processing the bbcode we'll
// replace all of the event code with a reformatted version.
$ev = Event::fromBBCode($text);
// Replace any html brackets with HTML Entities to prevent executing HTML or script // Replace any html brackets with HTML Entities to prevent executing HTML or script
// Don't use strip_tags here because it breaks [url] search by replacing & with amp // Don't use strip_tags here because it breaks [url] search by replacing & with amp
@ -1375,11 +1470,6 @@ class BBCode
$text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text); $text = preg_replace("/\s?\[share(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1]$2[/share]\n", $text);
$text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text); $text = preg_replace("/\s?\[quote(.*?)\]\s?(.*?)\s?\[\/quote\]\s?/ism", "\n[quote$1]$2[/quote]\n", $text);
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
}
// Remove linefeeds inside of the table elements. See issue #6799 // Remove linefeeds inside of the table elements. See issue #6799
$search = [ $search = [
"\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ", "\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
@ -1428,6 +1518,38 @@ class BBCode
} while ($oldtext != $text); } while ($oldtext != $text);
} }
return $text;
}
private static function convertEventsToHtml(string $text, int $simple_html, int $uriid): string
{
// If we find any event code, turn it into an event.
// After we're finished processing the bbcode we'll
// replace all of the event code with a reformatted version.
$ev = Event::fromBBCode($text);
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
// start which is always required). Allow desc with a missing summary for compatibility.
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
$sub = Event::getHTML($ev, $simple_html, $uriid);
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
}
return $text;
}
private static function convertAttachmentsToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
{
/// @todo Have a closer look at the different html modes /// @todo Have a closer look at the different html modes
// Handle attached links or videos // Handle attached links or videos
if ($simple_html == self::NPF) { if ($simple_html == self::NPF) {
@ -1440,16 +1562,11 @@ class BBCode
$text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid); $text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid);
} }
$nosmile = strpos($text, '[nosmile]') !== false; return $text;
$text = str_replace('[nosmile]', '', $text);
// Replace non graphical smilies for external posts
if (!$nosmile) {
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark'], function ($text) use ($simple_html, $for_plaintext) {
return Smilies::replace($text, ($simple_html != self::INTERNAL) || $for_plaintext);
});
} }
private static function convertMapsToHtml(string $text, int $simple_html): string
{
// leave open the possibility of [map=something] // leave open the possibility of [map=something]
// this is replaced in Item::prepareBody() which has knowledge of the item location // this is replaced in Item::prepareBody() which has knowledge of the item location
if (strpos($text, '[/map]') !== false) { if (strpos($text, '[/map]') !== false) {
@ -1476,6 +1593,11 @@ class BBCode
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text); $text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
} }
return $text;
}
private static function convertHeaderToHtml(string $text, int $simple_html): string
{
// Check for headers // Check for headers
if ($simple_html == self::INTERNAL) { if ($simple_html == self::INTERNAL) {
@ -1504,20 +1626,48 @@ class BBCode
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '</p><h6>$1</h6><p>', $text); $text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '</p><h6>$1</h6><p>', $text);
} }
// Check for paragraph return $text;
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text); }
// Check for bold text private static function convertEmojisToHtml(string $text, int $simple_html): string
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text); {
// Mastodon Emoji (internal tag, do not document for users)
if ($simple_html == self::MASTODON_API) {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
} else {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
}
return $text;
}
// Check for Italics text private static function convertStylesToHtml(string $text, int $simple_html): string
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text); {
// Markdown is designed to pass through HTML elements that it can't handle itself,
// so that the other system would parse the original HTML element.
// But Diaspora has chosen not to do this and doesn't parse HTML elements.
// So we need to make some changes here.
if ($simple_html == BBCode::DIASPORA) {
$elements = ['big', 'small'];
foreach ($elements as $bbcode) {
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '$1', $text);
}
// Check for Underline text $elements = ['del' => 's', 'ins' => 'em', 'kbd' => 'code', 'mark' => 'strong',
$text = preg_replace("(\[u\](.*?)\[\/u\])ism", '<u>$1</u>', $text); 'samp' => 'code', 'u' => 'em', 'var' => 'em'];
foreach ($elements as $bbcode => $html) {
$text = preg_replace("(\[" . $bbcode . "\](.*?)\[\/" . $bbcode . "\])ism", '<' . $html . '>$1</' . $html . '>', $text);
}
}
// Check for strike-through text // Several easy to replace HTML elements
$text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<s>$1</s>', $text); // @todo add the new elements to the documentation by the end of 2024 so that most systems will support them.
$elements = ['b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
's', 'samp', 'small', 'strong', 'sub', 'sup', 'u', 'var'];
foreach ($elements as $element) {
$text = preg_replace("(\[" . $element . "\](.*?)\[\/" . $element . "\])ism", '<' . $element . '>$1</' . $element . '>', $text);
}
$text = preg_replace("(\[big\](.*?)\[\/big\])ism", "<span style=\"font-size: larger;\">$1</span>", $text);
// Check for over-line text // Check for over-line text
$text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text); $text = preg_replace("(\[o\](.*?)\[\/o\])ism", '<span class="overline">$1</span>', $text);
@ -1535,7 +1685,6 @@ class BBCode
$text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text); $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "$2", $text);
} }
// Check for centered text // Check for centered text
$text = preg_replace("(\[center\](.*?)\[\/center\])ism", '<div style="text-align:center;">$1</div>', $text); $text = preg_replace("(\[center\](.*?)\[\/center\])ism", '<div style="text-align:center;">$1</div>', $text);
@ -1545,13 +1694,6 @@ class BBCode
// Check for inline custom CSS // Check for inline custom CSS
$text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism", '<span style="$1">$2</span>', $text); $text = preg_replace("(\[style=(.*?)\](.*?)\[\/style\])ism", '<span style="$1">$2</span>', $text);
// Mastodon Emoji (internal tag, do not document for users)
if ($simple_html == self::MASTODON_API) {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '$2', $text);
} else {
$text = preg_replace("(\[emoji=(.*?)](.*?)\[/emoji])ism", '<span class="mastodon emoji"><img src="$1" alt="$2" title="$2"/></span>', $text);
}
// Check for CSS classes // Check for CSS classes
// @deprecated since 2021.12, left for backward-compatibility reasons // @deprecated since 2021.12, left for backward-compatibility reasons
$text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism", '<span class="$1">$2</span>', $text); $text = preg_replace("(\[class=(.*?)\](.*?)\[\/class\])ism", '<span class="$1">$2</span>', $text);
@ -1559,6 +1701,27 @@ class BBCode
$text = str_replace("\n\n", '</p><p>', $text); $text = str_replace("\n\n", '</p><p>', $text);
$text = str_replace("\n", '<br>', $text); $text = str_replace("\n", '<br>', $text);
// Check for font change text
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
return $text;
}
private static function convertTablesToHtml(string $text): string
{
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text);
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '</p><table>$1</table><p>', $text);
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '</p><table border="1" >$1</table><p>', $text);
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '</p><table border="0" >$1</table><p>', $text);
return $text;
}
private static function convertListsToHtml(string $text): string
{
// handle nested lists // handle nested lists
$endlessloop = 0; $endlessloop = 0;
@ -1582,25 +1745,11 @@ class BBCode
$text = str_replace("[*]", "<li>", $text); $text = str_replace("[*]", "<li>", $text);
$text = str_replace("[li]", "<li>", $text); $text = str_replace("[li]", "<li>", $text);
$text = preg_replace("/\[th\](.*?)\[\/th\]/sm", '<th>$1</th>', $text); return $text;
$text = preg_replace("/\[td\](.*?)\[\/td\]/sm", '<td>$1</td>', $text);
$text = preg_replace("/\[tr\](.*?)\[\/tr\]/sm", '<tr>$1</tr>', $text);
$text = preg_replace("/\[table\](.*?)\[\/table\]/sm", '</p><table>$1</table><p>', $text);
$text = preg_replace("/\[table border=1\](.*?)\[\/table\]/sm", '</p><table border="1" >$1</table><p>', $text);
$text = preg_replace("/\[table border=0\](.*?)\[\/table\]/sm", '</p><table border="0" >$1</table><p>', $text);
$text = str_replace('[hr]', '</p><hr /><p>', $text);
if (!$for_plaintext) {
$text = self::performWithEscapedTags($text, ['url', 'img', 'audio', 'video', 'youtube', 'vimeo', 'share', 'attachment', 'iframe', 'bookmark'], function ($text) {
return preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
});
} }
// Check for font change text private static function convertSpoilersToHtml(string $text): string
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text); {
// Declare the format for [spoiler] layout // Declare the format for [spoiler] layout
$SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>'; $SpoilerLayout = '<details class="spoiler"><summary>' . DI::l10n()->t('Click to open/close') . '</summary>$1</details>';
@ -1623,6 +1772,28 @@ class BBCode
); );
} }
return $text;
}
private static function convertStructuresToHtml(string $text): string
{
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
// Check for paragraph
return str_replace('[hr]', '</p><hr /><p>', $text);
}
private static function convertSmileysToHtml(string $text, int $simple_html, bool $for_plaintext): string
{
if (strpos($text, '[nosmile]') !== false) {
$text = str_replace('[nosmile]', '', $text);
return $text;
}
return Smilies::replace($text, ($simple_html != self::INTERNAL) || $for_plaintext);
}
private static function convertQuotesToHtml(string $text): string
{
// Declare the format for [quote] layout // Declare the format for [quote] layout
$QuoteLayout = '</p><blockquote>$1</blockquote><p>'; $QuoteLayout = '</p><blockquote>$1</blockquote><p>';
@ -1647,7 +1818,11 @@ class BBCode
); );
} }
return $text;
}
private static function convertImagesToHtml(string $text, int $simple_html, int $uriid): string
{
// [img=widthxheight]image source[/img] // [img=widthxheight]image source[/img]
$text = preg_replace_callback( $text = preg_replace_callback(
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", "/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
@ -1700,10 +1875,18 @@ class BBCode
$text = self::convertImages($text, $simple_html, $uriid); $text = self::convertImages($text, $simple_html, $uriid);
return $text;
}
private static function convertCryptToHtml(string $text): string
{
$text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br>', $text); $text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
$text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text); $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' . DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text);
//$text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '<br><img src="' .DI::baseUrl() . '/images/lock_icon.gif" alt="' . DI::l10n()->t('Encrypted content') . '" title="' . '$1' . ' ' . DI::l10n()->t('Encrypted content') . '" /><br>', $text); return $text;
}
private static function convertAudioVideoToHtml(string $text, int $simple_html, bool $try_oembed, \Closure $try_oembed_callback): string
{
// Simplify "video" element // Simplify "video" element
$text = preg_replace('(\[video[^\]]*?\ssrc\s?=\s?([^\s\]]+)[^\]]*?\].*?\[/video\])ism', '[video]$1[/video]', $text); $text = preg_replace('(\[video[^\]]*?\ssrc\s?=\s?([^\s\]]+)[^\]]*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
@ -1732,190 +1915,54 @@ class BBCode
$text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text); $text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
$text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text); $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
$text = preg_replace( $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
"/\[video\](.*?)\[\/video\]/ism",
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
$text
);
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text); $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text);
} else { } else {
$text = preg_replace( $text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
"/\[video\](.*?)\[\/video\]/ism", $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '[url]$1[/url]', $text);
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', }
$text return $text;
);
$text = preg_replace(
"/\[audio\](.*?)\[\/audio\]/ism",
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
$text
);
} }
private static function convertIFramesToHtml(string $text): string
{
// Backward compatibility, [iframe] support has been removed in version 2020.12 // Backward compatibility, [iframe] support has been removed in version 2020.12
$text = preg_replace_callback("/\[(iframe)\](.*?)\[\/iframe\]/ism", [self::class, 'sanitizeLinksCallback'], $text); $text = preg_replace_callback("/\[(iframe)\](.*?)\[\/iframe\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
$text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '<a href="$1">$1</a>', $text); $text = preg_replace("/\[iframe\](.*?)\[\/iframe\]/ism", '[url]$1[/url]', $text);
return $text;
}
private static function convertVideoPlatformsToHtml(string $text, bool $try_oembed): string
{
$a = DI::app();
$text = self::normalizeVideoLinks($text); $text = self::normalizeVideoLinks($text);
// Youtube extensions // Youtube extensions
if ($try_oembed && OEmbed::isAllowedURL('https://www.youtube.com/embed/')) { if ($try_oembed && OEmbed::isAllowedURL('https://www.youtube.com/embed/')) {
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text); $text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://www.youtube.com/embed/$1" frameborder="0" ></iframe>', $text);
} else { } else {
$text = preg_replace( $text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '[url]https://www.youtube.com/watch?v=$1[/url]', $text);
"/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism",
'<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=$1</a>',
$text
);
} }
// Vimeo extensions // Vimeo extensions
if ($try_oembed && OEmbed::isAllowedURL('https://player.vimeo.com/video')) { if ($try_oembed && OEmbed::isAllowedURL('https://player.vimeo.com/video')) {
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text); $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->getThemeInfoValue('videowidth') . '" height="' . $a->getThemeInfoValue('videoheight') . '" src="https://player.vimeo.com/video/$1" frameborder="0" ></iframe>', $text);
} else { } else {
$text = preg_replace( $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '[url]https://vimeo.com/$1[/url]', $text);
"/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", }
'<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>', return $text;
$text
);
} }
private static function convertOEmbedToHtml(string $text, int $uriid): string
{
// oembed tag // oembed tag
$text = OEmbed::BBCode2HTML($text, $uriid); $text = OEmbed::BBCode2HTML($text, $uriid);
// Avoid triple linefeeds through oembed // Avoid triple linefeeds through oembed
$text = str_replace("<br style='clear:left'></span><br><br>", "<br style='clear:left'></span><br>", $text); $text = str_replace("<br style='clear:left'></span><br><br>", "<br style='clear:left'></span><br>", $text);
// If we found an event earlier, strip out all the event code and replace with a reformatted version.
// Replace the event-start section with the entire formatted event. The other bbcode is stripped.
// Summary (e.g. title) is required, earlier revisions only required description (in addition to
// start which is always required). Allow desc with a missing summary for compatibility.
if ((!empty($ev['desc']) || !empty($ev['summary'])) && !empty($ev['start'])) {
$sub = Event::getHTML($ev, $simple_html, $uriid);
$text = preg_replace("/\[event\-summary\](.*?)\[\/event\-summary\]/ism", '', $text);
$text = preg_replace("/\[event\-description\](.*?)\[\/event\-description\]/ism", '', $text);
$text = preg_replace("/\[event\-start\](.*?)\[\/event\-start\]/ism", $sub, $text);
$text = preg_replace("/\[event\-finish\](.*?)\[\/event\-finish\]/ism", '', $text);
$text = preg_replace("/\[event\-location\](.*?)\[\/event\-location\]/ism", '', $text);
$text = preg_replace("/\[event\-id\](.*?)\[\/event\-id\]/ism", '', $text);
}
if (!$for_plaintext && DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA) && Smilies::isEmojiPost($text)) {
$text = '<span style="font-size: xx-large; line-height: normal;">' . $text . '</span>';
}
$text = self::convertUrlToHtml($text, $simple_html, $for_plaintext, $try_oembed, $try_oembed_callback);
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
$text = preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
// Perform MAIL Search
$text = preg_replace_callback("/\[(mail)\](.*?)\[\/mail\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
/// @todo What is the meaning of these lines?
$text = preg_replace('/\[\&amp\;([#a-z0-9]+)\;\]/', '&$1;', $text);
$text = preg_replace('/\&\#039\;/', '\'', $text);
// Currently deactivated, it made problems with " inside of alt texts.
//$text = preg_replace('/\&quot\;/', '"', $text);
// fix any escaped ampersands that may have been converted into links
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
array_walk($allowed_src_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$text = preg_replace(
'#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">',
$text
);
// sanitize href attributes (only allowlisted protocols URLs)
// default value for backward compatibility
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
// Always allowed protocol even if config isn't set or not including it
$allowed_link_protocols[] = '//';
$allowed_link_protocols[] = 'http://';
$allowed_link_protocols[] = 'https://';
$allowed_link_protocols[] = 'contact/redir/';
array_walk($allowed_link_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
// Shared content
$text = self::convertShare(
$text,
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
},
$uriid
);
$text = self::interpolateSavedImagesIntoItemBody($uriid, $text, $saved_image);
return $text; return $text;
}); // Escaped noparse, nobb, pre
// Remove escaping tags and replace new lines that remain
$text = preg_replace_callback('/\[(noparse|nobb)](.*?)\[\/\1]/ism', function ($match) {
return str_replace("\n", "<br>", $match[2]);
}, $text);
// Additionally, [pre] tags preserve spaces
$text = preg_replace_callback("/\[pre\](.*?)\[\/pre\]/ism", function ($match) {
return str_replace([' ', "\n"], ['&nbsp;', "<br>"], htmlentities($match[1], ENT_NOQUOTES, 'UTF-8'));
}, $text);
return $text;
}); // Escaped code
$text = preg_replace_callback(
"#\[code(?:=([^\]]*))?\](.*?)\[\/code\]#ism",
function ($matches) {
if (strpos($matches[2], "\n") !== false) {
$return = '<pre><code class="language-' . trim($matches[1]) . '">' . htmlentities(trim($matches[2], "\n\r"), ENT_NOQUOTES, 'UTF-8') . '</code></pre>';
} else {
$return = '<code>' . htmlentities($matches[2], ENT_NOQUOTES, 'UTF-8') . '</code>';
}
return $return;
},
$text
);
// Default iframe allowed domains/path
$allowedIframeDomains = DI::config()->get('system', 'no_oembed_rich_content') ? [] : ['www.youtube.com/embed/', 'player.vimeo.com/video/'];
$allowedIframeDomains = array_merge(
$allowedIframeDomains,
DI::config()->get('system', 'allowed_oembed') ?
explode(',', DI::config()->get('system', 'allowed_oembed'))
: []
);
if (strpos($text, '<p>') !== false || strpos($text, '</p>') !== false) {
$text = '<p>' . $text . '</p>';
}
$text = HTML::purify($text, $allowedIframeDomains);
DI::profiler()->stopRecording();
return trim($text);
} }
private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext, bool $try_oembed, \Closure $try_oembed_callback): string private static function convertUrlToHtml(string $text, int $simple_html, bool $for_plaintext, bool $try_oembed, \Closure $try_oembed_callback): string
@ -2046,7 +2093,10 @@ class BBCode
$text = preg_replace("/\[url\=(" . preg_quote(DI::baseUrl(), '/') . ".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text); $text = preg_replace("/\[url\=(" . preg_quote(DI::baseUrl(), '/') . ".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
$text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text); $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
return $text;
// we may need to restrict this further if it picks up too many strays
// link acct:user@host to a webfinger profile redirector
return preg_replace('/acct:([^@]+)@((?!\-)(?:[a-zA-Z\d\-]{0,62}[a-zA-Z\d]\.){1,126}(?!\d+)[a-zA-Z\d]{1,63})/', '<a href="' . DI::baseUrl() . '/acctlink?addr=$1@$2" target="extlink">acct:$1@$2</a>', $text);
} }
private static function escapeUrl(string $url): string private static function escapeUrl(string $url): string
@ -2088,6 +2138,78 @@ class BBCode
return $text; return $text;
} }
private static function convertMailToHtml(string $text): string
{
$text = preg_replace_callback("/\[(mail)\](.*?)\[\/mail\]/ism", [self::class, 'sanitizeLinksCallback'], $text);
$text = preg_replace("/\[mail\](.*?)\[\/mail\]/", '<a href="mailto:$1">$1</a>', $text);
$text = preg_replace("/\[mail\=(.*?)\](.*?)\[\/mail\]/", '<a href="mailto:$1">$2</a>', $text);
return $text;
}
private static function convertSharesToHtml(string $text, int $simple_html, bool $try_oembed, int $uriid): string
{
// Shared content
// when the content is meant exporting to other systems then remove the avatar picture since this doesn't really look good on these systems
if (!$try_oembed) {
$text = preg_replace("/\[share(.*?)avatar\s?=\s?'.*?'\s?(.*?)\]\s?(.*?)\s?\[\/share\]\s?/ism", "\n[share$1$2]$3[/share]", $text);
}
$text = self::convertShare(
$text,
function (array $attributes, array $author_contact, $content, $is_quote_share) use ($simple_html) {
return self::convertShareCallback($attributes, $author_contact, $content, $is_quote_share, $simple_html);
},
$uriid
);
return $text;
}
private static function cleanupHtml(string $text): string
{
/// @todo What is the meaning of these lines?
$text = preg_replace('/\[\&amp\;([#a-z0-9]+)\;\]/', '&$1;', $text);
$text = preg_replace('/\&\#039\;/', '\'', $text);
// Currently deactivated, it made problems with " inside of alt texts.
//$text = preg_replace('/\&quot\;/', '"', $text);
// fix any escaped ampersands that may have been converted into links
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&amp\;(.*?)\>/ism', '<$1$2=$3&$4>', $text);
// sanitizes src attributes (http and redir URLs for displaying in a web page, cid used for inline images in emails)
$allowed_src_protocols = ['//', 'http://', 'https://', 'contact/redir/', 'cid:'];
array_walk($allowed_src_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$text = preg_replace(
'#<([^>]*?)(src)="(?!' . implode('|', $allowed_src_protocols) . ')(.*?)"(.*?)>#ism',
'<$1$2=""$4 data-original-src="$3" class="invalid-src" title="' . DI::l10n()->t('Invalid source protocol') . '">',
$text
);
// sanitize href attributes (only allowlisted protocols URLs)
// default value for backward compatibility
$allowed_link_protocols = DI::config()->get('system', 'allowed_link_protocols', []);
// Always allowed protocol even if config isn't set or not including it
$allowed_link_protocols[] = '//';
$allowed_link_protocols[] = 'http://';
$allowed_link_protocols[] = 'https://';
$allowed_link_protocols[] = 'contact/redir/';
array_walk($allowed_link_protocols, function (&$value) {
$value = preg_quote($value, '#');
});
$regex = '#<([^>]*?)(href)="(?!' . implode('|', $allowed_link_protocols) . ')(.*?)"(.*?)>#ism';
$text = preg_replace($regex, '<$1$2="javascript:void(0)"$4 data-original-href="$3" class="invalid-href" title="' . DI::l10n()->t('Invalid link protocol') . '">', $text);
return $text;
}
/** /**
* Strips the "abstract" tag from the provided text * Strips the "abstract" tag from the provided text
* *

View file

@ -253,13 +253,12 @@ class HTML
self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]'); self::tagToBBCode($doc, 'span', ['class' => 'type-link'], '[class=type-link]', '[/class]');
self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]'); self::tagToBBCode($doc, 'span', ['class' => 'type-video'], '[class=type-video]', '[/class]');
self::tagToBBCode($doc, 'strong', [], '[b]', '[/b]'); $elements = ['b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]'); 's', 'samp', 'strong', 'sub', 'sup', 'u', 'var'];
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]'); foreach ($elements as $element) {
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]'); self::tagToBBCode($doc, $element, [], '[' . $element . ']', '[/' . $element . ']');
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]'); }
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]'); self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]"); self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");

View file

@ -67,7 +67,7 @@ class DirectMessageTest extends FixtureTest
->toArray(); ->toArray();
self::assertEquals('item_title', $directMessage['title']); self::assertEquals('item_title', $directMessage['title']);
self::assertEquals('<strong>item_body</strong>', $directMessage['text']); self::assertEquals('<b>item_body</b>', $directMessage['text']);
} }
/** /**

View file

@ -8,7 +8,7 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: 2024.06-dev\n" "Project-Id-Version: 2024.06-dev\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-04-07 16:31+0000\n" "POT-Creation-Date: 2024-04-13 11:02+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n" "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n" "Language-Team: LANGUAGE <LL@li.org>\n"
@ -957,7 +957,7 @@ msgstr ""
msgid "Enter user nickname: " msgid "Enter user nickname: "
msgstr "" msgstr ""
#: src/Console/User.php:182 src/Model/User.php:820 #: src/Console/User.php:182 src/Model/User.php:822
#: src/Module/Api/Twitter/ContactEndpoint.php:74 #: src/Module/Api/Twitter/ContactEndpoint.php:74
#: src/Module/Moderation/Users/Active.php:71 #: src/Module/Moderation/Users/Active.php:71
#: src/Module/Moderation/Users/Blocked.php:71 #: src/Module/Moderation/Users/Blocked.php:71
@ -1381,7 +1381,7 @@ msgstr ""
msgid "Public post" msgid "Public post"
msgstr "" msgstr ""
#: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:130 #: src/Content/Conversation.php:424 src/Content/Widget/VCard.php:131
#: src/Model/Profile.php:482 src/Module/Admin/Logs/View.php:92 #: src/Model/Profile.php:482 src/Module/Admin/Logs/View.php:92
#: src/Module/Post/Edit.php:181 #: src/Module/Post/Edit.php:181
msgid "Message" msgid "Message"
@ -1733,7 +1733,7 @@ msgstr ""
#: src/Content/Feature.php:130 src/Content/GroupManager.php:147 #: src/Content/Feature.php:130 src/Content/GroupManager.php:147
#: src/Content/Nav.php:278 src/Content/Text/HTML.php:881 #: src/Content/Nav.php:278 src/Content/Text/HTML.php:881
#: src/Content/Widget.php:538 src/Model/User.php:1386 #: src/Content/Widget.php:538 src/Model/User.php:1388
msgid "Groups" msgid "Groups"
msgstr "" msgstr ""
@ -2266,39 +2266,39 @@ msgstr ""
msgid "last" msgid "last"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:767 src/Content/Text/BBCode.php:1764 #: src/Content/Text/BBCode.php:701 src/Content/Text/BBCode.php:1843
#: src/Content/Text/BBCode.php:1765 #: src/Content/Text/BBCode.php:1844
msgid "Image/photo" msgid "Image/photo"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:985 #: src/Content/Text/BBCode.php:919
#, php-format #, php-format
msgid "" msgid ""
"<a href=\"%1$s\" target=\"_blank\" rel=\"noopener noreferrer\">%2$s</a> %3$s" "<a href=\"%1$s\" target=\"_blank\" rel=\"noopener noreferrer\">%2$s</a> %3$s"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1010 src/Model/Item.php:4014 #: src/Content/Text/BBCode.php:944 src/Model/Item.php:4021
#: src/Model/Item.php:4020 src/Model/Item.php:4021 #: src/Model/Item.php:4027 src/Model/Item.php:4028
msgid "Link to source" msgid "Link to source"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1671 src/Content/Text/HTML.php:905 #: src/Content/Text/BBCode.php:1724 src/Content/Text/HTML.php:905
msgid "Click to open/close" msgid "Click to open/close"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1704 #: src/Content/Text/BBCode.php:1779
msgid "$1 wrote:" msgid "$1 wrote:"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:1769 src/Content/Text/BBCode.php:1770 #: src/Content/Text/BBCode.php:1853 src/Content/Text/BBCode.php:1854
msgid "Encrypted content" msgid "Encrypted content"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:2033 #: src/Content/Text/BBCode.php:2159
msgid "Invalid source protocol" msgid "Invalid source protocol"
msgstr "" msgstr ""
#: src/Content/Text/BBCode.php:2052 #: src/Content/Text/BBCode.php:2178
msgid "Invalid link protocol" msgid "Invalid link protocol"
msgstr "" msgstr ""
@ -2310,7 +2310,7 @@ msgstr ""
msgid "The end" msgid "The end"
msgstr "" msgstr ""
#: src/Content/Text/HTML.php:860 src/Content/Widget/VCard.php:126 #: src/Content/Text/HTML.php:860 src/Content/Widget/VCard.php:127
#: src/Model/Profile.php:476 src/Module/Contact/Profile.php:477 #: src/Model/Profile.php:476 src/Module/Contact/Profile.php:477
msgid "Follow" msgid "Follow"
msgstr "" msgstr ""
@ -2483,27 +2483,27 @@ msgstr[1] ""
msgid "More Trending Tags" msgid "More Trending Tags"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:104 src/Model/Contact.php:1205 #: src/Content/Widget/VCard.php:105 src/Model/Contact.php:1205
#: src/Model/Profile.php:461 #: src/Model/Profile.php:461
msgid "Post to group" msgid "Post to group"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:109 src/Model/Contact.php:1209 #: src/Content/Widget/VCard.php:110 src/Model/Contact.php:1209
#: src/Model/Profile.php:465 src/Module/Moderation/Item/Source.php:85 #: src/Model/Profile.php:465 src/Module/Moderation/Item/Source.php:85
msgid "Mention" msgid "Mention"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:119 src/Model/Profile.php:380 #: src/Content/Widget/VCard.php:120 src/Model/Profile.php:380
#: src/Module/Contact/Profile.php:413 src/Module/Profile/Profile.php:199 #: src/Module/Contact/Profile.php:413 src/Module/Profile/Profile.php:199
msgid "XMPP:" msgid "XMPP:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:120 src/Model/Profile.php:381 #: src/Content/Widget/VCard.php:121 src/Model/Profile.php:381
#: src/Module/Contact/Profile.php:415 src/Module/Profile/Profile.php:203 #: src/Module/Contact/Profile.php:415 src/Module/Profile/Profile.php:203
msgid "Matrix:" msgid "Matrix:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:121 src/Model/Event.php:82 #: src/Content/Widget/VCard.php:122 src/Model/Event.php:82
#: src/Model/Event.php:109 src/Model/Event.php:471 src/Model/Event.php:960 #: src/Model/Event.php:109 src/Model/Event.php:471 src/Model/Event.php:960
#: src/Model/Profile.php:375 src/Module/Contact/Profile.php:411 #: src/Model/Profile.php:375 src/Module/Contact/Profile.php:411
#: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187 #: src/Module/Directory.php:147 src/Module/Notifications/Introductions.php:187
@ -2511,18 +2511,18 @@ msgstr ""
msgid "Location:" msgid "Location:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:124 src/Model/Profile.php:489 #: src/Content/Widget/VCard.php:125 src/Model/Profile.php:489
#: src/Module/Notifications/Introductions.php:201 #: src/Module/Notifications/Introductions.php:201
msgid "Network:" msgid "Network:"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:128 src/Model/Contact.php:1237 #: src/Content/Widget/VCard.php:129 src/Model/Contact.php:1237
#: src/Model/Contact.php:1249 src/Model/Profile.php:478 #: src/Model/Contact.php:1249 src/Model/Profile.php:478
#: src/Module/Contact/Profile.php:469 #: src/Module/Contact/Profile.php:469
msgid "Unfollow" msgid "Unfollow"
msgstr "" msgstr ""
#: src/Content/Widget/VCard.php:134 src/Model/Contact.php:1207 #: src/Content/Widget/VCard.php:135 src/Model/Contact.php:1207
#: src/Model/Profile.php:463 #: src/Model/Profile.php:463
msgid "View group" msgid "View group"
msgstr "" msgstr ""
@ -3568,7 +3568,7 @@ msgstr[1] ""
msgid "Poll end: %s" msgid "Poll end: %s"
msgstr "" msgstr ""
#: src/Model/Item.php:3997 src/Model/Item.php:3998 #: src/Model/Item.php:4004 src/Model/Item.php:4005
msgid "View on separate page" msgid "View on separate page"
msgstr "" msgstr ""
@ -3726,145 +3726,145 @@ msgstr ""
msgid "Contact information and Social Networks" msgid "Contact information and Social Networks"
msgstr "" msgstr ""
#: src/Model/User.php:229 src/Model/User.php:1299 #: src/Model/User.php:231 src/Model/User.php:1301
msgid "SERIOUS ERROR: Generation of security keys failed." msgid "SERIOUS ERROR: Generation of security keys failed."
msgstr "" msgstr ""
#: src/Model/User.php:729 src/Model/User.php:762 #: src/Model/User.php:731 src/Model/User.php:764
msgid "Login failed" msgid "Login failed"
msgstr "" msgstr ""
#: src/Model/User.php:794 #: src/Model/User.php:796
msgid "Not enough information to authenticate" msgid "Not enough information to authenticate"
msgstr "" msgstr ""
#: src/Model/User.php:919 #: src/Model/User.php:921
msgid "Password can't be empty" msgid "Password can't be empty"
msgstr "" msgstr ""
#: src/Model/User.php:961 #: src/Model/User.php:963
msgid "Empty passwords are not allowed." msgid "Empty passwords are not allowed."
msgstr "" msgstr ""
#: src/Model/User.php:965 #: src/Model/User.php:967
msgid "" msgid ""
"The new password has been exposed in a public data dump, please choose " "The new password has been exposed in a public data dump, please choose "
"another." "another."
msgstr "" msgstr ""
#: src/Model/User.php:969 #: src/Model/User.php:971
msgid "The password length is limited to 72 characters." msgid "The password length is limited to 72 characters."
msgstr "" msgstr ""
#: src/Model/User.php:973 #: src/Model/User.php:975
msgid "The password can't contain white spaces nor accentuated letters" msgid "The password can't contain white spaces nor accentuated letters"
msgstr "" msgstr ""
#: src/Model/User.php:1182 #: src/Model/User.php:1184
msgid "Passwords do not match. Password unchanged." msgid "Passwords do not match. Password unchanged."
msgstr "" msgstr ""
#: src/Model/User.php:1189 #: src/Model/User.php:1191
msgid "An invitation is required." msgid "An invitation is required."
msgstr "" msgstr ""
#: src/Model/User.php:1193 #: src/Model/User.php:1195
msgid "Invitation could not be verified." msgid "Invitation could not be verified."
msgstr "" msgstr ""
#: src/Model/User.php:1201 #: src/Model/User.php:1203
msgid "Invalid OpenID url" msgid "Invalid OpenID url"
msgstr "" msgstr ""
#: src/Model/User.php:1214 src/Security/Authentication.php:230 #: src/Model/User.php:1216 src/Security/Authentication.php:230
msgid "" msgid ""
"We encountered a problem while logging in with the OpenID you provided. " "We encountered a problem while logging in with the OpenID you provided. "
"Please check the correct spelling of the ID." "Please check the correct spelling of the ID."
msgstr "" msgstr ""
#: src/Model/User.php:1214 src/Security/Authentication.php:230 #: src/Model/User.php:1216 src/Security/Authentication.php:230
msgid "The error message was:" msgid "The error message was:"
msgstr "" msgstr ""
#: src/Model/User.php:1220 #: src/Model/User.php:1222
msgid "Please enter the required information." msgid "Please enter the required information."
msgstr "" msgstr ""
#: src/Model/User.php:1234 #: src/Model/User.php:1236
#, php-format #, php-format
msgid "" msgid ""
"system.username_min_length (%s) and system.username_max_length (%s) are " "system.username_min_length (%s) and system.username_max_length (%s) are "
"excluding each other, swapping values." "excluding each other, swapping values."
msgstr "" msgstr ""
#: src/Model/User.php:1241 #: src/Model/User.php:1243
#, php-format #, php-format
msgid "Username should be at least %s character." msgid "Username should be at least %s character."
msgid_plural "Username should be at least %s characters." msgid_plural "Username should be at least %s characters."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/Model/User.php:1245 #: src/Model/User.php:1247
#, php-format #, php-format
msgid "Username should be at most %s character." msgid "Username should be at most %s character."
msgid_plural "Username should be at most %s characters." msgid_plural "Username should be at most %s characters."
msgstr[0] "" msgstr[0] ""
msgstr[1] "" msgstr[1] ""
#: src/Model/User.php:1253 #: src/Model/User.php:1255
msgid "That doesn't appear to be your full (First Last) name." msgid "That doesn't appear to be your full (First Last) name."
msgstr "" msgstr ""
#: src/Model/User.php:1258 #: src/Model/User.php:1260
msgid "Your email domain is not among those allowed on this site." msgid "Your email domain is not among those allowed on this site."
msgstr "" msgstr ""
#: src/Model/User.php:1262 #: src/Model/User.php:1264
msgid "Not a valid email address." msgid "Not a valid email address."
msgstr "" msgstr ""
#: src/Model/User.php:1265 #: src/Model/User.php:1267
msgid "The nickname was blocked from registration by the nodes admin." msgid "The nickname was blocked from registration by the nodes admin."
msgstr "" msgstr ""
#: src/Model/User.php:1269 src/Model/User.php:1275 #: src/Model/User.php:1271 src/Model/User.php:1277
msgid "Cannot use that email." msgid "Cannot use that email."
msgstr "" msgstr ""
#: src/Model/User.php:1281 #: src/Model/User.php:1283
msgid "Your nickname can only contain a-z, 0-9 and _." msgid "Your nickname can only contain a-z, 0-9 and _."
msgstr "" msgstr ""
#: src/Model/User.php:1289 src/Model/User.php:1346 #: src/Model/User.php:1291 src/Model/User.php:1348
msgid "Nickname is already registered. Please choose another." msgid "Nickname is already registered. Please choose another."
msgstr "" msgstr ""
#: src/Model/User.php:1333 src/Model/User.php:1337 #: src/Model/User.php:1335 src/Model/User.php:1339
msgid "An error occurred during registration. Please try again." msgid "An error occurred during registration. Please try again."
msgstr "" msgstr ""
#: src/Model/User.php:1360 #: src/Model/User.php:1362
msgid "An error occurred creating your default profile. Please try again." msgid "An error occurred creating your default profile. Please try again."
msgstr "" msgstr ""
#: src/Model/User.php:1367 #: src/Model/User.php:1369
msgid "An error occurred creating your self contact. Please try again." msgid "An error occurred creating your self contact. Please try again."
msgstr "" msgstr ""
#: src/Model/User.php:1372 #: src/Model/User.php:1374
msgid "Friends" msgid "Friends"
msgstr "" msgstr ""
#: src/Model/User.php:1376 #: src/Model/User.php:1378
msgid "" msgid ""
"An error occurred creating your default contact circle. Please try again." "An error occurred creating your default contact circle. Please try again."
msgstr "" msgstr ""
#: src/Model/User.php:1418 #: src/Model/User.php:1420
msgid "Profile Photos" msgid "Profile Photos"
msgstr "" msgstr ""
#: src/Model/User.php:1600 #: src/Model/User.php:1602
#, php-format #, php-format
msgid "" msgid ""
"\n" "\n"
@ -3872,7 +3872,7 @@ msgid ""
"\t\t\tthe administrator of %2$s has set up an account for you." "\t\t\tthe administrator of %2$s has set up an account for you."
msgstr "" msgstr ""
#: src/Model/User.php:1603 #: src/Model/User.php:1605
#, php-format #, php-format
msgid "" msgid ""
"\n" "\n"
@ -3908,12 +3908,12 @@ msgid ""
"\t\tThank you and welcome to %4$s." "\t\tThank you and welcome to %4$s."
msgstr "" msgstr ""
#: src/Model/User.php:1635 src/Model/User.php:1741 #: src/Model/User.php:1637 src/Model/User.php:1743
#, php-format #, php-format
msgid "Registration details for %s" msgid "Registration details for %s"
msgstr "" msgstr ""
#: src/Model/User.php:1655 #: src/Model/User.php:1657
#, php-format #, php-format
msgid "" msgid ""
"\n" "\n"
@ -3929,12 +3929,12 @@ msgid ""
"\t\t" "\t\t"
msgstr "" msgstr ""
#: src/Model/User.php:1674 #: src/Model/User.php:1676
#, php-format #, php-format
msgid "Registration at %s" msgid "Registration at %s"
msgstr "" msgstr ""
#: src/Model/User.php:1698 #: src/Model/User.php:1700
#, php-format #, php-format
msgid "" msgid ""
"\n" "\n"
@ -3943,7 +3943,7 @@ msgid ""
"\t\t\t" "\t\t\t"
msgstr "" msgstr ""
#: src/Model/User.php:1706 #: src/Model/User.php:1708
#, php-format #, php-format
msgid "" msgid ""
"\n" "\n"
@ -3981,7 +3981,7 @@ msgid ""
"\t\t\tThank you and welcome to %2$s." "\t\t\tThank you and welcome to %2$s."
msgstr "" msgstr ""
#: src/Model/User.php:1768 #: src/Model/User.php:1770
msgid "" msgid ""
"User with delegates can't be removed, please remove delegate users first" "User with delegates can't be removed, please remove delegate users first"
msgstr "" msgstr ""
@ -8920,11 +8920,11 @@ msgstr ""
msgid "Show unread" msgid "Show unread"
msgstr "" msgstr ""
#: src/Module/Notifications/Ping.php:223 #: src/Module/Notifications/Ping.php:220
msgid "{0} requested registration" msgid "{0} requested registration"
msgstr "" msgstr ""
#: src/Module/Notifications/Ping.php:232 #: src/Module/Notifications/Ping.php:229
#, php-format #, php-format
msgid "{0} and %d others requested registration" msgid "{0} and %d others requested registration"
msgstr "" msgstr ""