]> git.mxchange.org Git - friendica.git/blobdiff - src/Module/Conversation/Timeline.php
Use ISO-639-1 for the language detection
[friendica.git] / src / Module / Conversation / Timeline.php
index 8f97a38d8db5e5a9b291822da961a529d37e1e14..725634eb0920ffd68e5ab27ba6962aa8979af1ca 100644 (file)
@@ -25,7 +25,8 @@ use Friendica\App;
 use Friendica\App\Mode;
 use Friendica\BaseModule;
 use Friendica\Content\Conversation\Collection\Timelines;
-use Friendica\Content\Conversation\Entity\Timeline as TimelineEntity;
+use Friendica\Content\Conversation\Entity\Channel as ChannelEntity;
+use Friendica\Content\Conversation\Repository\UserDefinedChannel;
 use Friendica\Core\Cache\Capability\ICanCache;
 use Friendica\Core\Cache\Enum\Duration;
 use Friendica\Core\Config\Capability\IManageConfigValues;
@@ -49,9 +50,9 @@ class Timeline extends BaseModule
        /** @var string */
        protected $selectedTab;
        /** @var mixed */
-       protected $min_id;
+       protected $minId;
        /** @var mixed */
-       protected $max_id;
+       protected $maxId;
        /** @var string */
        protected $accountTypeString;
        /** @var int */
@@ -62,10 +63,14 @@ class Timeline extends BaseModule
        protected $itemsPerPage;
        /** @var bool */
        protected $noSharer;
+       /** @var bool */
+       protected $force;
+       /** @var bool */
+       protected $update;
 
        /** @var App\Mode $mode */
        protected $mode;
-       /** @var UserSession */
+       /** @var IHandleUserSessions */
        protected $session;
        /** @var Database */
        protected $database;
@@ -75,17 +80,20 @@ class Timeline extends BaseModule
        protected $config;
        /** @var ICanCache */
        protected $cache;
+       /** @var UserDefinedChannel */
+       protected $channelRepository;
 
-       public function __construct(Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
+       public function __construct(UserDefinedChannel $channel, Mode $mode, IHandleUserSessions $session, Database $database, IManagePersonalConfigValues $pConfig, IManageConfigValues $config, ICanCache $cache, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
        {
                parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
 
-               $this->mode     = $mode;
-               $this->session  = $session;
-               $this->database = $database;
-               $this->pConfig  = $pConfig;
-               $this->config   = $config;
-               $this->cache    = $cache;
+               $this->channelRepository = $channel;
+               $this->mode              = $mode;
+               $this->session           = $session;
+               $this->database          = $database;
+               $this->pConfig           = $pConfig;
+               $this->config            = $config;
+               $this->cache             = $cache;
        }
 
        /**
@@ -97,7 +105,7 @@ class Timeline extends BaseModule
        protected function parseRequest(array $request)
        {
                $this->logger->debug('Got request', $request);
-               $this->selectedTab = $this->parameters['content'] ?? '';
+               $this->selectedTab = $this->parameters['content'] ?? $request['channel'] ?? '';
 
                $this->accountTypeString = $request['accounttype'] ?? $this->parameters['accounttype'] ?? '';
                $this->accountType       = User::getAccountTypeByString($this->accountTypeString);
@@ -119,16 +127,18 @@ class Timeline extends BaseModule
                }
 
                if (!empty($request['item'])) {
-                       $item = Post::selectFirst(['parent', 'parent-uri-id'], ['id' => $request['item']]);
+                       $item            = Post::selectFirst(['parent', 'parent-uri-id'], ['id' => $request['item']]);
                        $this->itemUriId = $item['parent-uri-id'] ?? 0;
                } else {
                        $this->itemUriId = 0;
                }
 
-               $this->min_id = $request['min_id'] ?? null;
-               $this->max_id = $request['max_id'] ?? null;
+               $this->minId = $request['min_id'] ?? null;
+               $this->maxId = $request['max_id'] ?? null;
 
                $this->noSharer = !empty($request['no_sharer']);
+               $this->force    = !empty($request['force']) && !empty($request['item']);
+               $this->update   = !empty($request['force']) && !empty($request['first_received']) && !empty($request['first_created']) && !empty($request['first_uriid']) && !empty($request['first_commented']);
        }
 
        protected function getNoSharerWidget(string $base): string
@@ -139,11 +149,11 @@ class Timeline extends BaseModule
                }
                $query_parameters = [];
 
-               if (!empty($this->min_id)) {
-                       $query_parameters['min_id'] = $this->min_id;
+               if (!empty($this->minId)) {
+                       $query_parameters['min_id'] = $this->minId;
                }
-               if (!empty($this->max_id)) {
-                       $query_parameters['max_id'] = $this->max_id;
+               if (!empty($this->maxId)) {
+                       $query_parameters['max_id'] = $this->maxId;
                }
 
                $path_all       = $path . (!empty($query_parameters) ? '?' . http_build_query($query_parameters) : '');
@@ -159,14 +169,20 @@ class Timeline extends BaseModule
                ]);
        }
 
-       protected function getTabArray(Timelines $timelines, string $prefix): array
+       protected function getTabArray(Timelines $timelines, string $prefix, string $parameter = ''): array
        {
                $tabs = [];
 
                foreach ($timelines as $tab) {
-                       $tabs[] = [
+                       if (is_null($tab->path) && !empty($parameter)) {
+                               $path = $prefix . '?' . http_build_query([$parameter => $tab->code]);
+                       } else {
+                               $path = $tab->path ?? $prefix . '/' . $tab->code;
+                       }
+                       $tabs[$tab->code] = [
+                               'code'      => $tab->code,
                                'label'     => $tab->label,
-                               'url'       => $tab->path ?? $prefix . '/' . $tab->code,
+                               'url'       => $path,
                                'sel'       => $this->selectedTab == $tab->code ? 'active' : '',
                                'title'     => $tab->description,
                                'id'        => $prefix . '-' . $tab->code . '-tab',
@@ -183,28 +199,95 @@ class Timeline extends BaseModule
         * @throws \Exception
         */
        protected function getChannelItems()
+       {
+               $items = $this->getRawChannelItems();
+
+               $contacts = $this->database->selectToArray('user-contact', ['cid'], ['channel-frequency' => Contact\User::FREQUENCY_REDUCED, 'cid' => array_column($items, 'owner-id')]);
+               $reduced  = array_column($contacts, 'cid');
+
+               $maxpostperauthor = $this->config->get('channel', 'max_posts_per_author');
+
+               if ($maxpostperauthor != 0) {
+                       $count          = 1;
+                       $owner_posts    = [];
+                       $selected_items = [];
+
+                       while (count($selected_items) < $this->itemsPerPage && ++$count < 50 && count($items) > 0) {
+                               $maxposts = round((count($items) / $this->itemsPerPage) * $maxpostperauthor);
+                               $minId = $items[array_key_first($items)]['created'];
+                               $maxId = $items[array_key_last($items)]['created'];
+
+                               foreach ($items as $item) {
+                                       if (!in_array($item['owner-id'], $reduced)) {
+                                               continue;
+                                       }
+                                       $owner_posts[$item['owner-id']][$item['uri-id']] = (($item['comments'] * 100) + $item['activities']);
+                               }
+                               foreach ($owner_posts as $posts) {
+                                       if (count($posts) <= $maxposts) {
+                                               continue;
+                                       }
+                                       asort($posts);
+                                       while (count($posts) > $maxposts) {
+                                               $uri_id = array_key_first($posts);
+                                               unset($posts[$uri_id]);
+                                               unset($items[$uri_id]);
+                                       }
+                               }
+                               $selected_items = array_merge($selected_items, $items);
+
+                               // If we're looking at a "previous page", the lookup continues forward in time because the list is
+                               // sorted in chronologically decreasing order
+                               if (!empty($this->minId)) {
+                                       $this->minId = $minId;
+                               } else {
+                                       // In any other case, the lookup continues backwards in time
+                                       $this->maxId = $maxId;
+                               }
+
+                               if (count($selected_items) < $this->itemsPerPage) {
+                                       $items = $this->getRawChannelItems();
+                               }
+                       }
+               } else {
+                       $selected_items = $items;
+               }
+
+               $condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($selected_items, 'uri-id')];
+               $this->setItemsSeenByCondition($condition);
+
+               return $selected_items;
+       }
+
+       /**
+        * Database query for the channel page
+        *
+        * @return array
+        * @throws \Exception
+        */
+       private function getRawChannelItems()
        {
                $uid = $this->session->getLocalUserId();
 
-               if ($this->selectedTab == TimelineEntity::WHATSHOT) {
+               if ($this->selectedTab == ChannelEntity::WHATSHOT) {
                        if (!is_null($this->accountType)) {
-                               $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` = ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $this->accountType];
+                               $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` = ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $this->accountType];
                        } else {
-                               $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` != ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), Contact::TYPE_COMMUNITY];
+                               $condition = ["(`comments` > ? OR `activities` > ?) AND `contact-type` != ?", $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), Contact::TYPE_COMMUNITY];
                        }
-               } elseif ($this->selectedTab == TimelineEntity::FORYOU) {
+               } elseif ($this->selectedTab == ChannelEntity::FORYOU) {
                        $cid = Contact::getPublicIdByUserId($uid);
 
                        $condition = [
                                "(`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `relation-cid` = ? AND `relation-thread-score` > ?) OR
                                ((`comments` >= ? OR `activities` >= ?) AND `owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?)) OR
-                               (`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?) AND `notify_new_posts`)))",
+                               (`owner-id` IN (SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND (`notify_new_posts` OR `channel-frequency` = ?))))",
                                $cid, $this->getMedianRelationThreadScore($cid, 4), $this->getMedianComments($uid, 4), $this->getMedianActivities($uid, 4), $cid,
-                               $uid, Contact::FRIEND, Contact::SHARING
+                               $uid, Contact\User::FREQUENCY_ALWAYS
                        ];
-               } elseif ($this->selectedTab == TimelineEntity::FOLLOWERS) {
+               } elseif ($this->selectedTab == ChannelEntity::FOLLOWERS) {
                        $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER];
-               } elseif ($this->selectedTab == TimelineEntity::SHARERSOFSHARERS) {
+               } elseif ($this->selectedTab == ChannelEntity::SHARERSOFSHARERS) {
                        $cid = Contact::getPublicIdByUserId($uid);
 
                        // @todo Suggest posts from contacts that are followed most by our followers
@@ -214,23 +297,27 @@ class Timeline extends BaseModule
                                AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))",
                                DateTimeFormat::utc('now - ' . $this->config->get('channel', 'sharer_interaction_days') . ' day'), $cid, $this->getMedianRelationThreadScore($cid, 4), $cid
                        ];
-               } elseif ($this->selectedTab == TimelineEntity::IMAGE) {
+               } elseif ($this->selectedTab == ChannelEntity::IMAGE) {
                        $condition = ["`media-type` & ?", 1];
-               } elseif ($this->selectedTab == TimelineEntity::VIDEO) {
+               } elseif ($this->selectedTab == ChannelEntity::VIDEO) {
                        $condition = ["`media-type` & ?", 2];
-               } elseif ($this->selectedTab == TimelineEntity::AUDIO) {
+               } elseif ($this->selectedTab == ChannelEntity::AUDIO) {
                        $condition = ["`media-type` & ?", 4];
-               } elseif ($this->selectedTab == TimelineEntity::LANGUAGE) {
-                       $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", $this->l10n->convertCodeForLanguageDetection(User::getLanguageCode($uid))];
+               } elseif ($this->selectedTab == ChannelEntity::LANGUAGE) {
+                       $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", User::getLanguageCode($uid)];
+               } elseif (is_numeric($this->selectedTab)) {
+                       $condition = $this->getUserChannelConditions($this->selectedTab, $this->session->getLocalUserId());
                }
 
-               if ($this->selectedTab != TimelineEntity::LANGUAGE) {
+               if ($this->selectedTab != ChannelEntity::LANGUAGE) {
                        $condition = $this->addLanguageCondition($uid, $condition);
                }
 
-               $condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))", $uid]);
+               $condition = DBA::mergeConditions($condition, ["(NOT `restricted` OR EXISTS(SELECT `id` FROM `post-user` WHERE `uid` = ? AND `uri-id` = `post-engagement`.`uri-id`))", $uid]);
+
+               $condition = DBA::mergeConditions($condition, ["NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed` OR `is-blocked` OR `channel-frequency` = ?))", $uid, Contact\User::FREQUENCY_NEVER]);
 
-               if (($this->selectedTab != TimelineEntity::WHATSHOT) && !is_null($this->accountType)) {
+               if (($this->selectedTab != ChannelEntity::WHATSHOT) && !is_null($this->accountType)) {
                        $condition = DBA::mergeConditions($condition, ['contact-type' => $this->accountType]);
                }
 
@@ -243,40 +330,97 @@ class Timeline extends BaseModule
                                $condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)", $this->session->getLocalUserId()]);
                        }
 
-                       if (isset($this->max_id)) {
-                               $condition = DBA::mergeConditions($condition, ["`created` < ?", $this->max_id]);
+                       if (isset($this->maxId)) {
+                               $condition = DBA::mergeConditions($condition, ["`created` < ?", $this->maxId]);
                        }
 
-                       if (isset($this->min_id)) {
-                               $condition = DBA::mergeConditions($condition, ["`created` > ?", $this->min_id]);
+                       if (isset($this->minId)) {
+                               $condition = DBA::mergeConditions($condition, ["`created` > ?", $this->minId]);
 
                                // Previous page case: we want the items closest to min_id but for that we need to reverse the query order
-                               if (!isset($this->max_id)) {
+                               if (!isset($this->maxId)) {
                                        $params['order']['created'] = false;
                                }
                        }
                }
 
-               $items = $this->database->selectToArray('post-engagement', ['uri-id', 'created'], $condition, $params);
+               $items = [];
+               $result = $this->database->select('post-engagement', ['uri-id', 'created', 'owner-id', 'comments', 'activities'], $condition, $params);
+               if ($this->database->errorNo()) {
+                       throw new \Exception($this->database->errorMessage(), $this->database->errorNo());
+               }
+
+               while ($item = $this->database->fetch($result)) {
+                       $items[$item['uri-id']] = $item;
+               }
+               $this->database->close($result);
+
                if (empty($items)) {
                        return [];
                }
 
                // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order
-               if (empty($this->itemUriId) && isset($this->min_id) && !isset($this->max_id)) {
-                       $items = array_reverse($items);
+               if (empty($this->itemUriId) && isset($this->minId) && !isset($this->maxId)) {
+                       $items = array_reverse($items, true);
                }
 
-               Item::update(['unseen' => false], ['unseen' => true, 'uid' => $uid, 'uri-id' => array_column($items, 'uri-id')]);
+               $condition = ['unseen' => true, 'uid' => $uid, 'parent-uri-id' => array_column($items, 'uri-id')];
+               $this->setItemsSeenByCondition($condition);
 
                return $items;
        }
 
+       private function getUserChannelConditions(int $id, int $uid): array
+       {
+               $channel = $this->channelRepository->selectById($id, $uid);
+               if (empty($channel)) {
+                       return [];
+               }
+
+               $condition = [];
+
+               if (!empty($channel->circle)) {
+                       if ($channel->circle == -1) {
+                               $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` IN (?, ?))", $uid, Contact::SHARING, Contact::FRIEND];
+                       } elseif ($channel->circle == -2) {
+                               $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $uid, Contact::FOLLOWER];
+                       } elseif ($channel->circle > 0) {
+                               $condition = DBA::mergeConditions($condition, ["`owner-id` IN (SELECT `pid` FROM `group_member` INNER JOIN `account-user-view` ON `group_member`.`contact-id` = `account-user-view`.`id` WHERE `gid` = ? AND `account-user-view`.`uid` = ?)", $channel->circle, $uid]);
+                       }
+               }
+
+               if (!empty($channel->fullTextSearch)) {
+                       $search = $channel->fullTextSearch;
+                       foreach (['from', 'to', 'group', 'tag', 'network', 'visibility'] as $keyword) {
+                               $search = preg_replace('~(' . $keyword . ':.[\w@\.-]+)~', '"$1"', $search);
+                       }
+                       $condition = DBA::mergeConditions($condition, ["MATCH (`searchtext`) AGAINST (? IN BOOLEAN MODE)", $search]);
+               }
+
+               if (!empty($channel->includeTags)) {
+                       $search       = explode(',', mb_strtolower($channel->includeTags));
+                       $placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
+                       $condition    = DBA::mergeConditions($condition, array_merge(["`uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
+               }
+
+               if (!empty($channel->excludeTags)) {
+                       $search       = explode(',', mb_strtolower($channel->excludeTags));
+                       $placeholders = substr(str_repeat("?, ", count($search)), 0, -2);
+                       $condition    = DBA::mergeConditions($condition, array_merge(["NOT `uri-id` IN (SELECT `uri-id` FROM `post-tag` INNER JOIN `tag` ON `tag`.`id` = `post-tag`.`tid` WHERE `post-tag`.`type` = 1 AND `name` IN (" . $placeholders . "))"], $search));
+               }
+
+               if (!empty($channel->mediaType)) {
+                       $condition = DBA::mergeConditions($condition, ["`media-type` & ?", $channel->mediaType]);
+               }
+
+               // For "addLanguageCondition" to work, the condition must not be empty
+               return $condition ?: ["true"];
+       }
+
        private function addLanguageCondition(int $uid, array $condition): array
        {
                $conditions = [];
                $languages  = $this->pConfig->get($uid, 'channel', 'languages', [User::getLanguageCode($uid)]);
-               $languages  = $this->l10n->convertForLanguageDetection($languages);
                foreach ($languages as $language) {
                        $conditions[] = "JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?";
                        $condition[]  = $language;
@@ -296,7 +440,7 @@ class Timeline extends BaseModule
                        return $comments;
                }
 
-               $condition = ["`contact-type` != ? AND `comments` > ?", Contact::TYPE_COMMUNITY, 0];
+               $condition = ["`contact-type` != ? AND `comments` > ? AND NOT `restricted`", Contact::TYPE_COMMUNITY, 0];
                $condition = $this->addLanguageCondition($uid, $condition);
 
                $limit    = $this->database->count('post-engagement', $condition) / $divider;
@@ -320,7 +464,7 @@ class Timeline extends BaseModule
                        return $activities;
                }
 
-               $condition = ["`contact-type` != ? AND `activities` > ?", Contact::TYPE_COMMUNITY, 0];
+               $condition = ["`contact-type` != ? AND `activities` > ? AND NOT `restricted`", Contact::TYPE_COMMUNITY, 0];
                $condition = $this->addLanguageCondition($uid, $condition);
 
                $limit      = $this->database->count('post-engagement', $condition) / $divider;
@@ -393,11 +537,11 @@ class Timeline extends BaseModule
 
                                // If we're looking at a "previous page", the lookup continues forward in time because the list is
                                // sorted in chronologically decreasing order
-                               if (isset($this->min_id)) {
-                                       $this->min_id = $items[0]['commented'];
+                               if (isset($this->minId)) {
+                                       $this->minId = $items[0]['received'];
                                } else {
                                        // In any other case, the lookup continues backwards in time
-                                       $this->max_id = $items[count($items) - 1]['commented'];
+                                       $this->maxId = $items[count($items) - 1]['received'];
                                }
 
                                $items = $this->selectItems();
@@ -406,6 +550,9 @@ class Timeline extends BaseModule
                        $selected_items = $items;
                }
 
+               $condition = ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'parent-uri-id' => array_column($selected_items, 'uri-id')];
+               $this->setItemsSeenByCondition($condition);
+
                return $selected_items;
        }
 
@@ -419,22 +566,18 @@ class Timeline extends BaseModule
        private function selectItems()
        {
                if ($this->selectedTab == 'local') {
-                       if (!is_null($this->accountType)) {
-                               $condition = ["`wall` AND `origin` AND `private` = ? AND `owner-contact-type` = ?", Item::PUBLIC, $this->accountType];
-                       } else {
-                               $condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC];
-                       }
+                       $condition = ["`wall` AND `origin` AND `private` = ?", Item::PUBLIC];
                } elseif ($this->selectedTab == 'global') {
-                       if (!is_null($this->accountType)) {
-                               $condition = ["`uid` = ? AND `private` = ? AND `owner-contact-type` = ?", 0, Item::PUBLIC, $this->accountType];
-                       } else {
-                               $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC];
-                       }
+                       $condition = ["`uid` = ? AND `private` = ?", 0, Item::PUBLIC];
                } else {
                        return [];
                }
 
-               $params = ['order' => ['commented' => true], 'limit' => $this->itemsPerPage];
+               if (!is_null($this->accountType)) {
+                       $condition = DBA::mergeConditions($condition, ['owner-contact-type' => $this->accountType]);
+               }
+
+               $params = ['order' => ['received' => true], 'limit' => $this->itemsPerPage];
 
                if (!empty($this->itemUriId)) {
                        $condition = DBA::mergeConditions($condition, ['uri-id' => $this->itemUriId]);
@@ -443,21 +586,21 @@ class Timeline extends BaseModule
                                $condition = DBA::mergeConditions($condition, ["NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-thread-user-view`.`uri-id`)", $this->session->getLocalUserId()]);
                        }
 
-                       if (isset($this->max_id)) {
-                               $condition = DBA::mergeConditions($condition, ["`commented` < ?", $this->max_id]);
+                       if (isset($this->maxId)) {
+                               $condition = DBA::mergeConditions($condition, ["`received` < ?", $this->maxId]);
                        }
 
-                       if (isset($this->min_id)) {
-                               $condition = DBA::mergeConditions($condition, ["`commented` > ?", $this->min_id]);
+                       if (isset($this->minId)) {
+                               $condition = DBA::mergeConditions($condition, ["`received` > ?", $this->minId]);
 
                                // Previous page case: we want the items closest to min_id but for that we need to reverse the query order
-                               if (!isset($this->max_id)) {
-                                       $params['order']['commented'] = false;
+                               if (!isset($this->maxId)) {
+                                       $params['order']['received'] = false;
                                }
                        }
                }
 
-               $r = Post::selectThreadForUser($this->session->getLocalUserId() ?: 0, ['uri-id', 'commented', 'author-link'], $condition, $params);
+               $r = Post::selectThreadForUser($this->session->getLocalUserId() ?: 0, ['uri-id', 'received', 'author-link'], $condition, $params);
 
                $items = Post::toArray($r);
                if (empty($items)) {
@@ -465,10 +608,30 @@ class Timeline extends BaseModule
                }
 
                // Previous page case: once we get the relevant items closest to min_id, we need to restore the expected display order
-               if (empty($this->itemUriId) && isset($this->min_id) && !isset($this->max_id)) {
+               if (empty($this->itemUriId) && isset($this->minId) && !isset($this->maxId)) {
                        $items = array_reverse($items);
                }
 
                return $items;
        }
+
+       /**
+        * Sets items as seen
+        *
+        * @param array $condition The array with the SQL condition
+        * @throws \Friendica\Network\HTTPException\InternalServerErrorException
+        */
+       protected function setItemsSeenByCondition(array $condition)
+       {
+               if (empty($condition)) {
+                       return;
+               }
+
+               $unseen = Post::exists($condition);
+
+               if ($unseen) {
+                       /// @todo handle huge "unseen" updates in the background to avoid timeout errors
+                       Item::update(['unseen' => false], $condition);
+               }
+       }
 }