Merge pull request #11242 from annando/private-forums

Private forums are now working via AP
This commit is contained in:
Hypolite Petovan 2022-02-15 12:06:34 -05:00 committed by GitHub
commit 361fdccdc7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 228 additions and 187 deletions

View file

@ -1,6 +1,6 @@
-- ------------------------------------------ -- ------------------------------------------
-- Friendica 2022.05-dev (Siberian Iris) -- Friendica 2022.05-dev (Siberian Iris)
-- DB_UPDATE_VERSION 1451 -- DB_UPDATE_VERSION 1452
-- ------------------------------------------ -- ------------------------------------------
@ -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', `wall` boolean NOT NULL DEFAULT '0' COMMENT 'This item was posted to the wall of uid',
`mention` boolean NOT NULL DEFAULT '0' COMMENT '', `mention` boolean NOT NULL DEFAULT '0' COMMENT '',
`pubmail` 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', `contact-id` int unsigned NOT NULL DEFAULT 0 COMMENT 'contact.id',
`unseen` boolean NOT NULL DEFAULT '1' COMMENT 'post has not been seen', `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', `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`.`deleted` AS `deleted`,
`post-user`.`origin` AS `origin`, `post-user`.`origin` AS `origin`,
`post-thread-user`.`origin` AS `parent-origin`, `post-thread-user`.`origin` AS `parent-origin`,
`post-thread-user`.`forum_mode` AS `forum_mode`,
`post-thread-user`.`mention` AS `mention`, `post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`, `post-user`.`global` AS `global`,
`post-user`.`network` AS `network`, `post-user`.`network` AS `network`,
@ -1773,7 +1772,6 @@ CREATE VIEW `post-thread-user-view` AS SELECT
`post-thread-user`.`unseen` AS `unseen`, `post-thread-user`.`unseen` AS `unseen`,
`post-user`.`deleted` AS `deleted`, `post-user`.`deleted` AS `deleted`,
`post-thread-user`.`origin` AS `origin`, `post-thread-user`.`origin` AS `origin`,
`post-thread-user`.`forum_mode` AS `forum_mode`,
`post-thread-user`.`mention` AS `mention`, `post-thread-user`.`mention` AS `mention`,
`post-user`.`global` AS `global`, `post-user`.`global` AS `global`,
`post-thread-user`.`network` AS `network`, `post-thread-user`.`network` AS `network`,

View file

@ -24,7 +24,7 @@ Fields
| wall | This item was posted to the wall of uid | boolean | NO | | 0 | | | wall | This item was posted to the wall of uid | boolean | NO | | 0 | |
| mention | | boolean | NO | | 0 | | | mention | | boolean | NO | | 0 | |
| pubmail | | 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 | | | contact-id | contact.id | int unsigned | NO | | 0 | |
| unseen | post has not been seen | boolean | NO | | 1 | | | unseen | post has not been seen | boolean | NO | | 1 | |
| hidden | Marker to hide the post from the user | boolean | NO | | 0 | | | hidden | Marker to hide the post from the user | boolean | NO | | 0 | |

View file

@ -29,7 +29,6 @@
*/ */
use Friendica\App; use Friendica\App;
use Friendica\Content\Item as ItemHelper;
use Friendica\Content\PageInfo; use Friendica\Content\PageInfo;
use Friendica\Content\Text\BBCode; use Friendica\Content\Text\BBCode;
use Friendica\Core\Hook; use Friendica\Core\Hook;
@ -40,11 +39,11 @@ use Friendica\Core\System;
use Friendica\Core\Worker; use Friendica\Core\Worker;
use Friendica\Database\DBA; use Friendica\Database\DBA;
use Friendica\DI; use Friendica\DI;
use Friendica\Model\APContact;
use Friendica\Model\Attach; use Friendica\Model\Attach;
use Friendica\Model\Contact; use Friendica\Model\Contact;
use Friendica\Model\Conversation; use Friendica\Model\Conversation;
use Friendica\Model\FileTag; use Friendica\Model\FileTag;
use Friendica\Model\Group;
use Friendica\Model\Item; use Friendica\Model\Item;
use Friendica\Model\ItemURI; use Friendica\Model\ItemURI;
use Friendica\Model\Notification; use Friendica\Model\Notification;
@ -404,7 +403,7 @@ function item_post(App $a) {
} }
$inform .= 'cid:' . $contact['id']; $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; continue;
} }
@ -437,26 +436,15 @@ function item_post(App $a) {
$postopts = ''; $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_contact_allow = '';
$str_group_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;
} }
} }

View file

@ -685,7 +685,7 @@ class Contact
*/ */
public static function updateSelfFromUserID($uid, $update_avatar = false) 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', 'xmpp', 'matrix', 'contact-type', 'forum', 'prv', 'avatar-date', 'url', 'nurl', 'unsearchable',
'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network']; 'photo', 'thumb', 'micro', 'header', 'addr', 'request', 'notify', 'poll', 'confirm', 'poco', 'network'];
$self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]); $self = DBA::selectFirst('contact', $fields, ['uid' => $uid, 'self' => true]);
@ -757,6 +757,7 @@ class Contact
$fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY; $fields['forum'] = $user['page-flags'] == User::PAGE_FLAGS_COMMUNITY;
$fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP; $fields['prv'] = $user['page-flags'] == User::PAGE_FLAGS_PRVGROUP;
$fields['unsearchable'] = !$profile['net-publish']; $fields['unsearchable'] = !$profile['net-publish'];
$fields['manually-approve'] = in_array($user['page-flags'], [User::PAGE_FLAGS_NORMAL, User::PAGE_FLAGS_PRVGROUP]);
$update = false; $update = false;

View file

@ -41,7 +41,7 @@ class Group
public static function getByUserId($uid, $includesDeleted = false) public static function getByUserId($uid, $includesDeleted = false)
{ {
$conditions = ['uid' => $uid]; $conditions = ['uid' => $uid, 'cid' => null];
if (!$includesDeleted) { if (!$includesDeleted) {
$conditions['deleted'] = false; $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)) { while ($group = DBA::fetch($stmt)) {
$display_groups[] = [ $display_groups[] = [
'name' => $group['name'], 'name' => $group['name'],
@ -465,7 +465,7 @@ class Group
$member_of = self::getIdsByContactId($cid); $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)) { while ($group = DBA::fetch($stmt)) {
$selected = (($group_id == $group['id']) ? ' group-selected' : ''); $selected = (($group_id == $group['id']) ? ' group-selected' : '');
@ -522,19 +522,17 @@ 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 * @param integer $id Contact ID
* @return integer Group IO
*/ */
public static function getMembersForForum(int $id) { public static function getIdForForum(int $id)
$contact = Contact::getById($id, ['uid', 'url', 'name']); {
if (empty($contact)) { Logger::info('Get id for forum id', ['id' => $id]);
return; $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]); $group = DBA::selectFirst('group', ['id'], ['uid' => $contact['uid'], 'cid' => $id]);
@ -550,6 +548,33 @@ class Group
$gid = $group['id']; $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]); $group_members = DBA::selectToArray('group_member', ['contact-id'], ['gid' => $gid]);
if (!empty($group_members)) { if (!empty($group_members)) {
$current = array_unique(array_column($group_members, 'contact-id')); $current = array_unique(array_column($group_members, 'contact-id'));
@ -557,7 +582,7 @@ class Group
$current = []; $current = [];
} }
foreach (ActivityPub::fetchItems($apcontact['followers']) as $follower) { foreach (ActivityPub::fetchItems($apcontact['followers'], $contact['uid']) as $follower) {
$id = Contact::getIdForURL($follower); $id = Contact::getIdForURL($follower);
if (!in_array($id, $current)) { if (!in_array($id, $current)) {
DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $id]); DBA::insert('group_member', ['gid' => $gid, 'contact-id' => $id]);
@ -568,5 +593,6 @@ class Group
} }
DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $current]); DBA::delete('group_member', ['gid' => $gid, 'contact-id' => $current]);
Logger::info('Updated forum members', ['id' => $id, 'count' => DBA::count('group_member', ['gid' => $gid])]);
} }
} }

View file

@ -100,7 +100,7 @@ class Item
'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity', 'inform', 'deleted', 'extid', 'post-type', 'post-reason', 'gravity',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'author-id', 'author-link', 'author-name', 'author-avatar', 'owner-id', 'owner-link', 'contact-uid', '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', 'thr-parent-id', 'parent-uri-id', 'postopts', 'pubmail',
'event-created', 'event-edited', 'event-start', 'event-finish', 'event-created', 'event-edited', 'event-start', 'event-finish',
'event-summary', 'event-desc', 'event-location', 'event-type', 'event-summary', 'event-desc', 'event-location', 'event-type',
@ -114,7 +114,7 @@ class Item
'postopts', 'plink', 'resource-id', 'event-id', 'inform', 'postopts', 'plink', 'resource-id', 'event-id', 'inform',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'post-type', 'post-reason',
'private', 'pubmail', 'visible', 'starred', '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', 'title', 'content-warning', 'body', 'location', 'coord', 'app',
'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target', 'rendered-hash', 'rendered-html', 'object-type', 'object', 'target-type', 'target',
'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network', 'author-id', 'author-link', 'author-name', 'author-avatar', 'author-network',
@ -655,7 +655,7 @@ class Item
$fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted', $fields = ['uid', 'uri', 'parent-uri', 'id', 'deleted',
'uri-id', 'parent-uri-id', 'uri-id', 'parent-uri-id',
'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid', 'allow_cid', 'allow_gid', 'deny_cid', 'deny_gid',
'wall', 'private', 'forum_mode', 'origin', 'author-id']; 'wall', 'private', 'origin', 'author-id'];
$condition = ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']]; $condition = ['uri-id' => $item['thr-parent-id'], 'uid' => $item['uid']];
$params = ['order' => ['id' => false]]; $params = ['order' => ['id' => false]];
$parent = Post::selectFirst($fields, $condition, $params); $parent = Post::selectFirst($fields, $condition, $params);
@ -818,6 +818,15 @@ class Item
$item['inform'] = trim($item['inform'] ?? ''); $item['inform'] = trim($item['inform'] ?? '');
$item['file'] = trim($item['file'] ?? ''); $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 ... // Items cannot be stored before they happen ...
if ($item['created'] > DateTimeFormat::utcNow()) { if ($item['created'] > DateTimeFormat::utcNow()) {
$item['created'] = DateTimeFormat::utcNow(); $item['created'] = DateTimeFormat::utcNow();
@ -881,10 +890,15 @@ class Item
$item['parent-uri'] = $toplevel_parent['uri']; $item['parent-uri'] = $toplevel_parent['uri'];
$item['parent-uri-id'] = $toplevel_parent['uri-id']; $item['parent-uri-id'] = $toplevel_parent['uri-id'];
$item['deleted'] = $toplevel_parent['deleted']; $item['deleted'] = $toplevel_parent['deleted'];
$item['allow_cid'] = $toplevel_parent['allow_cid'];
$item['allow_gid'] = $toplevel_parent['allow_gid']; // Reshares have to keep their permissions to allow forums to work
$item['deny_cid'] = $toplevel_parent['deny_cid']; if (!$item['origin'] || ($item['verb'] != Activity::ANNOUNCE)) {
$item['deny_gid'] = $toplevel_parent['deny_gid']; $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']; $parent_origin = $toplevel_parent['origin'];
// Don't federate received participation messages // Don't federate received participation messages
@ -905,15 +919,6 @@ class Item
$item['private'] = $toplevel_parent['private']; $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 its a post that originated here then tag the thread as "mention"
if ($item['origin'] && $item['uid']) { if ($item['origin'] && $item['uid']) {
DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]); DBA::update('post-thread-user', ['mention' => true], ['uri-id' => $item['parent-uri-id'], 'uid' => $item['uid']]);
@ -1406,9 +1411,15 @@ class Item
} }
if ((($item['gravity'] == GRAVITY_COMMENT) || $is_reshare) && !Post::exists(['uri-id' => $item['thr-parent-id'], 'uid' => $uid])) { 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']; $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]); Logger::info('Fetched thread parent', ['uri-id' => $item['thr-parent-id'], 'uid' => $uid, 'causer' => $causer, 'result' => $result]);
} }
@ -1417,6 +1428,46 @@ class Item
return $stored; 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 * Store a public item array for the given users
* *
@ -1443,6 +1494,7 @@ class Item
return 0; return 0;
} }
// Data from the "post-user" table
unset($item['id']); unset($item['id']);
unset($item['mention']); unset($item['mention']);
unset($item['starred']); unset($item['starred']);
@ -1451,11 +1503,14 @@ class Item
unset($item['pinned']); unset($item['pinned']);
unset($item['ignored']); unset($item['ignored']);
unset($item['pubmail']); unset($item['pubmail']);
unset($item['forum_mode']);
unset($item['event-id']); unset($item['event-id']);
unset($item['hidden']); unset($item['hidden']);
unset($item['notification-type']); 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['uid'] = $uid;
$item['origin'] = 0; $item['origin'] = 0;
@ -1928,41 +1983,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']]); 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']);
/** $allow_cid = '<' . $owner['id'] . '>';
* All the following lines are only needed for private forums and compatibility to older systems without AP support. $allow_gid = '<' . Group::getIdForForum($owner['id']) . '>';
* A possible way would be that the followers list of a forum would always be readable by all followers. $deny_cid = '';
* So this would mean that the comment distribution could be done exactly for the intended audience. $deny_gid = '';
* Or possibly we could store the receivers that had been in the "announce" message above and use this. self::performActivity($item['id'], 'announce', $uid, $allow_cid, $allow_gid, $deny_cid, $deny_gid);
*/
// 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;
} else { } 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']]); 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; return false;
} }
@ -2325,12 +2357,17 @@ class Item
* *
* Toggle activities as like,dislike,attend of an item * Toggle activities as like,dislike,attend of an item
* *
* @param int $item_id * @param int $item_id
* @param string $verb * @param string $verb
* Activity verb. One of * Activity verb. One of
* like, unlike, dislike, undislike, attendyes, unattendyes, * like, unlike, dislike, undislike, attendyes, unattendyes,
* attendno, unattendno, attendmaybe, unattendmaybe, * attendno, unattendno, attendmaybe, unattendmaybe,
* announce, unannouce * announce, unannouce
* @param int $uid
* @param string $allow_cid
* @param string $allow_gid
* @param string $deny_cid
* @param string $deny_gid
* @return bool * @return bool
* @throws \Friendica\Network\HTTPException\InternalServerErrorException * @throws \Friendica\Network\HTTPException\InternalServerErrorException
* @throws \ImagickException * @throws \ImagickException
@ -2338,7 +2375,7 @@ class Item
* array $arr * array $arr
* 'post_id' => ID of posted item * '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)) { if (empty($uid)) {
return false; return false;
@ -2499,10 +2536,10 @@ class Item
'body' => $activity, 'body' => $activity,
'verb' => $activity, 'verb' => $activity,
'object-type' => $objtype, 'object-type' => $objtype,
'allow_cid' => $item['allow_cid'], 'allow_cid' => $allow_cid ?? $item['allow_cid'],
'allow_gid' => $item['allow_gid'], 'allow_gid' => $allow_gid ?? $item['allow_gid'],
'deny_cid' => $item['deny_cid'], 'deny_cid' => $deny_cid ?? $item['deny_cid'],
'deny_gid' => $item['deny_gid'], 'deny_gid' => $deny_gid ?? $item['deny_gid'],
'visible' => 1, 'visible' => 1,
'unseen' => 1, 'unseen' => 1,
]; ];

View file

@ -33,6 +33,7 @@ use Friendica\Model\Contact;
use Friendica\Model\Post; use Friendica\Model\Post;
use Friendica\Model\Subscription; use Friendica\Model\Subscription;
use Friendica\Model\Tag; use Friendica\Model\Tag;
use Friendica\Model\User;
use Friendica\Navigation\Notifications; use Friendica\Navigation\Notifications;
use Friendica\Network\HTTPException; use Friendica\Network\HTTPException;
use Friendica\Protocol\Activity; use Friendica\Protocol\Activity;
@ -176,6 +177,11 @@ class UserNotification
return; 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; $notification_type = self::TYPE_NONE;
if (self::checkShared($item, $uid)) { if (self::checkShared($item, $uid)) {

View file

@ -56,7 +56,7 @@ class Ownership extends BaseApi
BaseApi::checkAllowedScope(BaseApi::SCOPE_READ); BaseApi::checkAllowedScope(BaseApi::SCOPE_READ);
$uid = BaseApi::getCurrentUserID(); $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 // loop through all groups
$lists = []; $lists = [];

View file

@ -93,6 +93,12 @@ class Receive extends BaseModule
$importer = User::getByGuid($this->parameters['guid']); $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'] ?? ''); $msg = $this->decodePost(false, $importer['prvkey'] ?? '');
$this->logger->info('Diaspora: Dispatching.'); $this->logger->info('Diaspora: Dispatching.');

View file

@ -263,7 +263,9 @@ class Receiver
{ {
$id = JsonLD::fetchElement($activity, '@id'); $id = JsonLD::fetchElement($activity, '@id');
if (!empty($id) && !$trust_source) { if (!empty($id) && !$trust_source) {
$fetched_activity = ActivityPub::fetchContent($id, $uid ?? 0); $fetch_uid = $uid ?: self::getBestUserForActivity($activity);
$fetched_activity = ActivityPub::fetchContent($id, $fetch_uid);
if (!empty($fetched_activity)) { if (!empty($fetched_activity)) {
$object = JsonLD::compact($fetched_activity); $object = JsonLD::compact($fetched_activity);
$fetched_id = JsonLD::fetchElement($object, '@id'); $fetched_id = JsonLD::fetchElement($object, '@id');
@ -302,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]))) { 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; $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'); $object_id = JsonLD::fetchElement($activity, 'as:object', '@id');
if (empty($object_id)) { if (empty($object_id)) {
Logger::info('No object found'); Logger::info('No object found');
@ -319,11 +321,11 @@ class Receiver
return []; 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) // 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']))) { 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)) { if (!empty($data)) {
$type = $object_type; $type = $object_type;
$activity = JsonLD::compact($data); $activity = JsonLD::compact($data);
@ -331,7 +333,7 @@ class Receiver
// Some variables need to be refetched since the activity changed // Some variables need to be refetched since the activity changed
$actor = JsonLD::fetchElement($activity, 'as:actor', '@id'); $actor = JsonLD::fetchElement($activity, 'as:actor', '@id');
$object_id = JsonLD::fetchElement($activity, 'as:object', '@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);
} }
} }
@ -348,7 +350,7 @@ class Receiver
// Fetch the content only on activities where this matters // Fetch the content only on activities where this matters
// We can receive "#emojiReaction" when fetching content from Hubzilla systems // We can receive "#emojiReaction" when fetching content from Hubzilla systems
// Always fetch on "Announce" // 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)) { if (empty($object_data)) {
Logger::info("Object data couldn't be processed"); Logger::info("Object data couldn't be processed");
return []; return [];
@ -396,7 +398,7 @@ class Receiver
// An Undo is done on the object of an object, so we need that type as well // 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'])) { 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);
} }
} }
@ -643,6 +645,31 @@ class Receiver
} }
} }
/**
* Fetch a user id from an activity array
*
* @param array $activity
* @param string $actor
*
* @return int user id
*/
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) {
return 0;
}
if (empty($uid) || ($receiver['type'] == self::TARGET_TO)) {
$uid = $receiver['uid'];
}
}
return $uid;
}
/** /**
* Fetch the receiver list from an activity array * Fetch the receiver list from an activity array
* *

View file

@ -184,7 +184,7 @@ class Transmitter
// Allow fetching the contact list when the requester is part of the list. // Allow fetching the contact list when the requester is part of the list.
if (($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) && !empty($requester)) { 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) { if (!$show_contacts) {
@ -600,23 +600,32 @@ class Transmitter
continue; 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']; $data['to'][] = $profile['url'];
} }
} }
} }
foreach ($receiver_list as $receiver) { if (!$exclusive) {
$contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]); foreach ($receiver_list as $receiver) {
if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) { $contact = DBA::selectFirst('contact', ['url', 'hidden', 'network', 'protocol', 'gsid'], ['id' => $receiver, 'network' => Protocol::FEDERATED]);
continue; if (!DBA::isResult($contact) || !self::isAPContact($contact, $networks)) {
} continue;
}
if (!empty($profile = APContact::getByURL($contact['url'], false))) { if (!empty($profile = APContact::getByURL($contact['url'], false))) {
if ($contact['hidden'] || $always_bcc) { if ($contact['hidden'] || $always_bcc) {
$data['bcc'][] = $profile['url']; $data['bcc'][] = $profile['url'];
} else { } else {
$data['cc'][] = $profile['url']; $data['cc'][] = $profile['url'];
}
} }
} }
} }
@ -1079,20 +1088,6 @@ class Transmitter
return false; 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'])) { if (empty($item['uri-id'])) {
Logger::warning('Item without uri-id', ['item' => $item]); Logger::warning('Item without uri-id', ['item' => $item]);
return false; return false;

View file

@ -1557,22 +1557,11 @@ class DFRN
// was the top-level post for this action written by somebody on this site? // was the top-level post for this action written by somebody on this site?
// Specifically, the recipient? // Specifically, the recipient?
$parent = Post::selectFirst(['forum_mode', 'wall'], $parent = Post::selectFirst(['wall'],
["`uri` = ? AND `uid` = ?" . $sql_extra, $item["thr-parent"], $importer["importer_uid"]]); ["`uri` = ? AND `uid` = ?" . $sql_extra, $item["thr-parent"], $importer["importer_uid"]]);
$is_a_remote_action = DBA::isResult($parent); $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) { if ($is_a_remote_action) {
return DFRN::REPLY_RC; return DFRN::REPLY_RC;
} else { } else {

View file

@ -858,10 +858,6 @@ class Diaspora
} elseif (($contact["rel"] == Contact::SHARING) || ($contact["rel"] == Contact::FRIEND)) { } elseif (($contact["rel"] == Contact::SHARING) || ($contact["rel"] == Contact::FRIEND)) {
// Yes, then it is fine. // Yes, then it is fine.
return true; 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? // Is the message a global user or a comment?
} elseif (($importer["uid"] == 0) || $is_comment) { } elseif (($importer["uid"] == 0) || $is_comment) {
// Messages for the global users and comments are always accepted // Messages for the global users and comments are always accepted
@ -3473,9 +3469,8 @@ class Diaspora
private static function prependParentAuthorMention($body, $profile_url) 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']) if (!empty($profile['addr'])
&& $profile['contact-type'] != Contact::TYPE_COMMUNITY
&& !strstr($body, $profile['addr']) && !strstr($body, $profile['addr'])
&& !strstr($body, $profile_url) && !strstr($body, $profile_url)
) { ) {

View file

@ -46,7 +46,6 @@ class Delivery
const DELETION = 'drop'; const DELETION = 'drop';
const POST = 'wall-new'; const POST = 'wall-new';
const POKE = 'poke'; const POKE = 'poke';
const UPLINK = 'uplink';
const REMOVAL = 'removeme'; const REMOVAL = 'removeme';
const PROFILEUPDATE = 'profileupdate'; const PROFILEUPDATE = 'profileupdate';

View file

@ -153,7 +153,7 @@ class Notifier
} }
// Should the post be transmitted to Diaspora? // 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 // If this is a public conversation, notify the feed hub
$public_message = true; $public_message = true;
@ -223,10 +223,6 @@ class Notifier
$relay_to_owner = true; $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 // until the 'origin' flag has been in use for several months
// we will just use it as a fallback test // 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. // 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_people = $aclFormatter->expand($parent['deny_cid']);
$deny_groups = Group::expand($uid, $aclFormatter->expand($parent['deny_gid'])); $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) { foreach ($items as $item) {
$recipients[] = $item['contact-id']; $recipients[] = $item['contact-id'];
// pull out additional tagged people to notify (if public message) // pull out additional tagged people to notify (if public message)
@ -813,15 +800,4 @@ class Notifier
return ['count' => $delivery_queue_count, 'contacts' => $contacts]; 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']);
}
} }

View file

@ -55,7 +55,7 @@
use Friendica\Database\DBA; use Friendica\Database\DBA;
if (!defined('DB_UPDATE_VERSION')) { if (!defined('DB_UPDATE_VERSION')) {
define('DB_UPDATE_VERSION', 1451); define('DB_UPDATE_VERSION', 1452);
} }
return [ return [
@ -1301,7 +1301,7 @@ return [
"wall" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "This item was posted to the wall of uid"], "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" => ""], "mention" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => ""],
"pubmail" => ["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"], "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"], "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"], "hidden" => ["type" => "boolean", "not null" => "1", "default" => "0", "comment" => "Marker to hide the post from the user"],

View file

@ -91,7 +91,6 @@
"deleted" => ["post-user", "deleted"], "deleted" => ["post-user", "deleted"],
"origin" => ["post-user", "origin"], "origin" => ["post-user", "origin"],
"parent-origin" => ["post-thread-user", "origin"], "parent-origin" => ["post-thread-user", "origin"],
"forum_mode" => ["post-thread-user", "forum_mode"],
"mention" => ["post-thread-user", "mention"], "mention" => ["post-thread-user", "mention"],
"global" => ["post-user", "global"], "global" => ["post-user", "global"],
"network" => ["post-user", "network"], "network" => ["post-user", "network"],
@ -250,7 +249,6 @@
"unseen" => ["post-thread-user", "unseen"], "unseen" => ["post-thread-user", "unseen"],
"deleted" => ["post-user", "deleted"], "deleted" => ["post-user", "deleted"],
"origin" => ["post-thread-user", "origin"], "origin" => ["post-thread-user", "origin"],
"forum_mode" => ["post-thread-user", "forum_mode"],
"mention" => ["post-thread-user", "mention"], "mention" => ["post-thread-user", "mention"],
"global" => ["post-user", "global"], "global" => ["post-user", "global"],
"network" => ["post-thread-user", "network"], "network" => ["post-thread-user", "network"],