diff --git a/database.sql b/database.sql index 2528b0145e..87866c2912 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2023.09-dev (Giant Rhubarb) --- DB_UPDATE_VERSION 1520 +-- DB_UPDATE_VERSION 1521 -- ------------------------------------------ @@ -1968,6 +1968,7 @@ CREATE VIEW `post-user-view` AS SELECT `author`.`addr` AS `author-addr`, IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`, `author`.`nick` AS `author-nick`, + `author`.`alias` AS `author-alias`, IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`, `author`.`network` AS `author-network`, `author`.`blocked` AS `author-blocked`, @@ -1980,6 +1981,7 @@ CREATE VIEW `post-user-view` AS SELECT `owner`.`addr` AS `owner-addr`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`, `owner`.`nick` AS `owner-nick`, + `owner`.`alias` AS `owner-alias`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`, `owner`.`network` AS `owner-network`, `owner`.`blocked` AS `owner-blocked`, @@ -2145,6 +2147,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT `author`.`addr` AS `author-addr`, IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`, `author`.`nick` AS `author-nick`, + `author`.`alias` AS `author-alias`, IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`, `author`.`network` AS `author-network`, `author`.`blocked` AS `author-blocked`, @@ -2157,6 +2160,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT `owner`.`addr` AS `owner-addr`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`, `owner`.`nick` AS `owner-nick`, + `owner`.`alias` AS `owner-alias`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`, `owner`.`network` AS `owner-network`, `owner`.`blocked` AS `owner-blocked`, @@ -2308,6 +2312,7 @@ CREATE VIEW `post-view` AS SELECT `author`.`addr` AS `author-addr`, `author`.`name` AS `author-name`, `author`.`nick` AS `author-nick`, + `author`.`alias` AS `author-alias`, `author`.`thumb` AS `author-avatar`, `author`.`network` AS `author-network`, `author`.`blocked` AS `author-blocked`, @@ -2320,6 +2325,7 @@ CREATE VIEW `post-view` AS SELECT `owner`.`addr` AS `owner-addr`, `owner`.`name` AS `owner-name`, `owner`.`nick` AS `owner-nick`, + `owner`.`alias` AS `owner-alias`, `owner`.`thumb` AS `owner-avatar`, `owner`.`network` AS `owner-network`, `owner`.`blocked` AS `owner-blocked`, @@ -2448,6 +2454,7 @@ CREATE VIEW `post-thread-view` AS SELECT `author`.`addr` AS `author-addr`, `author`.`name` AS `author-name`, `author`.`nick` AS `author-nick`, + `author`.`alias` AS `author-alias`, `author`.`thumb` AS `author-avatar`, `author`.`network` AS `author-network`, `author`.`blocked` AS `author-blocked`, @@ -2460,6 +2467,7 @@ CREATE VIEW `post-thread-view` AS SELECT `owner`.`addr` AS `owner-addr`, `owner`.`name` AS `owner-name`, `owner`.`nick` AS `owner-nick`, + `owner`.`alias` AS `owner-alias`, `owner`.`thumb` AS `owner-avatar`, `owner`.`network` AS `owner-network`, `owner`.`blocked` AS `owner-blocked`, diff --git a/mod/photos.php b/mod/photos.php index 233d28a5e7..1b9120576d 100644 --- a/mod/photos.php +++ b/mod/photos.php @@ -117,7 +117,7 @@ function photos_init(App $a) $tpl = Renderer::getMarkupTemplate("photos_head.tpl"); - DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl,[ + DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$ispublic' => DI::l10n()->t('everybody') ]); } @@ -214,13 +214,15 @@ function photos_post(App $a) // get the list of photos we are about to delete if ($visitor) { - $r = DBA::toArray(DBA::p("SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `contact-id` = ? AND `uid` = ? AND `album` = ?", + $r = DBA::toArray(DBA::p( + "SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `contact-id` = ? AND `uid` = ? AND `album` = ?", $visitor, $page_owner_uid, $album )); } else { - $r = DBA::toArray(DBA::p("SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `uid` = ? AND `album` = ?", + $r = DBA::toArray(DBA::p( + "SELECT distinct(`resource-id`) as `rid` FROM `photo` WHERE `uid` = ? AND `album` = ?", DI::userSession()->getLocalUserId(), $album )); @@ -258,7 +260,6 @@ function photos_post(App $a) // same as above but remove single photo if ($visitor) { $condition = ['contact-id' => $visitor, 'uid' => $page_owner_uid, 'resource-id' => DI::args()->getArgv()[3]]; - } else { $condition = ['uid' => DI::userSession()->getLocalUserId(), 'resource-id' => DI::args()->getArgv()[3]]; } @@ -405,7 +406,7 @@ function photos_post(App $a) if (strpos($tag, '@') === 0) { $profile = ''; $contact = null; - $name = substr($tag,1); + $name = substr($tag, 1); if ((strpos($name, '@')) || (strpos($name, 'http://'))) { $newname = $name; @@ -441,13 +442,15 @@ function photos_post(App $a) if ($tagcid) { $contact = DBA::selectFirst('contact', [], ['id' => $tagcid, 'uid' => $page_owner_uid]); } else { - $newname = str_replace('_',' ',$name); + $newname = str_replace('_', ' ', $name); //select someone from this user's contacts by name $contact = DBA::selectFirst('contact', [], ['name' => $newname, 'uid' => $page_owner_uid]); if (!DBA::isResult($contact)) { //select someone by attag or nick and the name passed in - $contact = DBA::selectFirst('contact', [], + $contact = DBA::selectFirst( + 'contact', + [], ['(`attag` = ? OR `nick` = ?) AND `uid` = ?', $name, $name, $page_owner_uid], ['order' => ['attag' => true]] ); @@ -689,11 +692,13 @@ function photos_content(App $a) $uploader = ''; - $ret = ['post_url' => 'profile/' . $user['nickname'] . '/photos', - 'addon_text' => $uploader, - 'default_upload' => true]; + $ret = [ + 'post_url' => 'profile/' . $user['nickname'] . '/photos', + 'addon_text' => $uploader, + 'default_upload' => true + ]; - Hook::callAll('photo_upload_form',$ret); + Hook::callAll('photo_upload_form', $ret); $default_upload_box = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_box.tpl'), []); $default_upload_submit = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_submit.tpl'), [ @@ -705,7 +710,7 @@ function photos_content(App $a) $umf_bytes = Strings::getBytesFromShorthand(ini_get('upload_max_filesize')); // Per Friendica definition a value of '0' means unlimited: - If ($mis_bytes == 0) { + if ($mis_bytes == 0) { $mis_bytes = INF; } @@ -719,7 +724,7 @@ function photos_content(App $a) $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId())); - $o .= Renderer::replaceMacros($tpl,[ + $o .= Renderer::replaceMacros($tpl, [ '$pagename' => DI::l10n()->t('Upload Photos'), '$sessid' => session_id(), '$usage' => $usage_message, @@ -747,7 +752,7 @@ function photos_content(App $a) if ($datatype === 'album') { // if $datum is not a valid hex, redirect to the default page if (is_null($datum) || !Strings::isHex($datum)) { - DI::baseUrl()->redirect('photos/' . $user['nickname']. '/album'); + DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album'); } $album = hex2bin($datum); @@ -756,7 +761,8 @@ function photos_content(App $a) } $total = 0; - $r = DBA::toArray(DBA::p("SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = ? AND `album` = ? + $r = DBA::toArray(DBA::p( + "SELECT `resource-id`, max(`scale`) AS `scale` FROM `photo` WHERE `uid` = ? AND `album` = ? AND `scale` <= 4 $sql_extra GROUP BY `resource-id`", $owner_uid, $album @@ -775,7 +781,8 @@ function photos_content(App $a) $order = 'DESC'; } - $r = DBA::toArray(DBA::p("SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, + $r = DBA::toArray(DBA::p( + "SELECT `resource-id`, ANY_VALUE(`id`) AS `id`, ANY_VALUE(`filename`) AS `filename`, ANY_VALUE(`type`) AS `type`, max(`scale`) AS `scale`, ANY_VALUE(`desc`) as `desc`, ANY_VALUE(`created`) as `created` FROM `photo` WHERE `uid` = ? AND `album` = ? @@ -809,7 +816,7 @@ function photos_content(App $a) $album_e = $album; - $o .= Renderer::replaceMacros($edit_tpl,[ + $o .= Renderer::replaceMacros($edit_tpl, [ '$nametext' => DI::l10n()->t('New album name: '), '$nickname' => $user['nickname'], '$album' => $album_e, @@ -843,16 +850,16 @@ function photos_content(App $a) $desc_e = $rr['desc']; $photos[] = [ - 'id' => $rr['id'], - 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4), - 'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'] + 'id' => $rr['id'], + 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2, 4), + 'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'] . ($order_field === 'created' ? '?order=created' : ''), 'title' => DI::l10n()->t('View Photo'), - 'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' .$ext, - 'alt' => $imgalt_e, - 'desc'=> $desc_e, - 'ext' => $ext, - 'hash'=> $rr['resource-id'], + 'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' . $ext, + 'alt' => $imgalt_e, + 'desc' => $desc_e, + 'ext' => $ext, + 'hash' => $rr['resource-id'], ]; } } @@ -870,7 +877,6 @@ function photos_content(App $a) ]); return $o; - } // Display one photo @@ -955,7 +961,7 @@ function photos_content(App $a) } $tpl = Renderer::getMarkupTemplate('photo_edit_head.tpl'); - DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl,[ + DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ '$prevlink' => $prevlink, '$nextlink' => $nextlink ]); @@ -967,7 +973,7 @@ function photos_content(App $a) if ($nextlink) { $nextlink = [$nextlink, '
']; } - } + } } if (count($ph) == 1) { @@ -1007,12 +1013,12 @@ function photos_content(App $a) } $photo = [ - 'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']], - 'title'=> DI::l10n()->t('View Full Size'), - 'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?_u=' . DateTimeFormat::utcNow('ymdhis'), - 'height' => $hires['height'], - 'width' => $hires['width'], - 'album' => $hires['album'], + 'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']], + 'title' => DI::l10n()->t('View Full Size'), + 'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?_u=' . DateTimeFormat::utcNow('ymdhis'), + 'height' => $hires['height'], + 'width' => $hires['width'], + 'album' => $hires['album'], 'filename' => $hires['filename'], ]; @@ -1079,12 +1085,12 @@ function photos_content(App $a) $edit = Renderer::replaceMacros($edit_tpl, [ '$id' => $ph[0]['id'], - '$album' => ['albname', DI::l10n()->t('New album name'), $album_e,''], + '$album' => ['albname', DI::l10n()->t('New album name'), $album_e, ''], '$caption' => ['desc', DI::l10n()->t('Caption'), $caption_e, ''], '$tags' => ['newtag', DI::l10n()->t('Add a Tag'), "", DI::l10n()->t('Example: @bob, @Barbara_Jensen, @jim@example.com, #California, #camping')], - '$rotate_none' => ['rotate', DI::l10n()->t('Do not rotate'),0,'', true], - '$rotate_cw' => ['rotate', DI::l10n()->t("Rotate CW \x28right\x29"),1,''], - '$rotate_ccw' => ['rotate', DI::l10n()->t("Rotate CCW \x28left\x29"),2,''], + '$rotate_none' => ['rotate', DI::l10n()->t('Do not rotate'), 0, '', true], + '$rotate_cw' => ['rotate', DI::l10n()->t("Rotate CW \x28right\x29"), 1, ''], + '$rotate_ccw' => ['rotate', DI::l10n()->t("Rotate CCW \x28left\x29"), 2, ''], '$nickname' => $user['nickname'], '$resource_id' => $ph[0]['resource-id'], @@ -1179,7 +1185,7 @@ function photos_content(App $a) $qcomment = $words ? explode("\n", $words) : []; } - $comments .= Renderer::replaceMacros($cmnt_tpl,[ + $comments .= Renderer::replaceMacros($cmnt_tpl, [ '$return_path' => '', '$jsreload' => $return_path, '$id' => $link_item['id'], @@ -1203,13 +1209,19 @@ function photos_content(App $a) $activity = DI::activity(); if (($activity->match($item['verb'], Activity::LIKE) || - $activity->match($item['verb'], Activity::DISLIKE)) && - ($item['gravity'] != Item::GRAVITY_PARENT)) { + $activity->match($item['verb'], Activity::DISLIKE)) && + ($item['gravity'] != Item::GRAVITY_PARENT) + ) { continue; } - $author = ['uid' => 0, 'id' => $item['author-id'], - 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'] + ]; $profile_url = Contact::magicLinkByContact($author); if (strpos($profile_url, 'contact/redir/') === 0) { $sparkle = ' sparkle'; @@ -1228,7 +1240,7 @@ function photos_content(App $a) $title_e = $item['title']; $body_e = BBCode::convertForUriId($item['uri-id'], $item['body']); - $comments .= Renderer::replaceMacros($template,[ + $comments .= Renderer::replaceMacros($template, [ '$id' => $item['id'], '$profile_url' => $profile_url, '$name' => $item['author-name'], diff --git a/src/Content/Conversation.php b/src/Content/Conversation.php index be858e05dc..93dad904a2 100644 --- a/src/Content/Conversation.php +++ b/src/Content/Conversation.php @@ -154,7 +154,8 @@ class Conversation 'uid' => 0, 'id' => $activity['author-id'], 'network' => $activity['author-network'], - 'url' => $activity['author-link'] + 'url' => $activity['author-link'], + 'alias' => $activity['author-alias'], ]; $url = Contact::magicLinkByContact($author); if (strpos($url, 'contact/redir/') === 0) { @@ -272,7 +273,7 @@ class Conversation $phrase = $this->l10n->tt(' attends', ' attend', $total, $spanatts); break; case 'attendno': - $phrase = $this->l10n->tt(' doesn\'t attend',' don\'t attend', $total, $spanatts); + $phrase = $this->l10n->tt(' doesn\'t attend', ' don\'t attend', $total, $spanatts); break; case 'attendmaybe': $phrase = $this->l10n->tt(' attends maybe', ' attend maybe', $total, $spanatts); @@ -538,7 +539,7 @@ class Conversation if (!$update) { $live_update_div = '' . "\r\n" . "\r\n"; + . "?f='; \r\n"; } } elseif ($mode === self::MODE_SEARCH) { $live_update_div = '' . "\r\n"; @@ -626,7 +627,13 @@ class Conversation $tags = Tag::populateFromItem($item); - $author = ['uid' => 0, 'id' => $item['author-id'], 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'], + ]; $profile_link = Contact::magicLinkByContact($author); $sparkle = ''; @@ -857,7 +864,8 @@ class Conversation $row['causer-avatar'] = $contact['thumb']; $row['causer-name'] = $contact['name']; } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) && - ($row['author-id'] == $activity['causer-id'])) { + ($row['author-id'] == $activity['causer-id']) + ) { return $row; } } @@ -893,9 +901,15 @@ class Conversation } if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) { - $causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']]; + $causer = [ + 'uid' => 0, + 'id' => $row['causer-id'], + 'network' => $row['causer-network'], + 'url' => $row['causer-link'], + 'alias' => $row['causer-alias'], + ]; - $row['reshared'] = $this->l10n->t('%s reshared this.', '' . htmlentities($row['causer-name']) . ''); + $row['reshared'] = $this->l10n->t('%s reshared this.', '' . htmlentities($row['causer-name']) . ''); } $row['direction'] = ['direction' => 3, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Reshared') : $this->l10n->t('Reshared by %s <%s>', $row['causer-name'], $row['causer-link']))]; break; @@ -995,15 +1009,21 @@ class Conversation $condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]); } - $condition = DBA::mergeConditions($condition, - ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]); + $condition = DBA::mergeConditions( + $condition, + ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)] + ); $condition = DBA::mergeConditions($condition, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]); - $condition = DBA::mergeConditions($condition, - ["`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked` + $condition = DBA::mergeConditions( + $condition, + [ + "`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked` AND ((NOT `contact-pending` AND (`contact-rel` IN (?, ?))) OR `self` OR `contact-uid` = ?)", - Contact::SHARING, Contact::FRIEND, 0]); + Contact::SHARING, Contact::FRIEND, 0 + ] + ); $thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]); @@ -1110,8 +1130,10 @@ class Conversation $items[$key]['user-collapsed-author'] = !$always_display && in_array($row['author-id'], $collapses); $items[$key]['user-collapsed-owner'] = !$always_display && in_array($row['owner-id'], $collapses); - if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_NETWORK]) && - (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))) { + if ( + in_array($mode, [self::MODE_COMMUNITY, self::MODE_NETWORK]) && + (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores)) + ) { unset($items[$key]); } } @@ -1146,7 +1168,7 @@ class Conversation $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]); $separator = chr(255) . chr(255) . chr(255); - $sql = "SELECT `thr-parent-id`, `body`, `verb`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '". $separator ."' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`"; + $sql = "SELECT `thr-parent-id`, `body`, `verb`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '" . $separator . "' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`"; $emojis = []; @@ -1287,7 +1309,7 @@ class Conversation // Searches the post item in the children $j = 0; while ($child['children'][$j]['verb'] !== Activity::POST && $j < count($child['children'])) { - $j ++; + $j++; } $moved_item = $child['children'][$j]; @@ -1361,8 +1383,10 @@ class Conversation * items and add them as children of their top-level post. */ foreach ($parents as $i => $parent) { - $parents[$i]['children'] = array_merge($this->getItemChildren($item_array, $parent, true), - $this->getItemChildren($item_array, $parent, false)); + $parents[$i]['children'] = array_merge( + $this->getItemChildren($item_array, $parent, true), + $this->getItemChildren($item_array, $parent, false) + ); } foreach ($parents as $i => $parent) { diff --git a/src/Content/GroupManager.php b/src/Content/GroupManager.php index d7848b41bf..fc43080d90 100644 --- a/src/Content/GroupManager.php +++ b/src/Content/GroupManager.php @@ -78,7 +78,7 @@ class GroupManager $groupList = []; - $fields = ['id', 'url', 'name', 'micro', 'thumb', 'avatar', 'network', 'uid']; + $fields = ['id', 'url', 'alias', 'name', 'micro', 'thumb', 'avatar', 'network', 'uid']; $contacts = DBA::select('account-user-view', $fields, $condition, $params); if (!$contacts) { return $groupList; @@ -87,6 +87,7 @@ class GroupManager while ($contact = DBA::fetch($contacts)) { $groupList[] = [ 'url' => $contact['url'], + 'alias' => $contact['alias'], 'name' => $contact['name'], 'id' => $contact['id'], 'micro' => $contact['micro'], diff --git a/src/Content/Item.php b/src/Content/Item.php index 3981978f4e..49193627f6 100644 --- a/src/Content/Item.php +++ b/src/Content/Item.php @@ -305,18 +305,20 @@ class Item } $author_arr = [ - 'uid' => 0, - 'id' => $item['author-id'], + 'uid' => 0, + 'id' => $item['author-id'], 'network' => $item['author-network'], - 'url' => $item['author-link'], + 'url' => $item['author-link'], + 'alias' => $item['author-lias'], ]; $author = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $item['author-name'] . '[/url]'; $author_arr = [ - 'uid' => 0, - 'id' => $obj['author-id'], + 'uid' => 0, + 'id' => $obj['author-id'], 'network' => $obj['author-network'], - 'url' => $obj['author-link'], + 'url' => $obj['author-link'], + 'alias' => $obj['author-alias'], ]; $objauthor = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $obj['author-name'] . '[/url]'; @@ -372,10 +374,11 @@ class Item } $author = [ - 'uid' => 0, - 'id' => $item['author-id'], + 'uid' => 0, + 'id' => $item['author-id'], 'network' => $item['author-network'], - 'url' => $item['author-link'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'], ]; $profile_link = Contact::magicLinkByContact($author, $item['author-link']); if (strpos($profile_link, 'contact/redir/') === 0) { diff --git a/src/Content/Text/HTML.php b/src/Content/Text/HTML.php index 01151a6a52..d30e7ead03 100644 --- a/src/Content/Text/HTML.php +++ b/src/Content/Text/HTML.php @@ -422,7 +422,8 @@ class HTML { $URLSearchString = "^\[\]"; - $matches = ["/\[url\=([$URLSearchString]*)\].*?\[\/url\]/ism", + $matches = [ + "/\[url\=([$URLSearchString]*)\].*?\[\/url\]/ism", "/\[url\]([$URLSearchString]*)\[\/url\]/ism", "/\[img\=[0-9]*x[0-9]*\](.*?)\[\/img\]/ism", "/\[img\](.*?)\[\/img\]/ism", @@ -531,8 +532,10 @@ class HTML $ignore = false; // A list of some links that should be ignored - $list = ["/user/", "/tag/", "/group/", "/circle/", "/profile/", "/search?search=", "/search?tag=", "mailto:", "/u/", "/node/", - "//plus.google.com/", "//twitter.com/"]; + $list = [ + "/user/", "/tag/", "/group/", "/circle/", "/profile/", "/search?search=", "/search?tag=", "mailto:", "/u/", "/node/", + "//plus.google.com/", "//twitter.com/" + ]; foreach ($list as $listitem) { if (strpos($treffer[1], $listitem) !== false) { $ignore = true; @@ -941,7 +944,8 @@ class HTML $domain = '(?:(?!-)[A-Za-z0-9-]{1,63}(?set('URI.SafeIframeRegexp', + $config->set( + 'URI.SafeIframeRegexp', '%^https://(?: ' . implode('|', $allowedIframeDomains) . ' ) @@ -1050,7 +1054,8 @@ class HTML if (isset($mediaType->parameters['charset'])) { return strtolower($mediaType->parameters['charset']); } - } catch(\InvalidArgumentException $e) {} + } catch (\InvalidArgumentException $e) { + } return null; } diff --git a/src/Content/Widget.php b/src/Content/Widget.php index c18a3041b0..c8c0230c68 100644 --- a/src/Content/Widget.php +++ b/src/Content/Widget.php @@ -476,7 +476,6 @@ class Widget $nextday = substr($nextday, 4); $dnow = substr($dnow, 0, 8) . '01'; $dthen = substr($dthen, 0, 8) . '01'; - /* * Starting with the current month, get the first and last days of every @@ -496,7 +495,6 @@ class Widget $ret[$dyear][] = [$str, $end_month, $start_month]; $dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d'); - } } @@ -508,7 +506,7 @@ class Widget $cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years; $cutoff = array_key_exists($cutoff_year, $ret); - $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/posted_date.tpl'),[ + $o = Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/posted_date.tpl'), [ '$title' => DI::l10n()->t('Archives'), '$size' => $visible_years, '$cutoff_year' => $cutoff_year, @@ -543,7 +541,14 @@ class Widget ['ref' => 'community', 'name' => DI::l10n()->t('Groups')], ]; - return self::filter('accounttype', DI::l10n()->t('Account Types'), '', - DI::l10n()->t('All'), $base, $accounts, $accounttype); + return self::filter( + 'accounttype', + DI::l10n()->t('Account Types'), + '', + DI::l10n()->t('All'), + $base, + $accounts, + $accounttype + ); } } diff --git a/src/Content/Widget/ContactBlock.php b/src/Content/Widget/ContactBlock.php index e206dbcb4e..fbc492eaad 100644 --- a/src/Content/Widget/ContactBlock.php +++ b/src/Content/Widget/ContactBlock.php @@ -104,7 +104,7 @@ class ContactBlock $contact_uriids = array_column($personal_contacts, 'uri-id'); if (!empty($contact_uriids)) { - $contacts_stmt = DBA::select('contact', ['id', 'uid', 'addr', 'url', 'name', 'thumb', 'avatar', 'network'], ['uri-id' => $contact_uriids, 'uid' => $contact_uid]); + $contacts_stmt = DBA::select('contact', ['id', 'uid', 'addr', 'url', 'alias', 'name', 'thumb', 'avatar', 'network'], ['uri-id' => $contact_uriids, 'uid' => $contact_uid]); if (DBA::isResult($contacts_stmt)) { $contacts_title = DI::l10n()->tt('%d Contact', '%d Contacts', $total); diff --git a/src/Content/Widget/VCard.php b/src/Content/Widget/VCard.php index e462ab0aaa..62a8e90853 100644 --- a/src/Content/Widget/VCard.php +++ b/src/Content/Widget/VCard.php @@ -29,6 +29,7 @@ use Friendica\Core\Renderer; use Friendica\Core\System; use Friendica\DI; use Friendica\Model\Contact; +use Friendica\Util\Network; use Friendica\Util\Strings; /** @@ -50,9 +51,15 @@ class VCard Logger::warning('Incomplete contact', ['contact' => $contact ?? [], 'callstack' => System::callstack(20)]); } + if (!Network::isValidHttpUrl($contact['url']) && Network::isValidHttpUrl($contact['alias'])) { + $contact_url = $contact['alias']; + } else { + $contact_url = $contact['url']; + } + if ($contact['network'] != '') { - $network_link = Strings::formatNetworkName($contact['network'], $contact['url']); - $network_avatar = ContactSelector::networkToIcon($contact['network'], $contact['url']); + $network_link = Strings::formatNetworkName($contact['network'], $contact_url); + $network_avatar = ContactSelector::networkToIcon($contact['network'], $contact_url); } else { $network_link = ''; $network_avatar = ''; @@ -83,9 +90,9 @@ class VCard if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) { if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) { - $unfollow_link = 'contact/unfollow?url=' . urlencode($contact['url']) . '&auto=1'; + $unfollow_link = 'contact/unfollow?url=' . urlencode($contact_url) . '&auto=1'; } elseif (!$pending) { - $follow_link = 'contact/follow?url=' . urlencode($contact['url']) . '&auto=1'; + $follow_link = 'contact/follow?url=' . urlencode($contact_url) . '&auto=1'; } } @@ -97,7 +104,7 @@ class VCard return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [ '$contact' => $contact, '$photo' => $photo, - '$url' => Contact::magicLinkByContact($contact, $contact['url']), + '$url' => Contact::magicLinkByContact($contact, $contact_url), '$about' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'] ?? ''), '$xmpp' => DI::l10n()->t('XMPP:'), '$matrix' => DI::l10n()->t('Matrix:'), diff --git a/src/Model/APContact.php b/src/Model/APContact.php index dc062fe5b5..2a5b89928f 100644 --- a/src/Model/APContact.php +++ b/src/Model/APContact.php @@ -119,6 +119,11 @@ class APContact return []; } + if (!Network::isValidHttpUrl($url) && !filter_var($url, FILTER_VALIDATE_EMAIL)) { + Logger::info('Invalid URL', ['url' => $url]); + return []; + } + $fetched_contact = []; if (empty($update)) { diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 925cc81798..d079a4ef62 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -23,7 +23,7 @@ namespace Friendica\Model; use Friendica\Contact\Avatar; use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException; -use Friendica\Content\Conversation As ConversationContent; +use Friendica\Content\Conversation as ConversationContent; use Friendica\Content\Pager; use Friendica\Content\Text\HTML; use Friendica\Core\Hook; @@ -111,12 +111,12 @@ class Contact * @} */ - const MIRROR_DEACTIVATED = 0; - const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST - const MIRROR_OWN_POST = 2; - const MIRROR_NATIVE_RESHARE = 3; + const MIRROR_DEACTIVATED = 0; + const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST + const MIRROR_OWN_POST = 2; + const MIRROR_NATIVE_RESHARE = 3; - /** + /** * @param array $fields Array of selected fields, empty for all * @param array $condition Array of fields for condition * @param array $params Array of several parameters @@ -725,8 +725,11 @@ class Contact */ public static function createSelfFromUserId(int $uid): bool { - $user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'], - ['uid' => $uid, 'account_expired' => false]); + $user = DBA::selectFirst( + 'user', + ['uid', 'username', 'nickname', 'pubkey', 'prvkey'], + ['uid' => $uid, 'account_expired' => false] + ); if (!DBA::isResult($user)) { return false; } @@ -786,9 +789,11 @@ class Contact */ public static function updateSelfFromUserID(int $uid, bool $update_avatar = false): bool { - $fields = ['id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve', + $fields = [ + 'id', 'uri-id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve', 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable', - 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network']; + 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network' + ]; $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); if (!DBA::isResult($self)) { return false; @@ -800,8 +805,10 @@ class Contact return false; } - $fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', - 'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish']; + $fields = [ + 'name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', + 'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish' + ]; $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]); if (!DBA::isResult($profile)) { return false; @@ -830,7 +837,7 @@ class Contact 'addr' => $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3), 'request' => DI::baseUrl() . '/dfrn_request/' . $user['nickname'], 'notify' => DI::baseUrl() . '/dfrn_notify/' . $user['nickname'], - 'poll' => DI::baseUrl() . '/dfrn_poll/'. $user['nickname'], + 'poll' => DI::baseUrl() . '/dfrn_poll/' . $user['nickname'], 'confirm' => DI::baseUrl() . '/dfrn_confirm/' . $user['nickname'], ]; @@ -850,7 +857,7 @@ class Contact // We are adding a timestamp value so that other systems won't use cached content $timestamp = strtotime($fields['avatar-date']); - $prefix = DI::baseUrl() . '/photo/' .$avatar['resource-id'] . '-'; + $prefix = DI::baseUrl() . '/photo/' . $avatar['resource-id'] . '-'; $suffix = '.' . $file_suffix . '?ts=' . $timestamp; $fields['photo'] = $prefix . '4' . $suffix; @@ -1188,22 +1195,22 @@ class Contact */ if (empty($contact['uid'])) { $menu = [ - 'profile' => [DI::l10n()->t('View Profile') , $profile_link , true ], - 'network' => [DI::l10n()->t('Network Posts') , $posts_link , false], - 'edit' => [DI::l10n()->t('View Contact') , $contact_url , false], - 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true ], - 'unfollow' => [DI::l10n()->t('Unfollow') , $unfollow_link, true ], + 'profile' => [DI::l10n()->t('View Profile'), $profile_link, true], + 'network' => [DI::l10n()->t('Network Posts'), $posts_link, false], + 'edit' => [DI::l10n()->t('View Contact'), $contact_url, false], + 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link, true], + 'unfollow' => [DI::l10n()->t('Unfollow'), $unfollow_link, true], ]; } else { $menu = [ - 'status' => [DI::l10n()->t('View Status') , $status_link , true ], - 'profile' => [DI::l10n()->t('View Profile') , $profile_link , true ], - 'photos' => [DI::l10n()->t('View Photos') , $photos_link , true ], - 'network' => [DI::l10n()->t('Network Posts') , $posts_link , false], - 'edit' => [DI::l10n()->t('View Contact') , $contact_url , false], - 'pm' => [DI::l10n()->t('Send PM') , $pm_url , false], - 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true ], - 'unfollow' => [DI::l10n()->t('Unfollow') , $unfollow_link, true ], + 'status' => [DI::l10n()->t('View Status'), $status_link, true], + 'profile' => [DI::l10n()->t('View Profile'), $profile_link, true], + 'photos' => [DI::l10n()->t('View Photos'), $photos_link, true], + 'network' => [DI::l10n()->t('Network Posts'), $posts_link, false], + 'edit' => [DI::l10n()->t('View Contact'), $contact_url, false], + 'pm' => [DI::l10n()->t('Send PM'), $pm_url, false], + 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link, true], + 'unfollow' => [DI::l10n()->t('Unfollow'), $unfollow_link, true], ]; if (!empty($contact['pending'])) { @@ -1310,9 +1317,11 @@ class Contact if (($uid == 0) && (empty($data['network']) || ($data['network'] == Protocol::PHANTOM))) { // Fetch data for the public contact via the first found personal contact /// @todo Check if this case can happen at all (possibly with mail accounts?) - $fields = ['name', 'nick', 'url', 'addr', 'alias', 'avatar', 'header', 'contact-type', + $fields = [ + 'name', 'nick', 'url', 'addr', 'alias', 'avatar', 'header', 'contact-type', 'keywords', 'location', 'about', 'unsearchable', 'batch', 'notify', 'poll', - 'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid']; + 'request', 'confirm', 'poco', 'subscribe', 'network', 'baseurl', 'gsid' + ]; $personal_contact = DBA::selectFirst('contact', $fields, ["`addr` = ? AND `uid` != 0", $url]); if (!DBA::isResult($personal_contact)) { @@ -1458,7 +1467,7 @@ class Contact if (!empty($contact['batch'])) { $condition = ['archive' => true, 'uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY]; return DBA::exists('contact', $condition); - } + } return false; } @@ -1551,11 +1560,15 @@ class Contact $contact_field = ((($contact["contact-type"] == self::TYPE_COMMUNITY) || ($contact['network'] == Protocol::MAIL)) ? 'owner-id' : 'author-id'); if ($thread_mode) { - $condition = ["((`$contact_field` = ? AND `gravity` = ?) OR (`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `protocol` != ? AND `thr-parent-id` = `parent-uri-id`)) AND " . $sql, - $cid, Item::GRAVITY_PARENT, $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA, DI::userSession()->getLocalUserId()]; + $condition = [ + "((`$contact_field` = ? AND `gravity` = ?) OR (`author-id` = ? AND `gravity` = ? AND `vid` = ? AND `protocol` != ? AND `thr-parent-id` = `parent-uri-id`)) AND " . $sql, + $cid, Item::GRAVITY_PARENT, $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA, DI::userSession()->getLocalUserId() + ]; } else { - $condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql, - $cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId()]; + $condition = [ + "`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql, + $cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId() + ]; } if (!empty($parent)) { @@ -1568,16 +1581,26 @@ class Contact } if ($only_media) { - $condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))", - Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]); + $condition = DBA::mergeConditions($condition, [ + "`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))", + Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO + ]); } if (DI::mode()->isMobile()) { - $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', - DI::config()->get('system', 'itemspage_network_mobile')); + $itemsPerPage = DI::pConfig()->get( + DI::userSession()->getLocalUserId(), + 'system', + 'itemspage_mobile_network', + DI::config()->get('system', 'itemspage_network_mobile') + ); } else { - $itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', - DI::config()->get('system', 'itemspage_network')); + $itemsPerPage = DI::pConfig()->get( + DI::userSession()->getLocalUserId(), + 'system', + 'itemspage_network', + DI::config()->get('system', 'itemspage_network') + ); } $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); @@ -1611,8 +1634,10 @@ class Contact if ($pager->getStart() == 0) { $cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId()); if (!empty($cdata['public'])) { - $condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", - $cdata['public'], Post\Collection::FEATURED]; + $condition = [ + "`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", + $cdata['public'], Post\Collection::FEATURED + ]; $pinned = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params)); $items = array_merge($pinned, $items); } @@ -1719,7 +1744,7 @@ class Contact } elseif (DI::config()->get('system', 'avatar_cache') && (empty($contact['photo']) || empty($contact['thumb']) || empty($contact['micro']))) { Logger::info('Adding avatar cache file', ['id' => $cid, 'contact' => $contact]); self::updateAvatar($cid, $contact['avatar'], true); - return; + return; } } @@ -2121,8 +2146,10 @@ class Contact */ public static function getAvatarUrlForUrl(string $url, int $uid, string $size = ''): string { - $condition = ["`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)", - Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0]; + $condition = [ + "`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)", + Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0 + ]; $contact = self::selectFirst(['id', 'updated'], $condition, ['order' => ['uid' => true]]); return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? ''); } @@ -2193,8 +2220,11 @@ class Contact */ public static function updateAvatar(int $cid, string $avatar, bool $force = false, bool $create_cache = false) { - $contact = DBA::selectFirst('contact', ['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'], - ['id' => $cid, 'self' => false]); + $contact = DBA::selectFirst( + 'contact', + ['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'], + ['id' => $cid, 'self' => false] + ); if (!DBA::isResult($contact)) { return; } @@ -2268,10 +2298,12 @@ class Contact } if ($default_avatar && Proxy::isLocalImage($avatar)) { - $fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), + $fields = [ + 'avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), 'photo' => $avatar, 'thumb' => self::getDefaultAvatar($contact, Proxy::SIZE_THUMB), - 'micro' => self::getDefaultAvatar($contact, Proxy::SIZE_MICRO)]; + 'micro' => self::getDefaultAvatar($contact, Proxy::SIZE_MICRO) + ]; Logger::debug('Use default avatar', ['id' => $cid, 'uid' => $uid]); } @@ -2330,8 +2362,11 @@ class Contact $uids = []; if (($uid == 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { // Collect all user contacts of the given public contact - $personal_contacts = DBA::select('contact', ['id', 'uid'], - ["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid]); + $personal_contacts = DBA::select( + 'contact', + ['id', 'uid'], + ["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid] + ); while ($personal_contact = DBA::fetch($personal_contacts)) { $cids[] = $personal_contact['id']; $uids[] = $personal_contact['uid']; @@ -2650,10 +2685,12 @@ class Contact // These fields aren't updated by this routine: // 'sensitive' - $fields = ['uid', 'uri-id', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe', + $fields = [ + 'uid', 'uri-id', 'avatar', 'header', 'name', 'nick', 'location', 'keywords', 'about', 'subscribe', 'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item', 'xmpp', 'matrix', - 'created', 'last-update']; + 'created', 'last-update' + ]; $contact = DBA::selectFirst('contact', $fields, ['id' => $id]); if (!DBA::isResult($contact)) { return false; @@ -2677,7 +2714,7 @@ class Contact return true; } - $has_local_data = self::hasLocalData($id, $contact); + $has_local_data = self::hasLocalData($id, $contact); $uid = $contact['uid']; unset($contact['uid']); @@ -2724,7 +2761,8 @@ class Contact // We must not try to update relay contacts via probe. They are no real contacts. // We check after the probing to be able to correct falsely detected contact types. if (($contact['contact-type'] == self::TYPE_RELAY) && - (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM]))) { + (!Strings::compareLink($ret['url'], $contact['url']) || in_array($ret['network'], [Protocol::FEED, Protocol::PHANTOM])) + ) { self::updateContact($id, $uid, $uriid, $contact['url'], ['failed' => false, 'local-data' => $has_local_data, 'last-update' => $updated, 'next-update' => $success_next_update, 'success_update' => $updated]); Logger::info('Not updating relais', ['id' => $id, 'url' => $contact['url']]); return true; @@ -2773,7 +2811,7 @@ class Contact } $update = false; - $guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url'], $ret['baseurl'] ?: $ret['alias']); + $guid = ($ret['guid'] ?? '') ?: Item::guidFromUri($ret['url'], $ret['baseurl'] ?? $ret['alias']); // make sure to not overwrite existing values with blank entries except some technical fields $keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl']; @@ -3078,11 +3116,11 @@ class Contact 'protocol' => $protocol, 'pubkey' => $ret['pubkey'], 'rel' => $new_relation, - 'priority'=> $ret['priority'], - 'writable'=> $writeable, + 'priority' => $ret['priority'], + 'writable' => $writeable, 'hidden' => $hidden, 'blocked' => 0, - 'readonly'=> 0, + 'readonly' => 0, 'pending' => $pending, 'subhub' => $subhub ]); @@ -3172,8 +3210,10 @@ class Contact } // Contact is blocked at user-level - if (!empty($contact['id']) && !empty($importer['id']) && - Contact\User::isBlocked($contact['id'], $importer['id'])) { + if ( + !empty($contact['id']) && !empty($importer['id']) && + Contact\User::isBlocked($contact['id'], $importer['id']) + ) { return false; } @@ -3181,9 +3221,12 @@ class Contact self::unmarkForArchival($contact); if (($contact['rel'] == self::SHARING) - || ($sharing && $contact['rel'] == self::FOLLOWER)) { - self::update(['rel' => self::FRIEND, 'writable' => true, 'pending' => false], - ['id' => $contact['id'], 'uid' => $importer['uid']]); + || ($sharing && $contact['rel'] == self::FOLLOWER) + ) { + self::update( + ['rel' => self::FRIEND, 'writable' => true, 'pending' => false], + ['id' => $contact['id'], 'uid' => $importer['uid']] + ); } // Ensure to always have the correct network type, independent from the connection request method @@ -3422,7 +3465,7 @@ class Contact */ public static function magicLinkById(int $cid, string $url = ''): string { - $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'uid'], ['id' => $cid]); + $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], ['id' => $cid]); return self::magicLinkByContact($contact, $url); } @@ -3439,7 +3482,7 @@ class Contact */ public static function magicLinkByContact(array $contact, string $url = ''): string { - $destination = $url ?: $contact['url']; + $destination = $url ?: (!Network::isValidHttpUrl($contact['url']) && !empty($contact['alias']) && Network::isValidHttpUrl($contact['alias']) ? $contact['alias'] : $contact['url']); if (!DI::userSession()->isAuthenticated()) { return $destination; @@ -3568,8 +3611,10 @@ class Contact $params['limit'] = $limit; } - $condition = DBA::mergeConditions($condition, - ["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]); + $condition = DBA::mergeConditions( + $condition, + ["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search] + ); return DBA::selectToArray('account-user-view', [], $condition, $params); } @@ -3619,7 +3664,7 @@ class Contact */ public static function getRandomContact(): array { - $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'uid'], [ + $contact = DBA::selectFirst('contact', ['id', 'network', 'url', 'alias', 'uid'], [ "`uid` = ? AND `network` = ? AND NOT `failed` AND `last-item` > ?", 0, Protocol::DFRN, DateTimeFormat::utc('now - 1 month'), ], ['order' => ['RAND()']]); diff --git a/src/Model/Event.php b/src/Model/Event.php index dc818d8f15..bc03370647 100644 --- a/src/Model/Event.php +++ b/src/Model/Event.php @@ -553,7 +553,8 @@ class Event WHERE `event`.`id` = ? AND `event`.`uid` = ? $sql_perms", - $event_id, $owner_uid + $event_id, + $owner_uid )); if (empty($events)) { throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.')); @@ -616,7 +617,8 @@ class Event AND `start` <= ? $sql_perms", $owner_uid, - $start, $start, + $start, + $start, $finish )); @@ -661,9 +663,9 @@ class Event $copy = null; $drop = null; if (DI::userSession()->getLocalUserId() && DI::userSession()->getLocalUserId() == $event['uid'] && $event['type'] == 'event') { - $edit = !$event['cid'] ? ['calendar/event/edit/' . $event['id'], DI::l10n()->t('Edit event') , '', ''] : null; - $copy = !$event['cid'] ? ['calendar/event/copy/' . $event['id'] , DI::l10n()->t('Duplicate event'), '', ''] : null; - $drop = ['calendar/api/delete/' . $event['id'] , DI::l10n()->t('Delete event') , '', '']; + $edit = !$event['cid'] ? ['calendar/event/edit/' . $event['id'], DI::l10n()->t('Edit event'), '', ''] : null; + $copy = !$event['cid'] ? ['calendar/event/copy/' . $event['id'], DI::l10n()->t('Duplicate event'), '', ''] : null; + $drop = ['calendar/api/delete/' . $event['id'], DI::l10n()->t('Delete event'), '', '']; } $title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary'])); @@ -708,7 +710,7 @@ class Event } switch ($format) { - // Format the exported data as a CSV file. + // Format the exported data as a CSV file. case "csv": $o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL; @@ -728,7 +730,7 @@ class Event } break; - // Format the exported data as a ics file. + // Format the exported data as a ics file. case "ical": $o = 'BEGIN:VCALENDAR' . PHP_EOL . 'VERSION:2.0' . PHP_EOL @@ -929,8 +931,13 @@ class Event $location = self::locationToArray($item['event-location']); // Construct the profile link (magic-auth). - $author = ['uid' => 0, 'id' => $item['author-id'], - 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'] + ]; $profile_link = Contact::magicLinkByContact($author); $tpl = Renderer::getMarkupTemplate('event_stream_item.tpl'); diff --git a/src/Model/Item.php b/src/Model/Item.php index cdeb6d3d13..17ac27cc3c 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -94,7 +94,7 @@ class Item 'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language', 'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object', 'quote-uri', 'quote-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'mention', 'global', - 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id', + 'author-id', 'author-link', 'author-alias', 'author-name', 'author-avatar', 'author-network', 'author-updated', 'author-gsid', 'author-addr', 'author-uri-id', 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'owner-network', 'owner-contact-type', 'owner-updated', 'causer-id', 'causer-link', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network', 'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar', @@ -108,31 +108,35 @@ class Item ]; // Field list that is used to deliver items via the protocols - const DELIVER_FIELDLIST = ['uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', - 'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target', - 'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app', - 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', - 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', - 'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', - 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin', - 'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail', - 'event-created', 'event-edited', 'event-start', 'event-finish', - 'event-summary', 'event-desc', 'event-location', 'event-type', - 'event-nofinish', 'event-ignore', 'event-id']; + const DELIVER_FIELDLIST = [ + 'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid', + 'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target', + 'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app', + 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', + 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', + 'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', + 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin', + 'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail', + 'event-created', 'event-edited', 'event-start', 'event-finish', + 'event-summary', 'event-desc', 'event-location', 'event-type', + 'event-nofinish', 'event-ignore', 'event-id' + ]; // All fields in the item table - const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', - 'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid', - 'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid', - 'created', 'edited', 'commented', 'received', 'changed', 'verb', - 'postopts', 'plink', 'resource-id', 'event-id', 'inform', - 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', - 'private', 'pubmail', 'visible', 'starred', - 'unseen', 'deleted', 'origin', 'mention', 'global', 'network', - 'title', 'content-warning', 'body', 'location', 'coord', 'app', - 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', - 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', - 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id']; + const ITEM_FIELDLIST = [ + 'id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', + 'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid', + 'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid', + 'created', 'edited', 'commented', 'received', 'changed', 'verb', + 'postopts', 'plink', 'resource-id', 'event-id', 'inform', + 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', + 'private', 'pubmail', 'visible', 'starred', + 'unseen', 'deleted', 'origin', 'mention', 'global', 'network', + 'title', 'content-warning', 'body', 'location', 'coord', 'app', + 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', + 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', + 'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id' + ]; // List of all verbs that don't need additional content data. // Never reorder or remove entries from this list. Just add new ones at the end, if needed. @@ -140,7 +144,8 @@ class Item Activity::LIKE, Activity::DISLIKE, Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, Activity::FOLLOW, - Activity::ANNOUNCE]; + Activity::ANNOUNCE + ]; // Privacy levels const PUBLIC = 0; @@ -191,8 +196,10 @@ class Item } // We only need to call the line by line update for specific fields - if (empty($fields['body']) && empty($fields['file']) && - empty($fields['attach']) && empty($fields['edited'])) { + if ( + empty($fields['body']) && empty($fields['file']) && + empty($fields['attach']) && empty($fields['edited']) + ) { return $rows; } @@ -319,9 +326,11 @@ class Item { Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]); // locate item to be deleted - $fields = ['id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri-id', 'origin', + $fields = [ + 'id', 'uri', 'uri-id', 'uid', 'parent', 'parent-uri-id', 'origin', 'deleted', 'resource-id', 'event-id', - 'verb', 'object-type', 'object', 'target', 'contact-id', 'psid', 'gravity']; + 'verb', 'object-type', 'object', 'target', 'contact-id', 'psid', 'gravity' + ]; $item = Post::selectFirst($fields, ['id' => $item_id]); if (!DBA::isResult($item)) { Logger::info('Item not found.', ['id' => $item_id]); @@ -359,7 +368,7 @@ class Item // If item has attachments, drop them $attachments = Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT]); - foreach($attachments as $attachment) { + foreach ($attachments as $attachment) { if (preg_match('|attach/(\d+)|', $attachment['url'], $matches)) { Attach::delete(['id' => $matches[1], 'uid' => $item['uid']]); } @@ -473,7 +482,7 @@ class Item private static function contactId(array $item): int { if ($item['uid'] == 0) { - return $item['author-id']; + return $item['owner-id']; } if ($item['origin']) { @@ -481,6 +490,23 @@ class Item return $owner['id']; } + $contact_id = 0; + $user_contact_id = 0; + foreach (['group-link', 'causer-link', 'owner-link', 'author-link'] as $field) { + if (empty($item[$field])) { + continue; + } + if (!$user_contact_id && Contact::isSharingByURL($item[$field], $item['uid'], true)) { + $user_contact_id = Contact::getIdForURL($item[$field], $item['uid']); + } elseif (!$contact_id) { + $contact_id = Contact::getIdForURL($item[$field]); + } + } + + if ($user_contact_id) { + return $user_contact_id; + } + if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) { $cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']); if (!empty($cdata['user'])) { @@ -488,24 +514,7 @@ class Item } } - if ($item['gravity'] == self::GRAVITY_PARENT) { - if (Contact::isSharingByURL($item['owner-link'], $item['uid'], true)) { - $contact_id = Contact::getIdForURL($item['owner-link'], $item['uid']); - } else { - $contact_id = Contact::getIdForURL($item['owner-link']); - } - if (!empty($contact_id)) { - return $contact_id; - } - } - - if (Contact::isSharingByURL($item['author-link'], $item['uid'], true)) { - $contact_id = Contact::getIdForURL($item['author-link'], $item['uid']); - } else { - $contact_id = Contact::getIdForURL($item['author-link']); - } - - if (!empty($contact_id)) { + if ($contact_id) { return $contact_id; } @@ -551,8 +560,10 @@ class Item return true; } - $condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], - 'network' => [$item['network'], Protocol::DFRN]]; + $condition = [ + 'uri-id' => $item['uri-id'], 'uid' => $item['uid'], + 'network' => [$item['network'], Protocol::DFRN] + ]; if (Post::exists($condition)) { Logger::notice('duplicated item with the same uri found.', $condition); return true; @@ -567,8 +578,10 @@ class Item } } elseif ($item['network'] == Protocol::OSTATUS) { // Check for an existing post with the same content. There seems to be a problem with OStatus. - $condition = ["`body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ?", - $item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid']]; + $condition = [ + "`body` = ? AND `network` = ? AND `created` = ? AND `contact-id` = ? AND `uid` = ?", + $item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid'] + ]; if (Post::exists($condition)) { Logger::notice('duplicated item with the same body found.', $item); return true; @@ -642,8 +655,10 @@ class Item return false; } - $condition = ['verb' => Activity::FOLLOW, 'uid' => $item['uid'], - 'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']]; + $condition = [ + 'verb' => Activity::FOLLOW, 'uid' => $item['uid'], + 'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id'] + ]; if (Post::exists($condition)) { // It happens that we receive multiple follow requests by the same author - we only store one. Logger::info('Follow: Found existing follow request from author', ['author-id' => $item['author-id'], 'parent-uri' => $item['parent-uri']]); @@ -695,7 +710,8 @@ class Item private static function getDuplicateID(array $item): int { if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) { - $condition = ['`uri-id` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?)', + $condition = [ + '`uri-id` = ? AND `uid` = ? AND `network` IN (?, ?, ?, ?)', $item['uri-id'], $item['uid'], Protocol::ACTIVITYPUB, @@ -751,10 +767,12 @@ class Item */ private static function getTopLevelParent(array $item): array { - $fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted', + $fields = [ + 'uid', 'uri', 'parent-uri', 'id', 'deleted', 'uri-id', 'parent-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', - 'wall', 'private', 'origin', 'author-id']; + 'wall', 'private', 'origin', 'author-id' + ]; $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $item['uid']]; $params = ['order' => ['id' => false]]; $parent = Post::selectFirst($fields, $condition, $params); @@ -779,9 +797,11 @@ class Item return $parent; } - $condition = ['uri-id' => $parent['parent-uri-id'], + $condition = [ + 'uri-id' => $parent['parent-uri-id'], 'parent-uri-id' => $parent['parent-uri-id'], - 'uid' => $parent['uid']]; + 'uid' => $parent['uid'] + ]; $params = ['order' => ['id' => false]]; $toplevel_parent = Post::selectFirst($fields, $condition, $params); @@ -946,7 +966,7 @@ class Item // Communities aren't working with the Diaspora protocol if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) { $user = User::getById($uid, ['account-type']); - if ($user['account-type'] == Contact::TYPE_COMMUNITY) { + if ($user['account-type'] == Contact::TYPE_COMMUNITY) { Logger::info('Community posts are not supported via Diaspora'); return 0; } @@ -966,12 +986,16 @@ class Item $item['gravity'] = self::getGravity($item); - $default = ['url' => $item['author-link'], 'name' => $item['author-name'], - 'photo' => $item['author-avatar'], 'network' => $item['network']]; + $default = [ + 'url' => $item['author-link'], 'name' => $item['author-name'], + 'photo' => $item['author-avatar'], 'network' => $item['network'] + ]; $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, null, $default); - $default = ['url' => $item['owner-link'], 'name' => $item['owner-name'], - 'photo' => $item['owner-avatar'], 'network' => $item['network']]; + $default = [ + 'url' => $item['owner-link'], 'name' => $item['owner-name'], + 'photo' => $item['owner-avatar'], 'network' => $item['network'] + ]; $item['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, null, $default); $item['post-reason'] = self::getPostReason($item); @@ -982,8 +1006,10 @@ class Item $item['contact-id'] = self::contactId($item); - if (!empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) && - empty($item['origin']) && self::isTooOld($item)) { + if ( + !empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) && + empty($item['origin']) && self::isTooOld($item) + ) { Logger::info('Item is too old', ['item' => $item]); return 0; } @@ -1124,7 +1150,8 @@ class Item $item['allow_gid'], $item['deny_cid'], $item['deny_gid'] - ))->id; + ) + )->id; if (!empty($item['extid'])) { $item['external-id'] = ItemURI::getIdByURI($item['extid']); @@ -1356,7 +1383,8 @@ class Item // Don't relay participation messages if (($posted_item['verb'] == Activity::FOLLOW) && - (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid)))) { + (!$posted_item['origin'] || ($posted_item['author-id'] != Contact::getPublicIdByUserId($uid))) + ) { Logger::info('Participation messages will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'verb' => $posted_item['verb']]); $transmit = false; } @@ -1429,8 +1457,10 @@ class Item */ private static function setOwnerforResharedItem(array $item) { - $parent = Post::selectFirst(['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'], - ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); + $parent = Post::selectFirst( + ['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'], + ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']] + ); if (!DBA::isResult($parent)) { Logger::error('Parent not found', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); return; @@ -1502,9 +1532,11 @@ class Item } // Only distribute public items from native networks - $condition = ['id' => $itemid, 'uid' => 0, - 'network' => array_merge(Protocol::FEDERATED ,['']), - 'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED]]; + $condition = [ + 'id' => $itemid, 'uid' => 0, + 'network' => array_merge(Protocol::FEDERATED, ['']), + 'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED] + ]; $item = Post::selectFirst(array_merge(self::ITEM_FIELDLIST, ['protocol']), $condition); if (!DBA::isResult($item)) { Logger::warning('Item not found', ['condition' => $condition]); @@ -1625,7 +1657,8 @@ class Item if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) && DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE && - !in_array($item['post-reason'], [self::PR_FOLLOWER, self::PR_TAG, self::PR_TO, self::PR_CC, self::PR_ACTIVITY, self::PR_AUDIENCE])) { + !in_array($item['post-reason'], [self::PR_FOLLOWER, self::PR_TAG, self::PR_TO, self::PR_CC, self::PR_ACTIVITY, self::PR_AUDIENCE]) + ) { Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid, 'uri-id' => $uri_id, 'post-reason' => $item['post-reason']]); return 0; } @@ -1821,7 +1854,7 @@ class Item } // is it an entry from a connector? Only add an entry for natively connected networks - if (!in_array($item["network"], array_merge(Protocol::FEDERATED ,['']))) { + if (!in_array($item["network"], array_merge(Protocol::FEDERATED, ['']))) { return; } @@ -2124,12 +2157,16 @@ class Item } // Now do the same for the system wide contacts with uid=0 if ($arr['private'] != self::PRIVATE) { - Contact::update(['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], - ['id' => $arr['owner-id']]); + Contact::update( + ['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], + ['id' => $arr['owner-id']] + ); if ($arr['owner-id'] != $arr['author-id']) { - Contact::update(['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], - ['id' => $arr['author-id']]); + Contact::update( + ['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], + ['id' => $arr['author-id']] + ); } } } @@ -2155,29 +2192,44 @@ class Item // All hashtags should point to the home server if "local_tags" is activated if (DI::config()->get('system', 'local_tags')) { - $body = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", - "#[url=" . DI::baseUrl() . "/search?tag=$2]$2[/url]", $body); + $body = preg_replace( + "/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + "#[url=" . DI::baseUrl() . "/search?tag=$2]$2[/url]", + $body + ); } // mask hashtags inside of url, bookmarks and attachments to avoid urls in urls - $body = preg_replace_callback("/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + $body = preg_replace_callback( + "/\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", function ($match) { return ("[url=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/url]"); - }, $body); + }, + $body + ); - $body = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", + $body = preg_replace_callback( + "/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", function ($match) { return ("[bookmark=" . str_replace("#", "#", $match[1]) . "]" . str_replace("#", "#", $match[2]) . "[/bookmark]"); - }, $body); + }, + $body + ); - $body = preg_replace_callback("/\[attachment (.*?)\](.*?)\[\/attachment\]/ism", + $body = preg_replace_callback( + "/\[attachment (.*?)\](.*?)\[\/attachment\]/ism", function ($match) { return ("[attachment " . str_replace("#", "#", $match[1]) . "]" . $match[2] . "[/attachment]"); - }, $body); + }, + $body + ); // Repair recursive urls - $body = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", - "#$2", $body); + $body = preg_replace( + "/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", + "#$2", + $body + ); foreach ($tags as $tag) { if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') { @@ -2210,8 +2262,6 @@ class Item */ private static function tagDeliver(int $uid, int $item_id): bool { - $mention = false; - $owner = User::getOwnerDataById($uid); if (!DBA::isResult($owner)) { Logger::warning('User not found, quitting here.', ['uid' => $uid]); @@ -2331,10 +2381,13 @@ class Item } $datarray2 = $datarray; - Logger::info('remote-self start', ['contact' => $contact['url'], 'remote_self'=> $contact['remote_self'], 'item' => $datarray]); + Logger::info('remote-self start', ['contact' => $contact['url'], 'remote_self' => $contact['remote_self'], 'item' => $datarray]); - $self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'], - ['uid' => $contact['uid'], 'self' => true]); + $self = DBA::selectFirst( + 'contact', + ['id', 'name', 'url', 'thumb'], + ['uid' => $contact['uid'], 'self' => true] + ); if (!DBA::isResult($self)) { Logger::error('Self contact not found', ['uid' => $contact['uid']]); return false; @@ -2370,7 +2423,7 @@ class Item // Store the original post $result = self::insert($datarray2); - Logger::info('remote-self post original item', ['contact' => $contact['url'], 'result'=> $result, 'item' => $datarray2]); + Logger::info('remote-self post original item', ['contact' => $contact['url'], 'result' => $result, 'item' => $datarray2]); } else { $datarray['private'] = self::PUBLIC; $datarray['app'] = 'Feed'; @@ -2497,7 +2550,8 @@ class Item if (($obj1['allow_cid'] == $obj2['allow_cid']) && ($obj1['allow_gid'] == $obj2['allow_gid']) && ($obj1['deny_cid'] == $obj2['deny_cid']) - && ($obj1['deny_gid'] == $obj2['deny_gid'])) { + && ($obj1['deny_gid'] == $obj2['deny_gid']) + ) { return true; } @@ -2544,8 +2598,10 @@ class Item return; } - $condition = ["`uid` = ? AND NOT `deleted` AND `gravity` = ?", - $uid, self::GRAVITY_PARENT]; + $condition = [ + "`uid` = ? AND NOT `deleted` AND `gravity` = ?", + $uid, self::GRAVITY_PARENT + ]; /* * $expire_network_only = save your own wall posts @@ -2619,8 +2675,10 @@ class Item return false; } - $condition = ["`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?", - $uid, $wall, $user['register_date']]; + $condition = [ + "`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?", + $uid, $wall, $user['register_date'] + ]; $params = ['order' => ['received' => false]]; $thread = Post::selectFirstThread(['received'], $condition, $params); if (DBA::isResult($thread)) { @@ -2752,8 +2810,10 @@ class Item $vids = Verb::getID($activity); } - $condition = ['vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY, - 'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id]; + $condition = [ + 'vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY, + 'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id + ]; $like_item = Post::selectFirst(['id', 'guid', 'verb'], $condition); if (DBA::isResult($like_item)) { @@ -2857,12 +2917,14 @@ class Item // Profile owner - everything is visible $condition = []; } elseif ($remote_user) { - // Authenticated visitor - fetch the matching permissionsets + // Authenticated visitor - fetch the matching permissionsets $permissionSets = DI::permissionSet()->selectByContactId($remote_user, $owner_id); if (!empty($set)) { - $condition = ["(`private` != ? OR (`private` = ? AND `wall` + $condition = [ + "(`private` != ? OR (`private` = ? AND `wall` AND `psid` IN (" . implode(', ', array_fill(0, count($set), '?')) . ")))", - self::PRIVATE, self::PRIVATE]; + self::PRIVATE, self::PRIVATE + ]; $condition = array_merge($condition, $permissionSets->column('id')); } } @@ -2958,7 +3020,8 @@ class Item $rendered_hash = $item['rendered-hash'] ?? ''; $rendered_html = $item['rendered-html'] ?? ''; - if ($rendered_hash == '' + if ( + $rendered_hash == '' || $rendered_html == '' || $rendered_hash != hash('md5', BBCode::VERSION . '::' . $body) || DI::config()->get('system', 'ignore_cache') @@ -3263,8 +3326,10 @@ class Item } foreach ([0, 1, 2] as $size) { - if (preg_match('#/photo/.*-' . $size . '\.#ism', $url) && - strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url)) { + if ( + preg_match('#/photo/.*-' . $size . '\.#ism', $url) && + strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url) + ) { return true; } } @@ -3461,7 +3526,8 @@ class Item 'text' => '', 'title' => $attachment['name'] ?? '', 'type' => 'link', - 'url' => $attachment['url']]; + 'url' => $attachment['url'] + ]; if ($preview && !empty($attachment['preview'])) { if ($attachment['preview-width'] >= 500) { @@ -3551,8 +3617,13 @@ class Item continue; } - $author = ['uid' => 0, 'id' => $item['author-id'], - 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'] + ]; $the_url = Contact::magicLinkByContact($author, $attachment['url']); $title = Strings::escapeHtml(trim(($attachment['description'] ?? '') ?: $attachment['url'])); @@ -3601,7 +3672,7 @@ class Item $summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters']); } elseif (!empty($question['endtime'])) { $summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime'])); - } else { + } else { $summary = ''; } @@ -3610,7 +3681,7 @@ class Item '$options' => $options, '$summary' => $summary, ]); - } + } DI::profiler()->stopRecording(); return $content; } @@ -3639,8 +3710,13 @@ class Item ]; if (!empty($plink) && ($item['private'] == self::PRIVATE)) { - $author = ['uid' => 0, 'id' => $item['author-id'], - 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'], + ]; $plink = Contact::magicLinkByContact($author, $plink); } @@ -3664,6 +3740,7 @@ class Item /** * Does the given uri-id belongs to a post that is sent as starting post to a group? + * This does apply to posts that are sent via ! and not in parallel to a group via @ * * @param int $uri_id * @@ -3671,7 +3748,13 @@ class Item */ public static function isGroupPost(int $uri_id): bool { - foreach (Tag::getByURIId($uri_id, [Tag::EXCLUSIVE_MENTION]) as $tag) { + if (Post::exists(['private' => Item::PUBLIC, 'uri-id' => $uri_id])) { + return false; + } + + foreach (Tag::getByURIId($uri_id, [Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]) as $tag) { + // @todo Possibly check for a public audience in the future, see https://socialhub.activitypub.rocks/t/fep-1b12-group-federation/2724 + // and https://codeberg.org/fediverse/fep/src/branch/main/feps/fep-1b12.md if (DBA::exists('contact', ['uid' => 0, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) { return true; } diff --git a/src/Model/Tag.php b/src/Model/Tag.php index 9f0f8d29a6..1645dc1255 100644 --- a/src/Model/Tag.php +++ b/src/Model/Tag.php @@ -487,7 +487,7 @@ class Tag * * @return boolean */ - public static function isMentioned(int $uriId, string $url, array $type = [self::MENTION, self::EXCLUSIVE_MENTION]): bool + public static function isMentioned(int $uriId, string $url, array $type = [self::MENTION, self::EXCLUSIVE_MENTION, self::AUDIENCE]): bool { $tags = self::getByURIId($uriId, $type); foreach ($tags as $tag) { diff --git a/src/Module/Contact.php b/src/Module/Contact.php index aad97fd167..6e480c1931 100644 --- a/src/Module/Contact.php +++ b/src/Module/Contact.php @@ -219,8 +219,7 @@ class Contact extends BaseModule DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $rel_widget . $circles_widget . $networks_widget . $account_widget; $tpl = Renderer::getMarkupTemplate('contacts-head.tpl'); - DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ - ]); + DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, []); $o = ''; Nav::setSelected('contact'); @@ -412,20 +411,41 @@ class Contact extends BaseModule $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]); switch ($rel) { - case 'followers': $header = DI::l10n()->t('Followers'); break; - case 'following': $header = DI::l10n()->t('Following'); break; - case 'mutuals': $header = DI::l10n()->t('Mutual friends'); break; - case 'nothing': $header = DI::l10n()->t('No relationship'); break; - default: $header = DI::l10n()->t('Contacts'); + case 'followers': + $header = DI::l10n()->t('Followers'); + break; + case 'following': + $header = DI::l10n()->t('Following'); + break; + case 'mutuals': + $header = DI::l10n()->t('Mutual friends'); + break; + case 'nothing': + $header = DI::l10n()->t('No relationship'); + break; + default: + $header = DI::l10n()->t('Contacts'); } switch ($type) { - case 'pending': $header .= ' - ' . DI::l10n()->t('Pending'); break; - case 'blocked': $header .= ' - ' . DI::l10n()->t('Blocked'); break; - case 'hidden': $header .= ' - ' . DI::l10n()->t('Hidden'); break; - case 'ignored': $header .= ' - ' . DI::l10n()->t('Ignored'); break; - case 'collapsed': $header .= ' - ' . DI::l10n()->t('Collapsed'); break; - case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break; + case 'pending': + $header .= ' - ' . DI::l10n()->t('Pending'); + break; + case 'blocked': + $header .= ' - ' . DI::l10n()->t('Blocked'); + break; + case 'hidden': + $header .= ' - ' . DI::l10n()->t('Hidden'); + break; + case 'ignored': + $header .= ' - ' . DI::l10n()->t('Ignored'); + break; + case 'collapsed': + $header .= ' - ' . DI::l10n()->t('Collapsed'); + break; + case 'archived': + $header .= ' - ' . DI::l10n()->t('Archived'); + break; } $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : ''; @@ -512,7 +532,8 @@ class Contact extends BaseModule 'id' => 'media-tab', 'accesskey' => 'd', ], - ['label' => DI::l10n()->t('Contacts'), + [ + 'label' => DI::l10n()->t('Contacts'), 'url' => 'contact/' . $pcid . '/contacts', 'sel' => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''), 'title' => DI::l10n()->t('View all known contacts'), @@ -522,7 +543,8 @@ class Contact extends BaseModule ]; if (!empty($contact['network']) && in_array($contact['network'], [Protocol::FEED, Protocol::MAIL]) && ($cid != $pcid)) { - $tabs[] = ['label' => DI::l10n()->t('Advanced'), + $tabs[] = [ + 'label' => DI::l10n()->t('Advanced'), 'url' => 'contact/' . $cid . '/advanced/', 'sel' => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''), 'title' => DI::l10n()->t('Advanced Contact Settings'), diff --git a/src/Module/Contact/Unfollow.php b/src/Module/Contact/Unfollow.php index 9bbc34eeed..670fe57e69 100644 --- a/src/Module/Contact/Unfollow.php +++ b/src/Module/Contact/Unfollow.php @@ -93,7 +93,7 @@ class Unfollow extends \Friendica\BaseModule Strings::normaliseLink($url), Strings::normaliseLink($url), $url, ]; - $contact = $this->database->selectFirst('contact', ['url', 'id', 'uid', 'network', 'addr', 'name'], $condition); + $contact = $this->database->selectFirst('contact', ['url', 'alias', 'id', 'uid', 'network', 'addr', 'name'], $condition); if (!$this->database->isResult($contact)) { $this->systemMessages->addNotice($this->t("You aren't following this contact.")); $this->baseUrl->redirect($base_return_path); diff --git a/src/Module/Magic.php b/src/Module/Magic.php index 481a748fcf..dc0d4f8ad1 100644 --- a/src/Module/Magic.php +++ b/src/Module/Magic.php @@ -29,12 +29,14 @@ use Friendica\Core\Session\Capability\IHandleUserSessions; use Friendica\Core\System; use Friendica\Database\Database; use Friendica\Model\Contact; +use Friendica\Model\GServer; use Friendica\Model\User; use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Util\HTTPSignature; use Friendica\Util\Profiler; use Friendica\Util\Strings; +use GuzzleHttp\Psr7\Uri; use Psr\Log\LoggerInterface; /** @@ -83,6 +85,8 @@ class Magic extends BaseModule $this->logger->debug('bdest detected', ['dest' => $dest]); } + $target = $dest ?: $addr; + if ($addr ?: $dest) { $contact = Contact::getByURL($addr ?: $dest); } @@ -110,14 +114,21 @@ class Magic extends BaseModule // OpenWebAuth $owner = User::getOwnerDataById($this->userSession->getLocalUserId()); - $gserver = $this->dba->selectFirst('gserver', ['url'], ['id' => $contact['gsid']]); - if (empty($gserver)) { - $this->logger->notice('Server not found, redirecting to destination.', ['gsid' => $contact['gsid'], 'dest' => $dest]); + if (!empty($contact['gsid'])) { + $gserver = $this->dba->selectFirst('gserver', ['url'], ['id' => $contact['gsid']]); + if (empty($gserver)) { + $this->logger->notice('Server not found, redirecting to destination.', ['gsid' => $contact['gsid'], 'dest' => $dest]); + System::externalRedirect($dest); + } + + $basepath = $gserver['url']; + } elseif (GServer::check($target)) { + $basepath = (string)GServer::cleanUri(new Uri($target)); + } else { + $this->logger->notice('The target is not a server path, redirecting to destination.', ['target' => $target]); System::externalRedirect($dest); } - $basepath = $gserver['url']; - $header = [ 'Accept' => 'application/x-dfrn+json, application/x-zot+json', 'X-Open-Web-Auth' => Strings::getRandomHex() diff --git a/src/Object/Post.php b/src/Object/Post.php index 27f618fb6b..28db238949 100644 --- a/src/Object/Post.php +++ b/src/Object/Post.php @@ -91,9 +91,13 @@ class Post } $this->writable = $this->getDataValue('writable') || $this->getDataValue('self'); - $author = ['uid' => 0, 'id' => $this->getDataValue('author-id'), + $author = [ + 'uid' => 0, + 'id' => $this->getDataValue('author-id'), 'network' => $this->getDataValue('author-network'), - 'url' => $this->getDataValue('author-link')]; + 'url' => $this->getDataValue('author-link'), + 'alias' => $this->getDataValue('author-alias') + ]; $this->redirect_url = Contact::magicLinkByContact($author); if (!$this->isToplevel()) { $this->threaded = true; @@ -286,8 +290,13 @@ class Post } if (DI::userSession()->isAuthenticated()) { - $author = ['uid' => 0, 'id' => $item['author-id'], - 'network' => $item['author-network'], 'url' => $item['author-link']]; + $author = [ + 'uid' => 0, + 'id' => $item['author-id'], + 'network' => $item['author-network'], + 'url' => $item['author-link'], + 'alias' => $item['author-alias'], + ]; $profile_link = Contact::magicLinkByContact($author); } else { $profile_link = $item['author-link']; @@ -388,7 +397,7 @@ class Post } if ($conv->isWritable()) { - $buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29") , DI::l10n()->t('Like')]; + $buttons['like'] = [DI::l10n()->t("I like this \x28toggle\x29"), DI::l10n()->t('Like')]; $buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t('Dislike')]; if ($shareable) { $buttons['share'] = [DI::l10n()->t('Quote share this'), DI::l10n()->t('Quote Share')]; @@ -451,8 +460,10 @@ class Post // Fetching of Diaspora posts doesn't always work. There are issues with reshares and possibly comments if (!DI::userSession()->getLocalUserId() && ($item['network'] != Protocol::DIASPORA) && !empty(DI::session()->get('remote_comment'))) { - $remote_comment = [DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('Remote comment'), - str_replace('{uri}', urlencode($item['uri']), DI::session()->get('remote_comment'))]; + $remote_comment = [ + DI::l10n()->t('Comment this item on your system'), DI::l10n()->t('Remote comment'), + str_replace('{uri}', urlencode($item['uri']), DI::session()->get('remote_comment')) + ]; // Ensure to either display the remote comment or the local activities $buttons = []; @@ -670,7 +681,6 @@ class Post $title = DI::l10n()->t('Reacted with %s by: %s', $element['emoji'], $actors); $icon = []; break; - break; } $emojis[$index] = ['emoji' => $element['emoji'], 'total' => $element['total'], 'title' => $title, 'icon' => $icon]; } @@ -719,8 +729,10 @@ class Post if ($item->getDataValue('network') === Protocol::MAIL && DI::userSession()->getLocalUserId() != $item->getDataValue('uid')) { Logger::warning('Post object does not belong to local user', ['post' => $item, 'local_user' => DI::userSession()->getLocalUserId()]); return false; - } elseif (DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) || - DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE)) { + } elseif ( + DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) || + DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE) + ) { Logger::warning('Post objects is a like/dislike', ['post' => $item]); return false; } @@ -1004,8 +1016,10 @@ class Post } $profile = Contact::getByURL($term['url'], false, ['addr', 'contact-type']); - if (!empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) && - ($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr'])) { + if ( + !empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) && + ($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr']) + ) { $text .= '@' . $profile['addr'] . ' '; } } @@ -1134,6 +1148,7 @@ class Post 'id' => $this->getDataValue('owner-id'), 'network' => $this->getDataValue('owner-network'), 'url' => $this->getDataValue('owner-link'), + 'alias' => $this->getDataValue('owner-alias'), ]; $this->owner_url = Contact::magicLinkByContact($owner); } diff --git a/src/Protocol/ActivityPub/Delivery.php b/src/Protocol/ActivityPub/Delivery.php index ddf5816ed5..04f5841c2e 100644 --- a/src/Protocol/ActivityPub/Delivery.php +++ b/src/Protocol/ActivityPub/Delivery.php @@ -160,7 +160,7 @@ class Delivery if (!empty($actor)) { $drop = !ActivityPub\Transmitter::sendRelayFollow($actor); Logger::notice('Resubscribed to relay', ['url' => $actor, 'success' => !$drop]); - } elseif ($cmd = ProtocolDelivery::DELETION) { + } elseif ($cmd == ProtocolDelivery::DELETION) { // Remote systems not always accept our deletion requests, so we drop them if rejected. // Situation is: In Friendica we allow the thread owner to delete foreign comments to their thread. // Most AP systems don't allow this, so they will reject the deletion request. diff --git a/src/Protocol/ActivityPub/Processor.php b/src/Protocol/ActivityPub/Processor.php index 453525454a..9758e4fd48 100644 --- a/src/Protocol/ActivityPub/Processor.php +++ b/src/Protocol/ActivityPub/Processor.php @@ -432,9 +432,38 @@ class Processor $item['owner-link'] = $item['author-link']; $item['owner-id'] = $item['author-id']; + } + + if (!$item['isGroup'] && !empty($activity['receiver_urls']['as:audience'])) { + foreach ($activity['receiver_urls']['as:audience'] as $audience) { + $actor = APContact::getByURL($audience, false); + if (($actor['type'] ?? 'Person') == 'Group') { + Logger::debug('Group post detected via audience.', ['audience' => $audience, 'actor' => $activity['actor'], 'author' => $activity['author']]); + $item['isGroup'] = true; + $item['group-link'] = $audience; + } + } } else { - $actor = APContact::getByURL($item['owner-link'], false); - $item['isGroup'] = ($actor['type'] ?? 'Person') == 'Group'; + $owner = APContact::getByURL($item['owner-link'], false); + } + + if (!$item['isGroup'] && (($owner['type'] ?? 'Person') == 'Group')) { + Logger::debug('Group post detected via owner.', ['actor' => $activity['actor'], 'author' => $activity['author']]); + $item['isGroup'] = true; + $item['group-link'] = $item['owner-link']; + } elseif (!empty($item['causer-link'])) { + $causer = APContact::getByURL($item['causer-link'], false); + } + + if (!$item['isGroup'] && (($causer['type'] ?? 'Person') == 'Group')) { + Logger::debug('Group post detected via causer.', ['actor' => $activity['actor'], 'author' => $activity['author'], 'causer' => $item['causer-link']]); + $item['isGroup'] = true; + $item['group-link'] = $item['causer-link']; + } + + if (!empty($item['group-link']) && empty($item['causer-link'])) { + $item['causer-link'] = $item['group-link']; + $item['causer-id'] = Contact::getIdForURL($item['causer-link']); } $item['uri'] = $activity['id']; @@ -1059,10 +1088,10 @@ class Processor $item['causer-id'] = ($item['gravity'] == Item::GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id']; } - if ($item['isGroup'] ?? false) { - $item['contact-id'] = Contact::getIdForURL($activity['actor'], $receiver); + if ($item['isGroup']) { + $item['contact-id'] = Contact::getIdForURL($item['group-link'], $receiver); } else { - $item['contact-id'] = Contact::getIdForURL($activity['author'], $receiver); + $item['contact-id'] = Contact::getIdForURL($item['author-link'], $receiver); } if (($receiver != 0) && empty($item['contact-id'])) { @@ -1075,7 +1104,7 @@ class Processor } if (($receiver != 0) && ($item['gravity'] == Item::GRAVITY_PARENT) && !in_array($item['post-reason'], [Item::PR_FOLLOWER, Item::PR_TAG, item::PR_TO, Item::PR_CC, Item::PR_AUDIENCE])) { - if (!($item['isGroup'] ?? false)) { + if (!$item['isGroup']) { if ($item['post-reason'] == Item::PR_BCC) { Logger::info('Top level post via BCC from a non sharer, ignoring', ['uid' => $receiver, 'contact' => $item['contact-id'], 'url' => $item['uri']]); continue; @@ -1096,7 +1125,7 @@ class Processor } if ((DI::pConfig()->get($receiver, 'system', 'accept_only_sharer') == Item::COMPLETION_NONE) - && ((!$isGroup && !($item['isGroup'] ?? false) && ($activity['type'] != 'as:Announce')) + && ((!$isGroup && !$item['isGroup'] && ($activity['type'] != 'as:Announce')) || !Contact::isSharingByURL($activity['actor'], $receiver))) { Logger::info('Actor is a non sharer, is no group or it is no announce', ['uid' => $receiver, 'actor' => $activity['actor'], 'url' => $item['uri'], 'type' => $activity['type']]); continue; @@ -1533,6 +1562,7 @@ class Processor $activity['id'] = $object['id']; $activity['to'] = $object['to'] ?? []; $activity['cc'] = $object['cc'] ?? []; + $activity['audience'] = $object['audience'] ?? []; $activity['actor'] = $actor; $activity['object'] = $object; $activity['published'] = $published; diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index b272c31dd3..7a07e1a7f2 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -291,16 +291,17 @@ class Receiver /** * Prepare the object array * - * @param array $activity Array with activity data - * @param integer $uid User ID - * @param boolean $push Message had been pushed to our system - * @param boolean $trust_source Do we trust the source? + * @param array $activity Array with activity data + * @param integer $uid User ID + * @param boolean $push Message had been pushed to our system + * @param boolean $trust_source Do we trust the source? + * @param string $original_actor Actor of the original activity. Used for receiver detection. (Optional) * * @return array with object data * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException */ - public static function prepareObjectData(array $activity, int $uid, bool $push, bool &$trust_source): array + public static function prepareObjectData(array $activity, int $uid, bool $push, bool &$trust_source, string $original_actor = ''): array { $id = JsonLD::fetchElement($activity, '@id'); $type = JsonLD::fetchElement($activity, '@type'); @@ -319,7 +320,7 @@ class Receiver $fetched = false; if (!empty($id) && !$trust_source) { - $fetch_uid = $uid ?: self::getBestUserForActivity($activity); + $fetch_uid = $uid ?: self::getBestUserForActivity($activity, $original_actor); $fetched_activity = Processor::fetchCachedActivity($fetch_id, $fetch_uid); if (!empty($fetched_activity)) { @@ -355,7 +356,7 @@ class Receiver $type = JsonLD::fetchElement($activity, '@type'); // Fetch all receivers from to, cc, bto and bcc - $receiverdata = self::getReceivers($activity, $actor, [], false, $push || $fetched); + $receiverdata = self::getReceivers($activity, $original_actor ?: $actor, [], false, $push || $fetched); $receivers = $reception_types = []; foreach ($receiverdata as $key => $data) { $receivers[$key] = $data['uid']; @@ -379,7 +380,7 @@ class Receiver // We possibly need some user to fetch private content, // so we fetch one out of the receivers if no uid is provided. - $fetch_uid = $uid ?: self::getBestUserForActivity($activity); + $fetch_uid = $uid ?: self::getBestUserForActivity($activity, $original_actor); $object_id = JsonLD::fetchElement($activity, 'as:object', '@id'); if (empty($object_id)) { @@ -394,28 +395,6 @@ class Receiver $object_type = self::fetchObjectType($activity, $object_id, $fetch_uid); - // Fetch the activity on Lemmy "Announce" messages (announces of activities) - if (($type == 'as:Announce') && in_array($object_type, array_merge(self::ACTIVITY_TYPES, ['as:Delete', 'as:Undo', 'as:Update']))) { - Logger::debug('Fetch announced activity', ['object' => $object_id, 'uid' => $fetch_uid]); - $data = Processor::fetchCachedActivity($object_id, $fetch_uid); - if (!empty($data)) { - $type = $object_type; - $announced_activity = JsonLD::compact($data); - - // Some variables need to be refetched since the activity changed - $actor = JsonLD::fetchElement($announced_activity, 'as:actor', '@id'); - $announced_id = JsonLD::fetchElement($announced_activity, 'as:object', '@id'); - if (empty($announced_id)) { - Logger::warning('No object id in announced activity', ['id' => $object_id, 'activity' => $activity, 'announced' => $announced_activity]); - return []; - } else { - $activity = $announced_activity; - $object_id = $announced_id; - } - $object_type = self::fetchObjectType($activity, $object_id, $fetch_uid); - } - } - // Any activities on account types must not be altered if (in_array($type, ['as:Flag'])) { $object_data = []; @@ -454,7 +433,7 @@ class Receiver } elseif (in_array($type, array_merge(self::ACTIVITY_TYPES, ['as:Announce', 'as:Follow'])) && in_array($object_type, self::CONTENT_TYPES)) { // Create a mostly empty array out of the activity data (instead of the object). // This way we later don't have to check for the existence of each individual array element. - $object_data = self::processObject($activity); + $object_data = self::processObject($activity, $original_actor); $object_data['name'] = $type; $object_data['author'] = JsonLD::fetchElement($activity, 'as:actor', '@id'); $object_data['object_id'] = $object_id; @@ -598,18 +577,32 @@ class Receiver } } - // $trust_source is called by reference and is set to true if the content was retrieved successfully - $object_data = self::prepareObjectData($activity, $uid, $push, $trust_source); - if (empty($object_data)) { - Logger::info('No object data found', ['activity' => $activity]); - return true; + // Lemmy announces activities. + // To simplify the further processing, we modify the received object. + // For announced "create" activities we remove the middle layer. + // For the rest (like, dislike, update, ...) we just process the activity directly. + $original_actor = ''; + $object_type = JsonLD::fetchElement($activity['as:object'] ?? [], '@type'); + if (($type == 'as:Announce') && !empty($object_type) && !in_array($object_type, self::CONTENT_TYPES) && self::isGroup($actor)) { + $object_object_type = JsonLD::fetchElement($activity['as:object']['as:object'] ?? [], '@type'); + if (in_array($object_type, ['as:Create']) && in_array($object_object_type, self::CONTENT_TYPES)) { + Logger::debug('Replace "create" activity with inner object', ['type' => $object_type, 'object_type' => $object_object_type]); + $activity['as:object'] = $activity['as:object']['as:object']; + } elseif (in_array($object_type, array_merge(self::ACTIVITY_TYPES, ['as:Delete', 'as:Undo', 'as:Update']))) { + Logger::debug('Change announced activity to activity', ['type' => $object_type]); + $original_actor = $actor; + $type = $object_type; + $activity = $activity['as:object']; + } else { + Logger::info('Unhandled announced activity', ['type' => $object_type, 'object_type' => $object_object_type]); + } } - // Lemmy is announcing activities. - // We are changing the announces into regular activities. - if (($type == 'as:Announce') && in_array($object_data['type'] ?? '', array_merge(self::ACTIVITY_TYPES, ['as:Delete', 'as:Undo', 'as:Update']))) { - Logger::debug('Change type of announce to activity', ['type' => $object_data['type']]); - $type = $object_data['type']; + // $trust_source is called by reference and is set to true if the content was retrieved successfully + $object_data = self::prepareObjectData($activity, $uid, $push, $trust_source, $original_actor); + if (empty($object_data)) { + Logger::info('No object data found', ['activity' => $activity, 'callstack' => System::callstack(20)]); + return true; } if (!empty($body) && empty($object_data['raw'])) { @@ -688,6 +681,18 @@ class Receiver return true; } + /** + * Checks if the provided actor is a group account + * + * @param string $actor + * @return boolean + */ + private static function isGroup(string $actor): bool + { + $profile = APContact::getByURL($actor); + return ($profile['type'] ?? '') == 'Group'; + } + /** * Route activities * @@ -1009,10 +1014,10 @@ class Receiver * * @return int user id */ - public static function getBestUserForActivity(array $activity): int + public static function getBestUserForActivity(array $activity, string $actor = ''): int { $uid = 0; - $actor = JsonLD::fetchElement($activity, 'as:actor', '@id') ?? ''; + $actor = $actor ?: JsonLD::fetchElement($activity, 'as:actor', '@id') ?? ''; $receivers = self::getReceivers($activity, $actor, [], false, false); foreach ($receivers as $receiver) { @@ -1129,7 +1134,7 @@ class Receiver } // Fetch the receivers for the public and the followers collection - if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$isGroup)) && !empty($actor)) { + if ((($receiver == $followers) || (($receiver == self::PUBLIC_COLLECTION) && !$isGroup) || ($isGroup && ($element == 'as:audience'))) && !empty($actor)) { $receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target, $profile); continue; } @@ -1196,12 +1201,16 @@ class Receiver // "birdsitelive" is a service that mirrors tweets into the fediverse // These posts can be fetched without authentication, but are not marked as public // We treat them as unlisted posts to be able to handle them. + // We always process deletion activities. + $activity_type = JsonLD::fetchElement($activity, '@type'); if (empty($receivers) && $fetch_unlisted && Contact::isPlatform($actor, 'birdsitelive')) { $receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; $receivers[-1] = ['uid' => -1, 'type' => self::TARGET_GLOBAL]; Logger::notice('Post from "birdsitelive" is set to "unlisted"', ['id' => JsonLD::fetchElement($activity, '@id')]); + } elseif (empty($receivers) && in_array($activity_type, ['as:Delete', 'as:Undo'])) { + $receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; } elseif (empty($receivers)) { - Logger::notice('Post has got no receivers', ['fetch_unlisted' => $fetch_unlisted, 'actor' => $actor, 'id' => JsonLD::fetchElement($activity, '@id'), 'type' => JsonLD::fetchElement($activity, '@type')]); + Logger::notice('Post has got no receivers', ['fetch_unlisted' => $fetch_unlisted, 'actor' => $actor, 'id' => JsonLD::fetchElement($activity, '@id'), 'type' => $activity_type, 'callstack' => System::callstack(20)]); } return $receivers; @@ -1437,21 +1446,9 @@ class Receiver return false; } - // Lemmy is resharing "create" activities instead of content - // We fetch the content from the activity. - if (in_array($type, ['as:Create'])) { - $object = $object['as:object']; - $type = JsonLD::fetchElement($object, '@type'); - if (empty($type)) { - Logger::info('Empty type'); - return false; - } - $object_data = self::processObject($object); - } - // We currently don't handle 'pt:CacheFile', but with this step we avoid logging if (in_array($type, self::CONTENT_TYPES) || ($type == 'pt:CacheFile')) { - $object_data = self::processObject($object); + $object_data = self::processObject($object, ''); if (!empty($data)) { $object_data['raw-object'] = json_encode($data); @@ -1855,12 +1852,13 @@ class Receiver /** * Fetches data from the object part of an activity * - * @param array $object + * @param array $object + * @param string $actor * * @return array|bool Object data or FALSE if $object does not contain @id element * @throws \Exception */ - private static function processObject(array $object) + private static function processObject(array $object, string $actor) { if (!JsonLD::fetchElement($object, '@id')) { return false; @@ -1868,7 +1866,7 @@ class Receiver $object_data = self::getObjectDataFromActivity($object); - $receiverdata = self::getReceivers($object, $object_data['actor'] ?? '', $object_data['tags'], true, false); + $receiverdata = self::getReceivers($object, $actor ?: $object_data['actor'] ?? '', $object_data['tags'], true, false); $receivers = $reception_types = []; foreach ($receiverdata as $key => $data) { $receivers[$key] = $data['uid']; diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index e817198ec6..8aaa5ba09a 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -492,7 +492,6 @@ class Transmitter * Returns an array with permissions of the thread parent of the given item array * * @param array $item - * @param bool $is_group_thread * * @return array with permissions * @throws \Friendica\Network\HTTPException\InternalServerErrorException @@ -514,6 +513,7 @@ class Transmitter 'cc' => [], 'bto' => [], 'bcc' => [], + 'audience' => [], ]; $parent_profile = APContact::getByURL($parent['author-link']); @@ -525,8 +525,8 @@ class Transmitter $exclude[] = $item['owner-link']; } - $type = [Tag::TO => 'to', Tag::CC => 'cc', Tag::BTO => 'bto', Tag::BCC => 'bcc']; - foreach (Tag::getByURIId($item['thr-parent-id'], [Tag::TO, Tag::CC, Tag::BTO, Tag::BCC]) as $receiver) { + $type = [Tag::TO => 'to', Tag::CC => 'cc', Tag::BTO => 'bto', Tag::BCC => 'bcc', Tag::AUDIENCE => 'audience']; + foreach (Tag::getByURIId($item['thr-parent-id'], [Tag::TO, Tag::CC, Tag::BTO, Tag::BCC, Tag::AUDIENCE]) as $receiver) { if (!empty($parent_profile['followers']) && $receiver['url'] == $parent_profile['followers'] && !empty($item_profile['followers'])) { if (!$is_group_thread) { $permissions[$type[$receiver['type']]][] = $item_profile['followers']; @@ -600,6 +600,45 @@ class Transmitter $is_group_thread = false; } + $exclusive = false; + $mention = false; + $audience = []; + + $parent_tags = Tag::getByURIId($item['parent-uri-id'], [Tag::AUDIENCE, Tag::MENTION]); + if (!empty($parent_tags)) { + $is_group_thread = false; + foreach ($parent_tags as $tag) { + if ($tag['type'] != Tag::AUDIENCE) { + continue; + } + $profile = APContact::getByURL($tag['url'], false); + if (!empty($profile) && ($profile['type'] == 'Group')) { + $audience[] = $tag['url']; + $is_group_thread = true; + } + } + if ($is_group_thread) { + foreach ($parent_tags as $tag) { + if (($tag['type'] == Tag::MENTION) && in_array($tag['url'], $audience)) { + $mention = true; + } + } + $exclusive = !$mention; + } + } elseif ($is_group_thread) { + foreach (Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $term) { + $profile = APContact::getByURL($term['url'], false); + if (!empty($profile) && ($profile['type'] == 'Group')) { + if ($term['type'] == Tag::EXCLUSIVE_MENTION) { + $audience[] = $term['url']; + $exclusive = true; + } elseif ($term['type'] == Tag::MENTION) { + $mention = true; + } + } + } + } + if (self::isAnnounce($item) || self::isAPPost($last_id)) { // Will be activated in a later step $networks = Protocol::FEDERATED; @@ -608,7 +647,7 @@ class Transmitter $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS]; } - $data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => []]; + $data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => $audience]; if ($item['gravity'] == Item::GRAVITY_PARENT) { $actor_profile = APContact::getByURL($item['owner-link']); @@ -616,23 +655,7 @@ class Transmitter $actor_profile = APContact::getByURL($item['author-link']); } - $exclusive = false; - $mention = false; - - if ($is_group_thread) { - foreach (Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $term) { - $profile = APContact::getByURL($term['url'], false); - if (!empty($profile) && ($profile['type'] == 'Group')) { - if ($term['type'] == Tag::EXCLUSIVE_MENTION) { - $exclusive = true; - } elseif ($term['type'] == Tag::MENTION) { - $mention = true; - } - } - } - } - - $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]); + $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]); if ($item['private'] != Item::PRIVATE) { // Directly mention the original author upon a quoted reshare. @@ -644,7 +667,9 @@ class Transmitter $data['cc'][] = $announce['actor']['url']; } - $data = array_merge($data, self::fetchPermissionBlockFromThreadParent($item, $is_group_thread)); + if (!$exclusive) { + $data = array_merge($data, self::fetchPermissionBlockFromThreadParent($item, $is_group_thread)); + } // Check if the item is completely public or unlisted if ($item['private'] == Item::PUBLIC) { @@ -656,6 +681,9 @@ class Transmitter foreach ($terms as $term) { $profile = APContact::getByURL($term['url'], false); if (!empty($profile)) { + if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) { + $data['audience'][] = $profile['url']; + } if ($term['type'] == Tag::EXCLUSIVE_MENTION) { $exclusive = true; if (!empty($profile['followers']) && ($profile['type'] == 'Group')) { @@ -684,6 +712,9 @@ class Transmitter $profile = APContact::getByURL($term['url'], false); if (!empty($profile)) { + if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) { + $data['audience'][] = $profile['url']; + } if ($term['type'] == Tag::EXCLUSIVE_MENTION) { $exclusive = true; if (!empty($profile['followers']) && ($profile['type'] == 'Group')) { @@ -727,7 +758,7 @@ class Transmitter } } - if (!empty($item['parent'])) { + if (!empty($item['parent']) && (!$exclusive || ($item['private'] == Item::PRIVATE))) { if ($item['private'] == Item::PRIVATE) { $condition = ['parent' => $item['parent'], 'uri-id' => $item['thr-parent-id']]; } else { @@ -814,20 +845,13 @@ class Transmitter } } - $receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bcc' => array_values($data['bcc'])]; - - if (!empty($data['audience'])) { - $receivers['audience'] = array_values($data['audience']); - if (count($receivers['audience']) == 1) { - $receivers['audience'] = $receivers['audience'][0]; - } - } + $receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bcc' => array_values($data['bcc']), 'audience' => array_values($data['audience'])]; if (!$blindcopy) { unset($receivers['bcc']); } - foreach (['to' => Tag::TO, 'cc' => Tag::CC, 'bcc' => Tag::BCC] as $element => $type) { + foreach (['to' => Tag::TO, 'cc' => Tag::CC, 'bcc' => Tag::BCC, 'audience' => Tag::AUDIENCE] as $element => $type) { if (!empty($receivers[$element])) { foreach ($receivers[$element] as $receiver) { if ($receiver == ActivityPub::PUBLIC_COLLECTION) { @@ -840,6 +864,12 @@ class Transmitter } } + if (!$blindcopy && count($receivers['audience']) == 1) { + $receivers['audience'] = $receivers['audience'][0]; + } elseif (!$receivers['audience']) { + unset($receivers['audience']); + } + return $receivers; } @@ -976,7 +1006,7 @@ class Transmitter $profile_uid = User::getIdForURL($item_profile['url']); - foreach (['to', 'cc', 'bto', 'bcc'] as $element) { + foreach (['to', 'cc', 'bto', 'bcc', 'audience'] as $element) { if (empty($permissions[$element])) { continue; } @@ -1000,7 +1030,7 @@ class Transmitter } else { $target = $profile['sharedinbox']; } - if (!self::archivedInbox($target)) { + if (!self::archivedInbox($target) && !in_array($contact['id'], $inboxes[$target] ?? [])) { $inboxes[$target][] = $contact['id'] ?? 0; } } @@ -1101,12 +1131,14 @@ class Transmitter unset($data['cc']); unset($data['bcc']); + unset($data['audience']); $object['to'] = $data['to']; $object['tag'] = [['type' => 'Mention', 'href' => $object['to'][0], 'name' => '']]; unset($object['cc']); unset($object['bcc']); + unset($object['audience']); $data['directMessage'] = true; diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 92ed8c8df1..33fd222c03 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -56,7 +56,7 @@ use Friendica\Database\DBA; // This file is required several times during the test in DbaDefinition which justifies this condition if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1520); + define('DB_UPDATE_VERSION', 1521); } return [ diff --git a/static/dbview.config.php b/static/dbview.config.php index b960cfaf3d..17d99d9499 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -149,6 +149,7 @@ "author-addr" => ["author", "addr"], "author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)", "author-nick" => ["author", "nick"], + "author-alias" => ["author", "alias"], "author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)", "author-network" => ["author", "network"], "author-blocked" => ["author", "blocked"], @@ -161,6 +162,7 @@ "owner-addr" => ["owner", "addr"], "owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)", "owner-nick" => ["owner", "nick"], + "owner-alias" => ["owner", "alias"], "owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)", "owner-network" => ["owner", "network"], "owner-blocked" => ["owner", "blocked"], @@ -324,6 +326,7 @@ "author-addr" => ["author", "addr"], "author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)", "author-nick" => ["author", "nick"], + "author-alias" => ["author", "alias"], "author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)", "author-network" => ["author", "network"], "author-blocked" => ["author", "blocked"], @@ -336,6 +339,7 @@ "owner-addr" => ["owner", "addr"], "owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)", "owner-nick" => ["owner", "nick"], + "owner-alias" => ["owner", "alias"], "owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)", "owner-network" => ["owner", "network"], "owner-blocked" => ["owner", "blocked"], @@ -485,6 +489,7 @@ "author-addr" => ["author", "addr"], "author-name" => ["author", "name"], "author-nick" => ["author", "nick"], + "author-alias" => ["author", "alias"], "author-avatar" => ["author", "thumb"], "author-network" => ["author", "network"], "author-blocked" => ["author", "blocked"], @@ -497,6 +502,7 @@ "owner-addr" => ["owner", "addr"], "owner-name" => ["owner", "name"], "owner-nick" => ["owner", "nick"], + "owner-alias" => ["owner", "alias"], "owner-avatar" => ["owner", "thumb"], "owner-network" => ["owner", "network"], "owner-blocked" => ["owner", "blocked"], @@ -623,6 +629,7 @@ "author-addr" => ["author", "addr"], "author-name" => ["author", "name"], "author-nick" => ["author", "nick"], + "author-alias" => ["author", "alias"], "author-avatar" => ["author", "thumb"], "author-network" => ["author", "network"], "author-blocked" => ["author", "blocked"], @@ -635,6 +642,7 @@ "owner-addr" => ["owner", "addr"], "owner-name" => ["owner", "name"], "owner-nick" => ["owner", "nick"], + "owner-alias" => ["owner", "alias"], "owner-avatar" => ["owner", "thumb"], "owner-network" => ["owner", "network"], "owner-blocked" => ["owner", "blocked"], diff --git a/view/lang/C/messages.po b/view/lang/C/messages.po index d8dd10d6a9..25e64d4811 100644 --- a/view/lang/C/messages.po +++ b/view/lang/C/messages.po @@ -8,7 +8,7 @@ msgid "" msgstr "" "Project-Id-Version: 2023.09-dev\n" "Report-Msgid-Bugs-To: \n" -"POT-Creation-Date: 2023-06-15 12:51-0400\n" +"POT-Creation-Date: 2023-06-18 17:38+0000\n" "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n" "Last-Translator: FULL NAMEOr you can open the following URL in your mobile device:
" +"Or you can open the following URL in your mobile device:
" msgstr "" #: src/Module/Settings/TwoFactor/Verify.php:156 @@ -10531,9 +10531,9 @@ msgstr "" msgid "" "At any point in time a logged in user can export their account data from the " "account settings. If the user wants " -"to delete their account they can do so at %1$s/settings/removeme. The deletion of the account will be " -"permanent. Deletion of the data will also be requested from the nodes of the " +"to delete their account they can do so at " +"%1$s/settings/removeme. The deletion of the account will be permanent. " +"Deletion of the data will also be requested from the nodes of the " "communication partners." msgstr "" @@ -11259,254 +11259,254 @@ msgstr "" msgid "%s posted an update." msgstr "" -#: src/Object/Post.php:135 +#: src/Object/Post.php:139 msgid "Private Message" msgstr "" -#: src/Object/Post.php:139 +#: src/Object/Post.php:143 msgid "Public Message" msgstr "" -#: src/Object/Post.php:143 +#: src/Object/Post.php:147 msgid "Unlisted Message" msgstr "" -#: src/Object/Post.php:178 +#: src/Object/Post.php:182 msgid "This entry was edited" msgstr "" -#: src/Object/Post.php:206 +#: src/Object/Post.php:210 msgid "Connector Message" msgstr "" -#: src/Object/Post.php:222 src/Object/Post.php:224 +#: src/Object/Post.php:226 src/Object/Post.php:228 msgid "Edit" msgstr "" -#: src/Object/Post.php:248 +#: src/Object/Post.php:252 msgid "Delete globally" msgstr "" -#: src/Object/Post.php:248 +#: src/Object/Post.php:252 msgid "Remove locally" msgstr "" -#: src/Object/Post.php:266 +#: src/Object/Post.php:270 #, php-format msgid "Block %s" msgstr "" -#: src/Object/Post.php:271 +#: src/Object/Post.php:275 #, php-format msgid "Ignore %s" msgstr "" -#: src/Object/Post.php:276 +#: src/Object/Post.php:280 #, php-format msgid "Collapse %s" msgstr "" -#: src/Object/Post.php:281 +#: src/Object/Post.php:285 msgid "Save to folder" msgstr "" -#: src/Object/Post.php:316 +#: src/Object/Post.php:325 msgid "I will attend" msgstr "" -#: src/Object/Post.php:316 +#: src/Object/Post.php:325 msgid "I will not attend" msgstr "" -#: src/Object/Post.php:316 +#: src/Object/Post.php:325 msgid "I might attend" msgstr "" -#: src/Object/Post.php:346 +#: src/Object/Post.php:355 msgid "Ignore thread" msgstr "" -#: src/Object/Post.php:347 +#: src/Object/Post.php:356 msgid "Unignore thread" msgstr "" -#: src/Object/Post.php:348 +#: src/Object/Post.php:357 msgid "Toggle ignore status" msgstr "" -#: src/Object/Post.php:358 +#: src/Object/Post.php:367 msgid "Add star" msgstr "" -#: src/Object/Post.php:359 +#: src/Object/Post.php:368 msgid "Remove star" msgstr "" -#: src/Object/Post.php:360 +#: src/Object/Post.php:369 msgid "Toggle star status" msgstr "" -#: src/Object/Post.php:371 +#: src/Object/Post.php:380 msgid "Pin" msgstr "" -#: src/Object/Post.php:372 +#: src/Object/Post.php:381 msgid "Unpin" msgstr "" -#: src/Object/Post.php:373 +#: src/Object/Post.php:382 msgid "Toggle pin status" msgstr "" -#: src/Object/Post.php:376 +#: src/Object/Post.php:385 msgid "Pinned" msgstr "" -#: src/Object/Post.php:381 +#: src/Object/Post.php:390 msgid "Add tag" msgstr "" -#: src/Object/Post.php:394 +#: src/Object/Post.php:403 msgid "Quote share this" msgstr "" -#: src/Object/Post.php:394 +#: src/Object/Post.php:403 msgid "Quote Share" msgstr "" -#: src/Object/Post.php:397 +#: src/Object/Post.php:406 msgid "Reshare this" msgstr "" -#: src/Object/Post.php:397 +#: src/Object/Post.php:406 msgid "Reshare" msgstr "" -#: src/Object/Post.php:398 +#: src/Object/Post.php:407 msgid "Cancel your Reshare" msgstr "" -#: src/Object/Post.php:398 +#: src/Object/Post.php:407 msgid "Unshare" msgstr "" -#: src/Object/Post.php:449 +#: src/Object/Post.php:458 #, php-format msgid "%s (Received %s)" msgstr "" -#: src/Object/Post.php:454 +#: src/Object/Post.php:464 msgid "Comment this item on your system" msgstr "" -#: src/Object/Post.php:454 +#: src/Object/Post.php:464 msgid "Remote comment" msgstr "" -#: src/Object/Post.php:475 +#: src/Object/Post.php:486 msgid "Share via ..." msgstr "" -#: src/Object/Post.php:475 +#: src/Object/Post.php:486 msgid "Share via external services" msgstr "" -#: src/Object/Post.php:504 +#: src/Object/Post.php:515 msgid "to" msgstr "" -#: src/Object/Post.php:505 +#: src/Object/Post.php:516 msgid "via" msgstr "" -#: src/Object/Post.php:506 +#: src/Object/Post.php:517 msgid "Wall-to-Wall" msgstr "" -#: src/Object/Post.php:507 +#: src/Object/Post.php:518 msgid "via Wall-To-Wall:" msgstr "" -#: src/Object/Post.php:552 +#: src/Object/Post.php:563 #, php-format msgid "Reply to %s" msgstr "" -#: src/Object/Post.php:555 +#: src/Object/Post.php:566 msgid "More" msgstr "" -#: src/Object/Post.php:573 +#: src/Object/Post.php:584 msgid "Notifier task is pending" msgstr "" -#: src/Object/Post.php:574 +#: src/Object/Post.php:585 msgid "Delivery to remote servers is pending" msgstr "" -#: src/Object/Post.php:575 +#: src/Object/Post.php:586 msgid "Delivery to remote servers is underway" msgstr "" -#: src/Object/Post.php:576 +#: src/Object/Post.php:587 msgid "Delivery to remote servers is mostly done" msgstr "" -#: src/Object/Post.php:577 +#: src/Object/Post.php:588 msgid "Delivery to remote servers is done" msgstr "" -#: src/Object/Post.php:597 +#: src/Object/Post.php:608 #, php-format msgid "%d comment" msgid_plural "%d comments" msgstr[0] "" msgstr[1] "" -#: src/Object/Post.php:598 +#: src/Object/Post.php:609 msgid "Show more" msgstr "" -#: src/Object/Post.php:599 +#: src/Object/Post.php:610 msgid "Show fewer" msgstr "" -#: src/Object/Post.php:635 +#: src/Object/Post.php:646 #, php-format msgid "Reshared by: %s" msgstr "" -#: src/Object/Post.php:640 +#: src/Object/Post.php:651 #, php-format msgid "Viewed by: %s" msgstr "" -#: src/Object/Post.php:645 +#: src/Object/Post.php:656 #, php-format msgid "Liked by: %s" msgstr "" -#: src/Object/Post.php:650 +#: src/Object/Post.php:661 #, php-format msgid "Disliked by: %s" msgstr "" -#: src/Object/Post.php:655 +#: src/Object/Post.php:666 #, php-format msgid "Attended by: %s" msgstr "" -#: src/Object/Post.php:660 +#: src/Object/Post.php:671 #, php-format msgid "Maybe attended by: %s" msgstr "" -#: src/Object/Post.php:665 +#: src/Object/Post.php:676 #, php-format msgid "Not attended by: %s" msgstr "" -#: src/Object/Post.php:670 +#: src/Object/Post.php:681 #, php-format msgid "Reacted with %s by: %s" msgstr ""