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

@ -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;

View file

@ -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])]);
}
}

View file

@ -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);
@ -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();
@ -881,10 +890,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 +919,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']]);
@ -1406,9 +1411,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]);
}
@ -1417,6 +1428,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
*
@ -1443,6 +1494,7 @@ class Item
return 0;
}
// Data from the "post-user" table
unset($item['id']);
unset($item['mention']);
unset($item['starred']);
@ -1451,11 +1503,14 @@ class Item
unset($item['pinned']);
unset($item['ignored']);
unset($item['pubmail']);
unset($item['forum_mode']);
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;
@ -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']]);
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 +2357,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 +2375,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;
@ -2499,10 +2536,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 ?? $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,
];

View file

@ -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)) {