]> git.mxchange.org Git - friendica.git/commitdiff
Fixes the score calculation concerning the relation-cid / cid interaction
authorMichael <heluecht@pirati.ca>
Thu, 7 Sep 2023 12:34:46 +0000 (12:34 +0000)
committerRoland Häder <roland@mxchange.org>
Wed, 17 Jan 2024 00:03:07 +0000 (01:03 +0100)
src/Module/Conversation/Channel.php
src/Worker/Cron.php

index 0baa40f7f1cfc2403e3e14218efe83cc703dd819..27f5f9cad41198fc28398e0942bc954863346503 100644 (file)
@@ -48,13 +48,39 @@ use Friendica\Network\HTTPException;
 use Friendica\Database\Database;
 use Friendica\Module\Response;
 use Friendica\Navigation\SystemMessages;
+use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
 use Psr\Log\LoggerInterface;
 
 class Channel extends Timeline
 {
-       /** @var TimelineFactory */
-       protected $timeline;
+       const WHATSHOT  = 'whatshot';
+       const FORYOU    = 'foryou';
+       const FOLLOWERS = 'followers';
+       const SHARERSOFSHARERS = 'sharersofsharers';
+       const IMAGE     = 'image';
+       const VIDEO     = 'video';
+       const AUDIO     = 'audio';
+       const LANGUAGE  = 'language';
+
+       protected static $content;
+       protected static $accountTypeString;
+       protected static $accountType;
+       protected static $itemsPerPage;
+       protected static $min_id;
+       protected static $max_id;
+       protected static $item_id;
+
+       /** @var UserSession */
+       protected $session;
+       /** @var ICanCache */
+       protected $cache;
+       /** @var IManageConfigValues The config */
+       protected $config;
+       /** @var SystemMessages */
+       protected $systemMessages;
+       /** @var App\Page */
+       protected $page;
        /** @var Conversation */
        protected $conversation;
        /** @var App\Page */
@@ -103,10 +129,83 @@ class Channel extends Timeline
                        $o .= Renderer::replaceMacros($tpl, ['$reload_uri' => $this->args->getQueryString()]);
                }
 
-               if (!$this->raw) {
-                       $tabs = $this->getTabArray($this->channel->getTimelines($this->session->getLocalUserId()), 'channel');
-                       $tabs = array_merge($tabs, $this->getTabArray($this->channelRepository->selectByUid($this->session->getLocalUserId()), 'channel'));
-                       $tabs = array_merge($tabs, $this->getTabArray($this->community->getTimelines(true), 'channel'));
+               if (empty($request['mode']) || ($request['mode'] != 'raw')) {
+                       $tabs = [];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('For you'),
+                               'url'       => 'channel/' . self::FORYOU,
+                               'sel'       => self::$content == self::FORYOU ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts from contacts you interact with and who interact with you'),
+                               'id'        => 'channel-foryou-tab',
+                               'accesskey' => 'y'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('What\'s Hot'),
+                               'url'       => 'channel/' . self::WHATSHOT,
+                               'sel'       => self::$content == self::WHATSHOT ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts with a lot of interactions'),
+                               'id'        => 'channel-whatshot-tab',
+                               'accesskey' => 'h'
+                       ];
+
+                       $language  = User::getLanguageCode($this->session->getLocalUserId());
+                       $languages = $this->l10n->getAvailableLanguages(true);
+
+                       $tabs[] = [
+                               'label'     => $languages[$language],
+                               'url'       => 'channel/' . self::LANGUAGE,
+                               'sel'       => self::$content == self::LANGUAGE ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts in %s', $languages[$language]),
+                               'id'        => 'channel-language-tab',
+                               'accesskey' => 'g'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('Followers'),
+                               'url'       => 'channel/' . self::FOLLOWERS,
+                               'sel'       => self::$content == self::FOLLOWERS ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts from your followers that you don\'t follow'),
+                               'id'        => 'channel-followers-tab',
+                               'accesskey' => 'f'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('Sharers of sharers'),
+                               'url'       => 'channel/' . self::SHARERSOFSHARERS,
+                               'sel'       => self::$content == self::SHARERSOFSHARERS ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts from accounts that are followed by accounts that you follow'),
+                               'id'        => 'channel-' . self::SHARERSOFSHARERS . '-tab',
+                               'accesskey' => 'f'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('Images'),
+                               'url'       => 'channel/' . self::IMAGE,
+                               'sel'       => self::$content == self::IMAGE ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts with images'),
+                               'id'        => 'channel-image-tab',
+                               'accesskey' => 'i'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('Audio'),
+                               'url'       => 'channel/' . self::AUDIO,
+                               'sel'       => self::$content == self::AUDIO ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts with audio'),
+                               'id'        => 'channel-audio-tab',
+                               'accesskey' => 'd'
+                       ];
+
+                       $tabs[] = [
+                               'label'     => $this->l10n->t('Videos'),
+                               'url'       => 'channel/' . self::VIDEO,
+                               'sel'       => self::$content == self::VIDEO ? 'active' : '',
+                               'title'     => $this->l10n->t('Posts with videos'),
+                               'id'        => 'channel-video-tab',
+                               'accesskey' => 'v'
+                       ];
 
                        $tab_tpl = Renderer::getMarkupTemplate('common_tabs.tpl');
                        $o .= Renderer::replaceMacros($tab_tpl, ['$tabs' => $tabs]);
@@ -173,11 +272,215 @@ class Channel extends Timeline
                        $this->selectedTab = ChannelEntity::FORYOU;
                }
 
+<<<<<<< HEAD
                if (!$this->channel->isTimeline($this->selectedTab) && !$this->userDefinedChannel->isTimeline($this->selectedTab, $this->session->getLocalUserId()) && !$this->community->isTimeline($this->selectedTab)) {
+=======
+               if (!in_array(self::$content, [self::WHATSHOT, self::FORYOU, self::FOLLOWERS, self::SHARERSOFSHARERS, self::IMAGE, self::VIDEO, self::AUDIO, self::LANGUAGE])) {
+>>>>>>> 0818b4d1b3 (Fixes the score calculation concerning the relation-cid / cid interaction)
                        throw new HTTPException\BadRequestException($this->l10n->t('Channel not available.'));
                }
 
-               $this->maxId = $request['last_created'] ?? $this->maxId;
-               $this->minId = $request['first_created'] ?? $this->minId;
+               if ($this->mode->isMobile()) {
+                       self::$itemsPerPage = $this->pConfig->get(
+                               $this->session->getLocalUserId(),
+                               'system',
+                               'itemspage_mobile_network',
+                               $this->config->get('system', 'itemspage_network_mobile')
+                       );
+               } else {
+                       self::$itemsPerPage = $this->pConfig->get(
+                               $this->session->getLocalUserId(),
+                               'system',
+                               'itemspage_network',
+                               $this->config->get('system', 'itemspage_network')
+                       );
+               }
+
+               if (!empty($request['item'])) {
+                       $item          = Post::selectFirst(['parent-uri-id'], ['id' => $request['item']]);
+                       self::$item_id = $item['parent-uri-id'] ?? 0;
+               } else {
+                       self::$item_id = 0;
+               }
+
+               self::$min_id = $request['min_id']       ?? null;
+               self::$max_id = $request['last_created'] ?? $request['max_id'] ?? null;
+       }
+
+       /**
+        * Computes the displayed items.
+        *
+        * Community pages have a restriction on how many successive posts by the same author can show on any given page,
+        * so we may have to retrieve more content beyond the first query
+        *
+        * @return array
+        * @throws \Exception
+        */
+       protected function getItems(array $request)
+       {
+               if (self::$content == ChannelEntity::WHATSHOT) {
+                       if (!is_null(self::$accountType)) {
+                               $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` = ?", $this->getMedianComments(4), $this->getMedianActivities(4), self::$accountType];
+                       } else {
+                               $condition = ["(`comments` >= ? OR `activities` >= ?) AND `contact-type` != ?", $this->getMedianComments(4), $this->getMedianActivities(4), Contact::TYPE_COMMUNITY];
+                       }
+               } elseif (self::$content == ChannelEntity::FORYOU) {
+                       $cid = Contact::getPublicIdByUserId($this->session->getLocalUserId());
+
+                       $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`)))",
+                               $cid, $this->getMedianRelationThreadScore($cid, 4), $this->getMedianComments(4), $this->getMedianActivities(4), $cid,
+                               $this->session->getLocalUserId(), Contact::FRIEND, Contact::SHARING
+                       ];
+               } elseif (self::$content == self::FOLLOWERS) {
+                       $condition = ["`owner-id` IN (SELECT `pid` FROM `account-user-view` WHERE `uid` = ? AND `rel` = ?)", $this->session->getLocalUserId(), Contact::FOLLOWER];
+               } elseif (self::$content == self::SHARERSOFSHARERS) {
+                       $cid = Contact::getPublicIdByUserId($this->session->getLocalUserId());
+
+                       $condition = [
+                               "`owner-id` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `last-interaction` > ?
+                               AND `relation-cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ? AND `relation-thread-score` >= ?)
+                               AND NOT `cid` IN (SELECT `cid` FROM `contact-relation` WHERE `follows` AND `relation-cid` = ?))",
+                               DateTimeFormat::utc('now - 90 day'), $cid, $this->getMedianRelationThreadScore($cid, 4), $cid
+                       ];
+               } elseif (self::$content == self::IMAGE) {
+                       $condition = ["`media-type` & ?", 1];
+               } elseif (self::$content == ChannelEntity::VIDEO) {
+                       $condition = ["`media-type` & ?", 2];
+               } elseif (self::$content == ChannelEntity::AUDIO) {
+                       $condition = ["`media-type` & ?", 4];
+               } elseif (self::$content == ChannelEntity::LANGUAGE) {
+                       $condition = ["JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?", $this->l10n->convertCodeForLanguageDetection(User::getLanguageCode($this->session->getLocalUserId()))];
+               }
+
+               if (self::$content != ChannelEntity::LANGUAGE) {
+                       $condition = $this->addLanguageCondition($condition);
+               }
+
+               $condition[0] .= " AND NOT EXISTS(SELECT `cid` FROM `user-contact` WHERE `uid` = ? AND `cid` = `post-engagement`.`owner-id` AND (`ignored` OR `blocked` OR `collapsed`))";
+               $condition[] = $this->session->getLocalUserId();
+
+               if ((self::$content != ChannelEntity::WHATSHOT) && !is_null(self::$accountType)) {
+                       $condition[0] .= " AND `contact-type` = ?";
+                       $condition[] = self::$accountType;
+               }
+
+               $params = ['order' => ['created' => true], 'limit' => self::$itemsPerPage];
+
+               if (!empty(self::$item_id)) {
+                       $condition[0] .= " AND `uri-id` = ?";
+                       $condition[] = self::$item_id;
+               } else {
+                       if (!empty($request['no_sharer'])) {
+                               $condition[0] .= " AND NOT `uri-id` IN (SELECT `uri-id` FROM `post-user` WHERE `post-user`.`uid` = ? AND `post-user`.`uri-id` = `post-engagement`.`uri-id`)";
+                               $condition[] = $this->session->getLocalUserId();
+                       }
+
+                       if (isset(self::$max_id)) {
+                               $condition[0] .= " AND `created` < ?";
+                               $condition[] = self::$max_id;
+                       }
+
+                       if (isset(self::$min_id)) {
+                               $condition[0] .= " AND `created` > ?";
+                               $condition[] = self::$min_id;
+
+                               // Previous page case: we want the items closest to min_id but for that we need to reverse the query order
+                               if (!isset(self::$max_id)) {
+                                       $params['order']['created'] = false;
+                               }
+                       }
+               }
+
+               $items = $this->database->selectToArray('post-engagement', ['uri-id', 'created'], $condition, $params);
+               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(self::$item_id) && isset(self::$min_id) && !isset(self::$max_id)) {
+                       $items = array_reverse($items);
+               }
+
+               Item::update(['unseen' => false], ['unseen' => true, 'uid' => $this->session->getLocalUserId(), 'uri-id' => array_column($items, 'uri-id')]);
+
+               return $items;
+       }
+
+       private function addLanguageCondition(array $condition): array
+       {
+               $conditions = [];
+               $languages  = $this->pConfig->get($this->session->getLocalUserId(), 'channel', 'languages', [User::getLanguageCode($this->session->getLocalUserId())]);
+               $languages  = $this->l10n->convertForLanguageDetection($languages);
+               foreach ($languages as $language) {
+                       $conditions[] = "JSON_EXTRACT(JSON_KEYS(language), '$[0]') = ?";
+                       $condition[]  = $language;
+               }
+               if (!empty($conditions)) {
+                       $condition[0] .= " AND (`language` IS NULL OR " . implode(' OR ', $conditions) . ")";
+               }
+               return $condition;
+       }
+
+       private function getMedianComments(int $divider): int
+       {
+               $cache_key = 'Channel:getMedianComments:' . $divider;
+               $comments  = $this->cache->get($cache_key);
+               if (!empty($comments)) {
+                       return $comments;
+               }
+
+               $limit    = $this->database->count('post-engagement', ["`contact-type` != ? AND `comments` > ?", Contact::TYPE_COMMUNITY, 0]) / $divider;
+               $post     = $this->database->selectToArray('post-engagement', ['comments'], ["`contact-type` != ?", Contact::TYPE_COMMUNITY], ['order' => ['comments' => true], 'limit' => [$limit, 1]]);
+               $comments = $post[0]['comments'] ?? 0;
+               if (empty($comments)) {
+                       return 0;
+               }
+
+               $this->cache->set($cache_key, $comments, Duration::HALF_HOUR);
+               $this->logger->debug('Calculated median comments', ['divider' => $divider, 'median' => $comments]);
+               return $comments;
+       }
+
+       private function getMedianActivities(int $divider): int
+       {
+               $cache_key  = 'Channel:getMedianActivities:' . $divider;
+               $activities = $this->cache->get($cache_key);
+               if (!empty($activities)) {
+                       return $activities;
+               }
+
+               $limit      = $this->database->count('post-engagement', ["`contact-type` != ? AND `activities` > ?", Contact::TYPE_COMMUNITY, 0]) / $divider;
+               $post       = $this->database->selectToArray('post-engagement', ['activities'], ["`contact-type` != ?", Contact::TYPE_COMMUNITY], ['order' => ['activities' => true], 'limit' => [$limit, 1]]);
+               $activities = $post[0]['activities'] ?? 0;
+               if (empty($activities)) {
+                       return 0;
+               }
+
+               $this->cache->set($cache_key, $activities, Duration::HALF_HOUR);
+               $this->logger->debug('Calculated median activities', ['divider' => $divider, 'median' => $activities]);
+               return $activities;
+       }
+
+       private function getMedianRelationThreadScore(int $cid, int $divider): int
+       {
+               $cache_key = 'Channel:getThreadScore:' . $cid . ':' . $divider;
+               $score     = $this->cache->get($cache_key);
+               if (!empty($score)) {
+                       return $score;
+               }
+
+               $limit    = $this->database->count('contact-relation', ["`relation-cid` = ? AND `relation-thread-score` > ?", $cid, 0]) / $divider;
+               $relation = $this->database->selectToArray('contact-relation', ['relation-thread-score'], ['relation-cid' => $cid], ['order' => ['relation-thread-score' => true], 'limit' => [$limit, 1]]);
+               $score    = $relation[0]['relation-thread-score'] ?? 0;
+               if (empty($score)) {
+                       return 0;
+               }
+
+               $this->cache->set($cache_key, $score, Duration::HALF_HOUR);
+               $this->logger->debug('Calculated median score', ['cid' => $cid, 'divider' => $divider, 'median' => $score]);
+               return $score;
        }
 }
index 06e59ec97c6f90f585fc5eefa762cd85638c1325..40cc5e7a442de2f1c203eb04d06d7ad0f7beb065 100644 (file)
@@ -147,7 +147,7 @@ class Cron
                        DBA::close($users);
 
                        // Update contact relations for our users
-                       $users = DBA::select('user', ['uid'], ["`verified` AND NOT `blocked` AND NOT `account_removed` AND NOT `account_expired` AND `uid` > ?", 0]);
+                       $users = DBA::select('user', ['uid'], ["NOT `account_expired` AND NOT `account_removed` AND `uid` > ?", 0]);
                        while ($user = DBA::fetch($users)) {
                                Worker::add(Worker::PRIORITY_LOW, 'ContactDiscoveryForUser', $user['uid']);
                        }