From b5fb81f535b7daba255bc3a9750c89b9adcc5abe Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 17 Oct 2018 01:10:07 -0400 Subject: [PATCH 1/4] Add Mastodon/Pleroma protocol guessing --- src/Core/Protocol.php | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/Core/Protocol.php b/src/Core/Protocol.php index d41250a68..949e2350c 100644 --- a/src/Core/Protocol.php +++ b/src/Core/Protocol.php @@ -108,6 +108,13 @@ class Protocol } } + // Mastodon, Pleroma + if (preg_match('=https?://(.+?)/users/(.+)=ism', $profile_url, $matches) + || preg_match('=https?://(.+?)/@(.+)=ism', $profile_url, $matches) + ) { + return self::ACTIVITYPUB; + } + // pumpio (http://host.name/user) if (preg_match('=https?://([\.\w]+)/([\.\w]+)$=ism', $profile_url, $matches)) { return self::PUMPIO; From 45748da8a1d0580e5f1cd81c34ec3b807ed8ea3b Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 17 Oct 2018 01:11:50 -0400 Subject: [PATCH 2/4] Remove network exception in handle_tag --- mod/item.php | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/mod/item.php b/mod/item.php index 053a00c97..4d798ac1e 100644 --- a/mod/item.php +++ b/mod/item.php @@ -1015,11 +1015,7 @@ function handle_tag(App $a, &$body, &$inform, &$str_tags, $profile_uid, $tag, $n $profile = $contact["url"]; $alias = $contact["alias"]; - $newname = $contact["nick"]; - - if (($newname == "") || !in_array($contact["network"], [Protocol::ACTIVITYPUB, Protocol::OSTATUS, Protocol::TWITTER, Protocol::STATUSNET])) { - $newname = $contact["name"]; - } + $newname = defaults($contact, "name", $contact["nick"]); } //if there is an url for this persons profile From 9239ecc5e0207eb5e62bea36f6ee200e4581932c Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 17 Oct 2018 01:13:18 -0400 Subject: [PATCH 3/4] Rework BBCode::convertShare to accept a callback function - Create default convertShare callback from previous code - Remove $simplehtml = 8 case (moved to Twitter addon) --- src/Content/Text/BBCode.php | 243 ++++++++++++++---------------------- 1 file changed, 97 insertions(+), 146 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index c903ee694..f4872e7b3 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -859,187 +859,136 @@ class BBCode extends BaseObject } /** - * Processes [share] tags + * This function converts a [share] block to text according to a provided callback function whose signature is: + * + * function(array $attributes, array $author_contact, string $content): string + * + * Where: + * - $attributes is an array of attributes of the [share] block itself. Missing keys will be completed by the contact + * data lookup + * - $author_contact is a contact record array + * - $content is the inner content of the [share] block + * - Return value is the string that should replace the [share] block in the provided text + * + * This function is intended to be used by addon connector to format a share block like the target network is expecting it. + * + * @param string $text A BBCode string + * @param callable $callback + * @return string The BBCode string with all [share] blocks replaced + */ + public static function convertShare($text, callable $callback) + { + $return = preg_replace_callback( + "/\[share(.*?)\](.*?)\[\/share\]/ism", + function ($match) use ($callback) { + $attribute_string = $match[1]; + + $attributes = []; + foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) { + preg_match("/$field=(['\"])(.+?)\\1/ism", $attribute_string, $matches); + $attributes[$field] = html_entity_decode(defaults($matches, 2, ''), ENT_QUOTES, 'UTF-8'); + } + + // We only call this so that a previously unknown contact can be added. + // This is important for the function "Model\Contact::getDetailsByURL()". + // This function then can fetch an entry from the contact table. + Contact::getIdForURL($attributes['profile'], 0, true); + + $author_contact = Contact::getDetailsByURL($attributes['profile']); + $author_contact['addr'] = defaults($author_contact, 'addr' , Protocol::getAddrFromProfileUrl($attributes['profile'])); + + $attributes['author'] = defaults($author_contact, 'name' , $attributes['author']); + $attributes['avatar'] = defaults($author_contact, 'micro', $attributes['avatar']); + $attributes['profile'] = defaults($author_contact, 'url' , $attributes['profile']); + + if ($attributes['avatar']) { + $attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB); + } + + return $callback($attributes, $author_contact, $match[2]); + }, + $text + ); + + return $return; + } + + /** + * Default [share] tag conversion callback * * Note: Can produce a [bookmark] tag in the output * - * @brief Processes [share] tags - * @param array $share preg_match_callback result array - * @param bool|int $simplehtml + * @see BBCode::convertShare() + * @param array $attributes [share] block attribute values + * @param array $author_contact Contact row of the shared author + * @param string $content Inner content of the [share] block + * @param integer $simplehtml Mysterious integer value depending on the target network/formatting style * @return string */ - private static function convertShare($share, $simplehtml) + private static function convertShareCallback(array $attributes, array $author_contact, $content, $simplehtml) { - $attributes = $share[2]; - - $author = ""; - preg_match("/author='(.*?)'/ism", $attributes, $matches); - if (x($matches, 1)) { - $author = html_entity_decode($matches[1], ENT_QUOTES, 'UTF-8'); - } - - preg_match('/author="(.*?)"/ism', $attributes, $matches); - if (x($matches, 1)) { - $author = $matches[1]; - } - - $profile = ""; - preg_match("/profile='(.*?)'/ism", $attributes, $matches); - if (x($matches, 1)) { - $profile = $matches[1]; - } - - preg_match('/profile="(.*?)"/ism', $attributes, $matches); - if (x($matches, 1)) { - $profile = $matches[1]; - } - - $avatar = ""; - preg_match("/avatar='(.*?)'/ism", $attributes, $matches); - if (x($matches, 1)) { - $avatar = $matches[1]; - } - - preg_match('/avatar="(.*?)"/ism', $attributes, $matches); - if (x($matches, 1)) { - $avatar = $matches[1]; - } - - $link = ""; - preg_match("/link='(.*?)'/ism", $attributes, $matches); - if (x($matches, 1)) { - $link = $matches[1]; - } - - preg_match('/link="(.*?)"/ism', $attributes, $matches); - if (x($matches, 1)) { - $link = $matches[1]; - } - - $posted = ""; - - preg_match("/posted='(.*?)'/ism", $attributes, $matches); - if (x($matches, 1)) { - $posted = $matches[1]; - } - - preg_match('/posted="(.*?)"/ism', $attributes, $matches); - if (x($matches, 1)) { - $posted = $matches[1]; - } - - // We only call this so that a previously unknown contact can be added. - // This is important for the function "Model\Contact::getDetailsByURL()". - // This function then can fetch an entry from the contact table. - Contact::getIdForURL($profile, 0, true); - - $data = Contact::getDetailsByURL($profile); - - if (x($data, "name") && x($data, "addr")) { - $userid_compact = $data["name"] . " (" . $data["addr"] . ")"; - } else { - $userid_compact = Protocol::getAddrFromProfileUrl($profile, $author); - } - - if (x($data, "addr")) { - $userid = $data["addr"]; - } else { - $userid = Protocol::formatMention($profile, $author); - } - - if (x($data, "name")) { - $author = $data["name"]; - } - - if (x($data, "micro")) { - $avatar = $data["micro"]; - } - - $preshare = trim($share[1]); - if ($preshare != "") { - $preshare .= "
"; - } + $mention = Protocol::formatMention($attributes['profile'], $attributes['author']); switch ($simplehtml) { case 1: - $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid . ":
»" . $share[3] . "«"; + $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $mention . ':

' . "\n" . '«' . $content . '»'; break; case 2: - $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ":
" . $share[3]; + $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 3: // Diaspora - $headline = '' . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . $userid . ':
'; + $headline = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . $mention . ':

' . "\n"; - $text = trim($share[1]); - - if ($text != "") { - $text .= "
"; - } - - if (stripos(normalise_link($link), 'http://twitter.com/') === 0) { - $text .= '
' . $link . ''; + if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0) { + $text = '

' . $attributes['link'] . '

' . "\n"; } else { - $text .= $headline . '
' . trim($share[3]) . "

"; + $text = $headline . '
' . trim($content) . '
' . "\n"; - if ($link != "") { - $text .= '
[l]'; + if ($attributes['link'] != '') { + $text .= '

[l]

' . "\n"; } } break; case 4: - $headline = '
' . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8'); - $headline .= L10n::t('%2$s %3$s', $link, $userid, $posted); - $headline .= ":
"; + $headline = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8'); + $headline .= L10n::t('%2$s %3$s', $attributes['link'], $mention, $attributes['posted']); + $headline .= ':

' . "\n"; - $text = trim($share[1]); - - if ($text != "") { - $text .= "
"; - } - - $text .= $headline . '
' . trim($share[3]) . "

"; + $text = $headline . '
' . trim($content) . '
' . "\n"; break; case 5: - $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ":
" . $share[3]; + $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 7: // statusnet/GNU Social - $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . " @" . $userid_compact . ": " . $share[3]; - break; - case 8: // twitter - $text = $preshare . "RT @" . $userid_compact . ": " . $share[3]; + $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '

' . "\n"; break; case 9: // Google+ - $text = $preshare . html_entity_decode("♲ ", ENT_QUOTES, 'UTF-8') . ' ' . $userid_compact . ":
" . $share[3]; + $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n"; + $text .= '

' . $content . '

' . "\n"; - if ($link != "") { - $text .= "

" . $link; + if ($attributes['link'] != '') { + $text .= '

' . $attributes['link'] . '

'; } break; default: // Transforms quoted tweets in rich attachments to avoid nested tweets - if (stripos(normalise_link($link), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($link)) { + if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) { try { - $oembed = OEmbed::getHTML($link, $preshare); + $text = OEmbed::getHTML($attributes['link']); } catch (Exception $e) { - $oembed = sprintf('[bookmark=%s]%s[/bookmark]', $link, $preshare); + $text = sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content); } - - $text = $preshare . $oembed; } else { - $text = trim($share[1]) . "\n"; - - $avatar = ProxyUtils::proxifyUrl($avatar, false, ProxyUtils::SIZE_THUMB); - $tpl = get_markup_template('shared_content.tpl'); - $text .= replace_macros($tpl, [ - '$profile' => $profile, - '$avatar' => $avatar, - '$author' => $author, - '$link' => $link, - '$posted' => $posted, - '$content' => trim($share[3]) + $text = replace_macros($tpl, [ + '$profile' => $attributes['profile'], + '$avatar' => $attributes['avatar'], + '$author' => $attributes['author'], + '$link' => $attributes['link'], + '$posted' => $attributes['posted'], + '$content' => trim($content) ]); } break; @@ -1621,10 +1570,12 @@ class BBCode extends BaseObject $text = preg_replace("/\[zmg\](.*?)\[\/zmg\]/ism", '' . L10n::t('Image/photo') . '', $text); // Shared content - $text = preg_replace_callback("/(.*?)\[share(.*?)\](.*?)\[\/share\]/ism", - function ($match) use ($simple_html) { - return self::convertShare($match, $simple_html); - }, $text); + $text = self::convertShare( + $text, + function (array $attributes, array $author_contact, string $content) use ($simple_html) { + return self::convertShareCallback($attributes, $author_contact, $content, $simple_html); + } + ); $text = preg_replace("/\[crypt\](.*?)\[\/crypt\]/ism", '
' . L10n::t('Encrypted content') . '
', $text); $text = preg_replace("/\[crypt(.*?)\](.*?)\[\/crypt\]/ism", '
' . L10n::t('Encrypted content') . '
', $text); From 057385a4ec8d7d9e5c2a5aa097c9d83f808b2a47 Mon Sep 17 00:00:00 2001 From: Hypolite Petovan Date: Wed, 17 Oct 2018 08:20:56 -0400 Subject: [PATCH 4/4] Add $is_quote_share parameter to convert share callback - Restore former behavior when content is present before [share] block --- src/Content/Text/BBCode.php | 40 ++++++++++++++++++++----------------- 1 file changed, 22 insertions(+), 18 deletions(-) diff --git a/src/Content/Text/BBCode.php b/src/Content/Text/BBCode.php index f4872e7b3..61e48027e 100644 --- a/src/Content/Text/BBCode.php +++ b/src/Content/Text/BBCode.php @@ -861,13 +861,14 @@ class BBCode extends BaseObject /** * This function converts a [share] block to text according to a provided callback function whose signature is: * - * function(array $attributes, array $author_contact, string $content): string + * function(array $attributes, array $author_contact, string $content, boolean $is_quote_share): string * * Where: * - $attributes is an array of attributes of the [share] block itself. Missing keys will be completed by the contact * data lookup * - $author_contact is a contact record array * - $content is the inner content of the [share] block + * - $is_quote_share indicates whether there's any content before the [share] block * - Return value is the string that should replace the [share] block in the provided text * * This function is intended to be used by addon connector to format a share block like the target network is expecting it. @@ -879,9 +880,9 @@ 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[1]; + $attribute_string = $match[2]; $attributes = []; foreach(['author', 'profile', 'avatar', 'link', 'posted'] as $field) { @@ -905,7 +906,7 @@ class BBCode extends BaseObject $attributes['avatar'] = ProxyUtils::proxifyUrl($attributes['avatar'], false, ProxyUtils::SIZE_THUMB); } - return $callback($attributes, $author_contact, $match[2]); + return $callback($attributes, $author_contact, $match[3], trim($match[1]) != ''); }, $text ); @@ -922,27 +923,28 @@ class BBCode extends BaseObject * @param array $attributes [share] block attribute values * @param array $author_contact Contact row of the shared author * @param string $content Inner content of the [share] block + * @param boolean $is_quote_share Whether there is content before the [share] block * @param integer $simplehtml Mysterious integer value depending on the target network/formatting style * @return string */ - private static function convertShareCallback(array $attributes, array $author_contact, $content, $simplehtml) + private static function convertShareCallback(array $attributes, array $author_contact, $content, $is_quote_share, $simplehtml) { $mention = Protocol::formatMention($attributes['profile'], $attributes['author']); switch ($simplehtml) { case 1: - $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $mention . ':

' . "\n" . '«' . $content . '»'; + $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $mention . ':

' . "\n" . '«' . $content . '»'; break; case 2: - $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; + $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 3: // Diaspora $headline = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . $mention . ':

' . "\n"; if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0) { - $text = '

' . $attributes['link'] . '

' . "\n"; + $text = ($is_quote_share? '
' : '') . '

' . $attributes['link'] . '

' . "\n"; } else { - $text = $headline . '
' . trim($content) . '
' . "\n"; + $text = ($is_quote_share? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; if ($attributes['link'] != '') { $text .= '

[l]

' . "\n"; @@ -955,17 +957,17 @@ class BBCode extends BaseObject $headline .= L10n::t('%2$s %3$s', $attributes['link'], $mention, $attributes['posted']); $headline .= ':

' . "\n"; - $text = $headline . '
' . trim($content) . '
' . "\n"; + $text = ($is_quote_share? '
' : '') . $headline . '
' . trim($content) . '
' . "\n"; break; case 5: - $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; + $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n" . $content; break; case 7: // statusnet/GNU Social - $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '

' . "\n"; + $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' @' . $author_contact['addr'] . ': ' . $content . '

' . "\n"; break; case 9: // Google+ - $text = '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n"; + $text = ($is_quote_share? '
' : '') . '

' . html_entity_decode('♲ ', ENT_QUOTES, 'UTF-8') . ' ' . $author_contact['addr'] . ':

' . "\n"; $text .= '

' . $content . '

' . "\n"; if ($attributes['link'] != '') { @@ -976,13 +978,15 @@ class BBCode extends BaseObject // Transforms quoted tweets in rich attachments to avoid nested tweets if (stripos(normalise_link($attributes['link']), 'http://twitter.com/') === 0 && OEmbed::isAllowedURL($attributes['link'])) { try { - $text = OEmbed::getHTML($attributes['link']); + $text = ($is_quote_share? '
' : '') . OEmbed::getHTML($attributes['link']); } catch (Exception $e) { - $text = sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content); + $text = ($is_quote_share? '
' : '') . sprintf('[bookmark=%s]%s[/bookmark]', $attributes['link'], $content); } } else { + $text = ($is_quote_share? "\n" : ''); + $tpl = get_markup_template('shared_content.tpl'); - $text = replace_macros($tpl, [ + $text .= replace_macros($tpl, [ '$profile' => $attributes['profile'], '$avatar' => $attributes['avatar'], '$author' => $attributes['author'], @@ -1572,8 +1576,8 @@ class BBCode extends BaseObject // Shared content $text = self::convertShare( $text, - function (array $attributes, array $author_contact, string $content) use ($simple_html) { - return self::convertShareCallback($attributes, $author_contact, $content, $simple_html); + 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); } );