]> git.mxchange.org Git - friendica.git/blobdiff - src/Model/Item.php
doc/themes.md,FAQ-admin: point to live friendica-themes.com mirror
[friendica.git] / src / Model / Item.php
index 8c2f2e205ce865957a487bdc35f204b98cb31317..d040c876de6df1ab4a1ee614102173943bfbc66a 100644 (file)
@@ -74,6 +74,11 @@ class Item
        const PR_RELAY = 74;
        const PR_FETCHED = 75;
 
+       // system.accept_only_sharer setting values
+       const COMPLETION_NONE    = 1;
+       const COMPLETION_COMMENT = 0;
+       const COMPLETION_LIKE    = 2;
+
        // Field list that is used to display the items
        const DISPLAY_FIELDLIST = [
                'uid', 'id', 'parent', 'guid', 'network', 'gravity',
@@ -100,7 +105,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 +119,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 +660,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 +823,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 +895,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 +924,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']]);
@@ -1066,6 +1076,13 @@ class Item
                        unset($item['causer-id']);
                }
 
+               if (in_array($item['network'], [Protocol::ACTIVITYPUB, Protocol::DFRN])) {
+                       $content_warning = BBCode::getAbstract($item['body'], Protocol::ACTIVITYPUB);
+                       if (!empty($content_warning) && empty($item['content-warning'])) {
+                               $item['content-warning'] = $content_warning;
+                       }
+               }
+
                Post::insert($item['uri-id'], $item);
 
                if ($item['gravity'] == GRAVITY_PARENT) {
@@ -1226,8 +1243,11 @@ class Item
                        return;
                }
 
+               $self_contact = Contact::selectFirst(['id'], ['uid' => $item['uid'], 'self' => true]);
+               $self = !empty($self_contact) ? $self_contact['id'] : 0;
+
                $cid = Contact::getIdForURL($author['url'], $item['uid']);
-               if (empty($cid) || !Contact::isSharing($cid, $item['uid'])) {
+               if (empty($cid) || (!Contact::isSharing($cid, $item['uid']) && ($cid != $self))) {
                        Logger::info('The resharer is not a following contact: quit', ['resharer' => $author['url'], 'uid' => $item['uid'], 'cid' => $cid]);
                        return;
                }
@@ -1398,7 +1418,7 @@ class Item
                $is_reshare = ($item['gravity'] == GRAVITY_ACTIVITY) && ($item['verb'] == Activity::ANNOUNCE);
 
                if ((($item['gravity'] == GRAVITY_PARENT) || $is_reshare) &&
-                       DI::pConfig()->get($uid, 'system', 'accept_only_sharer') &&
+                       DI::pConfig()->get($uid, 'system', 'accept_only_sharer') == self::COMPLETION_NONE &&
                        !Contact::isSharingByURL($item['author-link'], $uid) &&
                        !Contact::isSharingByURL($item['owner-link'], $uid)) {
                        Logger::info('Contact is not a follower, thread will not be stored', ['author' => $item['author-link'], 'uid' => $uid]);
@@ -1406,9 +1426,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 +1443,56 @@ 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 (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;
+               }
+
+               if (Post::exists(['uri-id' => $uriid, 'uid' => 0])) {
+                       return 0;
+               }
+
+               // When the post belongs to a a forum then all forum users are allowed to access it
+               foreach (Tag::getByURIId($uriid, [Tag::MENTION, Tag::EXCLUSIVE_MENTION]) as $tag) {
+                       if (DBA::exists('contact', ['uid' => $uid, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) {
+                               $target_uid = User::getIdForURL($tag['url']);
+                               if (!empty($target_uid)) {
+                                       return $target_uid;
+                               }
+                       }
+               }
+
+               return null;
+       }
+
        /**
         * Store a public item array for the given users
         *
@@ -1428,10 +1504,22 @@ class Item
        private static function storeForUser(array $item, int $uid)
        {
                if (Post::exists(['uri-id' => $item['uri-id'], 'uid' => $uid])) {
+                       if (!empty($item['event-id'])) {
+                               $post = Post::selectFirst(['event-id'], ['uri-id' => $item['uri-id'], 'uid' => $uid]);
+                               if (!empty($post['event-id'])) {
+                                       $event = DBA::selectFirst('event', ['edited', 'start', 'finish', 'summary', 'desc', 'location', 'nofinish', 'adjust'], ['id' => $item['event-id']]);
+                                       if (!empty($event)) {
+                                               // We aren't using "Event::store" here, since we don't want to trigger any further action
+                                               $ret = DBA::update('event', $event, ['id' => $post['event-id']]);
+                                               Logger::info('Event updated', ['uid' => $uid, 'source-event' => $item['event-id'], 'target-event' => $post['event-id'], 'ret' => $ret]);
+                                       }
+                               }
+                       }
                        Logger::info('Item already exists', ['uri-id' => $item['uri-id'], 'uid' => $uid]);
                        return 0;
                }
 
+               // Data from the "post-user" table
                unset($item['id']);
                unset($item['mention']);
                unset($item['starred']);
@@ -1440,11 +1528,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;
@@ -1682,7 +1773,10 @@ class Item
        }
 
        /**
-        * Creates an unique guid out of a given uri
+        * Creates an unique guid out of a given uri.
+        * This function is used for messages outside the fediverse (Connector posts, feeds, Mails, ...)
+        * Posts that are created on this system are using System::createUUID.
+        * Received ActivityPub posts are using Processor::getGUIDByURL.
         *
         * @param string $uri uri of an item entry
         * @param string $host hostname for the GUID prefix
@@ -1694,19 +1788,14 @@ class Item
                // We have to avoid that different routines could accidentally create the same value
                $parsed = parse_url($uri);
 
-               // We use a hash of the hostname as prefix for the guid
-               $guid_prefix = hash("crc32", $host);
-
                // Remove the scheme to make sure that "https" and "http" doesn't make a difference
                unset($parsed["scheme"]);
 
                // Glue it together to be able to make a hash from it
                $host_id = implode("/", $parsed);
 
-               // We could use any hash algorithm since it isn't a security issue
-               $host_hash = hash("ripemd128", $host_id);
-
-               return $guid_prefix.$host_hash;
+               // Use a mixture of several hashes to provide some GUID like experience
+               return hash("crc32", $host) . '-'. hash('joaat', $host_id) . '-'. hash('fnv164', $host_id);
        }
 
        /**
@@ -1862,117 +1951,69 @@ class Item
        {
                $mention = false;
 
-               $user = DBA::selectFirst('user', [], ['uid' => $uid]);
-               if (!DBA::isResult($user)) {
+               $owner = User::getOwnerDataById($uid);
+               if (!DBA::isResult($owner)) {
+                       Logger::warning('User not found, quitting here.', ['uid' => $uid]);
                        return false;
                }
 
-               $community_page = (($user['page-flags'] == User::PAGE_FLAGS_COMMUNITY) ? true : false);
-               $prvgroup = (($user['page-flags'] == User::PAGE_FLAGS_PRVGROUP) ? true : false);
-
-               $item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id]);
-               if (!DBA::isResult($item)) {
+               if ($owner['contact-type'] != User::ACCOUNT_TYPE_COMMUNITY) {
+                       Logger::debug('Owner is no community, quitting here.', ['uid' => $uid, 'id' => $item_id]);
                        return false;
                }
 
-               $link = Strings::normaliseLink(DI::baseUrl() . '/profile/' . $user['nickname']);
-
-               /*
-                * Diaspora uses their own hardwired link URL in @-tags
-                * instead of the one we supply with webfinger
-                */
-               $dlink = Strings::normaliseLink(DI::baseUrl() . '/u/' . $user['nickname']);
-
-               $cnt = preg_match_all('/[\@\!]\[url\=(.*?)\](.*?)\[\/url\]/ism', $item['body'], $matches, PREG_SET_ORDER);
-               if ($cnt) {
-                       foreach ($matches as $mtch) {
-                               if (Strings::compareLink($link, $mtch[1]) || Strings::compareLink($dlink, $mtch[1])) {
-                                       $mention = true;
-                                       Logger::notice('mention found', ['mention' => $mtch[2]]);
-                               }
-                       }
+               $item = Post::selectFirst(self::ITEM_FIELDLIST, ['id' => $item_id, 'gravity' => [GRAVITY_PARENT, GRAVITY_COMMENT], 'origin' => false]);
+               if (!DBA::isResult($item)) {
+                       Logger::debug('Post is an activity or origin or not found at all, quitting here.', ['id' => $item_id]);
+                       return false;
                }
 
-               if (!$mention) {
+               if ($item['gravity'] == GRAVITY_PARENT) {
                        $tags = Tag::getByURIId($item['uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
                        foreach ($tags as $tag) {
-                               if (Strings::compareLink($link, $tag['url']) || Strings::compareLink($dlink, $tag['url'])) {
+                               if (Strings::compareLink($owner['url'], $tag['url'])) {
                                        $mention = true;
-                                       DI::logger()->info('mention found in tag.', ['url' => $tag['url']]);
+                                       Logger::info('Mention found in tag.', ['url' => $tag['url'], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
                                }
                        }
-               }
 
-               if (!$mention) {
-                       if (($community_page || $prvgroup) &&
-                                 !$item['wall'] && !$item['origin'] && ($item['gravity'] == GRAVITY_PARENT)) {
-                               Logger::info('Delete private group/communiy top-level item without mention', ['id' => $item['id'], 'guid'=> $item['guid']]);
+                       if (!$mention) {
+                               Logger::info('Top-level post without mention is deleted.', ['uri' => $item['uri'], $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
                                Post\User::delete(['uri-id' => $item['uri-id'], 'uid' => $item['uid']]);
                                return true;
                        }
-                       return false;
-               }
-
-               $arr = ['item' => $item, 'user' => $user];
-
-               Hook::callAll('tagged', $arr);
-
-               if (!$community_page && !$prvgroup) {
-                       return false;
-               }
-
-               /*
-                * tgroup delivery - setup a second delivery chain
-                * prevent delivery looping - only proceed
-                * if the message originated elsewhere and is a top-level post
-                */
-               if ($item['wall'] || $item['origin'] || ($item['id'] != $item['parent'])) {
-                       return false;
-               }
 
-               self::performActivity($item['id'], 'announce', $uid);
+                       $arr = ['item' => $item, 'user' => $owner];
 
-               /**
-                * 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.
-                */
+                       Hook::callAll('tagged', $arr);
+               } else {
+                       $tags = Tag::getByURIId($item['parent-uri-id'], [Tag::MENTION, Tag::EXCLUSIVE_MENTION]);
+                       foreach ($tags as $tag) {
+                               if (Strings::compareLink($owner['url'], $tag['url'])) {
+                                       $mention = true;
+                                       Logger::info('Mention found in parent tag.', ['url' => $tag['url'], 'uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
+                               }
+                       }
 
-               // now change this copy of the post to a forum head message and deliver to all the tgroup members
-               $self = DBA::selectFirst('contact', ['id', 'name', 'url', 'thumb'], ['uid' => $uid, 'self' => true]);
-               if (!DBA::isResult($self)) {
-                       return false;
+                       if (!$mention) {
+                               Logger::debug('No mentions found in parent, quitting here.', ['id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
+                               return false;
+                       }
                }
 
-               $owner_id = Contact::getIdForURL($self['url']);
+               Logger::info('Community post will be distributed', ['uri' => $item['uri'], 'uid' => $uid, 'id' => $item_id, 'uri-id' => $item['uri-id'], 'guid' => $item['guid']]);
 
-               // also reset all the privacy bits to the forum default permissions
-               if ($user['allow_cid'] || $user['allow_gid'] || $user['deny_cid'] || $user['deny_gid']) {
-                       $private = self::PRIVATE;
-               } elseif (DI::pConfig()->get($user['uid'], 'system', 'unlisted')) {
-                       $private = self::UNLISTED;
+               if ($owner['page-flags'] == User::PAGE_FLAGS_PRVGROUP) {
+                       $allow_cid = '';
+                       $allow_gid = '<' . Group::FOLLOWERS . '>';
+                       $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(
-                               $user['uid'],
-                               $user['allow_cid'],
-                               $user['allow_gid'],
-                               $user['deny_cid'],
-                               $user['deny_gid']
-                       ));
-
-               $forum_mode = ($prvgroup ? 2 : 1);
-
-               $fields = ['wall' => true, 'origin' => true, 'forum_mode' => $forum_mode, 'contact-id' => $self['id'],
-                       'owner-id' => $owner_id, '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;
        }
 
@@ -2334,12 +2375,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
@@ -2347,7 +2393,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;
@@ -2508,10 +2554,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,
                ];
@@ -3145,6 +3191,12 @@ class Item
         */
        public static function getPlink($item)
        {
+               if (!empty($item['plink']) && Network::isValidHttpUrl($item['plink'])) {
+                       $plink = $item['plink'];
+               } elseif (!empty($item['uri']) && Network::isValidHttpUrl($item['uri']) && !Network::isLocalLink($item['uri'])) {
+                       $plink = $item['uri'];
+               }
+
                if (local_user()) {
                        $ret = [
                                'href' => "display/" . $item['guid'],
@@ -3153,14 +3205,14 @@ class Item
                                'orig_title' => DI::l10n()->t('View on separate page'),
                        ];
 
-                       if (!empty($item['plink'])) {
-                               $ret['href'] = DI::baseUrl()->remove($item['plink']);
+                       if (!empty($plink)) {
+                               $ret['href'] = DI::baseUrl()->remove($plink);
                                $ret['title'] = DI::l10n()->t('Link to source');
                        }
-               } elseif (!empty($item['plink']) && ($item['private'] != self::PRIVATE)) {
+               } elseif (!empty($plink) && ($item['private'] != self::PRIVATE)) {
                        $ret = [
-                               'href' => $item['plink'],
-                               'orig' => $item['plink'],
+                               'href' => $plink,
+                               'orig' => $plink,
                                'title' => DI::l10n()->t('Link to source'),
                                'orig_title' => DI::l10n()->t('Link to source'),
                        ];
@@ -3172,30 +3224,20 @@ class Item
        }
 
        /**
-        * Is the given item array a post that is sent as starting post to a forum?
+        * Does the given uri-id belongs to a post that is sent as starting post to a forum?
         *
-        * @param array $item
-        * @param array $owner
+        * @param int $uri_id
         *
         * @return boolean "true" when it is a forum post
         */
-       public static function isForumPost(array $item, array $owner = [])
+       public static function isForumPost(int $uri_id)
        {
-               if (empty($owner)) {
-                       $owner = User::getOwnerDataById($item['uid']);
-                       if (empty($owner)) {
-                               return false;
+               foreach (Tag::getByURIId($uri_id, [Tag::EXCLUSIVE_MENTION]) as $tag) {
+                       if (DBA::exists('contact', ['uid' => 0, 'nurl' => Strings::normaliseLink($tag['url']), 'contact-type' => Contact::TYPE_COMMUNITY])) {
+                               return true;
                        }
                }
-
-               if (($item['author-id'] == $item['owner-id']) ||
-                       ($owner['id'] == $item['contact-id']) ||
-                       ($item['uri-id'] != $item['parent-uri-id']) ||
-                       $item['origin']) {
-                       return false;
-               }
-
-               return Contact::isForum($item['contact-id']);
+               return false;
        }
 
        /**