]> git.mxchange.org Git - friendica.git/blobdiff - src/Content/Text/BBCode.php
Merge pull request #7973 from tobiasd/2019.12-CHANGELOG
[friendica.git] / src / Content / Text / BBCode.php
index 1dffb05073b70f7f7f30c6fe35b86894986147b8..e5622d14b43e478107345dcfe4f3890313a007d3 100644 (file)
@@ -354,6 +354,9 @@ class BBCode extends BaseObject
                                $post['url'] = $links[0][1];
                        }
 
+                       // Simplify "video" element
+                       $post['text'] = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $post['text']);
+
                        // Now count the number of external media links
                        preg_match_all("(\[vimeo\](.*?)\[\/vimeo\])ism", $post['text'], $links1, PREG_SET_ORDER);
                        preg_match_all("(\[youtube\\](.*?)\[\/youtube\\])ism", $post['text'], $links2, PREG_SET_ORDER);
@@ -385,6 +388,29 @@ class BBCode extends BaseObject
                return $post;
        }
 
+       /**
+        * Remove [attachment] BBCode and replaces it with a regular [url]
+        *
+        * @param string  $body
+        * @param boolean $no_link_desc No link description
+        *
+        * @return string with replaced body
+        */
+       public static function removeAttachment($body, $no_link_desc = false)
+       {
+               return preg_replace_callback("/\s*\[attachment (.*)\](.*?)\[\/attachment\]\s*/ism",
+                       function ($match) use ($no_link_desc) {
+                               $attach_data = self::getAttachmentData($match[0]);
+                               if (empty($attach_data['url'])) {
+                                       return $match[0];
+                               } elseif (empty($attach_data['title']) || $no_link_desc) {
+                                       return "\n[url]" . $attach_data['url'] . "[/url]\n";
+                               } else {
+                                       return "\n[url=" . $attach_data['url'] . ']' . $attach_data['title'] . "[/url]\n";
+                               }
+               }, $body);
+       }
+
        /**
         * @brief Converts a BBCode text into plaintext
         *
@@ -600,53 +626,44 @@ class BBCode extends BaseObject
                }
 
                $return = '';
-               if (in_array($simplehtml, [7, 9])) {
-                       // Only add the link when it isn't already part of the body
-                       if (substr_count($text, $data['url']) == 1) {
-                               $return = self::convertUrlForActivityPub($data['url']);
+               try {
+                       if ($tryoembed && OEmbed::isAllowedURL($data['url'])) {
+                               $return = OEmbed::getHTML($data['url'], $data['title']);
+                       } else {
+                               throw new Exception('OEmbed is disabled for this attachment.');
                        }
-               } elseif (($simplehtml != 4) && ($simplehtml != 0)) {
-                       $return = sprintf('<a href="%s" target="_blank">%s</a><br>', $data['url'], $data['title']);
-               } else {
-                       try {
-                               if ($tryoembed && OEmbed::isAllowedURL($data['url'])) {
-                                       $return = OEmbed::getHTML($data['url'], $data['title']);
-                               } else {
-                                       throw new Exception('OEmbed is disabled for this attachment.');
-                               }
-                       } catch (Exception $e) {
-                               $data['title'] = ($data['title'] ?? '') ?: $data['url'];
+               } catch (Exception $e) {
+                       $data['title'] = ($data['title'] ?? '') ?: $data['url'];
 
-                               if ($simplehtml != 4) {
-                                       $return = sprintf('<div class="type-%s">', $data['type']);
-                               }
+                       if ($simplehtml != 4) {
+                               $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']);
-                                       } 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']);
-                                               } 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('<h4><a href="%s">%s</a></h4>', $data['url'], $data['title']);
+                       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']);
+                               } 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']);
+                                       } 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('<h4><a href="%s">%s</a></h4>', $data['url'], $data['title']);
                                }
+                       }
 
-                               if (!empty($data['description']) && $data['description'] != $data['title']) {
-                                       // Sanitize the HTML by converting it to BBCode
-                                       $bbcode = HTML::toBBCode($data['description']);
-                                       $return .= sprintf('<blockquote>%s</blockquote>', trim(self::convert($bbcode)));
-                               }
+                       if (!empty($data['description']) && $data['description'] != $data['title']) {
+                               // Sanitize the HTML by converting it to BBCode
+                               $bbcode = HTML::toBBCode($data['description']);
+                               $return .= sprintf('<blockquote>%s</blockquote>', trim(self::convert($bbcode)));
+                       }
 
-                               if (!empty($data['url'])) {
-                                       $return .= sprintf('<sup><a href="%s">%s</a></sup>', $data['url'], parse_url($data['url'], PHP_URL_HOST));
-                               }
+                       if (!empty($data['url'])) {
+                               $return .= sprintf('<sup><a href="%s">%s</a></sup>', $data['url'], parse_url($data['url'], PHP_URL_HOST));
+                       }
 
-                               if ($simplehtml != 4) {
-                                       $return .= '</div>';
-                               }
+                       if ($simplehtml != 4) {
+                               $return .= '</div>';
                        }
                }
 
@@ -939,12 +956,11 @@ class BBCode extends BaseObject
        public static function convertShare($text, callable $callback)
        {
                $return = preg_replace_callback(
-                       "/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism",
+                       "/(.*?)\[share(.*?)\](.*)\[\/share\]/ism",
                        function ($match) use ($callback) {
                                $attribute_string = $match[2];
-
                                $attributes = [];
-                               foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) {
+                               foreach (['author', 'profile', 'avatar', 'link', 'posted'] as $field) {
                                        preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches);
                                        $attributes[$field] = html_entity_decode($matches[2] ?? '', ENT_QUOTES, 'UTF-8');
                                }
@@ -1038,9 +1054,12 @@ class BBCode extends BaseObject
                                $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 9: // ActivityPub
                                $text = ($is_quote_share? '<br />' : '') . '<p>' . html_entity_decode('&#x2672; ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '</p>' . "\n";
                                break;
+                       case 9: // 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;
                        default:
                                // Transforms quoted tweets in rich attachments to avoid nested tweets
                                if (stripos(Strings::normaliseLink($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) {
@@ -1222,7 +1241,7 @@ class BBCode extends BaseObject
         * - 5: Unused
         * - 6: Unused
         * - 7: Used for dfrn, OStatus
-        * - 8: Used for WP backlink text setting
+        * - 8: Used for Twitter, WP backlink text setting
         * - 9: ActivityPub
         *
         * @param string $text
@@ -1358,8 +1377,15 @@ class BBCode extends BaseObject
                        } while ($oldtext != $text);
                }
 
+               /// @todo Have a closer look at the different html modes
                // Handle attached links or videos
-               $text = self::convertAttachment($text, $simple_html, $try_oembed);
+               if (in_array($simple_html, [9])) {
+                       $text = self::removeAttachment($text);
+               } elseif (!in_array($simple_html, [0, 4])) {
+                       $text = self::removeAttachment($text, true);
+               } else {
+                       $text = self::convertAttachment($text, $simple_html, $try_oembed);
+               }
 
                // leave open the posibility of [map=something]
                // this is replaced in Item::prepareBody() which has knowledge of the item location
@@ -1481,8 +1507,29 @@ class BBCode extends BaseObject
                $text = str_replace('[hr]', '<hr />', $text);
 
                if (!$for_plaintext) {
+                       $escaped = [];
+
+                       // Escaping BBCodes susceptible to contain rogue URL we don'' want the autolinker to catch
+                       $text = preg_replace_callback('#\[(url|img|audio|video|youtube|vimeo|share|attachment|iframe|bookmark).+?\[/\1\]#ism',
+                               function ($matches) use (&$escaped) {
+                                       $return = '{escaped-' . count($escaped) . '}';
+                                       $escaped[] = $matches[0];
+
+                                       return $return;
+                               },
+                               $text
+                       );
+
                        // Autolinker for isolated URLs
                        $text = preg_replace(Strings::autoLinkRegEx(), '[url]$1[/url]', $text);
+
+                       // Restoring escaped blocks
+                       $text = preg_replace_callback('/{escaped-([0-9]+)}/iU',
+                               function ($matches) use ($escaped) {
+                                       return $escaped[intval($matches[1])] ?? $matches[0];
+                               },
+                               $text
+                       );
                }
 
                // This is actually executed in Item::prepareBody()
@@ -1583,6 +1630,9 @@ class BBCode extends BaseObject
                $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . '$1' . ' ' . L10n::t('Encrypted content') . '" /><br />', $text);
                //$Text = preg_replace("/\[crypt=(.*?)\](.*?)\[\/crypt\]/ism", '<br/><img src="' .System::baseUrl() . '/images/lock_icon.gif" alt="' . L10n::t('Encrypted content') . '" title="' . '$1' . ' ' . L10n::t('Encrypted content') . '" /><br />', $Text);
 
+               // Simplify "video" element
+               $text = preg_replace('(\[video.*?\ssrc\s?=\s?([^\s\]]+).*?\].*?\[/video\])ism', '[video]$1[/video]', $text);
+
                // 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);
@@ -1680,7 +1730,7 @@ class BBCode extends BaseObject
                $text = str_replace(["\r","\n"], ['<br />', '<br />'], $text);
 
                // Remove all hashtag addresses
-               if ((!$try_oembed || $simple_html) && !in_array($simple_html, [3, 7, 9])) {
+               if ($simple_html && !in_array($simple_html, [3, 7, 9])) {
                        $text = preg_replace("/([#@!])\[url\=(.*?)\](.*?)\[\/url\]/ism", '$1$3', $text);
                } elseif ($simple_html == 3) {
                        // The ! is converted to @ since Diaspora only understands the @
@@ -1749,7 +1799,7 @@ class BBCode extends BaseObject
                 * - #[url=<anything>]<term>[/url]
                 * - [url=<anything>]#<term>[/url]
                 */
-               $text = preg_replace_callback("/(?:#\[url\=.*?\]|\[url\=.*?\]#)(.*?)\[\/url\]/ism", function($matches) {
+               $text = preg_replace_callback("/(?:#\[url\=[^\[\]]*\]|\[url\=[^\[\]]*\]#)(.*?)\[\/url\]/ism", function($matches) {
                        return '#<a href="'
                                . System::baseUrl()     . '/search?tag=' . rawurlencode($matches[1])
                                . '" class="tag" rel="tag" title="' . XML::escape($matches[1]) . '">'