From e394143148c63b73ad719cfb60b517bb20858b74 Mon Sep 17 00:00:00 2001 From: Michael Date: Sat, 12 Feb 2022 18:38:36 +0000 Subject: [PATCH 1/7] Private forums are now working via ActivityPub --- database.sql | 4 +- doc/database/db_post-thread-user.md | 2 +- mod/item.php | 30 +++----- src/Model/Contact.php | 3 +- src/Model/Group.php | 62 ++++++++++----- src/Model/Item.php | 90 +++++++++------------- src/Module/Api/Twitter/Lists/Ownership.php | 2 +- src/Protocol/ActivityPub/Transmitter.php | 16 +--- src/Protocol/DFRN.php | 13 +--- src/Worker/Delivery.php | 1 - src/Worker/Notifier.php | 24 ------ static/dbstructure.config.php | 2 +- static/dbview.config.php | 2 - 13 files changed, 97 insertions(+), 154 deletions(-) diff --git a/database.sql b/database.sql index ea129109dc..308e2e8a86 100644 --- a/database.sql +++ b/database.sql @@ -1277,7 +1277,7 @@ CREATE TABLE IF NOT EXISTS `post-thread-user` ( `wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid', `mention` boolean NOT NULL DEFAULT '0' COMMENT '', `pubmail` boolean NOT NULL DEFAULT '0' COMMENT '', - `forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '', + `forum_mode` tinyint unsigned NOT NULL DEFAULT 0 COMMENT 'Deprecated', `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id', `unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen', `hidden` boolean NOT NULL DEFAULT '0' COMMENT 'Marker to hide the post from the user', @@ -1612,7 +1612,6 @@ CREATE VIEW `post-user-view` AS SELECT `post-user`.`deleted` AS `deleted`, `post-user`.`origin` AS `origin`, `post-thread-user`.`origin` AS `parent-origin`, - `post-thread-user`.`forum_mode` AS `forum_mode`, `post-thread-user`.`mention` AS `mention`, `post-user`.`global` AS `global`, `post-user`.`network` AS `network`, @@ -1773,7 +1772,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT `post-thread-user`.`unseen` AS `unseen`, `post-user`.`deleted` AS `deleted`, `post-thread-user`.`origin` AS `origin`, - `post-thread-user`.`forum_mode` AS `forum_mode`, `post-thread-user`.`mention` AS `mention`, `post-user`.`global` AS `global`, `post-thread-user`.`network` AS `network`, diff --git a/doc/database/db_post-thread-user.md b/doc/database/db_post-thread-user.md index 7307dc78d6..0b74837411 100644 --- a/doc/database/db_post-thread-user.md +++ b/doc/database/db_post-thread-user.md @@ -24,7 +24,7 @@ Fields | wall | This item was posted to the wall of uid | boolean | NO | | 0 | | | mention | | boolean | NO | | 0 | | | pubmail | | boolean | NO | | 0 | | -| forum_mode | | tinyint unsigned | NO | | 0 | | +| forum_mode | Deprecated | tinyint unsigned | NO | | 0 | | | contact-id | contact.id | int unsigned | NO | | 0 | | | unseen | post has not been seen | boolean | NO | | 1 | | | hidden | Marker to hide the post from the user | boolean | NO | | 0 | | diff --git a/mod/item.php b/mod/item.php index 7cf2e53d49..bc35282e33 100644 --- a/mod/item.php +++ b/mod/item.php @@ -29,7 +29,6 @@ */ use Friendica\App; -use Friendica\Content\Item as ItemHelper; use Friendica\Content\PageInfo; use Friendica\Content\Text\BBCode; use Friendica\Core\Hook; @@ -40,11 +39,11 @@ use Friendica\Core\System; use Friendica\Core\Worker; use Friendica\Database\DBA; use Friendica\DI; -use Friendica\Model\APContact; use Friendica\Model\Attach; use Friendica\Model\Contact; use Friendica\Model\Conversation; use Friendica\Model\FileTag; +use Friendica\Model\Group; use Friendica\Model\Item; use Friendica\Model\ItemURI; use Friendica\Model\Notification; @@ -404,7 +403,7 @@ function item_post(App $a) { } $inform .= 'cid:' . $contact['id']; - if (!$toplevel_item_id || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) { + if ($toplevel_item_id || empty($contact['cid']) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY)) { continue; } @@ -437,26 +436,15 @@ function item_post(App $a) { $postopts = ''; } - if (!$private_forum) { + $str_contact_deny = ''; + $str_group_deny = ''; + + if ($private_forum) { + $str_contact_allow = '<' . $private_id . '>'; + $str_group_allow = '<' . Group::getIdForForum($forum_contact['id']) . '>'; + } else { $str_contact_allow = ''; $str_group_allow = ''; - $str_contact_deny = ''; - $str_group_deny = ''; - } - - if ($private_forum || !APContact::getByURL($forum_contact['url'])) { - $str_group_allow = ''; - $str_contact_deny = ''; - $str_group_deny = ''; - if ($private_forum) { - $str_contact_allow = '<' . $private_id . '>'; - } else { - $str_contact_allow = ''; - } - $contact_id = $private_id; - $contact_record = $forum_contact; - $_REQUEST['origin'] = false; - $wall = 0; } } diff --git a/src/Model/Contact.php b/src/Model/Contact.php index 1b39247f4e..d80827c93f 100644 --- a/src/Model/Contact.php +++ b/src/Model/Contact.php @@ -685,7 +685,7 @@ class Contact */ public static function updateSelfFromUserID($uid, $update_avatar = false) { - $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', + $fields = ['id', 'name', 'nick', 'location', 'about', 'keywords', 'avatar', 'prvkey', 'pubkey', 'manually-approve', 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable', 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network']; $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); @@ -757,6 +757,7 @@ class Contact $fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY; $fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP; $fields['unsearchable'] = !$profile['net-publish']; + $fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]); $update = false; diff --git a/src/Model/Group.php b/src/Model/Group.php index 390ed532e3..b9f4c7f277 100644 --- a/src/Model/Group.php +++ b/src/Model/Group.php @@ -41,7 +41,7 @@ class Group public static function getByUserId($uid, $includesDeleted = false) { - $conditions = ['uid' => $uid]; + $conditions = ['uid' => $uid, 'cid' => null]; if (!$includesDeleted) { $conditions['deleted'] = false; @@ -408,7 +408,7 @@ class Group ] ]; - $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => $uid], ['order' => ['name']]); + $stmt = DBA::select('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null], ['order' => ['name']]); while ($group = DBA::fetch($stmt)) { $display_groups[] = [ 'name' => $group['name'], @@ -465,7 +465,7 @@ class Group $member_of = self::getIdsByContactId($cid); } - $stmt = DBA::select('group', [], ['deleted' => 0, 'uid' => local_user()], ['order' => ['name']]); + $stmt = DBA::select('group', [], ['deleted' => false, 'uid' => local_user(), 'cid' => null], ['order' => ['name']]); while ($group = DBA::fetch($stmt)) { $selected = (($group_id == $group['id']) ? ' group-selected' : ''); @@ -522,21 +522,19 @@ class Group } /** - * Fetch the followers of a given contact id and store them as group members + * Fetch the group id for the given contact id * * @param integer $id Contact ID + * @return integer Group IO */ - public static function getMembersForForum(int $id) { - $contact = Contact::getById($id, ['uid', 'url', 'name']); - if (empty($contact)) { - return; + public static function getIdForForum(int $id) + { + Logger::info('Get id for forum id', ['id' => $id]); + $contact = Contact::getById($id, ['uid', 'name', 'contact-type', 'manually-approve']); + if (empty($contact) || ($contact['contact-type'] != Contact::TYPE_COMMUNITY) || !$contact['manually-approve']) { + return 0; } - - $apcontact = APContact::getByURL($contact['url']); - if (empty($apcontact['followers'])) { - return; - } - + $group = DBA::selectFirst('group', ['id'], ['uid' => $contact['uid'], 'cid' => $id]); if (empty($group)) { $fields = [ @@ -549,15 +547,42 @@ class Group } else { $gid = $group['id']; } - + + return $gid; + } + + /** + * Fetch the followers of a given contact id and store them as group members + * + * @param integer $id Contact ID + */ + public static function getMembersForForum(int $id) + { + Logger::info('Update forum members', ['id' => $id]); + + $contact = Contact::getById($id, ['uid', 'url']); + if (empty($contact)) { + return; + } + + $apcontact = APContact::getByURL($contact['url']); + if (empty($apcontact['followers'])) { + return; + } + + $gid = self::getIdForForum($id); + if (empty($gid)) { + return; + } + $group_members = DBA::selectToArray('group_member', ['contact-id'], ['gid' => $gid]); if (!empty($group_members)) { $current = array_unique(array_column($group_members, 'contact-id')); } else { $current = []; } - - foreach (ActivityPub::fetchItems($apcontact['followers']) as $follower) { + + foreach (ActivityPub::fetchItems($apcontact['followers'], $contact['uid']) as $follower) { $id = Contact::getIdForURL($follower); if (!in_array($id, $current)) { DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $id]); @@ -566,7 +591,8 @@ class Group unset($current[$key]); } } - + DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $current]); + Logger::info('Updated forum members', ['id' => $id, 'count' => DBA::count('group_member', ['gid' => $gid])]); } } diff --git a/src/Model/Item.php b/src/Model/Item.php index bf1f2585a1..6749dc2389 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -100,7 +100,7 @@ class Item 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'author-id', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', - 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'forum_mode', 'origin', + 'signed_text', 'network', 'wall', 'contact-id', 'plink', 'origin', 'thr-parent-id', 'parent-uri-id', 'postopts', 'pubmail', 'event-created', 'event-edited', 'event-start', 'event-finish', 'event-summary', 'event-desc', 'event-location', 'event-type', @@ -114,7 +114,7 @@ class Item 'postopts', 'plink', 'resource-id', 'event-id', 'inform', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', 'private', 'pubmail', 'visible', 'starred', - 'unseen', 'deleted', 'origin', 'forum_mode', 'mention', 'global', 'network', + 'unseen', 'deleted', 'origin', 'mention', 'global', 'network', 'title', 'content-warning', 'body', 'location', 'coord', 'app', 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', @@ -655,7 +655,7 @@ class Item $fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted', 'uri-id', 'parent-uri-id', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', - 'wall', 'private', 'forum_mode', 'origin', 'author-id']; + 'wall', 'private', 'origin', 'author-id']; $condition = ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]; $params = ['order' => ['id' => false]]; $parent = Post::selectFirst($fields, $condition, $params); @@ -881,10 +881,15 @@ class Item $item['parent-uri'] = $toplevel_parent['uri']; $item['parent-uri-id'] = $toplevel_parent['uri-id']; $item['deleted'] = $toplevel_parent['deleted']; - $item['allow_cid'] = $toplevel_parent['allow_cid']; - $item['allow_gid'] = $toplevel_parent['allow_gid']; - $item['deny_cid'] = $toplevel_parent['deny_cid']; - $item['deny_gid'] = $toplevel_parent['deny_gid']; + + // Reshares have to keep their permissions to allow forums to work + if (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE)) { + $item['allow_cid'] = $toplevel_parent['allow_cid']; + $item['allow_gid'] = $toplevel_parent['allow_gid']; + $item['deny_cid'] = $toplevel_parent['deny_cid']; + $item['deny_gid'] = $toplevel_parent['deny_gid']; + } + $parent_origin = $toplevel_parent['origin']; // Don't federate received participation messages @@ -905,15 +910,6 @@ class Item $item['private'] = $toplevel_parent['private']; } - /* - * Edge case. We host a public forum that was originally posted to privately. - * The original author commented, but as this is a comment, the permissions - * weren't fixed up so it will still show the comment as private unless we fix it here. - */ - if ((intval($toplevel_parent['forum_mode']) == 1) && ($toplevel_parent['private'] != self::PUBLIC)) { - $item['private'] = self::PUBLIC; - } - // If its a post that originated here then tag the thread as "mention" if ($item['origin'] && $item['uid']) { DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]); @@ -1451,7 +1447,6 @@ class Item unset($item['pinned']); unset($item['ignored']); unset($item['pubmail']); - unset($item['forum_mode']); unset($item['event-id']); unset($item['hidden']); @@ -1928,41 +1923,18 @@ class Item Logger::info('Community post will be distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); - self::performActivity($item['id'], 'announce', $uid); + if ($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) { + Group::getMembersForForum($owner['id']); - /** - * All the following lines are only needed for private forums and compatibility to older systems without AP support. - * A possible way would be that the followers list of a forum would always be readable by all followers. - * So this would mean that the comment distribution could be done exactly for the intended audience. - * Or possibly we could store the receivers that had been in the "announce" message above and use this. - */ - - // also reset all the privacy bits to the forum default permissions - if ($owner['allow_cid'] || $owner['allow_gid'] || $owner['deny_cid'] || $owner['deny_gid']) { - $private = self::PRIVATE; - } elseif (DI::pConfig()->get($owner['uid'], 'system', 'unlisted')) { - $private = self::UNLISTED; + $allow_cid = '<' . $owner['id'] . '>'; + $allow_gid = '<' . Group::getIdForForum($owner['id']) . '>'; + $deny_cid = ''; + $deny_gid = ''; + self::performActivity($item['id'], 'announce', $uid, $allow_cid, $allow_gid, $deny_cid, $deny_gid); } else { - $private = self::PUBLIC; + self::performActivity($item['id'], 'announce', $uid); } - $permissionSet = DI::permissionSet()->selectOrCreate( - DI::permissionSetFactory()->createFromString( - $owner['uid'], - $owner['allow_cid'], - $owner['allow_gid'], - $owner['deny_cid'], - $owner['deny_gid'] - )); - - $forum_mode = ($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) ? 2 : 1; - - $fields = ['wall' => true, 'origin' => true, 'forum_mode' => $forum_mode, 'contact-id' => $owner['id'], - 'owner-id' => Contact::getPublicIdByUserId($uid), 'private' => $private, 'psid' => $permissionSet->id]; - self::update($fields, ['id' => $item['id']]); - - Worker::add(['priority' => PRIORITY_HIGH, 'dont_fork' => true], 'Notifier', Delivery::POST, (int)$item['uri-id'], (int)$item['uid']); - Logger::info('Community post had been distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]); return false; } @@ -2325,12 +2297,17 @@ class Item * * Toggle activities as like,dislike,attend of an item * - * @param int $item_id + * @param int $item_id * @param string $verb * Activity verb. One of * like, unlike, dislike, undislike, attendyes, unattendyes, * attendno, unattendno, attendmaybe, unattendmaybe, * announce, unannouce + * @param int $uid + * @param string $allow_cid + * @param string $allow_gid + * @param string $deny_cid + * @param string $deny_gid * @return bool * @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \ImagickException @@ -2338,7 +2315,7 @@ class Item * array $arr * 'post_id' => ID of posted item */ - public static function performActivity(int $item_id, string $verb, int $uid) + public static function performActivity(int $item_id, string $verb, int $uid, string $allow_cid = null, string $allow_gid = null, string $deny_cid = null, string $deny_gid = null) { if (empty($uid)) { return false; @@ -2479,6 +2456,11 @@ class Item return true; } + $allow_cid = $allow_cid ?? $item['allow_cid']; + $allow_gid = $allow_gid ?? $item['allow_gid']; + $deny_cid = $deny_cid ?? $item['deny_cid']; + $deny_gid = $deny_gid ?? $item['deny_gid']; + $objtype = $item['resource-id'] ? Activity\ObjectType::IMAGE : Activity\ObjectType::NOTE; $new_item = [ @@ -2499,10 +2481,10 @@ class Item 'body' => $activity, 'verb' => $activity, 'object-type' => $objtype, - 'allow_cid' => $item['allow_cid'], - 'allow_gid' => $item['allow_gid'], - 'deny_cid' => $item['deny_cid'], - 'deny_gid' => $item['deny_gid'], + 'allow_cid' => $allow_cid, + 'allow_gid' => $allow_gid, + 'deny_cid' => $deny_cid, + 'deny_gid' => $deny_gid, 'visible' => 1, 'unseen' => 1, ]; diff --git a/src/Module/Api/Twitter/Lists/Ownership.php b/src/Module/Api/Twitter/Lists/Ownership.php index e5aca1ad5c..c3ff0030b4 100644 --- a/src/Module/Api/Twitter/Lists/Ownership.php +++ b/src/Module/Api/Twitter/Lists/Ownership.php @@ -56,7 +56,7 @@ class Ownership extends BaseApi BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); $uid = BaseApi::getCurrentUserID(); - $groups = $this->dba->select('group', [], ['deleted' => false, 'uid' => $uid]); + $groups = $this->dba->select('group', [], ['deleted' => false, 'uid' => $uid, 'cid' => null]); // loop through all groups $lists = []; diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index aafeee1e29..663862f79d 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -184,7 +184,7 @@ class Transmitter // Allow fetching the contact list when the requester is part of the list. if (($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) && !empty($requester)) { - $show_contacts = DBA::exists('contact', ['nurl' => Strings::normaliseLink($requester), 'rel' => $rel]); + $show_contacts = DBA::exists('contact', ['nurl' => Strings::normaliseLink($requester), 'uid' => $owner['uid'], 'blocked' => false]); } if (!$show_contacts) { @@ -1079,20 +1079,6 @@ class Transmitter return false; } - // In case of a forum post ensure to return the original post if author and forum are on the same machine - if (($item['gravity'] == GRAVITY_PARENT) && !empty($item['forum_mode'])) { - $author = Contact::getById($item['author-id'], ['nurl']); - if (!empty($author['nurl'])) { - $self = Contact::selectFirst(['uid'], ['nurl' => $author['nurl'], 'self' => true]); - 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; - } - } - } - } - if (empty($item['uri-id'])) { Logger::warning('Item without uri-id', ['item' => $item]); return false; diff --git a/src/Protocol/DFRN.php b/src/Protocol/DFRN.php index 94b1b1689d..50faf987aa 100644 --- a/src/Protocol/DFRN.php +++ b/src/Protocol/DFRN.php @@ -1557,22 +1557,11 @@ class DFRN // was the top-level post for this action written by somebody on this site? // Specifically, the recipient? - $parent = Post::selectFirst(['forum_mode', 'wall'], + $parent = Post::selectFirst(['wall'], ["`uri` = ? AND `uid` = ?" . $sql_extra, $item["thr-parent"], $importer["importer_uid"]]); $is_a_remote_action = DBA::isResult($parent); - /* - * Does this have the characteristics of a community or private group action? - * If it's an action to a wall post on a community/prvgroup page it's a - * valid community action. Also forum_mode makes it valid for sure. - * If neither, it's not. - */ - if ($is_a_remote_action && $community && (!$parent["forum_mode"]) && (!$parent["wall"])) { - $is_a_remote_action = false; - Logger::notice("not a community action"); - } - if ($is_a_remote_action) { return DFRN::REPLY_RC; } else { diff --git a/src/Worker/Delivery.php b/src/Worker/Delivery.php index 3be49a38fe..c09181d3e6 100644 --- a/src/Worker/Delivery.php +++ b/src/Worker/Delivery.php @@ -46,7 +46,6 @@ class Delivery const DELETION = 'drop'; const POST = 'wall-new'; const POKE = 'poke'; - const UPLINK = 'uplink'; const REMOVAL = 'removeme'; const PROFILEUPDATE = 'profileupdate'; diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index f46b1b0d9a..bfd38fe01f 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -223,10 +223,6 @@ class Notifier $relay_to_owner = true; } - if (($cmd === Delivery::UPLINK) && (intval($parent['forum_mode']) == 1) && !$top_level) { - $relay_to_owner = true; - } - // until the 'origin' flag has been in use for several months // we will just use it as a fallback test // later we will be able to use it as the primary test of whether or not to relay. @@ -333,15 +329,6 @@ class Notifier $deny_people = $aclFormatter->expand($parent['deny_cid']); $deny_groups = Group::expand($uid, $aclFormatter->expand($parent['deny_gid'])); - // if our parent is a public forum (forum_mode == 1), uplink to the origional author causing - // a delivery fork. private groups (forum_mode == 2) do not uplink - /// @todo Possibly we should not uplink when the author is the forum itself? - - if ((intval($parent['forum_mode']) == 1) && !$top_level && ($cmd !== Delivery::UPLINK) - && ($target_item['verb'] != Activity::ANNOUNCE)) { - Worker::add($a->getQueueValue('priority'), 'Notifier', Delivery::UPLINK, $post_uriid, $sender_uid); - } - foreach ($items as $item) { $recipients[] = $item['contact-id']; // pull out additional tagged people to notify (if public message) @@ -813,15 +800,4 @@ class Notifier return ['count' => $delivery_queue_count, 'contacts' => $contacts]; } - - /** - * Check if the delivered item is a forum post - * - * @param array $item - * @return boolean - */ - public static function isForumPost(array $item) - { - return ($item['gravity'] == GRAVITY_PARENT) && !empty($item['forum_mode']); - } } diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 0cf8543718..01419bf2b7 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -1301,7 +1301,7 @@ return [ "wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"], "mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], "pubmail" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""], - "forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => ""], + "forum_mode" => ["type" => "tinyint unsigned", "not null" => "1", "default" => "0", "comment" => "Deprecated"], "contact-id" => ["type" => "int unsigned", "not null" => "1", "default" => "0", "foreign" => ["contact" => "id"], "comment" => "contact.id"], "unseen" => ["type" => "boolean", "not null" => "1", "default" => "1", "comment" => "post has not been seen"], "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide the post from the user"], diff --git a/static/dbview.config.php b/static/dbview.config.php index 4188c7726d..014973de26 100644 --- a/static/dbview.config.php +++ b/static/dbview.config.php @@ -91,7 +91,6 @@ "deleted" => ["post-user", "deleted"], "origin" => ["post-user", "origin"], "parent-origin" => ["post-thread-user", "origin"], - "forum_mode" => ["post-thread-user", "forum_mode"], "mention" => ["post-thread-user", "mention"], "global" => ["post-user", "global"], "network" => ["post-user", "network"], @@ -250,7 +249,6 @@ "unseen" => ["post-thread-user", "unseen"], "deleted" => ["post-user", "deleted"], "origin" => ["post-thread-user", "origin"], - "forum_mode" => ["post-thread-user", "forum_mode"], "mention" => ["post-thread-user", "mention"], "global" => ["post-user", "global"], "network" => ["post-thread-user", "network"], From ee3a8ccb3bbb4c214f7410976bd9f6ae95be40f1 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 13 Feb 2022 05:45:06 +0000 Subject: [PATCH 2/7] No notifcations for forum / fetch user for fetching content --- src/Model/Post/UserNotification.php | 6 +++++ src/Protocol/ActivityPub/Receiver.php | 37 ++++++++++++++++++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/src/Model/Post/UserNotification.php b/src/Model/Post/UserNotification.php index ad7b9c4904..79499897dd 100644 --- a/src/Model/Post/UserNotification.php +++ b/src/Model/Post/UserNotification.php @@ -33,6 +33,7 @@ use Friendica\Model\Contact; use Friendica\Model\Post; use Friendica\Model\Subscription; use Friendica\Model\Tag; +use Friendica\Model\User; use Friendica\Navigation\Notifications; use Friendica\Network\HTTPException; use Friendica\Protocol\Activity; @@ -176,6 +177,11 @@ class UserNotification return; } + $user = User::getById($uid, ['account-type']); + if (in_array($user['account-type'], [User::ACCOUNT_TYPE_COMMUNITY, User::ACCOUNT_TYPE_RELAY])) { + return; + } + $notification_type = self::TYPE_NONE; if (self::checkShared($item, $uid)) { diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 3da53a033e..3d183aec56 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -263,7 +263,19 @@ class Receiver { $id = JsonLD::fetchElement($activity, '@id'); if (!empty($id) && !$trust_source) { - $fetched_activity = ActivityPub::fetchContent($id, $uid ?? 0); + if (empty($uid)) { + $actor = JsonLD::fetchElement($activity, 'as:actor', '@id'); + if (empty($actor)) { + $actor = ''; + } + + // Fetch a user out of the receivers of the message. + $fetch_uid = Receiver::getBestUserForActivity($activity, $actor); + } else { + $fetch_uid = $uid; + } + + $fetched_activity = ActivityPub::fetchContent($id, $fetch_uid); if (!empty($fetched_activity)) { $object = JsonLD::compact($fetched_activity); $fetched_id = JsonLD::fetchElement($object, '@id'); @@ -643,6 +655,29 @@ class Receiver } } + /** + * Fetch a user id from an activity array + * + * @param array $activity + * @param string $actor + * + * @return int user id + */ + private static function getBestUserForActivity(array $activity, string $actor) + { + $uid = 0; + $receivers = self::getReceivers($activity, $actor); + foreach ($receivers as $receiver) { + if ($receiver['type'] == self::TARGET_GLOBAL) { + return 0; + } + if (empty($uid) || ($receiver['type'] == self::TARGET_TO)) { + $uid = $receiver['uid']; + } + } + return $uid; + } + /** * Fetch the receiver list from an activity array * From a5a1c8179059a66886809dcfdfd8eb67f6c24828 Mon Sep 17 00:00:00 2001 From: Michael Date: Sun, 13 Feb 2022 16:42:43 +0000 Subject: [PATCH 3/7] Fetch the user id when not provided --- src/Protocol/ActivityPub/Receiver.php | 34 ++++++++++----------------- 1 file changed, 13 insertions(+), 21 deletions(-) diff --git a/src/Protocol/ActivityPub/Receiver.php b/src/Protocol/ActivityPub/Receiver.php index 3d183aec56..7f08410f69 100644 --- a/src/Protocol/ActivityPub/Receiver.php +++ b/src/Protocol/ActivityPub/Receiver.php @@ -263,17 +263,7 @@ class Receiver { $id = JsonLD::fetchElement($activity, '@id'); if (!empty($id) && !$trust_source) { - if (empty($uid)) { - $actor = JsonLD::fetchElement($activity, 'as:actor', '@id'); - if (empty($actor)) { - $actor = ''; - } - - // Fetch a user out of the receivers of the message. - $fetch_uid = Receiver::getBestUserForActivity($activity, $actor); - } else { - $fetch_uid = $uid; - } + $fetch_uid = $uid ?: self::getBestUserForActivity($activity); $fetched_activity = ActivityPub::fetchContent($id, $fetch_uid); if (!empty($fetched_activity)) { @@ -314,12 +304,12 @@ class Receiver if (empty($activity['thread-completion']) && (empty($reception_types[$uid]) || in_array($reception_types[$uid], [self::TARGET_UNKNOWN, self::TARGET_FOLLOWER, self::TARGET_ANSWER, self::TARGET_GLOBAL]))) { $reception_types[$uid] = self::TARGET_BCC; } - } else { - // We possibly need some user to fetch private content, - // so we fetch the first out ot the list. - $uid = self::getFirstUserFromReceivers($receivers); } + // We possibly need some user to fetch private content, + // so we fetch one out of the receivers if no uid is provided. + $fetch_uid = $uid ?: self::getBestUserForActivity($activity); + $object_id = JsonLD::fetchElement($activity, 'as:object', '@id'); if (empty($object_id)) { Logger::info('No object found'); @@ -331,11 +321,11 @@ class Receiver return []; } - $object_type = self::fetchObjectType($activity, $object_id, $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']))) { - $data = ActivityPub::fetchContent($object_id, $uid); + $data = ActivityPub::fetchContent($object_id, $fetch_uid); if (!empty($data)) { $type = $object_type; $activity = JsonLD::compact($data); @@ -343,7 +333,7 @@ class Receiver // Some variables need to be refetched since the activity changed $actor = JsonLD::fetchElement($activity, 'as:actor', '@id'); $object_id = JsonLD::fetchElement($activity, 'as:object', '@id'); - $object_type = self::fetchObjectType($activity, $object_id, $uid); + $object_type = self::fetchObjectType($activity, $object_id, $fetch_uid); } } @@ -360,7 +350,7 @@ class Receiver // Fetch the content only on activities where this matters // We can receive "#emojiReaction" when fetching content from Hubzilla systems // Always fetch on "Announce" - $object_data = self::fetchObject($object_id, $activity['as:object'], $trust_source && ($type != 'as:Announce'), $uid); + $object_data = self::fetchObject($object_id, $activity['as:object'], $trust_source && ($type != 'as:Announce'), $fetch_uid); if (empty($object_data)) { Logger::info("Object data couldn't be processed"); return []; @@ -408,7 +398,7 @@ class Receiver // An Undo is done on the object of an object, so we need that type as well if (($type == 'as:Undo') && !empty($object_data['object_object'])) { - $object_data['object_object_type'] = self::fetchObjectType([], $object_data['object_object'], $uid); + $object_data['object_object_type'] = self::fetchObjectType([], $object_data['object_object'], $fetch_uid); } } @@ -663,9 +653,11 @@ class Receiver * * @return int user id */ - private static function getBestUserForActivity(array $activity, string $actor) + public static function getBestUserForActivity(array $activity) { $uid = 0; + $actor = JsonLD::fetchElement($activity, 'as:actor', '@id') ?? ''; + $receivers = self::getReceivers($activity, $actor); foreach ($receivers as $receiver) { if ($receiver['type'] == self::TARGET_GLOBAL) { From d404f15312320dbe1f516db7bb91944182ad5d26 Mon Sep 17 00:00:00 2001 From: Michael Date: Mon, 14 Feb 2022 22:04:33 +0000 Subject: [PATCH 4/7] Improve local forum distribution --- src/Model/Item.php | 52 ++++++++++++++++++++++-- src/Protocol/ActivityPub/Transmitter.php | 31 +++++++++----- 2 files changed, 69 insertions(+), 14 deletions(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index 6749dc2389..fd0dbde2bf 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1402,9 +1402,15 @@ class Item } if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) { - // Only do an auto complete with the source uid "0" to prevent privavy problems + // Fetch the origin user for the post + $origin_uid = self::GetOriginUidForUriId($item['thr-parent-id'], $uid); + if (is_null($origin_uid)) { + Logger::info('Origin item was not found', ['uid' => $uid, 'uri-id' => $item['thr-parent-id']]); + return 0; + } + $causer = $item['causer-id'] ?: $item['author-id']; - $result = self::storeForUserByUriId($item['thr-parent-id'], $uid, ['causer-id' => $causer, 'post-reason' => self::PR_FETCHED]); + $result = self::storeForUserByUriId($item['thr-parent-id'], $uid, ['causer-id' => $causer, 'post-reason' => self::PR_FETCHED], $origin_uid); Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'causer' => $causer, 'result' => $result]); } @@ -1413,6 +1419,46 @@ class Item return $stored; } + /** + * Returns the origin uid of a post if the given user is allowed to see it. + * + * @param int $uriid + * @param int $uid + * @return int + */ + private static function GetOriginUidForUriId(int $uriid, int $uid) + { + if (Post::exists(['uri-id' => $uriid, 'uid' => $uid])) { + return $uid; + } + + $post = Post::selectFirst(['uid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'private'], ['uri-id' => $uriid, 'origin' => true]); + if (empty($post)) { + if (Post::exists(['uri-id' => $uriid, 'uid' => 0])) { + return 0; + } else { + return null; + } + } + + if (in_array($post['private'], [Item::PUBLIC, Item::UNLISTED])) { + return $post['uid']; + } + + $pcid = Contact::getPublicIdByUserId($uid); + if (empty($pcid)) { + return null; + } + + foreach (Item::enumeratePermissions($post, true) as $receiver) { + if ($receiver == $pcid) { + return $post['uid']; + } + } + + return null; + } + /** * Store a public item array for the given users * @@ -1928,7 +1974,7 @@ class Item $allow_cid = '<' . $owner['id'] . '>'; $allow_gid = '<' . Group::getIdForForum($owner['id']) . '>'; - $deny_cid = ''; + $deny_cid = ''; $deny_gid = ''; self::performActivity($item['id'], 'announce', $uid, $allow_cid, $allow_gid, $deny_cid, $deny_gid); } else { diff --git a/src/Protocol/ActivityPub/Transmitter.php b/src/Protocol/ActivityPub/Transmitter.php index 663862f79d..7be55898c8 100644 --- a/src/Protocol/ActivityPub/Transmitter.php +++ b/src/Protocol/ActivityPub/Transmitter.php @@ -600,23 +600,32 @@ class Transmitter continue; } - if (!empty($profile = APContact::getByURL($contact['url'], false))) { + $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']; } } } - foreach ($receiver_list as $receiver) { - $contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]); - if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) { - continue; - } + if (!$exclusive) { + foreach ($receiver_list as $receiver) { + $contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]); + if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) { + continue; + } - if (!empty($profile = APContact::getByURL($contact['url'], false))) { - if ($contact['hidden'] || $always_bcc) { - $data['bcc'][] = $profile['url']; - } else { - $data['cc'][] = $profile['url']; + if (!empty($profile = APContact::getByURL($contact['url'], false))) { + if ($contact['hidden'] || $always_bcc) { + $data['bcc'][] = $profile['url']; + } else { + $data['cc'][] = $profile['url']; + } } } } From 2b0518ac04b31fd37e617723d6492149aaa7e06f Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Feb 2022 06:21:46 +0000 Subject: [PATCH 5/7] unset several table fields before distribution --- src/Model/Item.php | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index fd0dbde2bf..46f41e9a93 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -1485,6 +1485,7 @@ class Item return 0; } + // Data from the "post-user" table unset($item['id']); unset($item['mention']); unset($item['starred']); @@ -1493,10 +1494,14 @@ class Item unset($item['pinned']); unset($item['ignored']); unset($item['pubmail']); - unset($item['event-id']); unset($item['hidden']); unset($item['notification-type']); + unset($item['post-reason']); + + // Data from the "post-delivery-data" table + unset($item['postopts']); + unset($item['inform']); $item['uid'] = $uid; $item['origin'] = 0; From 29d83c0ffb135a0cec8c6ad5fc2a80fbdd96683f Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Feb 2022 07:08:02 +0000 Subject: [PATCH 6/7] Block communication with Diaspora for communities --- src/Model/Item.php | 9 +++++++++ src/Module/Diaspora/Receive.php | 6 ++++++ src/Protocol/Diaspora.php | 7 +------ src/Worker/Notifier.php | 2 +- 4 files changed, 17 insertions(+), 7 deletions(-) diff --git a/src/Model/Item.php b/src/Model/Item.php index 46f41e9a93..9c67426344 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -818,6 +818,15 @@ class Item $item['inform'] = trim($item['inform'] ?? ''); $item['file'] = trim($item['file'] ?? ''); + // Communities aren't working with the Diaspora protoccol + if (($uid != 0) && ($item['network'] == Protocol::DIASPORA)) { + $user = User::getById($uid, ['account-type']); + if ($user['account-type'] == Contact::TYPE_COMMUNITY) { + Logger::info('Community posts are not supported via Diaspora'); + return 0; + } + } + // Items cannot be stored before they happen ... if ($item['created'] > DateTimeFormat::utcNow()) { $item['created'] = DateTimeFormat::utcNow(); diff --git a/src/Module/Diaspora/Receive.php b/src/Module/Diaspora/Receive.php index 498d1b13ed..19de0873ff 100644 --- a/src/Module/Diaspora/Receive.php +++ b/src/Module/Diaspora/Receive.php @@ -93,6 +93,12 @@ class Receive extends BaseModule $importer = User::getByGuid($this->parameters['guid']); + if ($importer['account-type'] == User::ACCOUNT_TYPE_COMMUNITY) { + // Communities aren't working with the Diaspora protoccol + // We throw an "accepted" here, so that the sender doesn't repeat the delivery + throw new HTTPException\AcceptedException(); + } + $msg = $this->decodePost(false, $importer['prvkey'] ?? ''); $this->logger->info('Diaspora: Dispatching.'); diff --git a/src/Protocol/Diaspora.php b/src/Protocol/Diaspora.php index aba79364f4..ea0b2b6749 100644 --- a/src/Protocol/Diaspora.php +++ b/src/Protocol/Diaspora.php @@ -858,10 +858,6 @@ class Diaspora } elseif (($contact["rel"] == Contact::SHARING) || ($contact["rel"] == Contact::FRIEND)) { // Yes, then it is fine. return true; - // Is it a post to a community? - } elseif (($contact["rel"] == Contact::FOLLOWER) && ($importer['account-type'] == User::ACCOUNT_TYPE_COMMUNITY)) { - // That's good - return true; // Is the message a global user or a comment? } elseif (($importer["uid"] == 0) || $is_comment) { // Messages for the global users and comments are always accepted @@ -3473,9 +3469,8 @@ class Diaspora private static function prependParentAuthorMention($body, $profile_url) { - $profile = Contact::getByURL($profile_url, false, ['addr', 'name', 'contact-type']); + $profile = Contact::getByURL($profile_url, false, ['addr', 'name']); if (!empty($profile['addr']) - && $profile['contact-type'] != Contact::TYPE_COMMUNITY && !strstr($body, $profile['addr']) && !strstr($body, $profile_url) ) { diff --git a/src/Worker/Notifier.php b/src/Worker/Notifier.php index bfd38fe01f..3a5c7f13e1 100644 --- a/src/Worker/Notifier.php +++ b/src/Worker/Notifier.php @@ -153,7 +153,7 @@ class Notifier } // Should the post be transmitted to Diaspora? - $diaspora_delivery = true; + $diaspora_delivery = ($owner['account-type'] != User::ACCOUNT_TYPE_COMMUNITY); // If this is a public conversation, notify the feed hub $public_message = true; From d019ef57d2f994f449bb48915cdbee27a2d95672 Mon Sep 17 00:00:00 2001 From: Michael Date: Tue, 15 Feb 2022 15:44:44 +0000 Subject: [PATCH 7/7] Database version increased, code simplified --- database.sql | 2 +- src/Model/Item.php | 13 ++++--------- static/dbstructure.config.php | 2 +- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/database.sql b/database.sql index 308e2e8a86..a36adad015 100644 --- a/database.sql +++ b/database.sql @@ -1,6 +1,6 @@ -- ------------------------------------------ -- Friendica 2022.05-dev (Siberian Iris) --- DB_UPDATE_VERSION 1451 +-- DB_UPDATE_VERSION 1452 -- ------------------------------------------ diff --git a/src/Model/Item.php b/src/Model/Item.php index 9c67426344..96cf5c488c 100644 --- a/src/Model/Item.php +++ b/src/Model/Item.php @@ -2516,11 +2516,6 @@ class Item return true; } - $allow_cid = $allow_cid ?? $item['allow_cid']; - $allow_gid = $allow_gid ?? $item['allow_gid']; - $deny_cid = $deny_cid ?? $item['deny_cid']; - $deny_gid = $deny_gid ?? $item['deny_gid']; - $objtype = $item['resource-id'] ? Activity\ObjectType::IMAGE : Activity\ObjectType::NOTE; $new_item = [ @@ -2541,10 +2536,10 @@ class Item 'body' => $activity, 'verb' => $activity, 'object-type' => $objtype, - 'allow_cid' => $allow_cid, - 'allow_gid' => $allow_gid, - 'deny_cid' => $deny_cid, - 'deny_gid' => $deny_gid, + 'allow_cid' => $allow_cid ?? $item['allow_cid'], + 'allow_gid' => $allow_gid ?? $item['allow_gid'], + 'deny_cid' => $deny_cid ?? $item['deny_cid'], + 'deny_gid' => $deny_gid ?? $item['deny_gid'], 'visible' => 1, 'unseen' => 1, ]; diff --git a/static/dbstructure.config.php b/static/dbstructure.config.php index 01419bf2b7..f7e02b10b9 100644 --- a/static/dbstructure.config.php +++ b/static/dbstructure.config.php @@ -55,7 +55,7 @@ use Friendica\Database\DBA; if (!defined('DB_UPDATE_VERSION')) { - define('DB_UPDATE_VERSION', 1451); + define('DB_UPDATE_VERSION', 1452); } return [