<?php
/**
- * @copyright Copyright (C) 2020, Friendica
+ * @copyright Copyright (C) 2010-2021, the Friendica project
*
* @license GNU AGPL version 3 or any later version
*
*/
public static function addRelayServerInboxes(array $inboxes = [])
{
- $contacts = DBA::select('apcontact', ['inbox'],
- ["`type` = ? AND `url` IN (SELECT `url` FROM `contact` WHERE `uid` = ? AND `rel` = ?)",
- 'Application', 0, Contact::FRIEND]);
- while ($contact = DBA::fetch($contacts)) {
+ foreach (Relay::getList(['inbox']) as $contact) {
$inboxes[$contact['inbox']] = $contact['inbox'];
}
- DBA::close($contacts);
return $inboxes;
}
return $inboxes;
}
- $relays = Relay::getList($item_id, [], [Protocol::ACTIVITYPUB]);
+ $relays = Relay::getDirectRelayList($item_id);
if (empty($relays)) {
return $inboxes;
}
return $success;
}
-
+
/**
* Collects a list of contacts of the given owner
*
*/
public static function getOutbox($owner, $page = null, $requester = '')
{
- $public_contact = Contact::getIdForURL($owner['url']);
- $condition = ['uid' => 0, 'contact-id' => $public_contact,
- 'private' => [Item::PUBLIC, Item::UNLISTED]];
+ $condition = ['private' => [Item::PUBLIC, Item::UNLISTED]];
if (!empty($requester)) {
$requester_id = Contact::getIdForURL($requester, $owner['uid']);
if (!empty($requester_id)) {
$permissionSets = DI::permissionSet()->selectByContactId($requester_id, $owner['uid']);
if (!empty($permissionSets)) {
- $condition = ['uid' => $owner['uid'], 'origin' => true,
- 'psid' => array_merge($permissionSets->column('id'),
+ $condition = ['psid' => array_merge($permissionSets->column('id'),
[DI::permissionSet()->getIdFromACL($owner['uid'], '', '', '', '')])];
}
}
}
$condition = array_merge($condition,
- ['author-id' => $public_contact,
- 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
- 'deleted' => false, 'visible' => true]);
+ ['uid' => $owner['uid'],
+ 'author-id' => Contact::getIdForURL($owner['url'], 0, false),
+ 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT],
+ 'network' => Protocol::FEDERATED,
+ 'parent-network' => Protocol::FEDERATED,
+ 'origin' => true,
+ 'deleted' => false,
+ 'visible' => true]);
$count = Post::count($condition);
$data['type'] = 'OrderedCollectionPage';
$list = [];
- $condition['parent-network'] = Protocol::NATIVE_SUPPORT;
-
$items = Post::select(['id'], $condition, ['limit' => [($page - 1) * 20, 20], 'order' => ['created' => true]]);
while ($item = Post::fetch($items)) {
$activity = self::createActivityFromItem($item['id'], true);
*/
public static function getProfile($uid)
{
- if ($uid != 0) {
- $condition = ['uid' => $uid, 'blocked' => false, 'account_expired' => false,
- 'account_removed' => false, 'verified' => true];
- $fields = ['guid', 'nickname', 'pubkey', 'account-type', 'page-flags'];
- $user = DBA::selectFirst('user', $fields, $condition);
- if (!DBA::isResult($user)) {
- return [];
- }
-
- $fields = ['locality', 'region', 'country-name'];
- $profile = DBA::selectFirst('profile', $fields, ['uid' => $uid]);
- if (!DBA::isResult($profile)) {
- return [];
- }
-
- $fields = ['name', 'url', 'location', 'about', 'avatar', 'photo'];
- $contact = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
- if (!DBA::isResult($contact)) {
- return [];
- }
- } else {
- $contact = User::getSystemAccount();
- $user = ['guid' => '', 'nickname' => $contact['nick'], 'pubkey' => $contact['pubkey'],
- 'account-type' => $contact['contact-type'], 'page-flags' => User::PAGE_FLAGS_NORMAL];
- $profile = ['locality' => '', 'region' => '', 'country-name' => ''];
- }
+ $owner = User::getOwnerDataById($uid);
$data = ['@context' => ActivityPub::CONTEXT];
- $data['id'] = $contact['url'];
+ $data['id'] = $owner['url'];
- if (!empty($user['guid'])) {
- $data['diaspora:guid'] = $user['guid'];
+ if (!empty($owner['guid'])) {
+ $data['diaspora:guid'] = $owner['guid'];
}
- $data['type'] = ActivityPub::ACCOUNT_TYPES[$user['account-type']];
-
+ $data['type'] = ActivityPub::ACCOUNT_TYPES[$owner['account-type']];
+
if ($uid != 0) {
- $data['following'] = DI::baseUrl() . '/following/' . $user['nickname'];
- $data['followers'] = DI::baseUrl() . '/followers/' . $user['nickname'];
- $data['inbox'] = DI::baseUrl() . '/inbox/' . $user['nickname'];
- $data['outbox'] = DI::baseUrl() . '/outbox/' . $user['nickname'];
+ $data['following'] = DI::baseUrl() . '/following/' . $owner['nick'];
+ $data['followers'] = DI::baseUrl() . '/followers/' . $owner['nick'];
+ $data['inbox'] = DI::baseUrl() . '/inbox/' . $owner['nick'];
+ $data['outbox'] = DI::baseUrl() . '/outbox/' . $owner['nick'];
} else {
$data['inbox'] = DI::baseUrl() . '/friendica/inbox';
}
- $data['preferredUsername'] = $user['nickname'];
- $data['name'] = $contact['name'];
+ $data['preferredUsername'] = $owner['nick'];
+ $data['name'] = $owner['name'];
- if (!empty($profile['country-name'] . $profile['region'] . $profile['locality'])) {
- $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $profile['country-name'],
- 'vcard:region' => $profile['region'], 'vcard:locality' => $profile['locality']];
+ if (!empty($owner['country-name'] . $owner['region'] . $owner['locality'])) {
+ $data['vcard:hasAddress'] = ['@type' => 'vcard:Home', 'vcard:country-name' => $owner['country-name'],
+ 'vcard:region' => $owner['region'], 'vcard:locality' => $owner['locality']];
}
- if (!empty($contact['about'])) {
- $data['summary'] = BBCode::convert($contact['about'], false);
+ if (!empty($owner['about'])) {
+ $data['summary'] = BBCode::convertForUriId($owner['uri-id'] ?? 0, $owner['about'], BBCode::EXTERNAL);
}
- $data['url'] = $contact['url'];
- $data['manuallyApprovesFollowers'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
- $data['publicKey'] = ['id' => $contact['url'] . '#main-key',
- 'owner' => $contact['url'],
- 'publicKeyPem' => $user['pubkey']];
+ $data['url'] = $owner['url'];
+ $data['manuallyApprovesFollowers'] = in_array($owner['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
+ $data['discoverable'] = $owner['net-publish'];
+ $data['publicKey'] = ['id' => $owner['url'] . '#main-key',
+ 'owner' => $owner['url'],
+ 'publicKeyPem' => $owner['pubkey']];
$data['endpoints'] = ['sharedInbox' => DI::baseUrl() . '/inbox'];
- $data['icon'] = ['type' => 'Image',
- 'url' => $contact['photo']];
+ $data['icon'] = ['type' => 'Image', 'url' => Contact::getAvatarUrlForId($owner['id'], '', $owner['updated'])];
+
+ $resourceid = Photo::ridFromURI($owner['photo']);
+ if (!empty($resourceid)) {
+ $photo = Photo::selectFirst(['type'], ["resource-id" => $resourceid]);
+ if (!empty($photo['type'])) {
+ $data['icon']['mediaType'] = $photo['type'];
+ }
+ }
+
+ if (!empty($owner['header'])) {
+ $data['image'] = ['type' => 'Image', 'url' => Contact::getHeaderUrlForId($owner['id'], '', $owner['updated'])];
+
+ $resourceid = Photo::ridFromURI($owner['header']);
+ if (!empty($resourceid)) {
+ $photo = Photo::selectFirst(['type'], ["resource-id" => $resourceid]);
+ if (!empty($photo['type'])) {
+ $data['image']['mediaType'] = $photo['type'];
+ }
+ }
+ }
$data['generator'] = self::getService();
$actor_profile = APContact::getByURL($item['author-link']);
}
+ $exclusive = false;
+
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
if ($item['private'] != Item::PRIVATE) {
foreach ($terms as $term) {
$profile = APContact::getByURL($term['url'], false);
if (!empty($profile)) {
+ if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
+ $exclusive = true;
+ if (!empty($profile['followers']) && ($profile['type'] == 'Group')) {
+ $data['cc'][] = $profile['followers'];
+ }
+ }
$data['to'][] = $profile['url'];
}
}
// But comments to forums aren't directed to the followers collection
// This rule is only valid when the actor isn't the forum.
// The forum needs to transmit their content to their followers.
- if (($profile['type'] == 'Group') && ($profile['url'] != $actor_profile['url'])) {
+ if (($profile['type'] == 'Group') && ($profile['url'] != ($actor_profile['url'] ?? ''))) {
$data['to'][] = $profile['url'];
} else {
$data['cc'][] = $profile['url'];
$data['cc'][] = $actor_profile['followers'];
}
}
- } else {
- // Public thread parent post always are directed to the followers
+ } elseif (!$exclusive) {
+ // Public thread parent post always are directed to the followers.
+ // This mustn't be done by posts that are directed to forum servers via the exclusive mention.
+ // But possibly in that case we could add the "followers" collection of the forum to the message.
if (($item['private'] != Item::PRIVATE) && !$forum_mode) {
$data['cc'][] = $actor_profile['followers'];
}
/**
* Check if a given contact should be delivered via AP
*
- * @param array $contact
- * @param array $networks
- * @return bool
- * @throws Exception
+ * @param array $contact
+ * @param array $networks
+ * @return bool
+ * @throws Exception
*/
private static function isAPContact(array $contact, array $networks)
{
$contacts = DBA::select('contact', ['id', 'url', 'network', 'protocol', 'gsid'], $condition);
while ($contact = DBA::fetch($contacts)) {
- if (Contact::isLocal($contact['url'])) {
- continue;
- }
-
if (!self::isAPContact($contact, $networks)) {
continue;
}
$profile = APContact::getByURL($contact['url'], false);
if (!empty($profile)) {
- if (empty($profile['sharedinbox']) || $personal) {
+ if (empty($profile['sharedinbox']) || $personal || Contact::isLocal($contact['url'])) {
$target = $profile['inbox'];
} else {
$target = $profile['sharedinbox'];
if ($item_profile && ($receiver == $item_profile['followers']) && ($uid == $profile_uid)) {
$inboxes = array_merge($inboxes, self::fetchTargetInboxesforUser($uid, $personal, self::isAPPost($last_id)));
} else {
- if (Contact::isLocal($receiver)) {
- continue;
- }
-
$profile = APContact::getByURL($receiver, false);
if (!empty($profile)) {
$contact = Contact::getByURLForUser($receiver, $uid, false, ['id']);
- if (empty($profile['sharedinbox']) || $personal || $blindcopy) {
+ if (empty($profile['sharedinbox']) || $personal || $blindcopy || Contact::isLocal($receiver)) {
$target = $profile['inbox'];
} else {
$target = $profile['sharedinbox'];
* @return array
* @throws \Exception
*/
- public static function ItemArrayFromMail($mail_id)
+ public static function ItemArrayFromMail($mail_id, $use_title = false)
{
$mail = DBA::selectFirst('mail', [], ['id' => $mail_id]);
if (!DBA::isResult($mail)) {
return [];
}
- $mail['uri-id'] = ItemURI::insert(['uri' => $mail['uri'], 'guid' => $mail['guid']]);
-
- $reply = DBA::selectFirst('mail', ['uri'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
+ $reply = DBA::selectFirst('mail', ['uri', 'uri-id', 'from-url'], ['parent-uri' => $mail['parent-uri'], 'reply' => false]);
// Making the post more compatible for Mastodon by:
// - Making it a note and not an article (no title)
// - Moving the title into the "summary" field that is used as a "content warning"
- $mail['body'] = '[abstract]' . $mail['title'] . "[/abstract]\n" . $mail['body'];
- $mail['title'] = '';
-
- $mail['author-link'] = $mail['owner-link'] = $mail['from-url'];
- $mail['allow_cid'] = '<'.$mail['contact-id'].'>';
- $mail['allow_gid'] = '';
- $mail['deny_cid'] = '';
- $mail['deny_gid'] = '';
- $mail['private'] = true;
- $mail['deleted'] = false;
- $mail['edited'] = $mail['created'];
- $mail['plink'] = $mail['uri'];
- $mail['thr-parent'] = $reply['uri'];
- $mail['gravity'] = ($mail['reply'] ? GRAVITY_COMMENT: GRAVITY_PARENT);
- $mail['event-type'] = '';
-
- $mail['parent'] = 0;
+ if (!$use_title) {
+ $mail['body'] = '[abstract]' . $mail['title'] . "[/abstract]\n" . $mail['body'];
+ $mail['title'] = '';
+ }
+
+ $mail['author-link'] = $mail['owner-link'] = $mail['from-url'];
+ $mail['owner-id'] = $mail['author-id'];
+ $mail['allow_cid'] = '<'.$mail['contact-id'].'>';
+ $mail['allow_gid'] = '';
+ $mail['deny_cid'] = '';
+ $mail['deny_gid'] = '';
+ $mail['private'] = Item::PRIVATE;
+ $mail['deleted'] = false;
+ $mail['edited'] = $mail['created'];
+ $mail['plink'] = DI::baseUrl() . '/message/' . $mail['id'];
+ $mail['parent-uri'] = $reply['uri'];
+ $mail['parent-uri-id'] = $reply['uri-id'];
+ $mail['parent-author-id'] = Contact::getIdForURL($reply['from-url'], 0, false);
+ $mail['gravity'] = ($mail['reply'] ? GRAVITY_COMMENT: GRAVITY_PARENT);
+ $mail['event-type'] = '';
+ $mail['language'] = '';
+ $mail['parent'] = 0;
return $mail;
}
if (!empty($self['uid'])) {
$forum_item = Post::selectFirst(Item::DELIVER_FIELDLIST, ['uri-id' => $item['uri-id'], 'uid' => $self['uid']]);
if (DBA::isResult($forum_item)) {
- $item = $forum_item;
+ $item = $forum_item;
}
}
}
{
$attachments = [];
- // Currently deactivated, since it creates side effects on Mastodon and Pleroma.
- // It will be reactivated, once this cleared.
- /*
- $attach_data = BBCode::getAttachmentData($item['body']);
- if (!empty($attach_data['url'])) {
- $attachment = ['type' => 'Page',
- 'mediaType' => 'text/html',
- 'url' => $attach_data['url']];
-
- if (!empty($attach_data['title'])) {
- $attachment['name'] = $attach_data['title'];
+ $uriids = [$item['uri-id']];
+ $shared = BBCode::fetchShareAttributes($item['body']);
+ if (!empty($shared['guid'])) {
+ $shared_item = Post::selectFirst(['uri-id'], ['guid' => $shared['guid']]);
+ if (!empty($shared_item['uri-id'])) {
+ $uriids[] = $shared_item['uri-id'];
}
+ }
- if (!empty($attach_data['description'])) {
- $attachment['summary'] = $attach_data['description'];
- }
+ $urls = [];
+ foreach ($uriids as $uriid) {
+ foreach (Post\Media::getByURIId($uriid, [Post\Media::DOCUMENT, Post\Media::TORRENT]) as $attachment) {
+ if (in_array($attachment['url'], $urls)) {
+ continue;
+ }
+ $urls[] = $attachment['url'];
- if (!empty($attach_data['image'])) {
- $imgdata = Images::getInfoFromURLCached($attach_data['image']);
- if ($imgdata) {
- $attachment['icon'] = ['type' => 'Image',
- 'mediaType' => $imgdata['mime'],
- 'width' => $imgdata[0],
- 'height' => $imgdata[1],
- 'url' => $attach_data['image']];
+ $attach = ['type' => 'Document',
+ 'mediaType' => $attachment['mimetype'],
+ 'url' => $attachment['url'],
+ 'name' => $attachment['description']];
+
+ if (!empty($attachment['height'])) {
+ $attach['height'] = $attachment['height'];
}
- }
- $attachments[] = $attachment;
- }
- */
- foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::DOCUMENT, Post\Media::TORRENT, Post\Media::UNKNOWN]) as $attachment) {
- $attachments[] = ['type' => 'Document',
- 'mediaType' => $attachment['mimetype'],
- 'url' => $attachment['url'],
- 'name' => $attachment['description']];
+ if (!empty($attachment['width'])) {
+ $attach['width'] = $attachment['width'];
+ }
+
+ if (!empty($attachment['preview'])) {
+ $attach['image'] = $attachment['preview'];
+ }
+
+ $attachments[] = $attach;
+ }
}
if ($type != 'Note') {
return $attachments;
}
- foreach (Post\Media::getByURIId($item['uri-id'], [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
- $attachments[] = ['type' => 'Document',
- 'mediaType' => $attachment['mimetype'],
- 'url' => $attachment['url'],
- 'name' => $attachment['description']];
+ foreach ($uriids as $uriid) {
+ foreach (Post\Media::getByURIId($uriid, [Post\Media::AUDIO, Post\Media::IMAGE, Post\Media::VIDEO]) as $attachment) {
+ if (in_array($attachment['url'], $urls)) {
+ continue;
+ }
+ $urls[] = $attachment['url'];
+
+ $attach = ['type' => 'Document',
+ 'mediaType' => $attachment['mimetype'],
+ 'url' => $attachment['url'],
+ 'name' => $attachment['description']];
+
+ if (!empty($attachment['height'])) {
+ $attach['height'] = $attachment['height'];
+ }
+
+ if (!empty($attachment['width'])) {
+ $attach['width'] = $attachment['width'];
+ }
+
+ if (!empty($attachment['preview'])) {
+ $attach['image'] = $attachment['preview'];
+ }
+
+ $attachments[] = $attach;
+ }
+ // Currently deactivated, since it creates side effects on Mastodon and Pleroma.
+ // It will be activated, once this cleared.
+ /*
+ foreach (Post\Media::getByURIId($uriid, [Post\Media::HTML]) as $attachment) {
+ if (in_array($attachment['url'], $urls)) {
+ continue;
+ }
+ $urls[] = $attachment['url'];
+
+ $attachments[] = ['type' => 'Page',
+ 'mediaType' => $attachment['mimetype'],
+ 'url' => $attachment['url'],
+ 'name' => $attachment['description']];
+ }*/
}
return $attachments;
return '[url=' . $data['url'] . ']@' . $data['nick'] . '[/url]';
}
+ /**
+ * Callback function to replace a Friendica style mention in a mention for a summary
+ *
+ * @param array $match Matching values for the callback
+ * @return string Replaced mention
+ * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+ */
+ private static function mentionAddrCallback($match)
+ {
+ if (empty($match[1])) {
+ return '';
+ }
+
+ $data = Contact::getByURL($match[1], false, ['addr']);
+ if (empty($data['addr'])) {
+ return $match[0];
+ }
+
+ return '@' . $data['addr'];
+ }
+
/**
* Remove image elements since they are added as attachment
*
* @return array with the event data
* @throws \Friendica\Network\HTTPException\InternalServerErrorException
*/
- public static function createEvent($item)
+ private static function createEvent($item)
{
$event = [];
$event['name'] = $item['event-summary'];
- $event['content'] = BBCode::convert($item['event-desc'], false, BBCode::ACTIVITYPUB);
+ $event['content'] = BBCode::convertForUriId($item['uri-id'], $item['event-desc'], BBCode::ACTIVITYPUB);
$event['startTime'] = DateTimeFormat::utc($item['event-start'] . '+00:00', DateTimeFormat::ATOM);
if (!$item['event-nofinish']) {
$event['location'] = self::createLocation($item);
}
+ $event['dfrn:adjust'] = (bool)$item['event-adjust'];
+
return $event;
}
if ($type == 'Note') {
$body = $item['raw-body'] ?? self::removePictures($body);
- } elseif (($type == 'Article') && empty($data['summary'])) {
- $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($body), 1000));
}
+ /**
+ * @todo Improve the automated summary
+ * This part is currently deactivated. The automated summary seems to be more
+ * confusing than helping. But possibly we will find a better way.
+ * So the code is left here for now as a reminder
+ *
+ * } elseif (($type == 'Article') && empty($data['summary'])) {
+ * $regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
+ * $summary = preg_replace_callback($regexp, ['self', 'mentionAddrCallback'], $body);
+ * $data['summary'] = BBCode::toPlaintext(Plaintext::shorten(self::removePictures($summary), 1000));
+ * }
+ */
+
if (empty($item['uid']) || !Feature::isEnabled($item['uid'], 'explicit_mentions')) {
$body = self::prependMentions($body, $item['uri-id'], $item['author-link']);
}
$regexp = "/[@!]\[url\=([^\[\]]*)\].*?\[\/url\]/ism";
$body = preg_replace_callback($regexp, ['self', 'mentionCallback'], $body);
- $data['content'] = BBCode::convert($body, false, BBCode::ACTIVITYPUB);
+ $data['content'] = BBCode::convertForUriId($item['uri-id'], $body, BBCode::ACTIVITYPUB);
}
// The regular "content" field does contain a minimized HTML. This is done since systems like
$richbody = preg_replace_callback($regexp, ['self', 'mentionCallback'], $item['body']);
$richbody = BBCode::removeAttachment($richbody);
- $data['contentMap'][$language] = BBCode::convert($richbody, false);
+ $data['contentMap'][$language] = BBCode::convertForUriId($item['uri-id'], $richbody, BBCode::EXTERNAL);
}
$data['source'] = ['content' => $item['body'], 'mediaType' => "text/bbcode"];