Porównaj commity

...

19 Commity

Autor SHA1 Wiadomość Data
Hypolite Petovan bae1f63424
Merge pull request #14097 from annando/enable-user-defined-channel
Enable user defined channels upon adding/editing
2024-04-15 19:16:02 -04:00
Michael 652802f758 Enable user defined channels upon adding/editing 2024-04-15 20:20:42 +00:00
Tobias Diekershoff ac195c7061
Merge pull request #14096 from annando/noproxy
The legacy proxy functionality is removed
2024-04-15 21:17:31 +02:00
Michael 9cf8678323 Unused function removed 2024-04-15 19:06:12 +00:00
Michael 0e79b5373b The legacy proxy functionality is removed 2024-04-15 18:58:02 +00:00
Hypolite Petovan c30e4c02de
Merge pull request #14095 from annando/warning
Fix warning: Undefined array key "post-reason"
2024-04-15 00:09:01 -04:00
Michael b351819986 Fix: Undefined array key "allow_cid" 2024-04-15 03:25:06 +00:00
Michael 642c55ee3e Fix: "Undefined property: stdClass::$personal" 2024-04-15 03:24:38 +00:00
Michael 6e0118f3fe Fix warning: Undefined array key "post-reason" 2024-04-15 03:10:15 +00:00
Hypolite Petovan 49a0b0fc3c
Merge pull request #14090 from annando/bbcode
The BBCode conversion is split into several smaller functions
2024-04-14 21:59:46 -04:00
Hypolite Petovan 4a0b989386
Merge pull request #14094 from mexon/mat/testing-doc
update testing documentation
2024-04-14 21:58:26 -04:00
Hypolite Petovan 9329eebec0
Merge pull request #14093 from mexon/mat/autotest-password
allow overriding password during local testing
2024-04-14 21:54:59 -04:00
Matthew Exon 619a63af6b update testing documentation 2024-04-14 11:11:00 +02:00
Michael 38da9013ff The BBCode conversion is split into several smaller functions 2024-04-14 07:45:56 +00:00
Matthew Exon e24d1da13b tidy up more password definitions 2024-04-14 09:06:48 +02:00
Hypolite Petovan ed01b0f409
Merge pull request #14075 from mexon/mat/empty-picture-scale
round scaled dimensions up to avoid zero size
2024-04-13 18:30:44 -04:00
Matthew Exon f616dc054f allow overriding password during local testing 2024-04-13 22:24:30 +02:00
Matthew Exon a20b876d67 add unit tests for getScalingDimensions 2024-04-13 21:50:17 +02:00
Matthew Exon c5b8abcaf0 round scaled dimensions up to avoid zero size 2024-04-13 21:50:17 +02:00
20 zmienionych plików z 1251 dodań i 1236 usunięć

Wyświetl plik

@ -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

Wyświetl plik

@ -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);

Wyświetl plik

@ -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"], ['&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.
$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('/\[\&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;
}); // 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
@ -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('/\[\&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
*

Wyświetl plik

@ -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]");

Wyświetl plik

@ -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.')],

Wyświetl plik

@ -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'];

Wyświetl plik

@ -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();
}
}

Wyświetl plik

@ -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);
}
}

Wyświetl plik

@ -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'] = '';

Wyświetl plik

@ -591,6 +591,9 @@ class Image
if (!$this->isValid()) {
return false;
}
if ($dest_width <= 0 || $dest_height <= 0) {
return false;
}
if ($this->isImagick()) {
/*

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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
*

Wyświetl plik

@ -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 ]],

Wyświetl plik

@ -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,

Wyświetl plik

@ -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.
```

Wyświetl plik

@ -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']);
}
/**

Wyświetl plik

@ -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

Wyświetl plik

@ -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>

Wyświetl plik

@ -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>