mirror of
https://github.com/friendica/friendica
synced 2024-12-23 04:00:15 +00:00
Better support for "audience" / simplified Lemmy processing
This commit is contained in:
parent
50988bf5f1
commit
6d911a8f39
9 changed files with 133 additions and 103 deletions
|
@ -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)) {
|
||||||
|
|
|
@ -2773,7 +2773,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'];
|
||||||
|
|
|
@ -2210,8 +2210,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]);
|
||||||
|
@ -3664,6 +3662,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 not apply to posts that are sent only in parallel to a group.
|
||||||
*
|
*
|
||||||
* @param int $uri_id
|
* @param int $uri_id
|
||||||
*
|
*
|
||||||
|
@ -3671,7 +3670,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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -341,7 +341,6 @@ class Probe
|
||||||
* @param string $uri Address that should be probed
|
* @param string $uri Address that should be probed
|
||||||
* @param string $network Test for this specific network
|
* @param string $network Test for this specific network
|
||||||
* @param integer $uid User ID for the probe (only used for mails)
|
* @param integer $uid User ID for the probe (only used for mails)
|
||||||
* @param boolean $cache Use cached values?
|
|
||||||
*
|
*
|
||||||
* @return array uri data
|
* @return array uri data
|
||||||
* @throws HTTPException\InternalServerErrorException
|
* @throws HTTPException\InternalServerErrorException
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -1533,6 +1533,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;
|
||||||
|
|
|
@ -295,12 +295,13 @@ class Receiver
|
||||||
* @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);
|
||||||
|
@ -1856,11 +1853,12 @@ 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'];
|
||||||
|
|
|
@ -492,13 +492,12 @@ 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
|
||||||
* @throws \ImagickException
|
* @throws \ImagickException
|
||||||
*/
|
*/
|
||||||
private static function fetchPermissionBlockFromThreadParent(array $item, bool $is_group_thread): array
|
private static function fetchPermissionBlockFromThreadParent(array $item): array
|
||||||
{
|
{
|
||||||
if (empty($item['thr-parent-id'])) {
|
if (empty($item['thr-parent-id'])) {
|
||||||
return [];
|
return [];
|
||||||
|
@ -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,12 +525,10 @@ 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) {
|
|
||||||
$permissions[$type[$receiver['type']]][] = $item_profile['followers'];
|
$permissions[$type[$receiver['type']]][] = $item_profile['followers'];
|
||||||
}
|
|
||||||
} elseif (!in_array($receiver['url'], $exclude)) {
|
} elseif (!in_array($receiver['url'], $exclude)) {
|
||||||
$permissions[$type[$receiver['type']]][] = $receiver['url'];
|
$permissions[$type[$receiver['type']]][] = $receiver['url'];
|
||||||
}
|
}
|
||||||
|
@ -600,6 +598,42 @@ class Transmitter
|
||||||
$is_group_thread = false;
|
$is_group_thread = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$exclusive = false;
|
||||||
|
$mention = false;
|
||||||
|
|
||||||
|
$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')) {
|
||||||
|
$is_group_thread = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($is_group_thread) {
|
||||||
|
foreach ($parent_tags as $tag) {
|
||||||
|
if (($tag['type'] == Tag::MENTION) && ($tag['url'] == $profile['url'])) {
|
||||||
|
$mention = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$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) {
|
||||||
|
$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;
|
||||||
|
@ -616,21 +650,6 @@ class Transmitter
|
||||||
$actor_profile = APContact::getByURL($item['author-link']);
|
$actor_profile = APContact::getByURL($item['author-link']);
|
||||||
}
|
}
|
||||||
|
|
||||||
$exclusive = false;
|
|
||||||
$mention = false;
|
|
||||||
|
|
||||||
if ($is_group_thread) {
|
|
||||||
foreach (Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $term) {
|
|
||||||
$profile = APContact::getByURL($term['url'], false);
|
|
||||||
if (!empty($profile) && ($profile['type'] == 'Group')) {
|
|
||||||
if ($term['type'] == Tag::EXCLUSIVE_MENTION) {
|
|
||||||
$exclusive = true;
|
|
||||||
} elseif ($term['type'] == Tag::MENTION) {
|
|
||||||
$mention = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
$terms = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::IMPLICIT_MENTION, Tag::EXCLUSIVE_MENTION]);
|
||||||
|
|
||||||
|
@ -644,7 +663,9 @@ class Transmitter
|
||||||
$data['cc'][] = $announce['actor']['url'];
|
$data['cc'][] = $announce['actor']['url'];
|
||||||
}
|
}
|
||||||
|
|
||||||
$data = array_merge($data, self::fetchPermissionBlockFromThreadParent($item, $is_group_thread));
|
if (!$is_group_thread) {
|
||||||
|
$data = array_merge($data, self::fetchPermissionBlockFromThreadParent($item));
|
||||||
|
}
|
||||||
|
|
||||||
// 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) {
|
||||||
|
@ -727,7 +748,7 @@ class Transmitter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!empty($item['parent'])) {
|
if (!empty($item['parent']) && (!$is_group_thread || ($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 +835,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 +854,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 +996,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 +1020,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 +1121,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;
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue