Merge pull request #13226 from annando/lemmy

Better support for "audience" / simplified Lemmy processing
This commit is contained in:
Hypolite Petovan 2023-06-23 17:12:38 -04:00 committed by GitHub
commit b731f8a0ad
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1123 additions and 802 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2023.09-dev (Giant Rhubarb) -- 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`, `author`.`addr` AS `author-addr`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`, IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
`author`.`nick` AS `author-nick`, `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`, IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
`author`.`network` AS `author-network`, `author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`, `author`.`blocked` AS `author-blocked`,
@ -1980,6 +1981,7 @@ CREATE VIEW `post-user-view` AS SELECT
`owner`.`addr` AS `owner-addr`, `owner`.`addr` AS `owner-addr`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
`owner`.`nick` AS `owner-nick`, `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`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
`owner`.`network` AS `owner-network`, `owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
@ -2145,6 +2147,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`author`.`addr` AS `author-addr`, `author`.`addr` AS `author-addr`,
IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`, IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`) AS `author-name`,
`author`.`nick` AS `author-nick`, `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`, IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`) AS `author-avatar`,
`author`.`network` AS `author-network`, `author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`, `author`.`blocked` AS `author-blocked`,
@ -2157,6 +2160,7 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`owner`.`addr` AS `owner-addr`, `owner`.`addr` AS `owner-addr`,
IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`) AS `owner-name`,
`owner`.`nick` AS `owner-nick`, `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`, IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`) AS `owner-avatar`,
`owner`.`network` AS `owner-network`, `owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
@ -2308,6 +2312,7 @@ CREATE VIEW `post-view` AS SELECT
`author`.`addr` AS `author-addr`, `author`.`addr` AS `author-addr`,
`author`.`name` AS `author-name`, `author`.`name` AS `author-name`,
`author`.`nick` AS `author-nick`, `author`.`nick` AS `author-nick`,
`author`.`alias` AS `author-alias`,
`author`.`thumb` AS `author-avatar`, `author`.`thumb` AS `author-avatar`,
`author`.`network` AS `author-network`, `author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`, `author`.`blocked` AS `author-blocked`,
@ -2320,6 +2325,7 @@ CREATE VIEW `post-view` AS SELECT
`owner`.`addr` AS `owner-addr`, `owner`.`addr` AS `owner-addr`,
`owner`.`name` AS `owner-name`, `owner`.`name` AS `owner-name`,
`owner`.`nick` AS `owner-nick`, `owner`.`nick` AS `owner-nick`,
`owner`.`alias` AS `owner-alias`,
`owner`.`thumb` AS `owner-avatar`, `owner`.`thumb` AS `owner-avatar`,
`owner`.`network` AS `owner-network`, `owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,
@ -2448,6 +2454,7 @@ CREATE VIEW `post-thread-view` AS SELECT
`author`.`addr` AS `author-addr`, `author`.`addr` AS `author-addr`,
`author`.`name` AS `author-name`, `author`.`name` AS `author-name`,
`author`.`nick` AS `author-nick`, `author`.`nick` AS `author-nick`,
`author`.`alias` AS `author-alias`,
`author`.`thumb` AS `author-avatar`, `author`.`thumb` AS `author-avatar`,
`author`.`network` AS `author-network`, `author`.`network` AS `author-network`,
`author`.`blocked` AS `author-blocked`, `author`.`blocked` AS `author-blocked`,
@ -2460,6 +2467,7 @@ CREATE VIEW `post-thread-view` AS SELECT
`owner`.`addr` AS `owner-addr`, `owner`.`addr` AS `owner-addr`,
`owner`.`name` AS `owner-name`, `owner`.`name` AS `owner-name`,
`owner`.`nick` AS `owner-nick`, `owner`.`nick` AS `owner-nick`,
`owner`.`alias` AS `owner-alias`,
`owner`.`thumb` AS `owner-avatar`, `owner`.`thumb` AS `owner-avatar`,
`owner`.`network` AS `owner-network`, `owner`.`network` AS `owner-network`,
`owner`.`blocked` AS `owner-blocked`, `owner`.`blocked` AS `owner-blocked`,

View file

@ -117,7 +117,7 @@ function photos_init(App $a)
$tpl = Renderer::getMarkupTemplate("photos_head.tpl"); $tpl = Renderer::getMarkupTemplate("photos_head.tpl");
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl,[ DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$ispublic' => DI::l10n()->t('everybody') '$ispublic' => DI::l10n()->t('everybody')
]); ]);
} }
@ -214,13 +214,15 @@ function photos_post(App $a)
// get the list of photos we are about to delete // get the list of photos we are about to delete
if ($visitor) { 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, $visitor,
$page_owner_uid, $page_owner_uid,
$album $album
)); ));
} else { } 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(), DI::userSession()->getLocalUserId(),
$album $album
)); ));
@ -258,7 +260,6 @@ function photos_post(App $a)
// same as above but remove single photo // same as above but remove single photo
if ($visitor) { if ($visitor) {
$condition = ['contact-id' => $visitor, 'uid' => $page_owner_uid, 'resource-id' => DI::args()->getArgv()[3]]; $condition = ['contact-id' => $visitor, 'uid' => $page_owner_uid, 'resource-id' => DI::args()->getArgv()[3]];
} else { } else {
$condition = ['uid' => DI::userSession()->getLocalUserId(), 'resource-id' => DI::args()->getArgv()[3]]; $condition = ['uid' => DI::userSession()->getLocalUserId(), 'resource-id' => DI::args()->getArgv()[3]];
} }
@ -405,7 +406,7 @@ function photos_post(App $a)
if (strpos($tag, '@') === 0) { if (strpos($tag, '@') === 0) {
$profile = ''; $profile = '';
$contact = null; $contact = null;
$name = substr($tag,1); $name = substr($tag, 1);
if ((strpos($name, '@')) || (strpos($name, 'http://'))) { if ((strpos($name, '@')) || (strpos($name, 'http://'))) {
$newname = $name; $newname = $name;
@ -441,13 +442,15 @@ function photos_post(App $a)
if ($tagcid) { if ($tagcid) {
$contact = DBA::selectFirst('contact', [], ['id' => $tagcid, 'uid' => $page_owner_uid]); $contact = DBA::selectFirst('contact', [], ['id' => $tagcid, 'uid' => $page_owner_uid]);
} else { } else {
$newname = str_replace('_',' ',$name); $newname = str_replace('_', ' ', $name);
//select someone from this user's contacts by name //select someone from this user's contacts by name
$contact = DBA::selectFirst('contact', [], ['name' => $newname, 'uid' => $page_owner_uid]); $contact = DBA::selectFirst('contact', [], ['name' => $newname, 'uid' => $page_owner_uid]);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
//select someone by attag or nick and the name passed in //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], ['(`attag` = ? OR `nick` = ?) AND `uid` = ?', $name, $name, $page_owner_uid],
['order' => ['attag' => true]] ['order' => ['attag' => true]]
); );
@ -689,11 +692,13 @@ function photos_content(App $a)
$uploader = ''; $uploader = '';
$ret = ['post_url' => 'profile/' . $user['nickname'] . '/photos', $ret = [
'addon_text' => $uploader, 'post_url' => 'profile/' . $user['nickname'] . '/photos',
'default_upload' => true]; '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_box = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_box.tpl'), []);
$default_upload_submit = Renderer::replaceMacros(Renderer::getMarkupTemplate('photos_default_uploader_submit.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')); $umf_bytes = Strings::getBytesFromShorthand(ini_get('upload_max_filesize'));
// Per Friendica definition a value of '0' means unlimited: // Per Friendica definition a value of '0' means unlimited:
If ($mis_bytes == 0) { if ($mis_bytes == 0) {
$mis_bytes = INF; $mis_bytes = INF;
} }
@ -719,7 +724,7 @@ function photos_content(App $a)
$aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId())); $aclselect_e = ($visitor ? '' : ACL::getFullSelectorHTML(DI::page(), $a->getLoggedInUserId()));
$o .= Renderer::replaceMacros($tpl,[ $o .= Renderer::replaceMacros($tpl, [
'$pagename' => DI::l10n()->t('Upload Photos'), '$pagename' => DI::l10n()->t('Upload Photos'),
'$sessid' => session_id(), '$sessid' => session_id(),
'$usage' => $usage_message, '$usage' => $usage_message,
@ -747,7 +752,7 @@ function photos_content(App $a)
if ($datatype === 'album') { if ($datatype === 'album') {
// if $datum is not a valid hex, redirect to the default page // if $datum is not a valid hex, redirect to the default page
if (is_null($datum) || !Strings::isHex($datum)) { if (is_null($datum) || !Strings::isHex($datum)) {
DI::baseUrl()->redirect('photos/' . $user['nickname']. '/album'); DI::baseUrl()->redirect('photos/' . $user['nickname'] . '/album');
} }
$album = hex2bin($datum); $album = hex2bin($datum);
@ -756,7 +761,8 @@ function photos_content(App $a)
} }
$total = 0; $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`", AND `scale` <= 4 $sql_extra GROUP BY `resource-id`",
$owner_uid, $owner_uid,
$album $album
@ -775,7 +781,8 @@ function photos_content(App $a)
$order = 'DESC'; $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(`type`) AS `type`, max(`scale`) AS `scale`, ANY_VALUE(`desc`) as `desc`,
ANY_VALUE(`created`) as `created` ANY_VALUE(`created`) as `created`
FROM `photo` WHERE `uid` = ? AND `album` = ? FROM `photo` WHERE `uid` = ? AND `album` = ?
@ -809,7 +816,7 @@ function photos_content(App $a)
$album_e = $album; $album_e = $album;
$o .= Renderer::replaceMacros($edit_tpl,[ $o .= Renderer::replaceMacros($edit_tpl, [
'$nametext' => DI::l10n()->t('New album name: '), '$nametext' => DI::l10n()->t('New album name: '),
'$nickname' => $user['nickname'], '$nickname' => $user['nickname'],
'$album' => $album_e, '$album' => $album_e,
@ -843,16 +850,16 @@ function photos_content(App $a)
$desc_e = $rr['desc']; $desc_e = $rr['desc'];
$photos[] = [ $photos[] = [
'id' => $rr['id'], 'id' => $rr['id'],
'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2,4), 'twist' => ' ' . ($twist ? 'rotleft' : 'rotright') . rand(2, 4),
'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id'] 'link' => 'photos/' . $user['nickname'] . '/image/' . $rr['resource-id']
. ($order_field === 'created' ? '?order=created' : ''), . ($order_field === 'created' ? '?order=created' : ''),
'title' => DI::l10n()->t('View Photo'), 'title' => DI::l10n()->t('View Photo'),
'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' .$ext, 'src' => 'photo/' . $rr['resource-id'] . '-' . $rr['scale'] . '.' . $ext,
'alt' => $imgalt_e, 'alt' => $imgalt_e,
'desc'=> $desc_e, 'desc' => $desc_e,
'ext' => $ext, 'ext' => $ext,
'hash'=> $rr['resource-id'], 'hash' => $rr['resource-id'],
]; ];
} }
} }
@ -870,7 +877,6 @@ function photos_content(App $a)
]); ]);
return $o; return $o;
} }
// Display one photo // Display one photo
@ -955,7 +961,7 @@ function photos_content(App $a)
} }
$tpl = Renderer::getMarkupTemplate('photo_edit_head.tpl'); $tpl = Renderer::getMarkupTemplate('photo_edit_head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl,[ DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
'$prevlink' => $prevlink, '$prevlink' => $prevlink,
'$nextlink' => $nextlink '$nextlink' => $nextlink
]); ]);
@ -967,7 +973,7 @@ function photos_content(App $a)
if ($nextlink) { if ($nextlink) {
$nextlink = [$nextlink, '<div class="icon next"></div>']; $nextlink = [$nextlink, '<div class="icon next"></div>'];
} }
} }
} }
if (count($ph) == 1) { if (count($ph) == 1) {
@ -1007,12 +1013,12 @@ function photos_content(App $a)
} }
$photo = [ $photo = [
'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']], 'href' => 'photo/' . $hires['resource-id'] . '-' . $hires['scale'] . '.' . $phototypes[$hires['type']],
'title'=> DI::l10n()->t('View Full Size'), 'title' => DI::l10n()->t('View Full Size'),
'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?_u=' . DateTimeFormat::utcNow('ymdhis'), 'src' => 'photo/' . $lores['resource-id'] . '-' . $lores['scale'] . '.' . $phototypes[$lores['type']] . '?_u=' . DateTimeFormat::utcNow('ymdhis'),
'height' => $hires['height'], 'height' => $hires['height'],
'width' => $hires['width'], 'width' => $hires['width'],
'album' => $hires['album'], 'album' => $hires['album'],
'filename' => $hires['filename'], 'filename' => $hires['filename'],
]; ];
@ -1079,12 +1085,12 @@ function photos_content(App $a)
$edit = Renderer::replaceMacros($edit_tpl, [ $edit = Renderer::replaceMacros($edit_tpl, [
'$id' => $ph[0]['id'], '$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, ''], '$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')], '$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_none' => ['rotate', DI::l10n()->t('Do not rotate'), 0, '', true],
'$rotate_cw' => ['rotate', DI::l10n()->t("Rotate CW \x28right\x29"),1,''], '$rotate_cw' => ['rotate', DI::l10n()->t("Rotate CW \x28right\x29"), 1, ''],
'$rotate_ccw' => ['rotate', DI::l10n()->t("Rotate CCW \x28left\x29"),2,''], '$rotate_ccw' => ['rotate', DI::l10n()->t("Rotate CCW \x28left\x29"), 2, ''],
'$nickname' => $user['nickname'], '$nickname' => $user['nickname'],
'$resource_id' => $ph[0]['resource-id'], '$resource_id' => $ph[0]['resource-id'],
@ -1179,7 +1185,7 @@ function photos_content(App $a)
$qcomment = $words ? explode("\n", $words) : []; $qcomment = $words ? explode("\n", $words) : [];
} }
$comments .= Renderer::replaceMacros($cmnt_tpl,[ $comments .= Renderer::replaceMacros($cmnt_tpl, [
'$return_path' => '', '$return_path' => '',
'$jsreload' => $return_path, '$jsreload' => $return_path,
'$id' => $link_item['id'], '$id' => $link_item['id'],
@ -1203,13 +1209,19 @@ function photos_content(App $a)
$activity = DI::activity(); $activity = DI::activity();
if (($activity->match($item['verb'], Activity::LIKE) || if (($activity->match($item['verb'], Activity::LIKE) ||
$activity->match($item['verb'], Activity::DISLIKE)) && $activity->match($item['verb'], Activity::DISLIKE)) &&
($item['gravity'] != Item::GRAVITY_PARENT)) { ($item['gravity'] != Item::GRAVITY_PARENT)
) {
continue; continue;
} }
$author = ['uid' => 0, 'id' => $item['author-id'], $author = [
'network' => $item['author-network'], 'url' => $item['author-link']]; 'uid' => 0,
'id' => $item['author-id'],
'network' => $item['author-network'],
'url' => $item['author-link'],
'alias' => $item['author-alias']
];
$profile_url = Contact::magicLinkByContact($author); $profile_url = Contact::magicLinkByContact($author);
if (strpos($profile_url, 'contact/redir/') === 0) { if (strpos($profile_url, 'contact/redir/') === 0) {
$sparkle = ' sparkle'; $sparkle = ' sparkle';
@ -1228,7 +1240,7 @@ function photos_content(App $a)
$title_e = $item['title']; $title_e = $item['title'];
$body_e = BBCode::convertForUriId($item['uri-id'], $item['body']); $body_e = BBCode::convertForUriId($item['uri-id'], $item['body']);
$comments .= Renderer::replaceMacros($template,[ $comments .= Renderer::replaceMacros($template, [
'$id' => $item['id'], '$id' => $item['id'],
'$profile_url' => $profile_url, '$profile_url' => $profile_url,
'$name' => $item['author-name'], '$name' => $item['author-name'],

View file

@ -154,7 +154,8 @@ class Conversation
'uid' => 0, 'uid' => 0,
'id' => $activity['author-id'], 'id' => $activity['author-id'],
'network' => $activity['author-network'], 'network' => $activity['author-network'],
'url' => $activity['author-link'] 'url' => $activity['author-link'],
'alias' => $activity['author-alias'],
]; ];
$url = Contact::magicLinkByContact($author); $url = Contact::magicLinkByContact($author);
if (strpos($url, 'contact/redir/') === 0) { if (strpos($url, 'contact/redir/') === 0) {
@ -272,7 +273,7 @@ class Conversation
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts); $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts);
break; break;
case 'attendno': case 'attendno':
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t attend','<button type="button" %2$s>%1$d people</button> don\'t attend', $total, $spanatts); $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t attend', '<button type="button" %2$s>%1$d people</button> don\'t attend', $total, $spanatts);
break; break;
case 'attendmaybe': case 'attendmaybe':
$phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends maybe', '<button type="button" %2$s>%1$d people</button> attend maybe', $total, $spanatts); $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends maybe', '<button type="button" %2$s>%1$d people</button> attend maybe', $total, $spanatts);
@ -538,7 +539,7 @@ class Conversation
if (!$update) { if (!$update) {
$live_update_div = '<div id="live-contact"></div>' . "\r\n" $live_update_div = '<div id="live-contact"></div>' . "\r\n"
. "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8) . "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
."?f='; </script>\r\n"; . "?f='; </script>\r\n";
} }
} elseif ($mode === self::MODE_SEARCH) { } elseif ($mode === self::MODE_SEARCH) {
$live_update_div = '<div id="live-search"></div>' . "\r\n"; $live_update_div = '<div id="live-search"></div>' . "\r\n";
@ -626,7 +627,13 @@ class Conversation
$tags = Tag::populateFromItem($item); $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); $profile_link = Contact::magicLinkByContact($author);
$sparkle = ''; $sparkle = '';
@ -857,7 +864,8 @@ class Conversation
$row['causer-avatar'] = $contact['thumb']; $row['causer-avatar'] = $contact['thumb'];
$row['causer-name'] = $contact['name']; $row['causer-name'] = $contact['name'];
} elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) && } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
($row['author-id'] == $activity['causer-id'])) { ($row['author-id'] == $activity['causer-id'])
) {
return $row; return $row;
} }
} }
@ -893,9 +901,15 @@ class Conversation
} }
if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) { 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.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>'); $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="' . htmlentities(Contact::magicLinkByContact($causer)) . '">' . htmlentities($row['causer-name']) . '</a>');
} }
$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']))]; $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; break;
@ -995,15 +1009,21 @@ class Conversation
$condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]); $condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]);
} }
$condition = DBA::mergeConditions($condition, $condition = DBA::mergeConditions(
["`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,
["`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, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]);
$condition = DBA::mergeConditions($condition, $condition = DBA::mergeConditions(
["`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked` $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` = ?)", 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']]); $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-author'] = !$always_display && in_array($row['author-id'], $collapses);
$items[$key]['user-collapsed-owner'] = !$always_display && in_array($row['owner-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]) && if (
(in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))) { 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]); unset($items[$key]);
} }
} }
@ -1146,7 +1168,7 @@ class Conversation
$condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]); $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]);
$separator = chr(255) . chr(255) . chr(255); $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 = []; $emojis = [];
@ -1287,7 +1309,7 @@ class Conversation
// Searches the post item in the children // Searches the post item in the children
$j = 0; $j = 0;
while ($child['children'][$j]['verb'] !== Activity::POST && $j < count($child['children'])) { while ($child['children'][$j]['verb'] !== Activity::POST && $j < count($child['children'])) {
$j ++; $j++;
} }
$moved_item = $child['children'][$j]; $moved_item = $child['children'][$j];
@ -1361,8 +1383,10 @@ class Conversation
* items and add them as children of their top-level post. * items and add them as children of their top-level post.
*/ */
foreach ($parents as $i => $parent) { foreach ($parents as $i => $parent) {
$parents[$i]['children'] = array_merge($this->getItemChildren($item_array, $parent, true), $parents[$i]['children'] = array_merge(
$this->getItemChildren($item_array, $parent, false)); $this->getItemChildren($item_array, $parent, true),
$this->getItemChildren($item_array, $parent, false)
);
} }
foreach ($parents as $i => $parent) { foreach ($parents as $i => $parent) {

View file

@ -78,7 +78,7 @@ class GroupManager
$groupList = []; $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); $contacts = DBA::select('account-user-view', $fields, $condition, $params);
if (!$contacts) { if (!$contacts) {
return $groupList; return $groupList;
@ -87,6 +87,7 @@ class GroupManager
while ($contact = DBA::fetch($contacts)) { while ($contact = DBA::fetch($contacts)) {
$groupList[] = [ $groupList[] = [
'url' => $contact['url'], 'url' => $contact['url'],
'alias' => $contact['alias'],
'name' => $contact['name'], 'name' => $contact['name'],
'id' => $contact['id'], 'id' => $contact['id'],
'micro' => $contact['micro'], 'micro' => $contact['micro'],

View file

@ -305,18 +305,20 @@ class Item
} }
$author_arr = [ $author_arr = [
'uid' => 0, 'uid' => 0,
'id' => $item['author-id'], 'id' => $item['author-id'],
'network' => $item['author-network'], '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 = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $item['author-name'] . '[/url]';
$author_arr = [ $author_arr = [
'uid' => 0, 'uid' => 0,
'id' => $obj['author-id'], 'id' => $obj['author-id'],
'network' => $obj['author-network'], '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]'; $objauthor = '[url=' . Contact::magicLinkByContact($author_arr) . ']' . $obj['author-name'] . '[/url]';
@ -372,10 +374,11 @@ class Item
} }
$author = [ $author = [
'uid' => 0, 'uid' => 0,
'id' => $item['author-id'], 'id' => $item['author-id'],
'network' => $item['author-network'], 'network' => $item['author-network'],
'url' => $item['author-link'], 'url' => $item['author-link'],
'alias' => $item['author-alias'],
]; ];
$profile_link = Contact::magicLinkByContact($author, $item['author-link']); $profile_link = Contact::magicLinkByContact($author, $item['author-link']);
if (strpos($profile_link, 'contact/redir/') === 0) { if (strpos($profile_link, 'contact/redir/') === 0) {

View file

@ -422,7 +422,8 @@ class HTML
{ {
$URLSearchString = "^\[\]"; $URLSearchString = "^\[\]";
$matches = ["/\[url\=([$URLSearchString]*)\].*?\[\/url\]/ism", $matches = [
"/\[url\=([$URLSearchString]*)\].*?\[\/url\]/ism",
"/\[url\]([$URLSearchString]*)\[\/url\]/ism", "/\[url\]([$URLSearchString]*)\[\/url\]/ism",
"/\[img\=[0-9]*x[0-9]*\](.*?)\[\/img\]/ism", "/\[img\=[0-9]*x[0-9]*\](.*?)\[\/img\]/ism",
"/\[img\](.*?)\[\/img\]/ism", "/\[img\](.*?)\[\/img\]/ism",
@ -531,8 +532,10 @@ class HTML
$ignore = false; $ignore = false;
// A list of some links that should be ignored // A list of some links that should be ignored
$list = ["/user/", "/tag/", "/group/", "/circle/", "/profile/", "/search?search=", "/search?tag=", "mailto:", "/u/", "/node/", $list = [
"//plus.google.com/", "//twitter.com/"]; "/user/", "/tag/", "/group/", "/circle/", "/profile/", "/search?search=", "/search?tag=", "mailto:", "/u/", "/node/",
"//plus.google.com/", "//twitter.com/"
];
foreach ($list as $listitem) { foreach ($list as $listitem) {
if (strpos($treffer[1], $listitem) !== false) { if (strpos($treffer[1], $listitem) !== false) {
$ignore = true; $ignore = true;
@ -941,7 +944,8 @@ class HTML
$domain = '(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)*' . preg_quote(trim($domain, '/'), '%'); $domain = '(?:(?!-)[A-Za-z0-9-]{1,63}(?<!-)\.)*' . preg_quote(trim($domain, '/'), '%');
}); });
$config->set('URI.SafeIframeRegexp', $config->set(
'URI.SafeIframeRegexp',
'%^https://(?: '%^https://(?:
' . implode('|', $allowedIframeDomains) . ' ' . implode('|', $allowedIframeDomains) . '
) )
@ -1050,7 +1054,8 @@ class HTML
if (isset($mediaType->parameters['charset'])) { if (isset($mediaType->parameters['charset'])) {
return strtolower($mediaType->parameters['charset']); return strtolower($mediaType->parameters['charset']);
} }
} catch(\InvalidArgumentException $e) {} } catch (\InvalidArgumentException $e) {
}
return null; return null;
} }

View file

@ -476,7 +476,6 @@ class Widget
$nextday = substr($nextday, 4); $nextday = substr($nextday, 4);
$dnow = substr($dnow, 0, 8) . '01'; $dnow = substr($dnow, 0, 8) . '01';
$dthen = substr($dthen, 0, 8) . '01'; $dthen = substr($dthen, 0, 8) . '01';
/* /*
* Starting with the current month, get the first and last days of every * 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]; $ret[$dyear][] = [$str, $end_month, $start_month];
$dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d'); $dnow = DateTimeFormat::utc($dnow . ' -1 month', 'Y-m-d');
} }
} }
@ -508,7 +506,7 @@ class Widget
$cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years; $cutoff_year = intval(DateTimeFormat::localNow('Y')) - $visible_years;
$cutoff = array_key_exists($cutoff_year, $ret); $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'), '$title' => DI::l10n()->t('Archives'),
'$size' => $visible_years, '$size' => $visible_years,
'$cutoff_year' => $cutoff_year, '$cutoff_year' => $cutoff_year,
@ -543,7 +541,14 @@ class Widget
['ref' => 'community', 'name' => DI::l10n()->t('Groups')], ['ref' => 'community', 'name' => DI::l10n()->t('Groups')],
]; ];
return self::filter('accounttype', DI::l10n()->t('Account Types'), '', return self::filter(
DI::l10n()->t('All'), $base, $accounts, $accounttype); 'accounttype',
DI::l10n()->t('Account Types'),
'',
DI::l10n()->t('All'),
$base,
$accounts,
$accounttype
);
} }
} }

View file

@ -104,7 +104,7 @@ class ContactBlock
$contact_uriids = array_column($personal_contacts, 'uri-id'); $contact_uriids = array_column($personal_contacts, 'uri-id');
if (!empty($contact_uriids)) { 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)) { if (DBA::isResult($contacts_stmt)) {
$contacts_title = DI::l10n()->tt('%d Contact', '%d Contacts', $total); $contacts_title = DI::l10n()->tt('%d Contact', '%d Contacts', $total);

View file

@ -29,6 +29,7 @@ use Friendica\Core\Renderer;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Util\Network;
use Friendica\Util\Strings; use Friendica\Util\Strings;
/** /**
@ -50,9 +51,15 @@ class VCard
Logger::warning('Incomplete contact', ['contact' => $contact ?? [], 'callstack' => System::callstack(20)]); 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'] != '') { if ($contact['network'] != '') {
$network_link = Strings::formatNetworkName($contact['network'], $contact['url']); $network_link = Strings::formatNetworkName($contact['network'], $contact_url);
$network_avatar = ContactSelector::networkToIcon($contact['network'], $contact['url']); $network_avatar = ContactSelector::networkToIcon($contact['network'], $contact_url);
} else { } else {
$network_link = ''; $network_link = '';
$network_avatar = ''; $network_avatar = '';
@ -83,9 +90,9 @@ class VCard
if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) { if (empty($contact['self']) && Protocol::supportsFollow($contact['network'])) {
if (in_array($rel, [Contact::SHARING, Contact::FRIEND])) { 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) { } 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'), [ return Renderer::replaceMacros(Renderer::getMarkupTemplate('widget/vcard.tpl'), [
'$contact' => $contact, '$contact' => $contact,
'$photo' => $photo, '$photo' => $photo,
'$url' => Contact::magicLinkByContact($contact, $contact['url']), '$url' => Contact::magicLinkByContact($contact, $contact_url),
'$about' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'] ?? ''), '$about' => BBCode::convertForUriId($contact['uri-id'] ?? 0, $contact['about'] ?? ''),
'$xmpp' => DI::l10n()->t('XMPP:'), '$xmpp' => DI::l10n()->t('XMPP:'),
'$matrix' => DI::l10n()->t('Matrix:'), '$matrix' => DI::l10n()->t('Matrix:'),

View file

@ -119,6 +119,11 @@ class APContact
return []; return [];
} }
if (!Network::isValidHttpUrl($url) && !filter_var($url, FILTER_VALIDATE_EMAIL)) {
Logger::info('Invalid URL', ['url' => $url]);
return [];
}
$fetched_contact = []; $fetched_contact = [];
if (empty($update)) { if (empty($update)) {

View file

@ -23,7 +23,7 @@ namespace Friendica\Model;
use Friendica\Contact\Avatar; use Friendica\Contact\Avatar;
use Friendica\Contact\Introduction\Exception\IntroductionNotFoundException; 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\Pager;
use Friendica\Content\Text\HTML; use Friendica\Content\Text\HTML;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -111,12 +111,12 @@ class Contact
* @} * @}
*/ */
const MIRROR_DEACTIVATED = 0; const MIRROR_DEACTIVATED = 0;
const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST const MIRROR_FORWARDED = 1; // Deprecated, now does the same like MIRROR_OWN_POST
const MIRROR_OWN_POST = 2; const MIRROR_OWN_POST = 2;
const MIRROR_NATIVE_RESHARE = 3; const MIRROR_NATIVE_RESHARE = 3;
/** /**
* @param array $fields Array of selected fields, empty for all * @param array $fields Array of selected fields, empty for all
* @param array $condition Array of fields for condition * @param array $condition Array of fields for condition
* @param array $params Array of several parameters * @param array $params Array of several parameters
@ -725,8 +725,11 @@ class Contact
*/ */
public static function createSelfFromUserId(int $uid): bool public static function createSelfFromUserId(int $uid): bool
{ {
$user = DBA::selectFirst('user', ['uid', 'username', 'nickname', 'pubkey', 'prvkey'], $user = DBA::selectFirst(
['uid' => $uid, 'account_expired' => false]); 'user',
['uid', 'username', 'nickname', 'pubkey', 'prvkey'],
['uid' => $uid, 'account_expired' => false]
);
if (!DBA::isResult($user)) { if (!DBA::isResult($user)) {
return false; return false;
} }
@ -786,9 +789,11 @@ class Contact
*/ */
public static function updateSelfFromUserID(int $uid, bool $update_avatar = false): bool 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', '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]); $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
if (!DBA::isResult($self)) { if (!DBA::isResult($self)) {
return false; return false;
@ -800,8 +805,10 @@ class Contact
return false; return false;
} }
$fields = ['name', 'photo', 'thumb', 'about', 'address', 'locality', 'region', $fields = [
'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish']; 'name', 'photo', 'thumb', 'about', 'address', 'locality', 'region',
'country-name', 'pub_keywords', 'xmpp', 'matrix', 'net-publish'
];
$profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]); $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
if (!DBA::isResult($profile)) { if (!DBA::isResult($profile)) {
return false; return false;
@ -830,7 +837,7 @@ class Contact
'addr' => $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3), 'addr' => $user['nickname'] . '@' . substr(DI::baseUrl(), strpos(DI::baseUrl(), '://') + 3),
'request' => DI::baseUrl() . '/dfrn_request/' . $user['nickname'], 'request' => DI::baseUrl() . '/dfrn_request/' . $user['nickname'],
'notify' => DI::baseUrl() . '/dfrn_notify/' . $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'], '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 // We are adding a timestamp value so that other systems won't use cached content
$timestamp = strtotime($fields['avatar-date']); $timestamp = strtotime($fields['avatar-date']);
$prefix = DI::baseUrl() . '/photo/' .$avatar['resource-id'] . '-'; $prefix = DI::baseUrl() . '/photo/' . $avatar['resource-id'] . '-';
$suffix = '.' . $file_suffix . '?ts=' . $timestamp; $suffix = '.' . $file_suffix . '?ts=' . $timestamp;
$fields['photo'] = $prefix . '4' . $suffix; $fields['photo'] = $prefix . '4' . $suffix;
@ -1188,22 +1195,22 @@ class Contact
*/ */
if (empty($contact['uid'])) { if (empty($contact['uid'])) {
$menu = [ $menu = [
'profile' => [DI::l10n()->t('View Profile') , $profile_link , true ], 'profile' => [DI::l10n()->t('View Profile'), $profile_link, true],
'network' => [DI::l10n()->t('Network Posts') , $posts_link , false], 'network' => [DI::l10n()->t('Network Posts'), $posts_link, false],
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false], 'edit' => [DI::l10n()->t('View Contact'), $contact_url, false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true ], 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link, true],
'unfollow' => [DI::l10n()->t('Unfollow') , $unfollow_link, true ], 'unfollow' => [DI::l10n()->t('Unfollow'), $unfollow_link, true],
]; ];
} else { } else {
$menu = [ $menu = [
'status' => [DI::l10n()->t('View Status') , $status_link , true ], 'status' => [DI::l10n()->t('View Status'), $status_link, true],
'profile' => [DI::l10n()->t('View Profile') , $profile_link , true ], 'profile' => [DI::l10n()->t('View Profile'), $profile_link, true],
'photos' => [DI::l10n()->t('View Photos') , $photos_link , true ], 'photos' => [DI::l10n()->t('View Photos'), $photos_link, true],
'network' => [DI::l10n()->t('Network Posts') , $posts_link , false], 'network' => [DI::l10n()->t('Network Posts'), $posts_link, false],
'edit' => [DI::l10n()->t('View Contact') , $contact_url , false], 'edit' => [DI::l10n()->t('View Contact'), $contact_url, false],
'pm' => [DI::l10n()->t('Send PM') , $pm_url , false], 'pm' => [DI::l10n()->t('Send PM'), $pm_url, false],
'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link , true ], 'follow' => [DI::l10n()->t('Connect/Follow'), $follow_link, true],
'unfollow' => [DI::l10n()->t('Unfollow') , $unfollow_link, true ], 'unfollow' => [DI::l10n()->t('Unfollow'), $unfollow_link, true],
]; ];
if (!empty($contact['pending'])) { if (!empty($contact['pending'])) {
@ -1310,9 +1317,11 @@ class Contact
if (($uid == 0) && (empty($data['network']) || ($data['network'] == Protocol::PHANTOM))) { if (($uid == 0) && (empty($data['network']) || ($data['network'] == Protocol::PHANTOM))) {
// Fetch data for the public contact via the first found personal contact // 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?) /// @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', '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]); $personal_contact = DBA::selectFirst('contact', $fields, ["`addr` = ? AND `uid` != 0", $url]);
if (!DBA::isResult($personal_contact)) { if (!DBA::isResult($personal_contact)) {
@ -1458,7 +1467,7 @@ class Contact
if (!empty($contact['batch'])) { if (!empty($contact['batch'])) {
$condition = ['archive' => true, 'uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY]; $condition = ['archive' => true, 'uid' => 0, 'network' => Protocol::FEDERATED, 'batch' => $contact['batch'], 'contact-type' => self::TYPE_RELAY];
return DBA::exists('contact', $condition); return DBA::exists('contact', $condition);
} }
return false; return false;
} }
@ -1551,11 +1560,15 @@ class Contact
$contact_field = ((($contact["contact-type"] == self::TYPE_COMMUNITY) || ($contact['network'] == Protocol::MAIL)) ? 'owner-id' : 'author-id'); $contact_field = ((($contact["contact-type"] == self::TYPE_COMMUNITY) || ($contact['network'] == Protocol::MAIL)) ? 'owner-id' : 'author-id');
if ($thread_mode) { 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, $condition = [
$cid, Item::GRAVITY_PARENT, $cid, Item::GRAVITY_ACTIVITY, Verb::getID(Activity::ANNOUNCE), Conversation::PARCEL_DIASPORA, DI::userSession()->getLocalUserId()]; "((`$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 { } else {
$condition = ["`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql, $condition = [
$cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId()]; "`$contact_field` = ? AND `gravity` IN (?, ?) AND " . $sql,
$cid, Item::GRAVITY_PARENT, Item::GRAVITY_COMMENT, DI::userSession()->getLocalUserId()
];
} }
if (!empty($parent)) { if (!empty($parent)) {
@ -1568,16 +1581,26 @@ class Contact
} }
if ($only_media) { if ($only_media) {
$condition = DBA::mergeConditions($condition, ["`uri-id` IN (SELECT `uri-id` FROM `post-media` WHERE `type` IN (?, ?, ?))", $condition = DBA::mergeConditions($condition, [
Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]); "`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()) { if (DI::mode()->isMobile()) {
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_mobile_network', $itemsPerPage = DI::pConfig()->get(
DI::config()->get('system', 'itemspage_network_mobile')); DI::userSession()->getLocalUserId(),
'system',
'itemspage_mobile_network',
DI::config()->get('system', 'itemspage_network_mobile')
);
} else { } else {
$itemsPerPage = DI::pConfig()->get(DI::userSession()->getLocalUserId(), 'system', 'itemspage_network', $itemsPerPage = DI::pConfig()->get(
DI::config()->get('system', 'itemspage_network')); DI::userSession()->getLocalUserId(),
'system',
'itemspage_network',
DI::config()->get('system', 'itemspage_network')
);
} }
$pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage); $pager = new Pager(DI::l10n(), DI::args()->getQueryString(), $itemsPerPage);
@ -1611,8 +1634,10 @@ class Contact
if ($pager->getStart() == 0) { if ($pager->getStart() == 0) {
$cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId()); $cdata = self::getPublicAndUserContactID($cid, DI::userSession()->getLocalUserId());
if (!empty($cdata['public'])) { if (!empty($cdata['public'])) {
$condition = ["`uri-id` IN (SELECT `uri-id` FROM `collection-view` WHERE `cid` = ? AND `type` = ?)", $condition = [
$cdata['public'], Post\Collection::FEATURED]; "`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)); $pinned = Post::toArray(Post::selectForUser(DI::userSession()->getLocalUserId(), $fields, $condition, $params));
$items = array_merge($pinned, $items); $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']))) { } 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]); Logger::info('Adding avatar cache file', ['id' => $cid, 'contact' => $contact]);
self::updateAvatar($cid, $contact['avatar'], true); 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 public static function getAvatarUrlForUrl(string $url, int $uid, string $size = ''): string
{ {
$condition = ["`nurl` = ? AND ((`uid` = ? AND `network` IN (?, ?)) OR `uid` = ?)", $condition = [
Strings::normaliseLink($url), $uid, Protocol::FEED, Protocol::MAIL, 0]; "`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]]); $contact = self::selectFirst(['id', 'updated'], $condition, ['order' => ['uid' => true]]);
return self::getAvatarUrlForId($contact['id'] ?? 0, $size, $contact['updated'] ?? ''); 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) 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'], $contact = DBA::selectFirst(
['id' => $cid, 'self' => false]); 'contact',
['uid', 'avatar', 'photo', 'thumb', 'micro', 'blurhash', 'xmpp', 'addr', 'nurl', 'url', 'network', 'uri-id'],
['id' => $cid, 'self' => false]
);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
return; return;
} }
@ -2268,10 +2298,12 @@ class Contact
} }
if ($default_avatar && Proxy::isLocalImage($avatar)) { if ($default_avatar && Proxy::isLocalImage($avatar)) {
$fields = ['avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(), $fields = [
'avatar' => $avatar, 'avatar-date' => DateTimeFormat::utcNow(),
'photo' => $avatar, 'photo' => $avatar,
'thumb' => self::getDefaultAvatar($contact, Proxy::SIZE_THUMB), '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]); Logger::debug('Use default avatar', ['id' => $cid, 'uid' => $uid]);
} }
@ -2330,8 +2362,11 @@ class Contact
$uids = []; $uids = [];
if (($uid == 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) { if (($uid == 0) && !in_array($contact['network'], [Protocol::FEED, Protocol::MAIL])) {
// Collect all user contacts of the given public contact // Collect all user contacts of the given public contact
$personal_contacts = DBA::select('contact', ['id', 'uid'], $personal_contacts = DBA::select(
["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid]); 'contact',
['id', 'uid'],
["`nurl` = ? AND `id` != ? AND NOT `self`", $contact['nurl'], $cid]
);
while ($personal_contact = DBA::fetch($personal_contacts)) { while ($personal_contact = DBA::fetch($personal_contacts)) {
$cids[] = $personal_contact['id']; $cids[] = $personal_contact['id'];
$uids[] = $personal_contact['uid']; $uids[] = $personal_contact['uid'];
@ -2650,10 +2685,12 @@ class Contact
// These fields aren't updated by this routine: // These fields aren't updated by this routine:
// 'sensitive' // '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', 'manually-approve', 'unsearchable', 'url', 'addr', 'batch', 'notify', 'poll', 'request', 'confirm', 'poco',
'network', 'alias', 'baseurl', 'gsid', 'forum', 'prv', 'contact-type', 'pubkey', 'last-item', 'xmpp', 'matrix', '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]); $contact = DBA::selectFirst('contact', $fields, ['id' => $id]);
if (!DBA::isResult($contact)) { if (!DBA::isResult($contact)) {
return false; return false;
@ -2677,7 +2714,7 @@ class Contact
return true; return true;
} }
$has_local_data = self::hasLocalData($id, $contact); $has_local_data = self::hasLocalData($id, $contact);
$uid = $contact['uid']; $uid = $contact['uid'];
unset($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 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. // We check after the probing to be able to correct falsely detected contact types.
if (($contact['contact-type'] == self::TYPE_RELAY) && 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]); 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']]); Logger::info('Not updating relais', ['id' => $id, 'url' => $contact['url']]);
return true; return true;
@ -2773,7 +2811,7 @@ class Contact
} }
$update = false; $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 // make sure to not overwrite existing values with blank entries except some technical fields
$keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl']; $keep = ['batch', 'notify', 'poll', 'request', 'confirm', 'poco', 'baseurl'];
@ -3078,11 +3116,11 @@ class Contact
'protocol' => $protocol, 'protocol' => $protocol,
'pubkey' => $ret['pubkey'], 'pubkey' => $ret['pubkey'],
'rel' => $new_relation, 'rel' => $new_relation,
'priority'=> $ret['priority'], 'priority' => $ret['priority'],
'writable'=> $writeable, 'writable' => $writeable,
'hidden' => $hidden, 'hidden' => $hidden,
'blocked' => 0, 'blocked' => 0,
'readonly'=> 0, 'readonly' => 0,
'pending' => $pending, 'pending' => $pending,
'subhub' => $subhub 'subhub' => $subhub
]); ]);
@ -3172,8 +3210,10 @@ class Contact
} }
// Contact is blocked at user-level // Contact is blocked at user-level
if (!empty($contact['id']) && !empty($importer['id']) && if (
Contact\User::isBlocked($contact['id'], $importer['id'])) { !empty($contact['id']) && !empty($importer['id']) &&
Contact\User::isBlocked($contact['id'], $importer['id'])
) {
return false; return false;
} }
@ -3181,9 +3221,12 @@ class Contact
self::unmarkForArchival($contact); self::unmarkForArchival($contact);
if (($contact['rel'] == self::SHARING) if (($contact['rel'] == self::SHARING)
|| ($sharing && $contact['rel'] == self::FOLLOWER)) { || ($sharing && $contact['rel'] == self::FOLLOWER)
self::update(['rel' => self::FRIEND, 'writable' => true, 'pending' => false], ) {
['id' => $contact['id'], 'uid' => $importer['uid']]); 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 // 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 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); return self::magicLinkByContact($contact, $url);
} }
@ -3439,7 +3482,7 @@ class Contact
*/ */
public static function magicLinkByContact(array $contact, string $url = ''): string 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()) { if (!DI::userSession()->isAuthenticated()) {
return $destination; return $destination;
@ -3568,8 +3611,10 @@ class Contact
$params['limit'] = $limit; $params['limit'] = $limit;
} }
$condition = DBA::mergeConditions($condition, $condition = DBA::mergeConditions(
["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]); $condition,
["(`addr` LIKE ? OR `name` LIKE ? OR `nick` LIKE ?)", $search, $search, $search]
);
return DBA::selectToArray('account-user-view', [], $condition, $params); return DBA::selectToArray('account-user-view', [], $condition, $params);
} }
@ -3619,7 +3664,7 @@ class Contact
*/ */
public static function getRandomContact(): array 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` > ?", "`uid` = ? AND `network` = ? AND NOT `failed` AND `last-item` > ?",
0, Protocol::DFRN, DateTimeFormat::utc('now - 1 month'), 0, Protocol::DFRN, DateTimeFormat::utc('now - 1 month'),
], ['order' => ['RAND()']]); ], ['order' => ['RAND()']]);

View file

@ -553,7 +553,8 @@ class Event
WHERE `event`.`id` = ? WHERE `event`.`id` = ?
AND `event`.`uid` = ? AND `event`.`uid` = ?
$sql_perms", $sql_perms",
$event_id, $owner_uid $event_id,
$owner_uid
)); ));
if (empty($events)) { if (empty($events)) {
throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.')); throw new HTTPException\NotFoundException(DI::l10n()->t('Event not found.'));
@ -616,7 +617,8 @@ class Event
AND `start` <= ? AND `start` <= ?
$sql_perms", $sql_perms",
$owner_uid, $owner_uid,
$start, $start, $start,
$start,
$finish $finish
)); ));
@ -661,9 +663,9 @@ class Event
$copy = null; $copy = null;
$drop = null; $drop = null;
if (DI::userSession()->getLocalUserId() && DI::userSession()->getLocalUserId() == $event['uid'] && $event['type'] == 'event') { 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; $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; $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') , '', '']; $drop = ['calendar/api/delete/' . $event['id'], DI::l10n()->t('Delete event'), '', ''];
} }
$title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary'])); $title = BBCode::convertForUriId($event['uri-id'], Strings::escapeHtml($event['summary']));
@ -708,7 +710,7 @@ class Event
} }
switch ($format) { switch ($format) {
// Format the exported data as a CSV file. // Format the exported data as a CSV file.
case "csv": case "csv":
$o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL; $o .= '"Subject", "Start Date", "Start Time", "Description", "End Date", "End Time", "Location"' . PHP_EOL;
@ -728,7 +730,7 @@ class Event
} }
break; break;
// Format the exported data as a ics file. // Format the exported data as a ics file.
case "ical": case "ical":
$o = 'BEGIN:VCALENDAR' . PHP_EOL $o = 'BEGIN:VCALENDAR' . PHP_EOL
. 'VERSION:2.0' . PHP_EOL . 'VERSION:2.0' . PHP_EOL
@ -929,8 +931,13 @@ class Event
$location = self::locationToArray($item['event-location']); $location = self::locationToArray($item['event-location']);
// Construct the profile link (magic-auth). // Construct the profile link (magic-auth).
$author = ['uid' => 0, 'id' => $item['author-id'], $author = [
'network' => $item['author-network'], 'url' => $item['author-link']]; 'uid' => 0,
'id' => $item['author-id'],
'network' => $item['author-network'],
'url' => $item['author-link'],
'alias' => $item['author-alias']
];
$profile_link = Contact::magicLinkByContact($author); $profile_link = Contact::magicLinkByContact($author);
$tpl = Renderer::getMarkupTemplate('event_stream_item.tpl'); $tpl = Renderer::getMarkupTemplate('event_stream_item.tpl');

View file

@ -94,7 +94,7 @@ class Item
'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language', 'wall', 'private', 'starred', 'origin', 'parent-origin', 'title', 'body', 'language',
'content-warning', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object', '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', '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', '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', 'causer-id', 'causer-link', 'causer-name', 'causer-avatar', 'causer-contact-type', 'causer-network',
'contact-id', 'contact-uid', 'contact-link', 'contact-name', 'contact-avatar', '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 // 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', const DELIVER_FIELDLIST = [
'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target', 'uid', 'id', 'parent', 'uri-id', 'uri', 'thr-parent', 'parent-uri', 'guid',
'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app', 'parent-guid', 'conversation', 'received', 'created', 'edited', 'verb', 'object-type', 'object', 'target',
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', 'private', 'title', 'body', 'raw-body', 'location', 'coord', 'app',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin', 'author-id', 'author-addr', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid',
'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail', 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin',
'event-created', 'event-edited', 'event-start', 'event-finish', 'thr-parent-id', 'parent-uri-id', 'quote-uri', 'quote-uri-id', 'postopts', 'pubmail',
'event-summary', 'event-desc', 'event-location', 'event-type', 'event-created', 'event-edited', 'event-start', 'event-finish',
'event-nofinish', 'event-ignore', 'event-id']; 'event-summary', 'event-desc', 'event-location', 'event-type',
'event-nofinish', 'event-ignore', 'event-id'
];
// All fields in the item table // All fields in the item table
const ITEM_FIELDLIST = ['id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent', const ITEM_FIELDLIST = [
'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid', 'id', 'uid', 'parent', 'uri', 'parent-uri', 'thr-parent',
'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid', 'guid', 'uri-id', 'parent-uri-id', 'thr-parent-id', 'conversation', 'vid',
'created', 'edited', 'commented', 'received', 'changed', 'verb', 'quote-uri', 'quote-uri-id', 'contact-id', 'wall', 'gravity', 'extid', 'psid',
'postopts', 'plink', 'resource-id', 'event-id', 'inform', 'created', 'edited', 'commented', 'received', 'changed', 'verb',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', 'postopts', 'plink', 'resource-id', 'event-id', 'inform',
'private', 'pubmail', 'visible', 'starred', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason',
'unseen', 'deleted', 'origin', 'mention', 'global', 'network', 'private', 'pubmail', 'visible', 'starred',
'title', 'content-warning', 'body', 'location', 'coord', 'app', 'unseen', 'deleted', 'origin', 'mention', 'global', 'network',
'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', 'title', 'content-warning', 'body', 'location', 'coord', 'app',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
'owner-id', 'owner-link', 'owner-name', 'owner-avatar', 'causer-id']; '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. // 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. // 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::LIKE, Activity::DISLIKE,
Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE, Activity::ATTEND, Activity::ATTENDNO, Activity::ATTENDMAYBE,
Activity::FOLLOW, Activity::FOLLOW,
Activity::ANNOUNCE]; Activity::ANNOUNCE
];
// Privacy levels // Privacy levels
const PUBLIC = 0; const PUBLIC = 0;
@ -191,8 +196,10 @@ class Item
} }
// We only need to call the line by line update for specific fields // We only need to call the line by line update for specific fields
if (empty($fields['body']) && empty($fields['file']) && if (
empty($fields['attach']) && empty($fields['edited'])) { empty($fields['body']) && empty($fields['file']) &&
empty($fields['attach']) && empty($fields['edited'])
) {
return $rows; return $rows;
} }
@ -319,9 +326,11 @@ class Item
{ {
Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]); Logger::info('Mark item for deletion by id', ['id' => $item_id, 'callstack' => System::callstack()]);
// locate item to be deleted // 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', '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]); $item = Post::selectFirst($fields, ['id' => $item_id]);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
Logger::info('Item not found.', ['id' => $item_id]); Logger::info('Item not found.', ['id' => $item_id]);
@ -359,7 +368,7 @@ class Item
// If item has attachments, drop them // If item has attachments, drop them
$attachments = Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT]); $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)) { if (preg_match('|attach/(\d+)|', $attachment['url'], $matches)) {
Attach::delete(['id' => $matches[1], 'uid' => $item['uid']]); Attach::delete(['id' => $matches[1], 'uid' => $item['uid']]);
} }
@ -473,7 +482,7 @@ class Item
private static function contactId(array $item): int private static function contactId(array $item): int
{ {
if ($item['uid'] == 0) { if ($item['uid'] == 0) {
return $item['author-id']; return $item['owner-id'];
} }
if ($item['origin']) { if ($item['origin']) {
@ -481,6 +490,23 @@ class Item
return $owner['id']; 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)) { if (!empty($item['causer-id']) && Contact::isSharing($item['causer-id'], $item['uid'], true)) {
$cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']); $cdata = Contact::getPublicAndUserContactID($item['causer-id'], $item['uid']);
if (!empty($cdata['user'])) { if (!empty($cdata['user'])) {
@ -488,24 +514,7 @@ class Item
} }
} }
if ($item['gravity'] == self::GRAVITY_PARENT) { if ($contact_id) {
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)) {
return $contact_id; return $contact_id;
} }
@ -551,8 +560,10 @@ class Item
return true; return true;
} }
$condition = ['uri-id' => $item['uri-id'], 'uid' => $item['uid'], $condition = [
'network' => [$item['network'], Protocol::DFRN]]; 'uri-id' => $item['uri-id'], 'uid' => $item['uid'],
'network' => [$item['network'], Protocol::DFRN]
];
if (Post::exists($condition)) { if (Post::exists($condition)) {
Logger::notice('duplicated item with the same uri found.', $condition); Logger::notice('duplicated item with the same uri found.', $condition);
return true; return true;
@ -567,8 +578,10 @@ class Item
} }
} elseif ($item['network'] == Protocol::OSTATUS) { } elseif ($item['network'] == Protocol::OSTATUS) {
// Check for an existing post with the same content. There seems to be a problem with 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` = ?", $condition = [
$item['body'], $item['network'], $item['created'], $item['contact-id'], $item['uid']]; "`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)) { if (Post::exists($condition)) {
Logger::notice('duplicated item with the same body found.', $item); Logger::notice('duplicated item with the same body found.', $item);
return true; return true;
@ -642,8 +655,10 @@ class Item
return false; return false;
} }
$condition = ['verb' => Activity::FOLLOW, 'uid' => $item['uid'], $condition = [
'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']]; 'verb' => Activity::FOLLOW, 'uid' => $item['uid'],
'parent-uri' => $item['parent-uri'], 'author-id' => $item['author-id']
];
if (Post::exists($condition)) { if (Post::exists($condition)) {
// It happens that we receive multiple follow requests by the same author - we only store one. // 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']]); 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 private static function getDuplicateID(array $item): int
{ {
if (empty($item['network']) || in_array($item['network'], Protocol::FEDERATED)) { 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['uri-id'],
$item['uid'], $item['uid'],
Protocol::ACTIVITYPUB, Protocol::ACTIVITYPUB,
@ -751,10 +767,12 @@ class Item
*/ */
private static function getTopLevelParent(array $item): array 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', 'uri-id', 'parent-uri-id',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', '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']]; $condition = ['uri-id' => [$item['thr-parent-id'], $item['parent-uri-id']], 'uid' => $item['uid']];
$params = ['order' => ['id' => false]]; $params = ['order' => ['id' => false]];
$parent = Post::selectFirst($fields, $condition, $params); $parent = Post::selectFirst($fields, $condition, $params);
@ -779,9 +797,11 @@ class Item
return $parent; return $parent;
} }
$condition = ['uri-id' => $parent['parent-uri-id'], $condition = [
'uri-id' => $parent['parent-uri-id'],
'parent-uri-id' => $parent['parent-uri-id'], 'parent-uri-id' => $parent['parent-uri-id'],
'uid' => $parent['uid']]; 'uid' => $parent['uid']
];
$params = ['order' => ['id' => false]]; $params = ['order' => ['id' => false]];
$toplevel_parent = Post::selectFirst($fields, $condition, $params); $toplevel_parent = Post::selectFirst($fields, $condition, $params);
@ -946,7 +966,7 @@ class Item
// Communities aren't working with the Diaspora protocol // Communities aren't working with the Diaspora protocol
if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) { if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) {
$user = User::getById($uid, ['account-type']); $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'); Logger::info('Community posts are not supported via Diaspora');
return 0; return 0;
} }
@ -966,12 +986,16 @@ class Item
$item['gravity'] = self::getGravity($item); $item['gravity'] = self::getGravity($item);
$default = ['url' => $item['author-link'], 'name' => $item['author-name'], $default = [
'photo' => $item['author-avatar'], 'network' => $item['network']]; '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); $item['author-id'] = ($item['author-id'] ?? 0) ?: Contact::getIdForURL($item['author-link'], 0, null, $default);
$default = ['url' => $item['owner-link'], 'name' => $item['owner-name'], $default = [
'photo' => $item['owner-avatar'], 'network' => $item['network']]; '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['owner-id'] = ($item['owner-id'] ?? 0) ?: Contact::getIdForURL($item['owner-link'], 0, null, $default);
$item['post-reason'] = self::getPostReason($item); $item['post-reason'] = self::getPostReason($item);
@ -982,8 +1006,10 @@ class Item
$item['contact-id'] = self::contactId($item); $item['contact-id'] = self::contactId($item);
if (!empty($item['direction']) && in_array($item['direction'], [Conversation::PUSH, Conversation::RELAY]) && if (
empty($item['origin']) && self::isTooOld($item)) { !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]); Logger::info('Item is too old', ['item' => $item]);
return 0; return 0;
} }
@ -1124,7 +1150,8 @@ class Item
$item['allow_gid'], $item['allow_gid'],
$item['deny_cid'], $item['deny_cid'],
$item['deny_gid'] $item['deny_gid']
))->id; )
)->id;
if (!empty($item['extid'])) { if (!empty($item['extid'])) {
$item['external-id'] = ItemURI::getIdByURI($item['extid']); $item['external-id'] = ItemURI::getIdByURI($item['extid']);
@ -1356,7 +1383,8 @@ class Item
// Don't relay participation messages // Don't relay participation messages
if (($posted_item['verb'] == Activity::FOLLOW) && 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']]); Logger::info('Participation messages will not be relayed', ['item' => $posted_item['id'], 'uri' => $posted_item['uri'], 'verb' => $posted_item['verb']]);
$transmit = false; $transmit = false;
} }
@ -1429,8 +1457,10 @@ class Item
*/ */
private static function setOwnerforResharedItem(array $item) private static function setOwnerforResharedItem(array $item)
{ {
$parent = Post::selectFirst(['id', 'causer-id', 'owner-id', 'author-id', 'author-link', 'origin', 'post-reason'], $parent = Post::selectFirst(
['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); ['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)) { if (!DBA::isResult($parent)) {
Logger::error('Parent not found', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]); Logger::error('Parent not found', ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]);
return; return;
@ -1502,9 +1532,11 @@ class Item
} }
// Only distribute public items from native networks // Only distribute public items from native networks
$condition = ['id' => $itemid, 'uid' => 0, $condition = [
'network' => array_merge(Protocol::FEDERATED ,['']), 'id' => $itemid, 'uid' => 0,
'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED]]; 'network' => array_merge(Protocol::FEDERATED, ['']),
'visible' => true, 'deleted' => false, 'private' => [self::PUBLIC, self::UNLISTED]
];
$item = Post::selectFirst(array_merge(self::ITEM_FIELDLIST, ['protocol']), $condition); $item = Post::selectFirst(array_merge(self::ITEM_FIELDLIST, ['protocol']), $condition);
if (!DBA::isResult($item)) { if (!DBA::isResult($item)) {
Logger::warning('Item not found', ['condition' => $condition]); Logger::warning('Item not found', ['condition' => $condition]);
@ -1625,7 +1657,8 @@ class Item
if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) && if (($uid != 0) && (($item['gravity'] == self::GRAVITY_PARENT) || $is_reshare) &&
DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE && 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']]); 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; return 0;
} }
@ -1821,7 +1854,7 @@ class Item
} }
// is it an entry from a connector? Only add an entry for natively connected networks // 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; return;
} }
@ -2124,12 +2157,16 @@ class Item
} }
// Now do the same for the system wide contacts with uid=0 // Now do the same for the system wide contacts with uid=0
if ($arr['private'] != self::PRIVATE) { if ($arr['private'] != self::PRIVATE) {
Contact::update(['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], Contact::update(
['id' => $arr['owner-id']]); ['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']],
['id' => $arr['owner-id']]
);
if ($arr['owner-id'] != $arr['author-id']) { if ($arr['owner-id'] != $arr['author-id']) {
Contact::update(['failed' => false, 'local-data' => true, 'success_update' => $arr['received'], 'last-item' => $arr['received']], Contact::update(
['id' => $arr['author-id']]); ['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 // All hashtags should point to the home server if "local_tags" is activated
if (DI::config()->get('system', 'local_tags')) { if (DI::config()->get('system', 'local_tags')) {
$body = preg_replace("/#\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body = preg_replace(
"#[url=" . DI::baseUrl() . "/search?tag=$2]$2[/url]", $body); "/#\[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 // 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) { function ($match) {
return ("[url=" . str_replace("#", "&num;", $match[1]) . "]" . str_replace("#", "&num;", $match[2]) . "[/url]"); return ("[url=" . str_replace("#", "&num;", $match[1]) . "]" . str_replace("#", "&num;", $match[2]) . "[/url]");
}, $body); },
$body
);
$body = preg_replace_callback("/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism", $body = preg_replace_callback(
"/\[bookmark\=([$URLSearchString]*)\](.*?)\[\/bookmark\]/ism",
function ($match) { function ($match) {
return ("[bookmark=" . str_replace("#", "&num;", $match[1]) . "]" . str_replace("#", "&num;", $match[2]) . "[/bookmark]"); return ("[bookmark=" . str_replace("#", "&num;", $match[1]) . "]" . str_replace("#", "&num;", $match[2]) . "[/bookmark]");
}, $body); },
$body
);
$body = preg_replace_callback("/\[attachment (.*?)\](.*?)\[\/attachment\]/ism", $body = preg_replace_callback(
"/\[attachment (.*?)\](.*?)\[\/attachment\]/ism",
function ($match) { function ($match) {
return ("[attachment " . str_replace("#", "&num;", $match[1]) . "]" . $match[2] . "[/attachment]"); return ("[attachment " . str_replace("#", "&num;", $match[1]) . "]" . $match[2] . "[/attachment]");
}, $body); },
$body
);
// Repair recursive urls // Repair recursive urls
$body = preg_replace("/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism", $body = preg_replace(
"&num;$2", $body); "/&num;\[url\=([$URLSearchString]*)\](.*?)\[\/url\]/ism",
"&num;$2",
$body
);
foreach ($tags as $tag) { foreach ($tags as $tag) {
if ((strpos($tag, '#') !== 0) || strpos($tag, '[url=') || strlen($tag) < 2 || $tag[1] == '#') { 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 private static function tagDeliver(int $uid, int $item_id): bool
{ {
$mention = false;
$owner = User::getOwnerDataById($uid); $owner = User::getOwnerDataById($uid);
if (!DBA::isResult($owner)) { if (!DBA::isResult($owner)) {
Logger::warning('User not found, quitting here.', ['uid' => $uid]); Logger::warning('User not found, quitting here.', ['uid' => $uid]);
@ -2331,10 +2381,13 @@ class Item
} }
$datarray2 = $datarray; $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'], $self = DBA::selectFirst(
['uid' => $contact['uid'], 'self' => true]); 'contact',
['id', 'name', 'url', 'thumb'],
['uid' => $contact['uid'], 'self' => true]
);
if (!DBA::isResult($self)) { if (!DBA::isResult($self)) {
Logger::error('Self contact not found', ['uid' => $contact['uid']]); Logger::error('Self contact not found', ['uid' => $contact['uid']]);
return false; return false;
@ -2370,7 +2423,7 @@ class Item
// Store the original post // Store the original post
$result = self::insert($datarray2); $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 { } else {
$datarray['private'] = self::PUBLIC; $datarray['private'] = self::PUBLIC;
$datarray['app'] = 'Feed'; $datarray['app'] = 'Feed';
@ -2497,7 +2550,8 @@ class Item
if (($obj1['allow_cid'] == $obj2['allow_cid']) if (($obj1['allow_cid'] == $obj2['allow_cid'])
&& ($obj1['allow_gid'] == $obj2['allow_gid']) && ($obj1['allow_gid'] == $obj2['allow_gid'])
&& ($obj1['deny_cid'] == $obj2['deny_cid']) && ($obj1['deny_cid'] == $obj2['deny_cid'])
&& ($obj1['deny_gid'] == $obj2['deny_gid'])) { && ($obj1['deny_gid'] == $obj2['deny_gid'])
) {
return true; return true;
} }
@ -2544,8 +2598,10 @@ class Item
return; return;
} }
$condition = ["`uid` = ? AND NOT `deleted` AND `gravity` = ?", $condition = [
$uid, self::GRAVITY_PARENT]; "`uid` = ? AND NOT `deleted` AND `gravity` = ?",
$uid, self::GRAVITY_PARENT
];
/* /*
* $expire_network_only = save your own wall posts * $expire_network_only = save your own wall posts
@ -2619,8 +2675,10 @@ class Item
return false; return false;
} }
$condition = ["`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?", $condition = [
$uid, $wall, $user['register_date']]; "`uid` = ? AND `wall` = ? AND NOT `deleted` AND `visible` AND `received` >= ?",
$uid, $wall, $user['register_date']
];
$params = ['order' => ['received' => false]]; $params = ['order' => ['received' => false]];
$thread = Post::selectFirstThread(['received'], $condition, $params); $thread = Post::selectFirstThread(['received'], $condition, $params);
if (DBA::isResult($thread)) { if (DBA::isResult($thread)) {
@ -2752,8 +2810,10 @@ class Item
$vids = Verb::getID($activity); $vids = Verb::getID($activity);
} }
$condition = ['vid' => $vids, 'deleted' => false, 'gravity' => self::GRAVITY_ACTIVITY, $condition = [
'author-id' => $author_id, 'uid' => $uid, 'thr-parent-id' => $uri_id]; '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); $like_item = Post::selectFirst(['id', 'guid', 'verb'], $condition);
if (DBA::isResult($like_item)) { if (DBA::isResult($like_item)) {
@ -2857,12 +2917,14 @@ class Item
// Profile owner - everything is visible // Profile owner - everything is visible
$condition = []; $condition = [];
} elseif ($remote_user) { } elseif ($remote_user) {
// Authenticated visitor - fetch the matching permissionsets // Authenticated visitor - fetch the matching permissionsets
$permissionSets = DI::permissionSet()->selectByContactId($remote_user, $owner_id); $permissionSets = DI::permissionSet()->selectByContactId($remote_user, $owner_id);
if (!empty($set)) { if (!empty($set)) {
$condition = ["(`private` != ? OR (`private` = ? AND `wall` $condition = [
"(`private` != ? OR (`private` = ? AND `wall`
AND `psid` IN (" . implode(', ', array_fill(0, count($set), '?')) . ")))", AND `psid` IN (" . implode(', ', array_fill(0, count($set), '?')) . ")))",
self::PRIVATE, self::PRIVATE]; self::PRIVATE, self::PRIVATE
];
$condition = array_merge($condition, $permissionSets->column('id')); $condition = array_merge($condition, $permissionSets->column('id'));
} }
} }
@ -2958,7 +3020,8 @@ class Item
$rendered_hash = $item['rendered-hash'] ?? ''; $rendered_hash = $item['rendered-hash'] ?? '';
$rendered_html = $item['rendered-html'] ?? ''; $rendered_html = $item['rendered-html'] ?? '';
if ($rendered_hash == '' if (
$rendered_hash == ''
|| $rendered_html == '' || $rendered_html == ''
|| $rendered_hash != hash('md5', BBCode::VERSION . '::' . $body) || $rendered_hash != hash('md5', BBCode::VERSION . '::' . $body)
|| DI::config()->get('system', 'ignore_cache') || DI::config()->get('system', 'ignore_cache')
@ -3263,8 +3326,10 @@ class Item
} }
foreach ([0, 1, 2] as $size) { foreach ([0, 1, 2] as $size) {
if (preg_match('#/photo/.*-' . $size . '\.#ism', $url) && if (
strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url)) { preg_match('#/photo/.*-' . $size . '\.#ism', $url) &&
strpos(preg_replace('#(/photo/.*)-[012]\.#ism', '$1-' . $size . '.', $body), $url)
) {
return true; return true;
} }
} }
@ -3461,7 +3526,8 @@ class Item
'text' => '', 'text' => '',
'title' => $attachment['name'] ?? '', 'title' => $attachment['name'] ?? '',
'type' => 'link', 'type' => 'link',
'url' => $attachment['url']]; 'url' => $attachment['url']
];
if ($preview && !empty($attachment['preview'])) { if ($preview && !empty($attachment['preview'])) {
if ($attachment['preview-width'] >= 500) { if ($attachment['preview-width'] >= 500) {
@ -3551,8 +3617,13 @@ class Item
continue; continue;
} }
$author = ['uid' => 0, 'id' => $item['author-id'], $author = [
'network' => $item['author-network'], 'url' => $item['author-link']]; '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']); $the_url = Contact::magicLinkByContact($author, $attachment['url']);
$title = Strings::escapeHtml(trim(($attachment['description'] ?? '') ?: $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']); $summary = DI::l10n()->tt('%d voter.', '%d voters.', $question['voters']);
} elseif (!empty($question['endtime'])) { } elseif (!empty($question['endtime'])) {
$summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime'])); $summary = DI::l10n()->t('Poll end: %s', Temporal::getRelativeDate($question['endtime']));
} else { } else {
$summary = ''; $summary = '';
} }
@ -3610,7 +3681,7 @@ class Item
'$options' => $options, '$options' => $options,
'$summary' => $summary, '$summary' => $summary,
]); ]);
} }
DI::profiler()->stopRecording(); DI::profiler()->stopRecording();
return $content; return $content;
} }
@ -3639,8 +3710,13 @@ class Item
]; ];
if (!empty($plink) && ($item['private'] == self::PRIVATE)) { if (!empty($plink) && ($item['private'] == self::PRIVATE)) {
$author = ['uid' => 0, 'id' => $item['author-id'], $author = [
'network' => $item['author-network'], 'url' => $item['author-link']]; 'uid' => 0,
'id' => $item['author-id'],
'network' => $item['author-network'],
'url' => $item['author-link'],
'alias' => $item['author-alias'],
];
$plink = Contact::magicLinkByContact($author, $plink); $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? * 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 * @param int $uri_id
* *
@ -3671,7 +3748,13 @@ class Item
*/ */
public static function isGroupPost(int $uri_id): bool 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])) { if (DBA::exists('contact', ['uid' => 0, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) {
return true; return true;
} }

View file

@ -487,7 +487,7 @@ class Tag
* *
* @return boolean * @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); $tags = self::getByURIId($uriId, $type);
foreach ($tags as $tag) { foreach ($tags as $tag) {

View file

@ -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; DI::page()['aside'] .= $vcard_widget . $findpeople_widget . $follow_widget . $rel_widget . $circles_widget . $networks_widget . $account_widget;
$tpl = Renderer::getMarkupTemplate('contacts-head.tpl'); $tpl = Renderer::getMarkupTemplate('contacts-head.tpl');
DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [ DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, []);
]);
$o = ''; $o = '';
Nav::setSelected('contact'); Nav::setSelected('contact');
@ -412,20 +411,41 @@ class Contact extends BaseModule
$tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]); $tabs_html = Renderer::replaceMacros($tabs_tpl, ['$tabs' => $tabs]);
switch ($rel) { switch ($rel) {
case 'followers': $header = DI::l10n()->t('Followers'); break; case 'followers':
case 'following': $header = DI::l10n()->t('Following'); break; $header = DI::l10n()->t('Followers');
case 'mutuals': $header = DI::l10n()->t('Mutual friends'); break; break;
case 'nothing': $header = DI::l10n()->t('No relationship'); break; case 'following':
default: $header = DI::l10n()->t('Contacts'); $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) { switch ($type) {
case 'pending': $header .= ' - ' . DI::l10n()->t('Pending'); break; case 'pending':
case 'blocked': $header .= ' - ' . DI::l10n()->t('Blocked'); break; $header .= ' - ' . DI::l10n()->t('Pending');
case 'hidden': $header .= ' - ' . DI::l10n()->t('Hidden'); break; break;
case 'ignored': $header .= ' - ' . DI::l10n()->t('Ignored'); break; case 'blocked':
case 'collapsed': $header .= ' - ' . DI::l10n()->t('Collapsed'); break; $header .= ' - ' . DI::l10n()->t('Blocked');
case 'archived': $header .= ' - ' . DI::l10n()->t('Archived'); break; 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) : ''; $header .= $nets ? ' - ' . ContactSelector::networkToName($nets) : '';
@ -512,7 +532,8 @@ class Contact extends BaseModule
'id' => 'media-tab', 'id' => 'media-tab',
'accesskey' => 'd', 'accesskey' => 'd',
], ],
['label' => DI::l10n()->t('Contacts'), [
'label' => DI::l10n()->t('Contacts'),
'url' => 'contact/' . $pcid . '/contacts', 'url' => 'contact/' . $pcid . '/contacts',
'sel' => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''), 'sel' => (($active_tab == self::TAB_CONTACTS) ? 'active' : ''),
'title' => DI::l10n()->t('View all known contacts'), '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)) { 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/', 'url' => 'contact/' . $cid . '/advanced/',
'sel' => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''), 'sel' => (($active_tab == self::TAB_ADVANCED) ? 'active' : ''),
'title' => DI::l10n()->t('Advanced Contact Settings'), 'title' => DI::l10n()->t('Advanced Contact Settings'),

View file

@ -93,7 +93,7 @@ class Unfollow extends \Friendica\BaseModule
Strings::normaliseLink($url), Strings::normaliseLink($url), $url, 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)) { if (!$this->database->isResult($contact)) {
$this->systemMessages->addNotice($this->t("You aren't following this contact.")); $this->systemMessages->addNotice($this->t("You aren't following this contact."));
$this->baseUrl->redirect($base_return_path); $this->baseUrl->redirect($base_return_path);

View file

@ -29,12 +29,14 @@ use Friendica\Core\Session\Capability\IHandleUserSessions;
use Friendica\Core\System; use Friendica\Core\System;
use Friendica\Database\Database; use Friendica\Database\Database;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\GServer;
use Friendica\Model\User; use Friendica\Model\User;
use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests; use Friendica\Network\HTTPClient\Capability\ICanSendHttpRequests;
use Friendica\Network\HTTPClient\Client\HttpClientOptions; use Friendica\Network\HTTPClient\Client\HttpClientOptions;
use Friendica\Util\HTTPSignature; use Friendica\Util\HTTPSignature;
use Friendica\Util\Profiler; use Friendica\Util\Profiler;
use Friendica\Util\Strings; use Friendica\Util\Strings;
use GuzzleHttp\Psr7\Uri;
use Psr\Log\LoggerInterface; use Psr\Log\LoggerInterface;
/** /**
@ -83,6 +85,8 @@ class Magic extends BaseModule
$this->logger->debug('bdest detected', ['dest' => $dest]); $this->logger->debug('bdest detected', ['dest' => $dest]);
} }
$target = $dest ?: $addr;
if ($addr ?: $dest) { if ($addr ?: $dest) {
$contact = Contact::getByURL($addr ?: $dest); $contact = Contact::getByURL($addr ?: $dest);
} }
@ -110,14 +114,21 @@ class Magic extends BaseModule
// OpenWebAuth // OpenWebAuth
$owner = User::getOwnerDataById($this->userSession->getLocalUserId()); $owner = User::getOwnerDataById($this->userSession->getLocalUserId());
$gserver = $this->dba->selectFirst('gserver', ['url'], ['id' => $contact['gsid']]); if (!empty($contact['gsid'])) {
if (empty($gserver)) { $gserver = $this->dba->selectFirst('gserver', ['url'], ['id' => $contact['gsid']]);
$this->logger->notice('Server not found, redirecting to destination.', ['gsid' => $contact['gsid'], 'dest' => $dest]); 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); System::externalRedirect($dest);
} }
$basepath = $gserver['url'];
$header = [ $header = [
'Accept' => 'application/x-dfrn+json, application/x-zot+json', 'Accept' => 'application/x-dfrn+json, application/x-zot+json',
'X-Open-Web-Auth' => Strings::getRandomHex() 'X-Open-Web-Auth' => Strings::getRandomHex()

View file

@ -91,9 +91,13 @@ class Post
} }
$this->writable = $this->getDataValue('writable') || $this->getDataValue('self'); $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'), '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); $this->redirect_url = Contact::magicLinkByContact($author);
if (!$this->isToplevel()) { if (!$this->isToplevel()) {
$this->threaded = true; $this->threaded = true;
@ -286,8 +290,13 @@ class Post
} }
if (DI::userSession()->isAuthenticated()) { if (DI::userSession()->isAuthenticated()) {
$author = ['uid' => 0, 'id' => $item['author-id'], $author = [
'network' => $item['author-network'], 'url' => $item['author-link']]; 'uid' => 0,
'id' => $item['author-id'],
'network' => $item['author-network'],
'url' => $item['author-link'],
'alias' => $item['author-alias'],
];
$profile_link = Contact::magicLinkByContact($author); $profile_link = Contact::magicLinkByContact($author);
} else { } else {
$profile_link = $item['author-link']; $profile_link = $item['author-link'];
@ -388,7 +397,7 @@ class Post
} }
if ($conv->isWritable()) { 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')]; $buttons['dislike'] = [DI::l10n()->t("I don't like this \x28toggle\x29"), DI::l10n()->t('Dislike')];
if ($shareable) { if ($shareable) {
$buttons['share'] = [DI::l10n()->t('Quote share this'), DI::l10n()->t('Quote Share')]; $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 // 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'))) { 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'), $remote_comment = [
str_replace('{uri}', urlencode($item['uri']), DI::session()->get('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 // Ensure to either display the remote comment or the local activities
$buttons = []; $buttons = [];
@ -670,7 +681,6 @@ class Post
$title = DI::l10n()->t('Reacted with %s by: %s', $element['emoji'], $actors); $title = DI::l10n()->t('Reacted with %s by: %s', $element['emoji'], $actors);
$icon = []; $icon = [];
break; break;
break;
} }
$emojis[$index] = ['emoji' => $element['emoji'], 'total' => $element['total'], 'title' => $title, 'icon' => $icon]; $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')) { 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()]); Logger::warning('Post object does not belong to local user', ['post' => $item, 'local_user' => DI::userSession()->getLocalUserId()]);
return false; return false;
} elseif (DI::activity()->match($item->getDataValue('verb'), Activity::LIKE) || } elseif (
DI::activity()->match($item->getDataValue('verb'), Activity::DISLIKE)) { 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]); Logger::warning('Post objects is a like/dislike', ['post' => $item]);
return false; return false;
} }
@ -1004,8 +1016,10 @@ class Post
} }
$profile = Contact::getByURL($term['url'], false, ['addr', 'contact-type']); $profile = Contact::getByURL($term['url'], false, ['addr', 'contact-type']);
if (!empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) && if (
($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr'])) { !empty($profile['addr']) && (($profile['contact-type'] ?? Contact::TYPE_UNKNOWN) != Contact::TYPE_COMMUNITY) &&
($profile['addr'] != $owner['addr']) && !strstr($text, $profile['addr'])
) {
$text .= '@' . $profile['addr'] . ' '; $text .= '@' . $profile['addr'] . ' ';
} }
} }
@ -1134,6 +1148,7 @@ class Post
'id' => $this->getDataValue('owner-id'), 'id' => $this->getDataValue('owner-id'),
'network' => $this->getDataValue('owner-network'), 'network' => $this->getDataValue('owner-network'),
'url' => $this->getDataValue('owner-link'), 'url' => $this->getDataValue('owner-link'),
'alias' => $this->getDataValue('owner-alias'),
]; ];
$this->owner_url = Contact::magicLinkByContact($owner); $this->owner_url = Contact::magicLinkByContact($owner);
} }

View file

@ -160,7 +160,7 @@ class Delivery
if (!empty($actor)) { if (!empty($actor)) {
$drop = !ActivityPub\Transmitter::sendRelayFollow($actor); $drop = !ActivityPub\Transmitter::sendRelayFollow($actor);
Logger::notice('Resubscribed to relay', ['url' => $actor, 'success' => !$drop]); 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. // 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. // 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. // Most AP systems don't allow this, so they will reject the deletion request.

View file

@ -432,9 +432,38 @@ class Processor
$item['owner-link'] = $item['author-link']; $item['owner-link'] = $item['author-link'];
$item['owner-id'] = $item['author-id']; $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 { } else {
$actor = APContact::getByURL($item['owner-link'], false); $owner = APContact::getByURL($item['owner-link'], false);
$item['isGroup'] = ($actor['type'] ?? 'Person') == 'Group'; }
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']; $item['uri'] = $activity['id'];
@ -1059,10 +1088,10 @@ class Processor
$item['causer-id'] = ($item['gravity'] == Item::GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id']; $item['causer-id'] = ($item['gravity'] == Item::GRAVITY_PARENT) ? $item['owner-id'] : $item['author-id'];
} }
if ($item['isGroup'] ?? false) { if ($item['isGroup']) {
$item['contact-id'] = Contact::getIdForURL($activity['actor'], $receiver); $item['contact-id'] = Contact::getIdForURL($item['group-link'], $receiver);
} else { } 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'])) { 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 (($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) { 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']]); Logger::info('Top level post via BCC from a non sharer, ignoring', ['uid' => $receiver, 'contact' => $item['contact-id'], 'url' => $item['uri']]);
continue; continue;
@ -1096,7 +1125,7 @@ class Processor
} }
if ((DI::pConfig()->get($receiver, 'system', 'accept_only_sharer') == Item::COMPLETION_NONE) 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))) { || !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']]); 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; continue;
@ -1533,6 +1562,7 @@ class Processor
$activity['id'] = $object['id']; $activity['id'] = $object['id'];
$activity['to'] = $object['to'] ?? []; $activity['to'] = $object['to'] ?? [];
$activity['cc'] = $object['cc'] ?? []; $activity['cc'] = $object['cc'] ?? [];
$activity['audience'] = $object['audience'] ?? [];
$activity['actor'] = $actor; $activity['actor'] = $actor;
$activity['object'] = $object; $activity['object'] = $object;
$activity['published'] = $published; $activity['published'] = $published;

View file

@ -291,16 +291,17 @@ class Receiver
/** /**
* Prepare the object array * Prepare the object array
* *
* @param array $activity Array with activity data * @param array $activity Array with activity data
* @param integer $uid User ID * @param integer $uid User ID
* @param boolean $push Message had been pushed to our system * @param boolean $push Message had been pushed to our system
* @param boolean $trust_source Do we trust the source? * @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 * @return array with object data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @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'); $id = JsonLD::fetchElement($activity, '@id');
$type = JsonLD::fetchElement($activity, '@type'); $type = JsonLD::fetchElement($activity, '@type');
@ -319,7 +320,7 @@ class Receiver
$fetched = false; $fetched = false;
if (!empty($id) && !$trust_source) { 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); $fetched_activity = Processor::fetchCachedActivity($fetch_id, $fetch_uid);
if (!empty($fetched_activity)) { if (!empty($fetched_activity)) {
@ -355,7 +356,7 @@ class Receiver
$type = JsonLD::fetchElement($activity, '@type'); $type = JsonLD::fetchElement($activity, '@type');
// Fetch all receivers from to, cc, bto and bcc // 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 = []; $receivers = $reception_types = [];
foreach ($receiverdata as $key => $data) { foreach ($receiverdata as $key => $data) {
$receivers[$key] = $data['uid']; $receivers[$key] = $data['uid'];
@ -379,7 +380,7 @@ class Receiver
// We possibly need some user to fetch private content, // We possibly need some user to fetch private content,
// so we fetch one out of the receivers if no uid is provided. // 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'); $object_id = JsonLD::fetchElement($activity, 'as:object', '@id');
if (empty($object_id)) { if (empty($object_id)) {
@ -394,28 +395,6 @@ class Receiver
$object_type = self::fetchObjectType($activity, $object_id, $fetch_uid); $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 // Any activities on account types must not be altered
if (in_array($type, ['as:Flag'])) { if (in_array($type, ['as:Flag'])) {
$object_data = []; $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)) { } 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). // 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. // 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['name'] = $type;
$object_data['author'] = JsonLD::fetchElement($activity, 'as:actor', '@id'); $object_data['author'] = JsonLD::fetchElement($activity, 'as:actor', '@id');
$object_data['object_id'] = $object_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 // Lemmy announces activities.
$object_data = self::prepareObjectData($activity, $uid, $push, $trust_source); // To simplify the further processing, we modify the received object.
if (empty($object_data)) { // For announced "create" activities we remove the middle layer.
Logger::info('No object data found', ['activity' => $activity]); // For the rest (like, dislike, update, ...) we just process the activity directly.
return true; $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. // $trust_source is called by reference and is set to true if the content was retrieved successfully
// We are changing the announces into regular activities. $object_data = self::prepareObjectData($activity, $uid, $push, $trust_source, $original_actor);
if (($type == 'as:Announce') && in_array($object_data['type'] ?? '', array_merge(self::ACTIVITY_TYPES, ['as:Delete', 'as:Undo', 'as:Update']))) { if (empty($object_data)) {
Logger::debug('Change type of announce to activity', ['type' => $object_data['type']]); Logger::info('No object data found', ['activity' => $activity, 'callstack' => System::callstack(20)]);
$type = $object_data['type']; return true;
} }
if (!empty($body) && empty($object_data['raw'])) { if (!empty($body) && empty($object_data['raw'])) {
@ -688,6 +681,18 @@ class Receiver
return true; 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 * Route activities
* *
@ -1009,10 +1014,10 @@ class Receiver
* *
* @return int user id * @return int user id
*/ */
public static function getBestUserForActivity(array $activity): int public static function getBestUserForActivity(array $activity, string $actor = ''): int
{ {
$uid = 0; $uid = 0;
$actor = JsonLD::fetchElement($activity, 'as:actor', '@id') ?? ''; $actor = $actor ?: JsonLD::fetchElement($activity, 'as:actor', '@id') ?? '';
$receivers = self::getReceivers($activity, $actor, [], false, false); $receivers = self::getReceivers($activity, $actor, [], false, false);
foreach ($receivers as $receiver) { foreach ($receivers as $receiver) {
@ -1129,7 +1134,7 @@ class Receiver
} }
// Fetch the receivers for the public and the followers collection // 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); $receivers = self::getReceiverForActor($actor, $tags, $receivers, $follower_target, $profile);
continue; continue;
} }
@ -1196,12 +1201,16 @@ class Receiver
// "birdsitelive" is a service that mirrors tweets into the fediverse // "birdsitelive" is a service that mirrors tweets into the fediverse
// These posts can be fetched without authentication, but are not marked as public // 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 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')) { if (empty($receivers) && $fetch_unlisted && Contact::isPlatform($actor, 'birdsitelive')) {
$receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL]; $receivers[0] = ['uid' => 0, 'type' => self::TARGET_GLOBAL];
$receivers[-1] = ['uid' => -1, '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')]); 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)) { } 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; return $receivers;
@ -1437,21 +1446,9 @@ class Receiver
return false; 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 // We currently don't handle 'pt:CacheFile', but with this step we avoid logging
if (in_array($type, self::CONTENT_TYPES) || ($type == 'pt:CacheFile')) { if (in_array($type, self::CONTENT_TYPES) || ($type == 'pt:CacheFile')) {
$object_data = self::processObject($object); $object_data = self::processObject($object, '');
if (!empty($data)) { if (!empty($data)) {
$object_data['raw-object'] = json_encode($data); $object_data['raw-object'] = json_encode($data);
@ -1855,12 +1852,13 @@ class Receiver
/** /**
* Fetches data from the object part of an activity * 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 * @return array|bool Object data or FALSE if $object does not contain @id element
* @throws \Exception * @throws \Exception
*/ */
private static function processObject(array $object) private static function processObject(array $object, string $actor)
{ {
if (!JsonLD::fetchElement($object, '@id')) { if (!JsonLD::fetchElement($object, '@id')) {
return false; return false;
@ -1868,7 +1866,7 @@ class Receiver
$object_data = self::getObjectDataFromActivity($object); $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 = []; $receivers = $reception_types = [];
foreach ($receiverdata as $key => $data) { foreach ($receiverdata as $key => $data) {
$receivers[$key] = $data['uid']; $receivers[$key] = $data['uid'];

View file

@ -492,7 +492,6 @@ class Transmitter
* Returns an array with permissions of the thread parent of the given item array * Returns an array with permissions of the thread parent of the given item array
* *
* @param array $item * @param array $item
* @param bool $is_group_thread
* *
* @return array with permissions * @return array with permissions
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
@ -514,6 +513,7 @@ class Transmitter
'cc' => [], 'cc' => [],
'bto' => [], 'bto' => [],
'bcc' => [], 'bcc' => [],
'audience' => [],
]; ];
$parent_profile = APContact::getByURL($parent['author-link']); $parent_profile = APContact::getByURL($parent['author-link']);
@ -525,8 +525,8 @@ class Transmitter
$exclude[] = $item['owner-link']; $exclude[] = $item['owner-link'];
} }
$type = [Tag::TO => 'to', Tag::CC => 'cc', Tag::BTO => 'bto', Tag::BCC => 'bcc']; $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]) as $receiver) { 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 (!empty($parent_profile['followers']) && $receiver['url'] == $parent_profile['followers'] && !empty($item_profile['followers'])) {
if (!$is_group_thread) { if (!$is_group_thread) {
$permissions[$type[$receiver['type']]][] = $item_profile['followers']; $permissions[$type[$receiver['type']]][] = $item_profile['followers'];
@ -600,6 +600,45 @@ class Transmitter
$is_group_thread = false; $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)) { if (self::isAnnounce($item) || self::isAPPost($last_id)) {
// Will be activated in a later step // Will be activated in a later step
$networks = Protocol::FEDERATED; $networks = Protocol::FEDERATED;
@ -608,7 +647,7 @@ class Transmitter
$networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS]; $networks = [Protocol::ACTIVITYPUB, Protocol::OSTATUS];
} }
$data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => []]; $data = ['to' => [], 'cc' => [], 'bcc' => [] , 'audience' => $audience];
if ($item['gravity'] == Item::GRAVITY_PARENT) { if ($item['gravity'] == Item::GRAVITY_PARENT) {
$actor_profile = APContact::getByURL($item['owner-link']); $actor_profile = APContact::getByURL($item['owner-link']);
@ -616,23 +655,7 @@ class Transmitter
$actor_profile = APContact::getByURL($item['author-link']); $actor_profile = APContact::getByURL($item['author-link']);
} }
$exclusive = false; $terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION, Tag::AUDIENCE]);
$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]);
if ($item['private'] != Item::PRIVATE) { if ($item['private'] != Item::PRIVATE) {
// Directly mention the original author upon a quoted reshare. // Directly mention the original author upon a quoted reshare.
@ -644,7 +667,9 @@ class Transmitter
$data['cc'][] = $announce['actor']['url']; $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 // Check if the item is completely public or unlisted
if ($item['private'] == Item::PUBLIC) { if ($item['private'] == Item::PUBLIC) {
@ -656,6 +681,9 @@ class Transmitter
foreach ($terms as $term) { foreach ($terms as $term) {
$profile = APContact::getByURL($term['url'], false); $profile = APContact::getByURL($term['url'], false);
if (!empty($profile)) { if (!empty($profile)) {
if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) {
$data['audience'][] = $profile['url'];
}
if ($term['type'] == Tag::EXCLUSIVE_MENTION) { if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
$exclusive = true; $exclusive = true;
if (!empty($profile['followers']) && ($profile['type'] == 'Group')) { if (!empty($profile['followers']) && ($profile['type'] == 'Group')) {
@ -684,6 +712,9 @@ class Transmitter
$profile = APContact::getByURL($term['url'], false); $profile = APContact::getByURL($term['url'], false);
if (!empty($profile)) { if (!empty($profile)) {
if (($term['type'] == Tag::AUDIENCE) && ($profile['type'] == 'Group')) {
$data['audience'][] = $profile['url'];
}
if ($term['type'] == Tag::EXCLUSIVE_MENTION) { if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
$exclusive = true; $exclusive = true;
if (!empty($profile['followers']) && ($profile['type'] == 'Group')) { 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) { if ($item['private'] == Item::PRIVATE) {
$condition = ['parent' => $item['parent'], 'uri-id' => $item['thr-parent-id']]; $condition = ['parent' => $item['parent'], 'uri-id' => $item['thr-parent-id']];
} else { } else {
@ -814,20 +845,13 @@ class Transmitter
} }
} }
$receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bcc' => array_values($data['bcc'])]; $receivers = ['to' => array_values($data['to']), 'cc' => array_values($data['cc']), 'bcc' => array_values($data['bcc']), 'audience' => array_values($data['audience'])];
if (!empty($data['audience'])) {
$receivers['audience'] = array_values($data['audience']);
if (count($receivers['audience']) == 1) {
$receivers['audience'] = $receivers['audience'][0];
}
}
if (!$blindcopy) { if (!$blindcopy) {
unset($receivers['bcc']); 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])) { if (!empty($receivers[$element])) {
foreach ($receivers[$element] as $receiver) { foreach ($receivers[$element] as $receiver) {
if ($receiver == ActivityPub::PUBLIC_COLLECTION) { 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; return $receivers;
} }
@ -976,7 +1006,7 @@ class Transmitter
$profile_uid = User::getIdForURL($item_profile['url']); $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])) { if (empty($permissions[$element])) {
continue; continue;
} }
@ -1000,7 +1030,7 @@ class Transmitter
} else { } else {
$target = $profile['sharedinbox']; $target = $profile['sharedinbox'];
} }
if (!self::archivedInbox($target)) { if (!self::archivedInbox($target) && !in_array($contact['id'], $inboxes[$target] ?? [])) {
$inboxes[$target][] = $contact['id'] ?? 0; $inboxes[$target][] = $contact['id'] ?? 0;
} }
} }
@ -1101,12 +1131,14 @@ class Transmitter
unset($data['cc']); unset($data['cc']);
unset($data['bcc']); unset($data['bcc']);
unset($data['audience']);
$object['to'] = $data['to']; $object['to'] = $data['to'];
$object['tag'] = [['type' => 'Mention', 'href' => $object['to'][0], 'name' => '']]; $object['tag'] = [['type' => 'Mention', 'href' => $object['to'][0], 'name' => '']];
unset($object['cc']); unset($object['cc']);
unset($object['bcc']); unset($object['bcc']);
unset($object['audience']);
$data['directMessage'] = true; $data['directMessage'] = true;

View file

@ -56,7 +56,7 @@ use Friendica\Database\DBA;
// This file is required several times during the test in DbaDefinition which justifies this condition // This file is required several times during the test in DbaDefinition which justifies this condition
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1520); define('DB_UPDATE_VERSION', 1521);
} }
return [ return [

View file

@ -149,6 +149,7 @@
"author-addr" => ["author", "addr"], "author-addr" => ["author", "addr"],
"author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)", "author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)",
"author-nick" => ["author", "nick"], "author-nick" => ["author", "nick"],
"author-alias" => ["author", "alias"],
"author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)", "author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)",
"author-network" => ["author", "network"], "author-network" => ["author", "network"],
"author-blocked" => ["author", "blocked"], "author-blocked" => ["author", "blocked"],
@ -161,6 +162,7 @@
"owner-addr" => ["owner", "addr"], "owner-addr" => ["owner", "addr"],
"owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)", "owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)",
"owner-nick" => ["owner", "nick"], "owner-nick" => ["owner", "nick"],
"owner-alias" => ["owner", "alias"],
"owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)", "owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)",
"owner-network" => ["owner", "network"], "owner-network" => ["owner", "network"],
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
@ -324,6 +326,7 @@
"author-addr" => ["author", "addr"], "author-addr" => ["author", "addr"],
"author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)", "author-name" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`name` != '', `contact`.`name`, `author`.`name`)",
"author-nick" => ["author", "nick"], "author-nick" => ["author", "nick"],
"author-alias" => ["author", "alias"],
"author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)", "author-avatar" => "IF (`contact`.`url` = `author`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `author`.`thumb`)",
"author-network" => ["author", "network"], "author-network" => ["author", "network"],
"author-blocked" => ["author", "blocked"], "author-blocked" => ["author", "blocked"],
@ -336,6 +339,7 @@
"owner-addr" => ["owner", "addr"], "owner-addr" => ["owner", "addr"],
"owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)", "owner-name" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`name` != '', `contact`.`name`, `owner`.`name`)",
"owner-nick" => ["owner", "nick"], "owner-nick" => ["owner", "nick"],
"owner-alias" => ["owner", "alias"],
"owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)", "owner-avatar" => "IF (`contact`.`url` = `owner`.`url` AND `contact`.`thumb` != '', `contact`.`thumb`, `owner`.`thumb`)",
"owner-network" => ["owner", "network"], "owner-network" => ["owner", "network"],
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
@ -485,6 +489,7 @@
"author-addr" => ["author", "addr"], "author-addr" => ["author", "addr"],
"author-name" => ["author", "name"], "author-name" => ["author", "name"],
"author-nick" => ["author", "nick"], "author-nick" => ["author", "nick"],
"author-alias" => ["author", "alias"],
"author-avatar" => ["author", "thumb"], "author-avatar" => ["author", "thumb"],
"author-network" => ["author", "network"], "author-network" => ["author", "network"],
"author-blocked" => ["author", "blocked"], "author-blocked" => ["author", "blocked"],
@ -497,6 +502,7 @@
"owner-addr" => ["owner", "addr"], "owner-addr" => ["owner", "addr"],
"owner-name" => ["owner", "name"], "owner-name" => ["owner", "name"],
"owner-nick" => ["owner", "nick"], "owner-nick" => ["owner", "nick"],
"owner-alias" => ["owner", "alias"],
"owner-avatar" => ["owner", "thumb"], "owner-avatar" => ["owner", "thumb"],
"owner-network" => ["owner", "network"], "owner-network" => ["owner", "network"],
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],
@ -623,6 +629,7 @@
"author-addr" => ["author", "addr"], "author-addr" => ["author", "addr"],
"author-name" => ["author", "name"], "author-name" => ["author", "name"],
"author-nick" => ["author", "nick"], "author-nick" => ["author", "nick"],
"author-alias" => ["author", "alias"],
"author-avatar" => ["author", "thumb"], "author-avatar" => ["author", "thumb"],
"author-network" => ["author", "network"], "author-network" => ["author", "network"],
"author-blocked" => ["author", "blocked"], "author-blocked" => ["author", "blocked"],
@ -635,6 +642,7 @@
"owner-addr" => ["owner", "addr"], "owner-addr" => ["owner", "addr"],
"owner-name" => ["owner", "name"], "owner-name" => ["owner", "name"],
"owner-nick" => ["owner", "nick"], "owner-nick" => ["owner", "nick"],
"owner-alias" => ["owner", "alias"],
"owner-avatar" => ["owner", "thumb"], "owner-avatar" => ["owner", "thumb"],
"owner-network" => ["owner", "network"], "owner-network" => ["owner", "network"],
"owner-blocked" => ["owner", "blocked"], "owner-blocked" => ["owner", "blocked"],

File diff suppressed because it is too large Load diff