kopia lustrzana https://github.com/friendica/friendica
Porównaj commity
19 Commity
9fe0d72461
...
bae1f63424
Autor | SHA1 | Data |
---|---|---|
Hypolite Petovan | bae1f63424 | |
Michael | 652802f758 | |
Tobias Diekershoff | ac195c7061 | |
Michael | 9cf8678323 | |
Michael | 0e79b5373b | |
Hypolite Petovan | c30e4c02de | |
Michael | b351819986 | |
Michael | 642c55ee3e | |
Michael | 6e0118f3fe | |
Hypolite Petovan | 49a0b0fc3c | |
Hypolite Petovan | 4a0b989386 | |
Hypolite Petovan | 9329eebec0 | |
Matthew Exon | 619a63af6b | |
Michael | 38da9013ff | |
Matthew Exon | e24d1da13b | |
Hypolite Petovan | ed01b0f409 | |
Matthew Exon | f616dc054f | |
Matthew Exon | a20b876d67 | |
Matthew Exon | c5b8abcaf0 |
|
@ -138,9 +138,9 @@ function execute_tests() {
|
|||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Fire up the mysql docker"
|
||||
DOCKER_CONTAINER_ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=friendica \
|
||||
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_USER="${DATABASE_USER}" \
|
||||
-e MYSQL_PASSWORD=friendica \
|
||||
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_DATABASE="${DATABASE_NAME}" \
|
||||
-d mysql)
|
||||
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
|
||||
|
@ -152,8 +152,8 @@ function execute_tests() {
|
|||
echo "To use the docker container set the USEDOCKER environment variable"
|
||||
exit 3
|
||||
fi
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
else
|
||||
DATABASE_HOST=mysql
|
||||
fi
|
||||
|
@ -171,9 +171,9 @@ function execute_tests() {
|
|||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Fire up the mariadb docker"
|
||||
DOCKER_CONTAINER_ID=$(docker run \
|
||||
-e MYSQL_ROOT_PASSWORD=friendica \
|
||||
-e MYSQL_ROOT_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_USER="${DATABASE_USER}" \
|
||||
-e MYSQL_PASSWORD=friendica \
|
||||
-e MYSQL_PASSWORD="${DATABASE_PASSWORD}" \
|
||||
-e MYSQL_DATABASE="${DATABASE_NAME}" \
|
||||
-d mariadb)
|
||||
DATABASE_HOST=$(docker inspect --format="{{.NetworkSettings.IPAddress}}" "${DOCKER_CONTAINER_ID}")
|
||||
|
@ -185,8 +185,8 @@ function execute_tests() {
|
|||
echo "To use the docker container set the USEDOCKER environment variable"
|
||||
exit 3
|
||||
fi
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -pfriendica -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "DROP DATABASE IF EXISTS ${DATABASE_NAME}" -h ${DATABASE_HOST} || true
|
||||
mysql -u "${DATABASE_USER}" -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE ${DATABASE_NAME} DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci" -h ${DATABASE_HOST}
|
||||
else
|
||||
DATABASE_HOST=mariadb
|
||||
fi
|
||||
|
@ -203,14 +203,14 @@ function execute_tests() {
|
|||
|
||||
if [ -n "${USEDOCKER}" ]; then
|
||||
echo "Initialize database..."
|
||||
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -pfriendica -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
|
||||
docker exec ${DOCKER_CONTAINER_ID} mysql -u root -p"${DATABASE_PASSWORD}" -e "CREATE DATABASE IF NOT EXISTS ${DATABASE_NAME};"
|
||||
fi
|
||||
|
||||
export MYSQL_HOST="${DATABASE_HOST}"
|
||||
|
||||
#call installer
|
||||
echo "Installing Friendica..."
|
||||
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass=friendica --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
|
||||
"${PHP}" ./bin/console.php autoinstall --dbuser="${DATABASE_USER}" --dbpass="${DATABASE_PASSWORD}" --dbdata="${DATABASE_NAME}" --dbhost="${DATABASE_HOST}" --url=https://friendica.local --admin=admin@friendica.local
|
||||
fi
|
||||
|
||||
#test execution
|
||||
|
|
|
@ -279,6 +279,7 @@ function item_process(array $post, array $request, bool $preview, string $return
|
|||
$post['body'] = BBCode::removeSharedData(Item::setHashtags($post['body']));
|
||||
$post['writable'] = true;
|
||||
$post['sensitive'] = false;
|
||||
$post['post-reason'] = Item::PR_LOCAL;
|
||||
|
||||
$o = DI::conversation()->render([$post], Conversation::MODE_SEARCH, false, true);
|
||||
|
||||
|
|
|
@ -317,7 +317,7 @@ class BBCode
|
|||
} elseif ($uriid > 0) {
|
||||
return Post\Link::getByLink($uriid, $image, $size);
|
||||
} else {
|
||||
return Proxy::proxifyUrl($image, $size);
|
||||
return $image;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1317,10 +1317,8 @@ class BBCode
|
|||
|
||||
Hook::callAll('bbcode', $text);
|
||||
|
||||
$a = DI::app();
|
||||
|
||||
$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) {
|
||||
$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) {
|
||||
/*
|
||||
* preg_match_callback function to replace potential Oembed tags with Oembed content
|
||||
*
|
||||
|
@ -1341,6 +1339,117 @@ class BBCode
|
|||
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"], [' ', "<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.
|
||||
$text = self::stripAbstract($text);
|
||||
|
||||
|
@ -1351,20 +1460,6 @@ class BBCode
|
|||
$text = preg_replace("#\[(\w*)](\n*)#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
|
||||
// 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?\[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
|
||||
$search = [
|
||||
"\n[th]", "[th]\n", " [th]", "\n[/th]", "[/th]\n", "[/th] ",
|
||||
|
@ -1428,6 +1518,38 @@ class BBCode
|
|||
} 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
|
||||
// Handle attached links or videos
|
||||
if ($simple_html == self::NPF) {
|
||||
|
@ -1440,16 +1562,11 @@ class BBCode
|
|||
$text = self::convertAttachment($text, $simple_html, $try_oembed, [], $uriid);
|
||||
}
|
||||
|
||||
$nosmile = strpos($text, '[nosmile]') !== false;
|
||||
$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);
|
||||
});
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertMapsToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// leave open the possibility of [map=something]
|
||||
// this is replaced in Item::prepareBody() which has knowledge of the item location
|
||||
if (strpos($text, '[/map]') !== false) {
|
||||
|
@ -1476,6 +1593,11 @@ class BBCode
|
|||
$text = preg_replace("/\[map\]/", '<p class="map"></p>', $text);
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertHeaderToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// Check for headers
|
||||
|
||||
if ($simple_html == self::INTERNAL) {
|
||||
|
@ -1504,20 +1626,48 @@ class BBCode
|
|||
$text = preg_replace("(\[h6\](.*?)\[\/h6\])ism", '</p><h6>$1</h6><p>', $text);
|
||||
}
|
||||
|
||||
// Check for paragraph
|
||||
$text = preg_replace("(\[p\](.*?)\[\/p\])ism", '<p>$1</p>', $text);
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Check for bold text
|
||||
$text = preg_replace("(\[b\](.*?)\[\/b\])ism", '<strong>$1</strong>', $text);
|
||||
private static function convertEmojisToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// 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
|
||||
$text = preg_replace("(\[i\](.*?)\[\/i\])ism", '<em>$1</em>', $text);
|
||||
private static function convertStylesToHtml(string $text, int $simple_html): string
|
||||
{
|
||||
// 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
|
||||
$text = preg_replace("(\[u\](.*?)\[\/u\])ism", '<u>$1</u>', $text);
|
||||
$elements = ['del' => 's', 'ins' => 'em', 'kbd' => 'code', 'mark' => 'strong',
|
||||
'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
|
||||
$text = preg_replace("(\[s\](.*?)\[\/s\])ism", '<s>$1</s>', $text);
|
||||
// Several easy to replace HTML elements
|
||||
// @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
|
||||
$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);
|
||||
}
|
||||
|
||||
|
||||
// Check for centered 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
|
||||
$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
|
||||
// @deprecated since 2021.12, left for backward-compatibility reasons
|
||||
$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", '<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
|
||||
$endlessloop = 0;
|
||||
|
||||
|
@ -1582,25 +1745,11 @@ class BBCode
|
|||
$text = str_replace("[*]", "<li>", $text);
|
||||
$text = str_replace("[li]", "<li>", $text);
|
||||
|
||||
$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);
|
||||
|
||||
$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);
|
||||
});
|
||||
return $text;
|
||||
}
|
||||
|
||||
// Check for font change text
|
||||
$text = preg_replace("/\[font=(.*?)\](.*?)\[\/font\]/sm", "<span style=\"font-family: $1;\">$2</span>", $text);
|
||||
|
||||
private static function convertSpoilersToHtml(string $text): string
|
||||
{
|
||||
// Declare the format for [spoiler] layout
|
||||
$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
|
||||
$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]
|
||||
$text = preg_replace_callback(
|
||||
"/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism",
|
||||
|
@ -1700,10 +1875,18 @@ class BBCode
|
|||
|
||||
$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="' . '$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
|
||||
$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("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
|
||||
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls><a href="$1">$1</a></audio>', $text);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[video\](.*?)\[\/video\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace(
|
||||
"/\[audio\](.*?)\[\/audio\]/ism",
|
||||
'<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace("/\[video\](.*?)\[\/video\]/ism", '[url]$1[/url]', $text);
|
||||
$text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '[url]$1[/url]', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertIFramesToHtml(string $text): string
|
||||
{
|
||||
// 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("/\[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);
|
||||
|
||||
// Youtube extensions
|
||||
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);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[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
|
||||
);
|
||||
$text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '[url]https://www.youtube.com/watch?v=$1[/url]', $text);
|
||||
}
|
||||
|
||||
// Vimeo extensions
|
||||
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);
|
||||
} else {
|
||||
$text = preg_replace(
|
||||
"/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism",
|
||||
'<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>',
|
||||
$text
|
||||
);
|
||||
$text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '[url]https://vimeo.com/$1[/url]', $text);
|
||||
}
|
||||
return $text;
|
||||
}
|
||||
|
||||
private static function convertOEmbedToHtml(string $text, int $uriid): string
|
||||
{
|
||||
// oembed tag
|
||||
$text = OEmbed::BBCode2HTML($text, $uriid);
|
||||
|
||||
// Avoid triple linefeeds through oembed
|
||||
$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('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/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;
|
||||
}); // 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"], [' ', "<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
|
||||
|
@ -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\=(.*?)\](.*?)\[\/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
|
||||
|
@ -2088,6 +2138,78 @@ class BBCode
|
|||
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('/\[\&\;([#a-z0-9]+)\;\]/', '&$1;', $text);
|
||||
$text = preg_replace('/\&\#039\;/', '\'', $text);
|
||||
|
||||
// Currently deactivated, it made problems with " inside of alt texts.
|
||||
//$text = preg_replace('/\"\;/', '"', $text);
|
||||
|
||||
// fix any escaped ampersands that may have been converted into links
|
||||
$text = preg_replace('/\<([^>]*?)(src|href)=(.*?)\&\;(.*?)\>/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
|
||||
*
|
||||
|
|
|
@ -253,13 +253,12 @@ class HTML
|
|||
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, 'strong', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'em', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'b', [], '[b]', '[/b]');
|
||||
self::tagToBBCode($doc, 'i', [], '[i]', '[/i]');
|
||||
self::tagToBBCode($doc, 'u', [], '[u]', '[/u]');
|
||||
self::tagToBBCode($doc, 's', [], '[s]', '[/s]');
|
||||
self::tagToBBCode($doc, 'del', [], '[s]', '[/s]');
|
||||
$elements = ['b', 'del', 'em', 'i', 'ins', 'kbd', 'mark',
|
||||
's', 'samp', 'strong', 'sub', 'sup', 'u', 'var'];
|
||||
foreach ($elements as $element) {
|
||||
self::tagToBBCode($doc, $element, [], '[' . $element . ']', '[/' . $element . ']');
|
||||
}
|
||||
|
||||
self::tagToBBCode($doc, 'strike', [], '[s]', '[/s]');
|
||||
|
||||
self::tagToBBCode($doc, 'big', [], "[size=large]", "[/size]");
|
||||
|
|
|
@ -97,7 +97,6 @@ class Site extends BaseAdmin
|
|||
$adjust_poll_frequency = !empty($_POST['adjust_poll_frequency']);
|
||||
$min_poll_interval = (!empty($_POST['min_poll_interval']) ? intval(trim($_POST['min_poll_interval'])) : 0);
|
||||
$explicit_content = !empty($_POST['explicit_content']);
|
||||
$proxify_content = !empty($_POST['proxify_content']);
|
||||
$local_search = !empty($_POST['local_search']);
|
||||
$blocked_tags = (!empty($_POST['blocked_tags']) ? trim($_POST['blocked_tags']) : '');
|
||||
$cache_contact_avatar = !empty($_POST['cache_contact_avatar']);
|
||||
|
@ -271,7 +270,6 @@ class Site extends BaseAdmin
|
|||
$transactionConfig->set('system', 'adjust_poll_frequency' , $adjust_poll_frequency);
|
||||
$transactionConfig->set('system', 'min_poll_interval' , $min_poll_interval);
|
||||
$transactionConfig->set('system', 'explicit_content' , $explicit_content);
|
||||
$transactionConfig->set('system', 'proxify_content' , $proxify_content);
|
||||
$transactionConfig->set('system', 'local_search' , $local_search);
|
||||
$transactionConfig->set('system', 'blocked_tags' , Strings::cleanTags($blocked_tags));
|
||||
$transactionConfig->set('system', 'cache_contact_avatar' , $cache_contact_avatar);
|
||||
|
@ -518,7 +516,6 @@ class Site extends BaseAdmin
|
|||
'$private_addons' => ['private_addons', DI::l10n()->t('Disallow public access to addons listed in the apps menu.'), DI::config()->get('config', 'private_addons'), DI::l10n()->t('Checking this box will restrict addons listed in the apps menu to members only.')],
|
||||
'$disable_embedded' => ['disable_embedded', DI::l10n()->t('Don\'t embed private images in posts'), DI::config()->get('system', 'disable_embedded'), DI::l10n()->t('Don\'t replace locally-hosted private photos in posts with an embedded copy of the image. This means that contacts who receive posts containing private photos will have to authenticate and load each image, which may take a while.')],
|
||||
'$explicit_content' => ['explicit_content', DI::l10n()->t('Explicit Content'), DI::config()->get('system', 'explicit_content'), DI::l10n()->t('Set this to announce that your node is used mostly for explicit content that might not be suited for minors. This information will be published in the node information and might be used, e.g. by the global directory, to filter your node from listings of nodes to join. Additionally a note about this will be shown at the user registration page.')],
|
||||
'$proxify_content' => ['proxify_content', DI::l10n()->t('Proxify external content'), DI::config()->get('system', 'proxify_content'), DI::l10n()->t('Route external content via the proxy functionality. This is used for example for some OEmbed accesses and in some other rare cases.')],
|
||||
'$local_search' => ['local_search', DI::l10n()->t('Only local search'), DI::config()->get('system', 'local_search'), DI::l10n()->t('Blocks search for users who are not logged in to prevent crawlers from blocking your system.')],
|
||||
'$blocked_tags' => ['blocked_tags', DI::l10n()->t('Blocked tags for trending tags'), DI::config()->get('system', 'blocked_tags'), DI::l10n()->t("Comma separated list of hashtags that shouldn't be displayed in the trending tags.")],
|
||||
'$cache_contact_avatar' => ['cache_contact_avatar', DI::l10n()->t('Cache contact avatars'), DI::config()->get('system', 'cache_contact_avatar'), DI::l10n()->t('Locally store the avatar pictures of the contacts. This uses a lot of storage space but it increases the performance.')],
|
||||
|
|
|
@ -83,6 +83,10 @@ class Statuses extends BaseApi
|
|||
$item['network'] = $post['network'];
|
||||
$item['gravity'] = $post['gravity'];
|
||||
$item['verb'] = $post['verb'];
|
||||
$item['allow_cid'] = $post['allow_cid'];
|
||||
$item['allow_gid'] = $post['allow_gid'];
|
||||
$item['deny_cid'] = $post['deny_cid'];
|
||||
$item['deny_gid'] = $post['deny_gid'];
|
||||
$item['app'] = $this->getApp();
|
||||
$item['sensitive'] = $request['sensitive'];
|
||||
|
||||
|
|
|
@ -1,211 +0,0 @@
|
|||
<?php
|
||||
/**
|
||||
* @copyright Copyright (C) 2010-2024, the Friendica project
|
||||
*
|
||||
* @license GNU AGPL version 3 or any later version
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as
|
||||
* published by the Free Software Foundation, either version 3 of the
|
||||
* License, or (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
namespace Friendica\Module;
|
||||
|
||||
use Friendica\BaseModule;
|
||||
use Friendica\Core\Logger;
|
||||
use Friendica\Core\System;
|
||||
use Friendica\DI;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientAccept;
|
||||
use Friendica\Network\HTTPClient\Client\HttpClientOptions;
|
||||
use Friendica\Network\HTTPException\NotModifiedException;
|
||||
use Friendica\Object\Image;
|
||||
use Friendica\Util\HTTPSignature;
|
||||
use Friendica\Util\Images;
|
||||
use Friendica\Util\Proxy as ProxyUtils;
|
||||
|
||||
/**
|
||||
* Module Proxy
|
||||
*
|
||||
* urls:
|
||||
* /proxy/[sub1/[sub2/]]<base64url image url>[.ext][:size]
|
||||
* /proxy?url=<image url>
|
||||
*/
|
||||
class Proxy extends BaseModule
|
||||
{
|
||||
|
||||
/**
|
||||
* Fetch remote image content
|
||||
*/
|
||||
protected function rawContent(array $request = [])
|
||||
{
|
||||
$request = $this->getRequestInfo();
|
||||
|
||||
if (!DI::config()->get('system', 'proxify_content')) {
|
||||
Logger::notice('Proxy access is forbidden', ['request' => $request, 'agent' => $_SERVER['HTTP_USER_AGENT'] ?? '', 'accept' => $_SERVER['HTTP_ACCEPT'] ?? '']);
|
||||
throw new \Friendica\Network\HTTPException\NotFoundException();
|
||||
}
|
||||
|
||||
if (isset($_SERVER['HTTP_IF_MODIFIED_SINCE'])) {
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
|
||||
if (!empty($_SERVER['HTTP_IF_NONE_MATCH'])) {
|
||||
header('Etag: ' . $_SERVER['HTTP_IF_NONE_MATCH']);
|
||||
}
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT');
|
||||
header('Cache-Control: max-age=31536000');
|
||||
if (function_exists('header_remove')) {
|
||||
header_remove('Last-Modified');
|
||||
header_remove('Expires');
|
||||
header_remove('Cache-Control');
|
||||
}
|
||||
throw new NotModifiedException();
|
||||
}
|
||||
|
||||
if (empty($request['url'])) {
|
||||
throw new \Friendica\Network\HTTPException\BadRequestException();
|
||||
}
|
||||
|
||||
if (!DI::userSession()->getLocalUserId()) {
|
||||
Logger::debug('Redirecting not logged in user to original address', ['url' => $request['url']]);
|
||||
System::externalRedirect($request['url']);
|
||||
}
|
||||
|
||||
// It shouldn't happen but it does - spaces in URL
|
||||
$request['url'] = str_replace(' ', '+', $request['url']);
|
||||
|
||||
// Fetch the content with the local user
|
||||
try {
|
||||
$fetchResult = HTTPSignature::fetchRaw($request['url'], DI::userSession()->getLocalUserId(), [HttpClientOptions::ACCEPT_CONTENT => [HttpClientAccept::IMAGE], 'timeout' => 10]);
|
||||
$img_str = $fetchResult->getBodyString();
|
||||
|
||||
if (!$fetchResult->isSuccess() || empty($img_str)) {
|
||||
Logger::notice('Error fetching image', ['image' => $request['url'], 'return' => $fetchResult->getReturnCode(), 'empty' => empty($img_str)]);
|
||||
self::responseError();
|
||||
// stop.
|
||||
}
|
||||
} catch (\Exception $exception) {
|
||||
Logger::notice('Error fetching image', ['image' => $request['url'], 'exception' => $exception]);
|
||||
self::responseError();
|
||||
}
|
||||
|
||||
Logger::debug('Got picture', ['Content-Type' => $fetchResult->getHeader('Content-Type'), 'uid' => DI::userSession()->getLocalUserId(), 'image' => $request['url']]);
|
||||
|
||||
$image = new Image($img_str, $fetchResult->getContentType(), $request['url']);
|
||||
if (!$image->isValid()) {
|
||||
Logger::notice('The image is invalid', ['image' => $request['url'], 'mime' => $fetchResult->getContentType()]);
|
||||
self::responseError();
|
||||
// stop.
|
||||
}
|
||||
|
||||
// reduce quality - if it is supported for this image type
|
||||
if (Images::canResize($image->getType())) {
|
||||
$image->scaleDown($request['size']);
|
||||
}
|
||||
|
||||
self::responseImageHttpCache($image);
|
||||
// stop.
|
||||
}
|
||||
|
||||
/**
|
||||
* Build info about requested image to be proxied
|
||||
*
|
||||
* @return array
|
||||
* [
|
||||
* 'url' => requested url,
|
||||
* 'size' => requested image size (int)
|
||||
* 'sizetype' => requested image size (string): ':micro', ':thumb', ':small', ':medium', ':large'
|
||||
* ]
|
||||
* @throws \Exception
|
||||
*/
|
||||
private function getRequestInfo(): array
|
||||
{
|
||||
$size = ProxyUtils::PIXEL_LARGE;
|
||||
$sizetype = '';
|
||||
|
||||
if (!empty($this->parameters['url']) && empty($_REQUEST['url'])) {
|
||||
$url = $this->parameters['url'];
|
||||
|
||||
// thumb, small, medium and large.
|
||||
if (substr($url, -6) == ':micro') {
|
||||
$size = ProxyUtils::PIXEL_MICRO;
|
||||
$sizetype = ':micro';
|
||||
$url = substr($url, 0, -6);
|
||||
} elseif (substr($url, -6) == ':thumb') {
|
||||
$size = ProxyUtils::PIXEL_THUMB;
|
||||
$sizetype = ':thumb';
|
||||
$url = substr($url, 0, -6);
|
||||
} elseif (substr($url, -6) == ':small') {
|
||||
$size = ProxyUtils::PIXEL_SMALL;
|
||||
$url = substr($url, 0, -6);
|
||||
$sizetype = ':small';
|
||||
} elseif (substr($url, -7) == ':medium') {
|
||||
$size = ProxyUtils::PIXEL_MEDIUM;
|
||||
$url = substr($url, 0, -7);
|
||||
$sizetype = ':medium';
|
||||
} elseif (substr($url, -6) == ':large') {
|
||||
$size = ProxyUtils::PIXEL_LARGE;
|
||||
$url = substr($url, 0, -6);
|
||||
$sizetype = ':large';
|
||||
}
|
||||
|
||||
$pos = strrpos($url, '=.');
|
||||
if ($pos) {
|
||||
$url = substr($url, 0, $pos + 1);
|
||||
}
|
||||
|
||||
$url = str_replace(['.jpg', '.jpeg', '.gif', '.png'], ['','','',''], $url);
|
||||
|
||||
$url = base64_decode(strtr($url, '-_', '+/'), true);
|
||||
} else {
|
||||
$url = $_REQUEST['url'] ?? '';
|
||||
}
|
||||
|
||||
return [
|
||||
'url' => $url,
|
||||
'size' => $size,
|
||||
'sizetype' => $sizetype,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* In case of an error just stop. We don't return content to avoid caching problems
|
||||
*
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function responseError()
|
||||
{
|
||||
throw new \Friendica\Network\HTTPException\InternalServerErrorException();
|
||||
}
|
||||
|
||||
/**
|
||||
* Output the image with cache headers
|
||||
*
|
||||
* @param Image $img
|
||||
* @return void
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
private static function responseImageHttpCache(Image $img)
|
||||
{
|
||||
if (is_null($img) || !$img->isValid()) {
|
||||
Logger::notice('The cached image is invalid');
|
||||
self::responseError();
|
||||
// stop.
|
||||
}
|
||||
header('Content-type: ' . $img->getType());
|
||||
header('Last-Modified: ' . gmdate('D, d M Y H:i:s', time()) . ' GMT');
|
||||
header('Etag: "' . md5($img->asString()) . '"');
|
||||
header('Expires: ' . gmdate('D, d M Y H:i:s', time() + (31536000)) . ' GMT');
|
||||
header('Cache-Control: max-age=31536000');
|
||||
echo $img->asString();
|
||||
System::exit();
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ use Friendica\Content\Conversation\Factory;
|
|||
use Friendica\Content\Conversation\Repository\UserDefinedChannel;
|
||||
use Friendica\Core\Config\Capability\IManageConfigValues;
|
||||
use Friendica\Core\L10n;
|
||||
use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
|
||||
use Friendica\Core\Renderer;
|
||||
use Friendica\Core\Session\Capability\IHandleUserSessions;
|
||||
use Friendica\Model\Circle;
|
||||
|
@ -41,18 +42,21 @@ class Channels extends BaseSettings
|
|||
{
|
||||
/** @var UserDefinedChannel */
|
||||
private $channel;
|
||||
/** @var IManagePersonalConfigValues */
|
||||
private $pConfig;
|
||||
/** @var Factory\UserDefinedChannel */
|
||||
private $userDefinedChannel;
|
||||
/** @var IManageConfigValues */
|
||||
private $config;
|
||||
|
||||
public function __construct(Factory\UserDefinedChannel $userDefinedChannel, UserDefinedChannel $channel, App\Page $page, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManageConfigValues $config, array $server, array $parameters = [])
|
||||
public function __construct(Factory\UserDefinedChannel $userDefinedChannel, UserDefinedChannel $channel, App\Page $page, IHandleUserSessions $session, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, array $server, array $parameters = [])
|
||||
{
|
||||
parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
|
||||
|
||||
$this->userDefinedChannel = $userDefinedChannel;
|
||||
$this->channel = $channel;
|
||||
$this->config = $config;
|
||||
$this->pConfig = $pConfig;
|
||||
}
|
||||
|
||||
protected function post(array $request = [])
|
||||
|
@ -91,6 +95,7 @@ class Channels extends BaseSettings
|
|||
]);
|
||||
$saved = $this->channel->save($channel);
|
||||
$this->logger->debug('New channel added', ['saved' => $saved]);
|
||||
$this->enableTimeline($uid, $saved->code);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -123,6 +128,7 @@ class Channels extends BaseSettings
|
|||
]);
|
||||
$saved = $this->channel->save($channel);
|
||||
$this->logger->debug('Save channel', ['id' => $id, 'saved' => $saved]);
|
||||
$this->enableTimeline($uid, $id);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -232,4 +238,21 @@ class Channels extends BaseSettings
|
|||
'$form_security_token' => self::getFormSecurityToken('settings_channels'),
|
||||
]);
|
||||
}
|
||||
|
||||
private function enableTimeline(int $uid, int $id)
|
||||
{
|
||||
$bookmarked_timelines = $this->pConfig->get($uid, 'system', 'network_timelines');
|
||||
$enabled_timelines = $this->pConfig->get($uid, 'system', 'enabled_timelines');
|
||||
|
||||
if (empty($enabled_timelines) || empty($bookmarked_timelines)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (in_array($id, $enabled_timelines) || in_array($id, $bookmarked_timelines)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$enabled_timelines[] = $id;
|
||||
$this->pConfig->set($uid, 'system', 'enabled_timelines', $enabled_timelines);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2000,8 +2000,8 @@ class Probe
|
|||
if (isset($adr)) {
|
||||
foreach ($adr as $feadr) {
|
||||
if ((strcasecmp($feadr->mailbox, $data['name']) == 0)
|
||||
&&(strcasecmp($feadr->host, $phost) == 0)
|
||||
&& (strlen($feadr->personal))
|
||||
&& (strcasecmp($feadr->host, $phost) == 0)
|
||||
&& !empty($feadr->personal)
|
||||
) {
|
||||
$personal = imap_mime_header_decode($feadr->personal);
|
||||
$data['name'] = '';
|
||||
|
|
|
@ -591,6 +591,9 @@ class Image
|
|||
if (!$this->isValid()) {
|
||||
return false;
|
||||
}
|
||||
if ($dest_width <= 0 || $dest_height <= 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($this->isImagick()) {
|
||||
/*
|
||||
|
|
|
@ -418,19 +418,19 @@ class Images
|
|||
|
||||
if ((($height * 9) / 16) > $width) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(($height * $max) / $width);
|
||||
$dest_height = intval(ceil(($height * $max) / $width));
|
||||
} elseif ($width > $height) {
|
||||
// else constrain both dimensions
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(($height * $max) / $width);
|
||||
$dest_height = intval(ceil(($height * $max) / $width));
|
||||
} else {
|
||||
$dest_width = intval(($width * $max) / $height);
|
||||
$dest_width = intval(ceil(($width * $max) / $height));
|
||||
$dest_height = $max;
|
||||
}
|
||||
} else {
|
||||
if ($width > $max) {
|
||||
$dest_width = $max;
|
||||
$dest_height = intval(($height * $max) / $width);
|
||||
$dest_height = intval(ceil(($height * $max) / $width));
|
||||
} else {
|
||||
if ($height > $max) {
|
||||
// very tall image (greater than 16:9)
|
||||
|
@ -440,7 +440,7 @@ class Images
|
|||
$dest_width = $width;
|
||||
$dest_height = $height;
|
||||
} else {
|
||||
$dest_width = intval(($width * $max) / $height);
|
||||
$dest_width = intval(ceil(($width * $max) / $height));
|
||||
$dest_height = $max;
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -49,19 +49,6 @@ class Proxy
|
|||
const PIXEL_MEDIUM = 640;
|
||||
const PIXEL_LARGE = 1024;
|
||||
|
||||
/**
|
||||
* Accepted extensions
|
||||
*
|
||||
* @var array
|
||||
* @todo Make this configurable?
|
||||
*/
|
||||
private static $extensions = [
|
||||
'jpg',
|
||||
'jpeg',
|
||||
'gif',
|
||||
'png',
|
||||
];
|
||||
|
||||
/**
|
||||
* Private constructor
|
||||
*/
|
||||
|
@ -69,63 +56,6 @@ class Proxy
|
|||
// No instances from utilities classes
|
||||
}
|
||||
|
||||
/**
|
||||
* Transform a remote URL into a local one.
|
||||
*
|
||||
* This function only performs the URL replacement on http URL and if the
|
||||
* provided URL isn't local
|
||||
*
|
||||
* @param string $url The URL to proxify
|
||||
* @param string $size One of the Proxy::SIZE_* constants
|
||||
* @return string The proxified URL or relative path
|
||||
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
|
||||
*/
|
||||
public static function proxifyUrl(string $url, string $size = ''): string
|
||||
{
|
||||
if (!DI::config()->get('system', 'proxify_content')) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Trim URL first
|
||||
$url = trim($url);
|
||||
|
||||
// Quit if not an HTTP/HTTPS link or if local
|
||||
if (!in_array(parse_url($url, PHP_URL_SCHEME), ['http', 'https']) || self::isLocalImage($url)) {
|
||||
return $url;
|
||||
}
|
||||
|
||||
// Image URL may have encoded ampersands for display which aren't desirable for proxy
|
||||
$url = html_entity_decode($url, ENT_NOQUOTES, 'utf-8');
|
||||
|
||||
$shortpath = hash('md5', $url);
|
||||
$longpath = substr($shortpath, 0, 2);
|
||||
|
||||
$longpath .= '/' . strtr(base64_encode($url), '+/', '-_');
|
||||
|
||||
// Extract the URL extension
|
||||
$extension = pathinfo(parse_url($url, PHP_URL_PATH), PATHINFO_EXTENSION);
|
||||
|
||||
if (in_array($extension, self::$extensions)) {
|
||||
$shortpath .= '.' . $extension;
|
||||
$longpath .= '.' . $extension;
|
||||
}
|
||||
|
||||
$proxypath = DI::baseUrl() . '/proxy/' . $longpath;
|
||||
|
||||
if ($size != '') {
|
||||
$size = ':' . $size;
|
||||
}
|
||||
|
||||
Logger::info('Created proxy link', ['url' => $url]);
|
||||
|
||||
// Too long files aren't supported by Apache
|
||||
if (strlen($proxypath) > 250) {
|
||||
return DI::baseUrl() . '/proxy/' . $shortpath . '?url=' . urlencode($url);
|
||||
} else {
|
||||
return $proxypath . $size;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* "Proxifies" HTML code's image tags
|
||||
*
|
||||
|
|
|
@ -595,13 +595,6 @@ return [
|
|||
'/u/{nickname}' => $profileRoutes,
|
||||
'/~{nickname}' => $profileRoutes,
|
||||
|
||||
'/proxy' => [
|
||||
'[/]' => [Module\Proxy::class, [R::GET]],
|
||||
'/{url}' => [Module\Proxy::class, [R::GET]],
|
||||
'/{sub1}/{url}' => [Module\Proxy::class, [R::GET]],
|
||||
'/{sub1}/{sub2}/{url}' => [Module\Proxy::class, [R::GET]],
|
||||
],
|
||||
|
||||
// OStatus stack modules
|
||||
'/ostatus/repair' => [Module\OStatus\Repair::class, [R::GET ]],
|
||||
'/ostatus/subscribe' => [Module\OStatus\Subscribe::class, [R::GET ]],
|
||||
|
|
|
@ -241,10 +241,6 @@ return [
|
|||
// Maximum amount of tags in a post before it is rejected as spam.
|
||||
'relay_max_tags' => 20,
|
||||
|
||||
// proxify_content (Boolean)
|
||||
// Use the proxy functionality for fetching external content
|
||||
'proxify_content' => true,
|
||||
|
||||
// relay_directly (Boolean)
|
||||
// Directly transmit content to relay subscribers without using a relay server
|
||||
'relay_directly' => false,
|
||||
|
|
|
@ -1,13 +1,83 @@
|
|||
# Using the Friendica tests
|
||||
|
||||
## Install PHPUnit
|
||||
## Install Tools
|
||||
|
||||
Please use [setup-phpunit.sh](https://github.com/friendica/friendica/bin/dev/setup-phpunit.sh) to install the necessary PHPUnit version.
|
||||
It will temporarily install the `phpunit` phar file into the `bin/` subdirectory
|
||||
You need to install the following software:
|
||||
|
||||
* PHP
|
||||
* MySQL or Mariadb (the latter is preferred)
|
||||
|
||||
Currently, Friendica uses PHPUnit 8.
|
||||
For example in Ubuntu you can run:
|
||||
|
||||
## Supported PHP versions of these tests
|
||||
```
|
||||
sudo apt install mariadb-server php
|
||||
```
|
||||
|
||||
The Unit-Tests of Friendica requires at least PHP 7.2.
|
||||
## Install PHP extensions
|
||||
|
||||
The following extensions must be installed:
|
||||
|
||||
* MySQL
|
||||
* Curl
|
||||
* GD
|
||||
* XML
|
||||
* DOM
|
||||
* SimpleXML
|
||||
* Intl
|
||||
* Multi-precision
|
||||
* Multi-byte string
|
||||
|
||||
For example in Ubuntu:
|
||||
|
||||
```
|
||||
sudo apt install php-mysql php-curl php-gd php-xml php-intl php-gmp php-mbstring
|
||||
```
|
||||
|
||||
## Create Local Database
|
||||
|
||||
The default database name is `test`, username `friendica`, password
|
||||
`friendica`. These can be overridden using environment variables
|
||||
`DATABASE_NAME`, `DATABASE_USER`, `DATABASE_HOST`, and
|
||||
`DATABASE_PASSWORD`. Whatever settings you choose, you must give the
|
||||
corresponding user the necessary privileges to create and destroy the
|
||||
chosen database.
|
||||
|
||||
```
|
||||
GRANT ALL PRIVILEGES ON test.* TO 'friendica'@'localhost' IDENTIFIED BY 'friendica' WITH GRANT OPTION;
|
||||
GRANT CREATE, DROP ON test.* TO 'friendica'@'localhost';
|
||||
```
|
||||
|
||||
## Use Docker Database
|
||||
|
||||
Instead of using a local database, you can also use a database running in a docker container.
|
||||
|
||||
TODO this section needs to be filled in with working examples.
|
||||
|
||||
## Running Tests
|
||||
|
||||
You can then run the tests using the `autotest.sh` script. You should
|
||||
specify the type of database as an argument, either `mysql` or
|
||||
`mariadb`:
|
||||
|
||||
```
|
||||
bin/dev/autotest.sh mariadb
|
||||
```
|
||||
|
||||
You can also run just one particular file of tests:
|
||||
|
||||
```
|
||||
bin/dev/autotest.sh mariadb src/Util/ImagesTest.php
|
||||
```
|
||||
|
||||
Example output of tests passing:
|
||||
|
||||
```
|
||||
OK (2 tests, 2 assertions)
|
||||
```
|
||||
|
||||
Failed tests look like this. Examine the output before this to see which tests failed.
|
||||
|
||||
```
|
||||
FAILURES!
|
||||
Tests: 2, Assertions: 2, Failures: 1.
|
||||
```
|
||||
|
|
|
@ -67,7 +67,7 @@ class DirectMessageTest extends FixtureTest
|
|||
->toArray();
|
||||
|
||||
self::assertEquals('item_title', $directMessage['title']);
|
||||
self::assertEquals('<strong>item_body</strong>', $directMessage['text']);
|
||||
self::assertEquals('<b>item_body</b>', $directMessage['text']);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -96,4 +96,104 @@ class ImagesTest extends MockedTest
|
|||
|
||||
self::assertArraySubset($assertion, Images::getInfoFromURL($url));
|
||||
}
|
||||
|
||||
public function dataScalingDimensions()
|
||||
{
|
||||
return [
|
||||
'landscape' => [
|
||||
'width' => 640,
|
||||
'height' => 480,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 320,
|
||||
'height' => 240,
|
||||
]
|
||||
],
|
||||
'wide_landscape' => [
|
||||
'width' => 640,
|
||||
'height' => 120,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 320,
|
||||
'height' => 60,
|
||||
]
|
||||
],
|
||||
'landscape_round_up' => [
|
||||
'width' => 640,
|
||||
'height' => 479,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 320,
|
||||
'height' => 240,
|
||||
]
|
||||
],
|
||||
'landscape_zero_height' => [
|
||||
'width' => 640,
|
||||
'height' => 1,
|
||||
'max' => 160,
|
||||
'assertion' => [
|
||||
'width' => 160,
|
||||
'height' => 1,
|
||||
]
|
||||
],
|
||||
'portrait' => [
|
||||
'width' => 480,
|
||||
'height' => 640,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 240,
|
||||
'height' => 320,
|
||||
]
|
||||
],
|
||||
// For portrait with aspect ratio <= 16:9, constrain height
|
||||
'portrait_16_9' => [
|
||||
'width' => 1080,
|
||||
'height' => 1920,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 180,
|
||||
'height' => 320,
|
||||
]
|
||||
],
|
||||
// For portrait with aspect ratio > 16:9, constrain width
|
||||
'portrait_over_16_9_too_wide' => [
|
||||
'width' => 1080,
|
||||
'height' => 1921,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 320,
|
||||
'height' => 570,
|
||||
]
|
||||
],
|
||||
// For portrait with aspect ratio > 16:9, constrain width
|
||||
'portrait_over_16_9_not_too_wide' => [
|
||||
'width' => 1080,
|
||||
'height' => 1921,
|
||||
'max' => 1080,
|
||||
'assertion' => [
|
||||
'width' => 1080,
|
||||
'height' => 1921,
|
||||
]
|
||||
],
|
||||
'portrait_round_up' => [
|
||||
'width' => 479,
|
||||
'height' => 640,
|
||||
'max' => 320,
|
||||
'assertion' => [
|
||||
'width' => 240,
|
||||
'height' => 320,
|
||||
]
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Test the Images::getScalingDimensions() method
|
||||
*
|
||||
* @dataProvider dataScalingDimensions
|
||||
*/
|
||||
public function testGetScalingDimensions(int $width, int $height, int $max, array $assertion)
|
||||
{
|
||||
self::assertArraySubset($assertion, Images::getScalingDimensions($width, $height, $max));
|
||||
}
|
||||
}
|
||||
|
|
Plik diff jest za duży
Load Diff
|
@ -88,7 +88,6 @@
|
|||
{{include file="field_checkbox.tpl" field=$allow_relay_channels}}
|
||||
{{include file="field_checkbox.tpl" field=$adjust_poll_frequency}}
|
||||
{{include file="field_checkbox.tpl" field=$explicit_content}}
|
||||
{{include file="field_checkbox.tpl" field=$proxify_content}}
|
||||
{{include file="field_checkbox.tpl" field=$local_search}}
|
||||
{{include file="field_input.tpl" field=$blocked_tags}}
|
||||
<div class="submit"><input type="submit" name="page_site" value="{{$submit}}"/></div>
|
||||
|
|
|
@ -168,7 +168,6 @@
|
|||
{{include file="field_checkbox.tpl" field=$allow_relay_channels}}
|
||||
{{include file="field_checkbox.tpl" field=$adjust_poll_frequency}}
|
||||
{{include file="field_checkbox.tpl" field=$explicit_content}}
|
||||
{{include file="field_checkbox.tpl" field=$proxify_content}}
|
||||
{{include file="field_checkbox.tpl" field=$local_search}}
|
||||
{{include file="field_input.tpl" field=$blocked_tags}}
|
||||
</div>
|
||||
|
|
Ładowanie…
Reference in New Issue