]> git.mxchange.org Git - friendica.git/blobdiff - src/Content/Text/BBCode.php
Merge pull request #8647 from annando/annando/issue8619
[friendica.git] / src / Content / Text / BBCode.php
index 20a81c42a9833d141c075759ce757f11b57907f3..508a325ca1bb2516db9557b268a0dda07059b107 100644 (file)
@@ -48,6 +48,15 @@ use Friendica\Util\XML;
 
 class BBCode
 {
+       const INTERNAL = 0;
+       const API = 2;
+       const DIASPORA = 3;
+       const CONNECTORS = 4;
+       const OSTATUS = 7;
+       const TWITTER = 8;
+       const BACKLINK = 8;
+       const ACTIVITYPUB = 9;
+
        /**
         * Fetches attachment data that were generated the old way
         *
@@ -434,25 +443,36 @@ class BBCode
         */
        public static function toPlaintext($text, $keep_urls = true)
        {
-               $naked_text = HTML::toPlaintext(BBCode::convert($text, false, 0, true), 0, !$keep_urls);
+               $naked_text = HTML::toPlaintext(self::convert($text, false, 0, true), 0, !$keep_urls);
 
                return $naked_text;
        }
 
-       private static function proxyUrl($image, $simplehtml = false)
+       private static function proxyUrl($image, $simplehtml = self::INTERNAL)
        {
                // Only send proxied pictures to API and for internal display
-               if (in_array($simplehtml, [false, 2])) {
+               if (in_array($simplehtml, [self::INTERNAL, self::API])) {
                        return ProxyUtils::proxifyUrl($image);
                } else {
                        return $image;
                }
        }
 
-       public static function scaleExternalImages($srctext)
+       /**
+        * This function changing the visual size (not the real size) of images.
+        * The function does not work for pictures with an alternate text description.
+        * This could only be changed by using some new "img" BBCode format.
+        *
+        * @param string $srctext The body with images
+        * @return string The body with possibly scaled images
+        */
+       public static function scaleExternalImages(string $srctext)
        {
                $s = $srctext;
 
+               // Simplify image links
+               $s = preg_replace("/\[img\=([0-9]*)x([0-9]*)\](.*?)\[\/img\]/ism", '[img]$3[/img]', $s);
+
                $matches = null;
                $c = preg_match_all('/\[img.*?\](.*?)\[\/img\]/ism', $s, $matches, PREG_SET_ORDER);
                if ($c) {
@@ -464,13 +484,14 @@ class BBCode
                                        continue;
                                }
 
-                               $i = Network::fetchUrl($mtch[1]);
-                               if (!$i) {
-                                       return $srctext;
+                               $curlResult = Network::curl($mtch[1], true);
+                               if (!$curlResult->isSuccess()) {
+                                       continue;
                                }
 
-                               // guess mimetype from headers or filename
-                               $type = Images::guessType($mtch[1], true);
+                               $i = $curlResult->getBody();
+                               $type = $curlResult->getContentType();
+                               $type = Images::getMimeTypeByData($i, $mtch[1], $type);
 
                                if ($i) {
                                        $Image = new Image($i, $type);
@@ -593,13 +614,13 @@ class BBCode
         *
         * Note: Can produce a [bookmark] tag in the returned string
         *
-        * @param string   $text
-        * @param bool|int $simplehtml
-        * @param bool     $tryoembed
+        * @param string  $text
+        * @param integer $simplehtml
+        * @param bool    $tryoembed
         * @return string
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       private static function convertAttachment($text, $simplehtml = false, $tryoembed = true)
+       private static function convertAttachment($text, $simplehtml = self::INTERNAL, $tryoembed = true)
        {
                $data = self::getAttachmentData($text);
                if (empty($data) || empty($data['url'])) {
@@ -628,18 +649,18 @@ class BBCode
                } catch (Exception $e) {
                        $data['title'] = ($data['title'] ?? '') ?: $data['url'];
 
-                       if ($simplehtml != 4) {
+                       if ($simplehtml != self::CONNECTORS) {
                                $return = sprintf('<div class="type-%s">', $data['type']);
                        }
 
                        if (!empty($data['title']) && !empty($data['url'])) {
                                if (!empty($data['image']) && empty($data['text']) && ($data['type'] == 'photo')) {
-                                       $return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']);
+                                       $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a>', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']);
                                } else {
                                        if (!empty($data['image'])) {
-                                               $return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br />', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']);
+                                               $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-image" /></a><br />', $data['url'], self::proxyUrl($data['image'], $simplehtml), $data['title']);
                                        } elseif (!empty($data['preview'])) {
-                                               $return .= sprintf('<a href="%s" target="_blank"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br />', $data['url'], self::proxyUrl($data['preview'], $simplehtml), $data['title']);
+                                               $return .= sprintf('<a href="%s" target="_blank" rel="noopener noreferrer"><img src="%s" alt="" title="%s" class="attachment-preview" /></a><br />', $data['url'], self::proxyUrl($data['preview'], $simplehtml), $data['title']);
                                        }
                                        $return .= sprintf('<h4><a href="%s">%s</a></h4>', $data['url'], $data['title']);
                                }
@@ -655,7 +676,7 @@ class BBCode
                                $return .= sprintf('<sup><a href="%s">%s</a></sup>', $data['url'], parse_url($data['url'], PHP_URL_HOST));
                        }
 
-                       if ($simplehtml != 4) {
+                       if ($simplehtml != self::CONNECTORS) {
                                $return .= '</div>';
                        }
                }
@@ -732,7 +753,7 @@ class BBCode
         */
        private static function convertUrlForActivityPub($url)
        {
-               $html = '<a href="%s" target="_blank">%s</a>';
+               $html = '<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>';
                return sprintf($html, $url, self::getStyledURL($url));
        }
 
@@ -975,7 +996,8 @@ class BBCode
                                Contact::getIdForURL($attributes['profile'], 0, true, $default);
 
                                $author_contact = Contact::getDetailsByURL($attributes['profile']);
-                               $author_contact['addr'] = ($author_contact['addr']  ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']);
+                               $author_contact['url'] = ($author_contact['url'] ?? $attributes['profile']);
+                               $author_contact['addr'] = ($author_contact['addr'] ?? '') ?: Protocol::getAddrFromProfileUrl($attributes['profile']);
 
                                $attributes['author']   = ($author_contact['name']  ?? '') ?: $attributes['author'];
                                $attributes['avatar']   = ($author_contact['micro'] ?? '') ?: $attributes['avatar'];
@@ -1012,13 +1034,10 @@ class BBCode
                $mention = Protocol::formatMention($attributes['profile'], $attributes['author']);
 
                switch ($simplehtml) {
-                       case 1:
-                               $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' <a href="' . $attributes['profile'] . '">' . $mention . '</a>: </p>' . "\n" . '«' . $content . '»';
-                               break;
-                       case 2:
+                       case self::API:
                                $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
                                break;
-                       case 3: // Diaspora
+                       case self::DIASPORA:
                                if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0) {
                                        $text = ($is_quote_share? '<hr />' : '') . '<p><a href="' . $attributes['link'] . '">' . $attributes['link'] . '</a></p>' . "\n";
                                } else {
@@ -1036,21 +1055,18 @@ class BBCode
                                }
 
                                break;
-                       case 4:
+                       case self::CONNECTORS:
                                $headline = '<p><b>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8');
-                               $headline .= DI::l10n()->t('<a href="%1$s" target="_blank">%2$s</a> %3$s', $attributes['link'], $mention, $attributes['posted']);
+                               $headline .= DI::l10n()->t('<a href="%1$s" target="_blank" rel="noopener noreferrer">%2$s</a> %3$s', $attributes['link'], $mention, $attributes['posted']);
                                $headline .= ':</b></p>' . "\n";
 
                                $text = ($is_quote_share? '<hr />' : '') . $headline . '<blockquote class="shared_content">' . trim($content) . '</blockquote>' . "\n";
 
                                break;
-                       case 5:
-                               $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ': </p>' . "\n" . $content;
-                               break;
-                       case 7: // statusnet/GNU Social
+                       case self::OSTATUS:
                                $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '</p>' . "\n";
                                break;
-                       case 9: // ActivityPub
+                       case self::ACTIVITYPUB:
                                $author = '@<span class="vcard"><a href="' . $author_contact['url'] . '" class="url u-url mention" title="' . $author_contact['addr'] . '"><span class="fn nickname mention">' . $author_contact['addr'] . '</span></a>:</span>';
                                $text = '<div><a href="' . $attributes['link'] . '">' . html_entity_decode('&#x2672;', ENT_QUOTES, 'UTF-8') . '</a> ' . $author . '<blockquote>' . $content . '</blockquote></div>' . "\n";
                                break;
@@ -1245,7 +1261,7 @@ class BBCode
         * @return string
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public static function convert($text, $try_oembed = true, $simple_html = 0, $for_plaintext = false)
+       public static function convert($text, $try_oembed = true, $simple_html = self::INTERNAL, $for_plaintext = false)
        {
                $a = DI::app();
 
@@ -1373,9 +1389,9 @@ class BBCode
 
                /// @todo Have a closer look at the different html modes
                // Handle attached links or videos
-               if (in_array($simple_html, [9])) {
+               if ($simple_html == self::ACTIVITYPUB) {
                        $text = self::removeAttachment($text);
-               } elseif (!in_array($simple_html, [0, 4])) {
+               } elseif (!in_array($simple_html, [self::INTERNAL, self::CONNECTORS])) {
                        $text = self::removeAttachment($text, true);
                } else {
                        $text = self::convertAttachment($text, $simple_html, $try_oembed);
@@ -1438,7 +1454,7 @@ class BBCode
 
                // Check for sized text
                // [size=50] --> font-size: 50px (with the unit).
-               if ($simple_html != 3) {
+               if ($simple_html != self::DIASPORA) {
                        $text = preg_replace("(\[size=(\d*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1px; line-height: initial;\">$2</span>", $text);
                        $text = preg_replace("(\[size=(.*?)\](.*?)\[\/size\])ism", "<span style=\"font-size: $1; line-height: initial;\">$2</span>", $text);
                } else {
@@ -1630,15 +1646,15 @@ class BBCode
                // Try to Oembed
                if ($try_oembed) {
                        $text = preg_replace("/\[video\](.*?\.(ogg|ogv|oga|ogm|webm|mp4).*?)\[\/video\]/ism", '<video src="$1" controls="controls" width="' . $a->videowidth . '" height="' . $a->videoheight . '" loop="true"><a href="$1">$1</a></video>', $text);
-                       $text = preg_replace("/\[audio\](.*?\.(ogg|ogv|oga|ogm|webm|mp4|mp3).*?)\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $text);
+                       $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism", '<audio src="$1" controls="controls"><a href="$1">$1</a></audio>', $text);
 
                        $text = preg_replace_callback("/\[video\](.*?)\[\/video\]/ism", $try_oembed_callback, $text);
                        $text = preg_replace_callback("/\[audio\](.*?)\[\/audio\]/ism", $try_oembed_callback, $text);
                } else {
                        $text = preg_replace("/\[video\](.*?)\[\/video\]/ism",
-                               '<a href="$1" target="_blank">$1</a>', $text);
+                               '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
                        $text = preg_replace("/\[audio\](.*?)\[\/audio\]/ism",
-                               '<a href="$1" target="_blank">$1</a>', $text);
+                               '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
                }
 
                // html5 video and audio
@@ -1665,7 +1681,7 @@ class BBCode
                        $text = preg_replace("/\[youtube\]([A-Za-z0-9\-_=]+)(.*?)\[\/youtube\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->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">https://www.youtube.com/watch?v=$1</a>', $text);
+                               '<a href="https://www.youtube.com/watch?v=$1" target="_blank" rel="noopener noreferrer">https://www.youtube.com/watch?v=$1</a>', $text);
                }
 
                if ($try_oembed) {
@@ -1680,7 +1696,7 @@ class BBCode
                        $text = preg_replace("/\[vimeo\]([0-9]+)(.*?)\[\/vimeo\]/ism", '<iframe width="' . $a->videowidth . '" height="' . $a->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">https://vimeo.com/$1</a>', $text);
+                               '<a href="https://vimeo.com/$1" target="_blank" rel="noopener noreferrer">https://vimeo.com/$1</a>', $text);
                }
 
                // oembed tag
@@ -1708,11 +1724,19 @@ class BBCode
 
                // Replace non graphical smilies for external posts
                if (!$nosmile && !$for_plaintext) {
-                       $text = Smilies::replace($text);
+                       $oldtext = $text;
+                       $text = Smilies::replace($text);                        
+                       if (DI::config()->get('system', 'big_emojis') && ($simple_html != self::DIASPORA) && ($oldtext != $text)) {
+                               $conv = html_entity_decode(str_replace([' ', "\n", "\r"], '', $text));
+                               // Emojis are always 4 byte Unicode characters
+                               if (strlen($conv) / mb_strlen($conv) == 4) {
+                                       $text = '<span style="font-size: xx-large; line-height: initial;">' . $text . '</span>';
+                               }
+                       }
                }
 
                if (!$for_plaintext) {
-                       if (in_array($simple_html, [7, 9])) {
+                       if (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
                                $text = preg_replace_callback("/\[url\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
                                $text = preg_replace_callback("/\[url\=(.*?)\](.*?)\[\/url\]/ism", 'self::convertUrlForActivityPubCallback', $text);
                        }
@@ -1724,14 +1748,14 @@ class BBCode
                $text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
 
                // Remove all hashtag addresses
-               if ($simple_html && !in_array($simple_html, [3, 7, 9])) {
+               if ($simple_html && !in_array($simple_html, [self::DIASPORA, self::OSTATUS, self::ACTIVITYPUB])) {
                        $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
-               } elseif ($simple_html == 3) {
+               } elseif ($simple_html == self::DIASPORA) {
                        // The ! is converted to @ since Diaspora only understands the @
                        $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
                                '@<a href="$2">$3</a>',
                                $text);
-               } elseif (in_array($simple_html, [7, 9])) {
+               } elseif (in_array($simple_html, [self::OSTATUS, self::ACTIVITYPUB])) {
                        $text = preg_replace("/([@!])\[url\=(.*?)\](.*?)\[\/url\]/ism",
                                '$1<span class="vcard"><a href="$2" class="url u-url mention" title="$3"><span class="fn nickname mention">$3</span></a></span>',
                                $text);
@@ -1747,26 +1771,18 @@ class BBCode
                $text = preg_replace("/#\[url\=.*?\]\^\[\/url\]\[url\=(.*?)\](.*?)\[\/url\]/i",
                                        "[bookmark=$1]$2[/bookmark]", $text);
 
-               if (in_array($simple_html, [2, 6, 7, 8])) {
+               if (in_array($simple_html, [self::API, self::OSTATUS, self::TWITTER])) {
                        $text = preg_replace_callback("/([^#@!])\[url\=([^\]]*)\](.*?)\[\/url\]/ism", "self::expandLinksCallback", $text);
                        //$Text = preg_replace("/[^#@!]\[url\=([^\]]*)\](.*?)\[\/url\]/ism", ' $2 [url]$1[/url]', $Text);
                        $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", ' $2 [url]$1[/url]',$text);
                }
 
-               if ($simple_html == 5) {
-                       $text = preg_replace("/[^#@!]\[url\=(.*?)\](.*?)\[\/url\]/ism", '[url]$1[/url]', $text);
-               }
-
                // Perform URL Search
                if ($try_oembed) {
                        $text = preg_replace_callback("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", $try_oembed_callback, $text);
                }
 
-               if ($simple_html == 5) {
-                       $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url]$1[/url]', $text);
-               } else {
-                       $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
-               }
+               $text = preg_replace("/\[bookmark\=([^\]]*)\](.*?)\[\/bookmark\]/ism", '[url=$1]$2[/url]', $text);
 
                // Handle Diaspora posts
                $text = preg_replace_callback(
@@ -1793,25 +1809,29 @@ class BBCode
                 * - #[url=<anything>]<term>[/url]
                 * - [url=<anything>]#<term>[/url]
                 */
-               $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) {
-                       return '#<a href="'
-                               . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
-                               . '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
-                               . XML::escape($matches[1])
-                               . '</a>';
+               $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) use ($simple_html) {
+                       if ($simple_html == BBCode::ACTIVITYPUB) {
+                               return '<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
+                                       . '" data-tag="' . XML::escape($matches[1]) . '" rel="tag ugc">#'
+                                       . XML::escape($matches[1]) . '</a>';
+                       } else {
+                               return '#<a href="' . DI::baseUrl() . '/search?tag=' . rawurlencode($matches[1])
+                                       . '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'
+                                       . XML::escape($matches[1]) . '</a>';
+                       }
                }, $text);
 
-               // We need no target="_blank" for local links
-               // convert links start with DI::baseUrl() as local link without the target="_blank" attribute
+               // We need no target="_blank" rel="noopener noreferrer" for local links
+               // convert links start with DI::baseUrl() as local link without the target="_blank" rel="noopener noreferrer" attribute
                $escapedBaseUrl = preg_quote(DI::baseUrl(), '/');
                $text = preg_replace("/\[url\](".$escapedBaseUrl.".*?)\[\/url\]/ism", '<a href="$1">$1</a>', $text);
                $text = preg_replace("/\[url\=(".$escapedBaseUrl.".*?)\](.*?)\[\/url\]/ism", '<a href="$1">$2</a>', $text);
 
-               $text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$1</a>', $text);
-               $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
+               $text = preg_replace("/\[url\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$1</a>', $text);
+               $text = preg_replace("/\[url\=(.*?)\](.*?)\[\/url\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
 
                // Red compatibility, though the link can't be authenticated on Friendica
-               $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank">$2</a>', $text);
+               $text = preg_replace("/\[zrl\=(.*?)\](.*?)\[\/zrl\]/ism", '<a href="$1" target="_blank" rel="noopener noreferrer">$2</a>', $text);
 
 
                // we may need to restrict this further if it picks up too many strays
@@ -2027,7 +2047,7 @@ class BBCode
 
                // Convert it to HTML - don't try oembed
                if ($for_diaspora) {
-                       $text = self::convert($text, false, 3);
+                       $text = self::convert($text, false, self::DIASPORA);
 
                        // Add all tags that maybe were removed
                        if (preg_match_all("/#\[url\=([$url_search_string]*)\](.*?)\[\/url\]/ism", $original_text, $tags)) {
@@ -2041,7 +2061,7 @@ class BBCode
                                $text = $text . " " . $tagline;
                        }
                } else {
-                       $text = self::convert($text, false, 4);
+                       $text = self::convert($text, false, self::CONNECTORS);
                }
 
                // If a link is followed by a quote then there should be a newline before it
@@ -2094,7 +2114,7 @@ class BBCode
                $ret = [];
 
                // Convert hashtag links to hashtags
-               $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2', $string);
+               $string = preg_replace('/#\[url\=([^\[\]]*)\](.*?)\[\/url\]/ism', '#$2 ', $string);
 
                // ignore anything in a code block
                $string = preg_replace('/\[code.*?\].*?\[\/code\]/sm', '', $string);