]> git.mxchange.org Git - friendica.git/blobdiff - src/Content/Conversation.php
Merge pull request #12986 from annando/exception
[friendica.git] / src / Content / Conversation.php
index f49fab51ccda86b08ceaf44548c9822bed47d308..458a0a690f19fe97ed5a481723cc4d9b2340e0fd 100644 (file)
@@ -1,6 +1,6 @@
 <?php
 /**
- * @copyright Copyright (C) 2010-2022, the Friendica project
+ * @copyright Copyright (C) 2010-2023, the Friendica project
  *
  * @license GNU AGPL version 3 or any later version
  *
@@ -32,29 +32,38 @@ use Friendica\Core\L10n;
 use Friendica\Core\PConfig\Capability\IManagePersonalConfigValues;
 use Friendica\Core\Protocol;
 use Friendica\Core\Renderer;
-use Friendica\Core\Session;
+use Friendica\Core\Session\Capability\IHandleUserSessions;
 use Friendica\Core\Theme;
 use Friendica\Database\DBA;
 use Friendica\Model\Contact;
 use Friendica\Model\Item as ItemModel;
-use Friendica\Model\Photo;
 use Friendica\Model\Post;
 use Friendica\Model\Tag;
 use Friendica\Model\User;
 use Friendica\Model\Verb;
+use Friendica\Network\HTTPException\InternalServerErrorException;
 use Friendica\Object\Post as PostObject;
 use Friendica\Object\Thread;
 use Friendica\Protocol\Activity;
 use Friendica\Util\Crypto;
 use Friendica\Util\DateTimeFormat;
 use Friendica\Util\Profiler;
-use Friendica\Util\Proxy;
 use Friendica\Util\Strings;
 use Friendica\Util\Temporal;
 use Psr\Log\LoggerInterface;
 
 class Conversation
 {
+       const MODE_COMMUNITY     = 'community';
+       const MODE_CONTACTS      = 'contacts';
+       const MODE_CONTACT_POSTS = 'contact-posts';
+       const MODE_DISPLAY       = 'display';
+       const MODE_FILED         = 'filed';
+       const MODE_NETWORK       = 'network';
+       const MODE_NOTES         = 'notes';
+       const MODE_SEARCH        = 'search';
+       const MODE_PROFILE       = 'profile';
+
        /** @var Activity */
        private $activity;
        /** @var L10n */
@@ -79,8 +88,10 @@ class Conversation
        private $page;
        /** @var App\Mode */
        private $mode;
+       /** @var IHandleUserSessions */
+       private $session;
 
-       public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app)
+       public function __construct(LoggerInterface $logger, Profiler $profiler, Activity $activity, L10n $l10n, Item $item, Arguments $args, BaseURL $baseURL, IManageConfigValues $config, IManagePersonalConfigValues $pConfig, App\Page $page, App\Mode $mode, App $app, IHandleUserSessions $session)
        {
                $this->activity = $activity;
                $this->item     = $item;
@@ -94,6 +105,7 @@ class Conversation
                $this->pConfig  = $pConfig;
                $this->page     = $page;
                $this->app      = $app;
+               $this->session  = $session;
        }
 
        /**
@@ -137,7 +149,7 @@ class Conversation
                                        return;
                        }
 
-                       if (!empty($activity['verb']) && $this->activity->match($activity['verb'], $verb) && ($activity['gravity'] != GRAVITY_PARENT)) {
+                       if (!empty($activity['verb']) && $this->activity->match($activity['verb'], $verb) && ($activity['gravity'] != ItemModel::GRAVITY_PARENT)) {
                                $author = [
                                        'uid'     => 0,
                                        'id'      => $activity['author-id'],
@@ -145,7 +157,7 @@ class Conversation
                                        'url'     => $activity['author-link']
                                ];
                                $url = Contact::magicLinkByContact($author);
-                               if (strpos($url, 'redir/') === 0) {
+                               if (strpos($url, 'contact/redir/') === 0) {
                                        $sparkle = ' class="sparkle" ';
                                }
 
@@ -156,7 +168,7 @@ class Conversation
                                }
 
                                // Skip when the causer of the parent is the same as the author of the announce
-                               if (($verb == Activity::ANNOUNCE) && !empty($thread_parent['causer-id'] && ($thread_parent['causer-id'] == $activity['author-id']))) {
+                               if (($verb == Activity::ANNOUNCE) && !empty($thread_parent['causer-id']) && ($thread_parent['causer-id'] == $activity['author-id'])) {
                                        continue;
                                }
 
@@ -170,7 +182,7 @@ class Conversation
                                        continue;
                                }
 
-                               if (public_contact() == $activity['author-id']) {
+                               if ($this->session->getPublicContactId() == $activity['author-id']) {
                                        $conv_responses[$mode][$activity['thr-parent-id']]['self'] = 1;
                                }
 
@@ -182,6 +194,52 @@ class Conversation
                }
        }
 
+       /**
+        * Returns the liker phrase based on a list of likers
+        *
+        * @param string $verb   the activity verb
+        * @param array  $likers a list of likers
+        *
+        * @return string the liker phrase
+        *
+        * @throws InternalServerErrorException in case either the verb is invalid or the list of likers is empty
+        */
+       private function getLikerPhrase(string $verb, array $likers): string
+       {
+               $total = count($likers);
+
+               if ($total === 0) {
+                       throw new InternalServerErrorException(sprintf('There has to be at least one Liker for verb "%s"', $verb));
+               } else if ($total === 1) {
+                       $likerString = $likers[0];
+               } else {
+                       if ($total < $this->config->get('system', 'max_likers')) {
+                               $likerString = implode(', ', array_slice($likers, 0, -1));
+                               $likerString .= ' ' . $this->l10n->t('and') . ' ' . $likers[count($likers) - 1];
+                       } else {
+                               $likerString = implode(', ', array_slice($likers, 0, $this->config->get('system', 'max_likers') - 1));
+                               $likerString .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
+                       }
+               }
+
+               switch ($verb) {
+                       case 'like':
+                               return $this->l10n->tt('%2$s likes this.', '%2$s like this.', $total, $likerString);
+                       case 'dislike':
+                               return $this->l10n->tt('%2$s doesn\'t like this.', '%2$s don\'t like this.', $total, $likerString);
+                       case 'attendyes':
+                               return $this->l10n->tt('%2$s attends.', '%2$s attend.', $total, $likerString);
+                       case 'attendno':
+                               return $this->l10n->tt('%2$s doesn\'t attend.', '%2$s don\'t attend.', $total, $likerString);
+                       case 'attendmaybe':
+                               return $this->l10n->tt('%2$s attends maybe.', '%2$s attend maybe.', $total, $likerString);
+                       case 'announce':
+                               return $this->l10n->tt('%2$s reshared this.', '%2$s reshared this.', $total, $likerString);
+                       default:
+                               throw new InternalServerErrorException(sprintf('Unknown verb "%s"', $verb));
+               }
+       }
+
        /**
         * Format the activity text for an item/photo/video
         *
@@ -191,93 +249,54 @@ class Conversation
         * @return string formatted text
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public function formatActivity(array $links, $verb, $id)
+       public function formatActivity(array $links, string $verb, int $id): string
        {
                $this->profiler->startRecording('rendering');
-               $o        = '';
                $expanded = '';
-               $phrase   = '';
 
-               $total = count($links);
-               if ($total == 1) {
-                       $likers = $links[0];
+               $phrase = $this->getLikerPhrase($verb, $links);
+               $total  = count($links);
 
-                       // Phrase if there is only one liker. In other cases it will be uses for the expanded
-                       // list which show all likers
-                       switch ($verb) {
-                               case 'like':
-                                       $phrase = $this->l10n->t('%s likes this.', $likers);
-                                       break;
-                               case 'dislike':
-                                       $phrase = $this->l10n->t('%s doesn\'t like this.', $likers);
-                                       break;
-                               case 'attendyes':
-                                       $phrase = $this->l10n->t('%s attends.', $likers);
-                                       break;
-                               case 'attendno':
-                                       $phrase = $this->l10n->t('%s doesn\'t attend.', $likers);
-                                       break;
-                               case 'attendmaybe':
-                                       $phrase = $this->l10n->t('%s attends maybe.', $likers);
-                                       break;
-                               case 'announce':
-                                       $phrase = $this->l10n->t('%s reshared this.', $likers);
-                                       break;
-                       }
-               } elseif ($total > 1) {
-                       if ($total < $this->config->get('system', 'max_likers')) {
-                               $likers = implode(', ', array_slice($links, 0, -1));
-                               $likers .= ' ' . $this->l10n->t('and') . ' ' . $links[count($links) - 1];
-                       } else {
-                               $likers = implode(', ', array_slice($links, 0, $this->config->get('system', 'max_likers') - 1));
-                               $likers .= ' ' . $this->l10n->t('and %d other people', $total - $this->config->get('system', 'max_likers'));
-                       }
-
-                       $spanatts = "class=\"fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+               if ($total > 1) {
+                       $spanatts  = "class=\"btn btn-link fakelink\" onclick=\"openClose('{$verb}list-$id');\"";
+                       $explikers = $phrase;
 
-                       $explikers = '';
                        switch ($verb) {
                                case 'like':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> like this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s like this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> likes this', '<button type="button" %2$s>%1$d people</button> like this', $total, $spanatts);
                                        break;
                                case 'dislike':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> don\'t like this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s don\'t like this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t like this', '<button type="button" %2$s>%1$d peiple</button> don\'t like this', $total, $spanatts);
                                        break;
                                case 'attendyes':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> attend', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s attend.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends', '<button type="button" %2$s>%1$d people</button> attend', $total, $spanatts);
                                        break;
                                case 'attendno':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> don\'t attend', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s don\'t attend.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> doesn\'t attend','<button type="button" %2$s>%1$d people</button> don\'t attend', $total, $spanatts);
                                        break;
                                case 'attendmaybe':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> attend maybe', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s attend maybe.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> attends maybe', '<button type="button" %2$s>%1$d people</button> attend maybe', $total, $spanatts);
                                        break;
                                case 'announce':
-                                       $phrase    = $this->l10n->t('<span  %1$s>%2$d people</span> reshared this', $spanatts, $total);
-                                       $explikers = $this->l10n->t('%s reshared this.', $likers);
+                                       $phrase = $this->l10n->tt('<button type="button" %2$s>%1$d person</button> reshared this', '<button type="button" %2$s>%1$d people</button> reshared this', $total, $spanatts);
                                        break;
                        }
 
-                       $expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . EOL . '</p>';
+                       $expanded .= "\t" . '<p class="wall-item-' . $verb . '-expanded" id="' . $verb . 'list-' . $id . '" style="display: none;" >' . $explikers . '</p>';
                }
 
-               $o .= Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
+               $output = Renderer::replaceMacros(Renderer::getMarkupTemplate('voting_fakelink.tpl'), [
                        '$phrase' => $phrase,
                        '$type'   => $verb,
                        '$id'     => $id
                ]);
-               $o .= $expanded;
+               $output .= $expanded;
 
                $this->profiler->stopRecording();
-               return $o;
+               return $output;
        }
 
-       public function statusEditor(array $x = [], $notes_cid = 0, $popup = false)
+       public function statusEditor(array $x = [], int $notes_cid = 0, bool $popup = false): string
        {
                $user = User::getById($this->app->getLoggedInUserId(), ['uid', 'nickname', 'allow_location', 'default-location']);
                if (empty($user['uid'])) {
@@ -295,7 +314,7 @@ class Conversation
                $x['bang']             = $x['bang']             ?? '';
                $x['visitor']          = $x['visitor']          ?? 'block';
                $x['is_owner']         = $x['is_owner']         ?? true;
-               $x['profile_uid']      = $x['profile_uid']      ?? local_user();
+               $x['profile_uid']      = $x['profile_uid']      ?? $this->session->getLocalUserId();
 
 
                $geotag = !empty($x['allow_location']) ? Renderer::replaceMacros(Renderer::getMarkupTemplate('jot_geotag.tpl'), []) : '';
@@ -303,7 +322,7 @@ class Conversation
                $tpl = Renderer::getMarkupTemplate('jot-header.tpl');
                $this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
                        '$newpost'   => 'true',
-                       '$baseurl'   => $this->baseURL->get(true),
+                       '$baseurl'   => $this->baseURL,
                        '$geotag'    => $geotag,
                        '$nickname'  => $x['nickname'],
                        '$ispublic'  => $this->l10n->t('Visible to <strong>everybody</strong>'),
@@ -330,7 +349,7 @@ class Conversation
                        $created_at = '';
                }
 
-               $tpl = Renderer::getMarkupTemplate("jot.tpl");
+               $tpl = Renderer::getMarkupTemplate('jot.tpl');
 
                $o .= Renderer::replaceMacros($tpl, [
                        '$new_post'            => $this->l10n->t('New Post'),
@@ -358,7 +377,7 @@ class Conversation
                        '$title'               => $x['title'] ?? '',
                        '$placeholdertitle'    => $this->l10n->t('Set title'),
                        '$category'            => $x['category'] ?? '',
-                       '$placeholdercategory' => Feature::isEnabled(local_user(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
+                       '$placeholdercategory' => Feature::isEnabled($this->session->getLocalUserId(), 'categories') ? $this->l10n->t("Categories \x28comma-separated list\x29") : '',
                        '$scheduled_at'        => Temporal::getDateTimeField(
                                new \DateTime(),
                                new \DateTime('now + 6 months'),
@@ -374,7 +393,7 @@ class Conversation
                        '$posttype'     => $notes_cid ? ItemModel::PT_PERSONAL_NOTE : ItemModel::PT_ARTICLE,
                        '$content'      => $x['content'] ?? '',
                        '$post_id'      => $x['post_id'] ?? '',
-                       '$baseurl'      => $this->baseURL->get(true),
+                       '$baseurl'      => $this->baseURL,
                        '$defloc'       => $x['default_location'],
                        '$visitor'      => $x['visitor'],
                        '$pvisit'       => $notes_cid ? 'none' : $x['visitor'],
@@ -395,7 +414,8 @@ class Conversation
                        '$message' => $this->l10n->t('Message'),
                        '$browser' => $this->l10n->t('Browser'),
 
-                       '$compose_link_title' => $this->l10n->t('Open Compose page'),
+                       '$compose_link_title'  => $this->l10n->t('Open Compose page'),
+                       '$always_open_compose' => $this->pConfig->get($this->session->getLocalUserId(), 'frio', 'always_open_compose', false),
                ]);
 
 
@@ -416,8 +436,8 @@ class Conversation
         * figures out how to determine page owner and other contextual items
         * that are based on unique features of the calling module.
         * @param array  $items
-        * @param        $mode
-        * @param        $update
+        * @param string $mode
+        * @param        $update @TODO Which type?
         * @param bool   $preview
         * @param string $order
         * @param int    $uid
@@ -425,7 +445,7 @@ class Conversation
         * @throws ImagickException
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       public function create(array $items, $mode, $update, $preview = false, $order = 'commented', $uid = 0)
+       public function create(array $items, string $mode, $update, bool $preview = false, string $order = 'commented', int $uid = 0): string
        {
                $this->profiler->startRecording('rendering');
 
@@ -434,15 +454,13 @@ class Conversation
                $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput.css'));
                $this->page->registerStylesheet(Theme::getPathForFile('js/friendica-tagsinput/friendica-tagsinput-typeahead.css'));
 
-               $ssl_state = (local_user() ? true : false);
-
                $live_update_div = '';
 
                $blocklist = $this->getBlocklist();
 
                $previewing = (($preview) ? ' preview ' : '');
 
-               if ($mode === 'network') {
+               if ($mode === self::MODE_NETWORK) {
                        $items = $this->addChildren($items, false, $order, $uid, $mode);
                        if (!$update) {
                                /*
@@ -468,7 +486,7 @@ class Conversation
 
                                        . "'; </script>\r\n";
                        }
-               } elseif ($mode === 'profile') {
+               } elseif ($mode === self::MODE_PROFILE) {
                        $items = $this->addChildren($items, false, $order, $uid, $mode);
 
                        if (!$update) {
@@ -485,23 +503,23 @@ class Conversation
                                                . "; var netargs = '?f='; </script>\r\n";
                                }
                        }
-               } elseif ($mode === 'notes') {
-                       $items = $this->addChildren($items, false, $order, local_user(), $mode);
+               } elseif ($mode === self::MODE_NOTES) {
+                       $items = $this->addChildren($items, false, $order, $this->session->getLocalUserId(), $mode);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-notes"></div>' . "\r\n"
-                                       . "<script> var profile_uid = " . local_user()
-                                       . "; var netargs = '/?f='; </script>\r\n";
+                                       . "<script> var profile_uid = " . $this->session->getLocalUserId()
+                                       . "; var netargs = '?f='; </script>\r\n";
                        }
-               } elseif ($mode === 'display') {
+               } elseif ($mode === self::MODE_DISPLAY) {
                        $items = $this->addChildren($items, false, $order, $uid, $mode);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-display"></div>' . "\r\n"
-                                       . "<script> var profile_uid = " . Session::get('uid', 0) . ";"
+                                       . "<script> var profile_uid = " . ($this->session->getLocalUserId() ?: 0) . ";"
                                        . "</script>";
                        }
-               } elseif ($mode === 'community') {
+               } elseif ($mode === self::MODE_COMMUNITY) {
                        $items = $this->addChildren($items, true, $order, $uid, $mode);
 
                        if (!$update) {
@@ -509,21 +527,22 @@ class Conversation
                                        . "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 10)
                                        . '?f='
                                        . (!empty($_GET['no_sharer']) ? '&no_sharer=' . rawurlencode($_GET['no_sharer']) : '')
+                                       . (!empty($_GET['accounttype']) ? '&accounttype=' . rawurlencode($_GET['accounttype']) : '')
                                        . "'; </script>\r\n";
                        }
-               } elseif ($mode === 'contacts') {
+               } elseif ($mode === self::MODE_CONTACTS) {
                        $items = $this->addChildren($items, false, $order, $uid, $mode);
 
                        if (!$update) {
                                $live_update_div = '<div id="live-contact"></div>' . "\r\n"
                                        . "<script> var profile_uid = -1; var netargs = '" . substr($this->args->getCommand(), 8)
-                                       ."/?f='; </script>\r\n";
+                                       ."?f='; </script>\r\n";
                        }
-               } elseif ($mode === 'search') {
+               } elseif ($mode === self::MODE_SEARCH) {
                        $live_update_div = '<div id="live-search"></div>' . "\r\n";
                }
 
-               $page_dropping = ((local_user() && local_user() == $uid) ? true : false);
+               $page_dropping = $this->session->getLocalUserId() && $this->session->getLocalUserId() == $uid && $mode != self::MODE_SEARCH;
 
                if (!$update) {
                        $_SESSION['return_path'] = $this->args->getQueryString();
@@ -543,7 +562,7 @@ class Conversation
                        'announce'    => [],
                ];
 
-               if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
+               if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
                        unset($conv_responses['dislike']);
                }
 
@@ -555,17 +574,17 @@ class Conversation
                $formSecurityToken = BaseModule::getFormSecurityToken('contact_action');
 
                if (!empty($items)) {
-                       if (in_array($mode, ['community', 'contacts', 'profile'])) {
+                       if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_CONTACTS, self::MODE_PROFILE])) {
                                $writable = true;
                        } else {
-                               $writable = ($items[0]['uid'] == 0) && in_array($items[0]['network'], Protocol::FEDERATED);
+                               $writable = $items[0]['writable'] || ($items[0]['uid'] == 0) && in_array($items[0]['network'], Protocol::FEDERATED);
                        }
 
-                       if (!local_user()) {
+                       if (!$this->session->getLocalUserId()) {
                                $writable = false;
                        }
 
-                       if (in_array($mode, ['filed', 'search', 'contact-posts'])) {
+                       if (in_array($mode, [self::MODE_FILED, self::MODE_SEARCH, self::MODE_CONTACT_POSTS])) {
 
                                /*
                                * "New Item View" on network page or search page results
@@ -583,7 +602,7 @@ class Conversation
 
                                        $uriids[] = $item['uri-id'];
 
-                                       if (!$this->item->visibleActivity($item)) {
+                                       if (!$this->item->isVisibleActivity($item)) {
                                                continue;
                                        }
 
@@ -594,7 +613,7 @@ class Conversation
                                        $threadsid++;
 
                                        // prevent private email from leaking.
-                                       if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
+                                       if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
                                                continue;
                                        }
 
@@ -609,7 +628,7 @@ class Conversation
                                        $profile_link = Contact::magicLinkByContact($author);
 
                                        $sparkle = '';
-                                       if (strpos($profile_link, 'redir/') === 0) {
+                                       if (strpos($profile_link, 'contact/redir/') === 0) {
                                                $sparkle = ' sparkle';
                                        }
 
@@ -618,7 +637,7 @@ class Conversation
                                        $location_html = $locate['html'] ?: Strings::escapeHtml($locate['location'] ?: $locate['coord'] ?: '');
 
                                        $this->item->localize($item);
-                                       if ($mode === 'filed') {
+                                       if ($mode === self::MODE_FILED) {
                                                $dropping = true;
                                        } else {
                                                $dropping = false;
@@ -638,17 +657,17 @@ class Conversation
                                                'announce' => null,
                                        ];
 
-                                       if ($this->pConfig->get(local_user(), 'system', 'hide_dislike')) {
+                                       if ($this->pConfig->get($this->session->getLocalUserId(), 'system', 'hide_dislike')) {
                                                unset($likebuttons['dislike']);
                                        }
 
                                        $body_html = ItemModel::prepareBody($item, true, $preview);
 
-                                       [$categories, $folders] = $this->item->determineCategoriesTerms($item, local_user());
+                                       [$categories, $folders] = $this->item->determineCategoriesTerms($item, $this->session->getLocalUserId());
 
                                        if (!empty($item['title'])) {
                                                $title = $item['title'];
-                                       } elseif (!empty($item['content-warning']) && $this->pConfig->get(local_user(), 'system', 'disable_cw', false)) {
+                                       } elseif (!empty($item['content-warning']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'disable_cw', false)) {
                                                $title = ucfirst($item['content-warning']);
                                        } else {
                                                $title = '';
@@ -742,11 +761,11 @@ class Conversation
                                        $this->builtinActivityPuller($item, $conv_responses);
 
                                        // Only add what is visible
-                                       if ($item['network'] === Protocol::MAIL && local_user() != $item['uid']) {
+                                       if ($item['network'] === Protocol::MAIL && $this->session->getLocalUserId() != $item['uid']) {
                                                continue;
                                        }
 
-                                       if (!$this->item->visibleActivity($item)) {
+                                       if (!$this->item->isVisibleActivity($item)) {
                                                continue;
                                        }
 
@@ -756,7 +775,7 @@ class Conversation
 
                                        $item['pagedrop'] = $page_dropping;
 
-                                       if ($item['gravity'] == GRAVITY_PARENT) {
+                                       if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
                                                $item_object = new PostObject($item);
                                                $conv->addParent($item_object);
                                        }
@@ -771,7 +790,7 @@ class Conversation
                }
 
                $o = Renderer::replaceMacros($page_template, [
-                       '$baseurl'     => $this->baseURL->get($ssl_state),
+                       '$baseurl'     => $this->baseURL,
                        '$return_path' => $this->args->getQueryString(),
                        '$live_update' => $live_update_div,
                        '$remove'      => $this->l10n->t('remove'),
@@ -785,13 +804,13 @@ class Conversation
                return $o;
        }
 
-       private function getBlocklist()
+       private function getBlocklist(): array
        {
-               if (!local_user()) {
+               if (!$this->session->getLocalUserId()) {
                        return [];
                }
 
-               $str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get(local_user(), 'system', 'blocked'));
+               $str_blocked = str_replace(["\n", "\r"], ",", $this->pConfig->get($this->session->getLocalUserId(), 'system', 'blocked') ?? '');
                if (empty($str_blocked)) {
                        return [];
                }
@@ -817,16 +836,16 @@ class Conversation
         *
         * @return array items with parents and comments
         */
-       private function addRowInformation(array $row, array $activity, array $thr_parent)
+       private function addRowInformation(array $row, array $activity, array $thr_parent): array
        {
                $this->profiler->startRecording('rendering');
 
-               if ($row['uid'] == 0) {
+               if (!$row['writable']) {
                        $row['writable'] = in_array($row['network'], Protocol::FEDERATED);
                }
 
                if (!empty($activity)) {
-                       if (($row['gravity'] == GRAVITY_PARENT)) {
+                       if (($row['gravity'] == ItemModel::GRAVITY_PARENT)) {
                                $row['post-reason'] = ItemModel::PR_ANNOUNCEMENT;
 
                                $row     = array_merge($row, $activity);
@@ -835,7 +854,7 @@ class Conversation
                                $row['causer-link']   = $contact['url'];
                                $row['causer-avatar'] = $contact['thumb'];
                                $row['causer-name']   = $contact['name'];
-                       } elseif (($row['gravity'] == GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
+                       } elseif (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && ($row['verb'] == Activity::ANNOUNCE) &&
                                ($row['author-id'] == $activity['causer-id'])) {
                                return $row;
                        }
@@ -855,20 +874,20 @@ class Conversation
                                $row['direction'] = ['direction' => 7, 'title' => $this->l10n->t('You had been addressed (%s).', 'bcc')];
                                break;
                        case ItemModel::PR_FOLLOWER:
-                               $row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('You are following %s.', $row['author-name'])];
+                               $row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('You are following %s.', $row['causer-name'] ?: $row['author-name'])];
                                break;
                        case ItemModel::PR_TAG:
-                               $row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('Tagged')];
+                               $row['direction'] = ['direction' => 4, 'title' => $this->l10n->t('You subscribed to one or more tags in this post.')];
                                break;
                        case ItemModel::PR_ANNOUNCEMENT:
-                               if (!empty($row['causer-id']) && $this->pConfig->get(local_user(), 'system', 'display_resharer')) {
+                               if (!empty($row['causer-id']) && $this->pConfig->get($this->session->getLocalUserId(), 'system', 'display_resharer')) {
                                        $row['owner-id']     = $row['causer-id'];
                                        $row['owner-link']   = $row['causer-link'];
                                        $row['owner-avatar'] = $row['causer-avatar'];
                                        $row['owner-name']   = $row['causer-name'];
                                }
 
-                               if (in_array($row['gravity'], [GRAVITY_PARENT, GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
+                               if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT]) && !empty($row['causer-id'])) {
                                        $causer = ['uid' => 0, 'id' => $row['causer-id'], 'network' => $row['causer-network'], 'url' => $row['causer-link']];
 
                                        $row['reshared'] = $this->l10n->t('%s reshared this.', '<a href="'. htmlentities(Contact::magicLinkByContact($causer)) .'">' . htmlentities($row['causer-name']) . '</a>');
@@ -879,17 +898,32 @@ class Conversation
                                $row['direction'] = ['direction' => 5, 'title' => $this->l10n->t('%s is participating in this thread.', $row['author-name'])];
                                break;
                        case ItemModel::PR_STORED:
-                               $row['direction'] = ['direction' => 8, 'title' => $this->l10n->t('Stored')];
+                               $row['direction'] = ['direction' => 8, 'title' => $this->l10n->t('Stored for general reasons')];
                                break;
                        case ItemModel::PR_GLOBAL:
-                               $row['direction'] = ['direction' => 9, 'title' => $this->l10n->t('Global')];
+                               $row['direction'] = ['direction' => 9, 'title' => $this->l10n->t('Global post')];
                                break;
                        case ItemModel::PR_RELAY:
-                               $row['direction'] = ['direction' => 10, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Relayed') : $this->l10n->t('Relayed by %s <%s>', $row['causer-name'], $row['causer-link']))];
+                               $row['direction'] = ['direction' => 10, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Sent via an relay server') : $this->l10n->t('Sent via the relay server %s <%s>', $row['causer-name'], $row['causer-link']))];
                                break;
                        case ItemModel::PR_FETCHED:
                                $row['direction'] = ['direction' => 2, 'title' => (empty($row['causer-id']) ? $this->l10n->t('Fetched') : $this->l10n->t('Fetched because of %s <%s>', $row['causer-name'], $row['causer-link']))];
                                break;
+                       case ItemModel::PR_COMPLETION:
+                               $row['direction'] = ['direction' => 2, 'title' => $this->l10n->t('Stored because of a child post to complete this thread.')];
+                               break;
+                       case ItemModel::PR_DIRECT:
+                               $row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('Local delivery')];
+                               break;
+                       case ItemModel::PR_ACTIVITY:
+                               $row['direction'] = ['direction' => 2, 'title' => $this->l10n->t('Stored because of your activity (like, comment, star, ...)')];
+                               break;
+                       case ItemModel::PR_DISTRIBUTE:
+                               $row['direction'] = ['direction' => 6, 'title' => $this->l10n->t('Distributed')];
+                               break;
+                       case ItemModel::PR_PUSHED:
+                               $row['direction'] = ['direction' => 1, 'title' => $this->l10n->t('Pushed to us')];
+                               break;
                }
 
                $row['thr-parent-row'] = $thr_parent;
@@ -912,7 +946,7 @@ class Conversation
         * @return array items with parents and comments
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode)
+       private function addChildren(array $parents, bool $block_authors, string $order, int $uid, string $mode): array
        {
                $this->profiler->startRecording('rendering');
                if (count($parents) > 1) {
@@ -927,7 +961,7 @@ class Conversation
                $activitycounter = [];
 
                foreach ($parents as $parent) {
-                       if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == GRAVITY_ACTIVITY)) {
+                       if (!empty($parent['thr-parent-id']) && !empty($parent['gravity']) && ($parent['gravity'] == ItemModel::GRAVITY_ACTIVITY)) {
                                $uriid = $parent['thr-parent-id'];
                                if (!empty($parent['author-id'])) {
                                        $activities[$uriid] = ['causer-id' => $parent['author-id']];
@@ -951,8 +985,20 @@ class Conversation
                        $condition['author-hidden'] = false;
                }
 
+               if ($this->config->get('system', 'emoji_activities')) {
+                       $emojis = $this->getEmojis($uriids);
+                       $condition = DBA::mergeConditions($condition, ["(`gravity` != ? OR `origin`)", ItemModel::GRAVITY_ACTIVITY]);
+               }
+
+               $condition = DBA::mergeConditions($condition,
+                       ["`uid` IN (0, ?) AND (NOT `vid` IN (?, ?, ?) OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW), Verb::getID(Activity::VIEW), Verb::getID(Activity::READ)]);
+
+               $condition = DBA::mergeConditions($condition, ["(`uid` != ? OR `private` != ?)", 0, ItemModel::PRIVATE]);
+
                $condition = DBA::mergeConditions($condition,
-                       ["`uid` IN (0, ?) AND (`vid` != ? OR `vid` IS NULL)", $uid, Verb::getID(Activity::FOLLOW)]);
+                       ["`visible` AND NOT `deleted` AND NOT `author-blocked` AND NOT `owner-blocked`
+                       AND ((NOT `contact-pending` AND (`contact-rel` IN (?, ?))) OR `self` OR `contact-uid` = ?)",
+                       Contact::SHARING, Contact::FRIEND, 0]);
 
                $thread_parents = Post::select(['uri-id', 'causer-id'], $condition, ['order' => ['uri-id' => false, 'uid']]);
 
@@ -965,39 +1011,159 @@ class Conversation
 
                $params = ['order' => ['uri-id' => true, 'uid' => true]];
 
-               $thread_items = Post::selectForUser($uid, array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
+               $thread_items = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), $condition, $params);
 
-               $items = [];
+               $items         = [];
+               $quote_uri_ids = [];
+               $authors       = [];
 
                while ($row = Post::fetch($thread_items)) {
                        if (!empty($items[$row['uri-id']]) && ($row['uid'] == 0)) {
                                continue;
                        }
 
-                       if (($mode != 'contacts') && !$row['origin']) {
+                       if (($mode != self::MODE_CONTACTS) && !$row['origin']) {
                                $row['featured'] = false;
                        }
 
                        if ($max_comments > 0) {
-                               if (($row['gravity'] == GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
+                               if (($row['gravity'] == ItemModel::GRAVITY_COMMENT) && (++$commentcounter[$row['parent-uri-id']] > $max_comments)) {
                                        continue;
                                }
-                               if (($row['gravity'] == GRAVITY_ACTIVITY) && (++$activitycounter[$row['parent-uri-id']] > $max_comments)) {
+                               if (($row['gravity'] == ItemModel::GRAVITY_ACTIVITY) && (++$activitycounter[$row['parent-uri-id']] > $max_comments)) {
                                        continue;
                                }
                        }
 
+                       $authors[] = $row['author-id'];
+                       $authors[] = $row['owner-id'];
+
+                       if (in_array($row['gravity'], [ItemModel::GRAVITY_PARENT, ItemModel::GRAVITY_COMMENT])) {
+                               $quote_uri_ids[$row['uri-id']] = [
+                                       'uri-id'        => $row['uri-id'],
+                                       'uri'           => $row['uri'],
+                                       'parent-uri-id' => $row['parent-uri-id'],
+                                       'parent-uri'    => $row['parent-uri'],
+                               ];
+                       }
+
                        $items[$row['uri-id']] = $this->addRowInformation($row, $activities[$row['uri-id']] ?? [], $thr_parent[$row['thr-parent-id']] ?? []);
                }
 
                DBA::close($thread_items);
 
+               $quotes = Post::select(array_merge(ItemModel::DISPLAY_FIELDLIST, ['featured', 'contact-uid', 'gravity', 'post-type', 'post-reason']), ['quote-uri-id' => array_column($quote_uri_ids, 'uri-id'), 'body' => '', 'uid' => 0]);
+               while ($quote = Post::fetch($quotes)) {
+                       $row = $quote;
+
+                       $row['uid']           = $uid;
+                       $row['verb']          = $row['body'] = $row['raw-body'] = Activity::ANNOUNCE;
+                       $row['gravity']       = ItemModel::GRAVITY_ACTIVITY;
+                       $row['object-type']   = Activity\ObjectType::NOTE;
+                       $row['parent-uri']    = $quote_uri_ids[$quote['quote-uri-id']]['parent-uri'];
+                       $row['parent-uri-id'] = $quote_uri_ids[$quote['quote-uri-id']]['parent-uri-id'];
+                       $row['thr-parent']    = $quote_uri_ids[$quote['quote-uri-id']]['uri'];
+                       $row['thr-parent-id'] = $quote_uri_ids[$quote['quote-uri-id']]['uri-id'];
+
+                       $authors[] = $row['author-id'];
+                       $authors[] = $row['owner-id'];
+
+                       $items[$row['uri-id']] = $this->addRowInformation($row, [], []);
+               }
+               DBA::close($quotes);
+
+               $authors = array_unique($authors);
+
+               $blocks    = [];
+               $ignores   = [];
+               $collapses = [];
+               if (!empty($authors)) {
+                       $usercontacts = DBA::select('user-contact', ['cid', 'blocked', 'ignored', 'collapsed'], ['uid' => $uid, 'cid' => $authors]);
+                       while ($usercontact = DBA::fetch($usercontacts)) {
+                               if ($usercontact['blocked']) {
+                                       $blocks[] = $usercontact['cid'];
+                               }
+                               if ($usercontact['ignored']) {
+                                       $ignores[] = $usercontact['cid'];
+                               }
+                               if ($usercontact['collapsed']) {
+                                       $collapses[] = $usercontact['cid'];
+                               }
+                       }
+                       DBA::close($usercontacts);
+               }
+
+               foreach ($items as $key => $row) {
+                       $items[$key]['emojis'] = $emojis[$key] ?? [];
+
+                       $always_display = in_array($mode, [self::MODE_CONTACTS, self::MODE_CONTACT_POSTS]);
+
+                       $items[$key]['user-blocked-author']   = !$always_display && in_array($row['author-id'], $blocks);
+                       $items[$key]['user-ignored-author']   = !$always_display && in_array($row['author-id'], $ignores);
+                       $items[$key]['user-blocked-owner']    = !$always_display && in_array($row['owner-id'], $blocks);
+                       $items[$key]['user-ignored-owner']    = !$always_display && in_array($row['owner-id'], $ignores);
+                       $items[$key]['user-collapsed-author'] = !$always_display && in_array($row['author-id'], $collapses);
+                       $items[$key]['user-collapsed-owner']  = !$always_display && in_array($row['owner-id'], $collapses);
+
+                       if (in_array($mode, [self::MODE_COMMUNITY, self::MODE_NETWORK]) &&
+                               (in_array($row['author-id'], $blocks) || in_array($row['owner-id'], $blocks) || in_array($row['author-id'], $ignores) || in_array($row['owner-id'], $ignores))) {
+                               unset($items[$key]);
+                       }
+               }
+
                $items = $this->convSort($items, $order);
 
                $this->profiler->stopRecording();
                return $items;
        }
 
+       /**
+        * Fetch emoji reaction from the conversation
+        *
+        * @param array $uriids
+        * @return array
+        */
+       private function getEmojis(array $uriids): array
+       {
+               $activity_emoji = [
+                       Activity::LIKE        => '👍',
+                       Activity::DISLIKE     => '👎',
+                       Activity::ATTEND      => '✔️',
+                       Activity::ATTENDMAYBE => '❓',
+                       Activity::ATTENDNO    => '❌',
+                       Activity::ANNOUNCE    => '♻',
+                       Activity::VIEW        => '📺',
+               ];
+
+               $index_list = array_values($activity_emoji);
+               $verbs      = array_merge(array_keys($activity_emoji), [Activity::EMOJIREACT]);
+
+               $condition = DBA::mergeConditions(['parent-uri-id' => $uriids, 'gravity' => ItemModel::GRAVITY_ACTIVITY, 'verb' => $verbs], ["NOT `deleted`"]);
+               $separator = chr(255) . chr(255) . chr(255);
+
+               $sql = "SELECT `thr-parent-id`, `body`, `verb`, COUNT(*) AS `total`, GROUP_CONCAT(REPLACE(`author-name`, '" . $separator . "', ' ') SEPARATOR '". $separator ."' LIMIT 50) AS `title` FROM `post-view` WHERE " . array_shift($condition) . " GROUP BY `thr-parent-id`, `verb`, `body`";
+
+               $emojis = [];
+
+               $rows = DBA::p($sql, $condition);
+               while ($row = DBA::fetch($rows)) {
+                       $row['verb'] = $row['body'] ? Activity::EMOJIREACT : $row['verb'];
+                       $emoji       = $row['body'] ?: $activity_emoji[$row['verb']];
+                       if (!isset($index_list[$emoji])) {
+                               $index_list[] = $emoji;
+                       }
+                       $index = array_search($emoji, $index_list);
+
+                       $emojis[$row['thr-parent-id']][$index]['emoji'] = $emoji;
+                       $emojis[$row['thr-parent-id']][$index]['verb']  = $row['verb'];
+                       $emojis[$row['thr-parent-id']][$index]['total'] = ($emojis[$row['thr-parent-id']][$index]['total'] ?? 0) + $row['total'];
+                       $emojis[$row['thr-parent-id']][$index]['title'] = array_unique(array_merge($emojis[$row['thr-parent-id']][$index]['title'] ?? [], explode($separator, $row['title'])));
+               }
+               DBA::close($rows);
+
+               return $emojis;
+       }
+
        /**
         * Plucks the children of the given parent from a given item list.
         *
@@ -1006,12 +1172,12 @@ class Conversation
         * @param bool  $recursive
         * @return array
         */
-       private function getItemChildren(array &$item_list, array $parent, $recursive = true)
+       private function getItemChildren(array &$item_list, array $parent, bool $recursive = true): array
        {
                $this->profiler->startRecording('rendering');
                $children = [];
                foreach ($item_list as $i => $item) {
-                       if ($item['gravity'] != GRAVITY_PARENT) {
+                       if ($item['gravity'] != ItemModel::GRAVITY_PARENT) {
                                if ($recursive) {
                                        // Fallback to parent-uri if thr-parent is not set
                                        $thr_parent = $item['thr-parent-id'];
@@ -1041,7 +1207,7 @@ class Conversation
         * @param array $items
         * @return array
         */
-       private function sortItemChildren(array $items)
+       private function sortItemChildren(array $items): array
        {
                $this->profiler->startRecording('rendering');
                $result = $items;
@@ -1087,7 +1253,7 @@ class Conversation
         * @param array $parent A tree-like array of items
         * @return array
         */
-       private function smartFlattenConversation(array $parent)
+       private function smartFlattenConversation(array $parent): array
        {
                $this->profiler->startRecording('rendering');
                if (!isset($parent['children']) || count($parent['children']) == 0) {
@@ -1143,7 +1309,7 @@ class Conversation
         * @return array
         * @throws \Friendica\Network\HTTPException\InternalServerErrorException
         */
-       private function convSort(array $item_list, $order)
+       private function convSort(array $item_list, string $order): array
        {
                $this->profiler->startRecording('rendering');
                $parents = [];
@@ -1168,7 +1334,7 @@ class Conversation
 
                // Extract the top level items
                foreach ($item_array as $item) {
-                       if ($item['gravity'] == GRAVITY_PARENT) {
+                       if ($item['gravity'] == ItemModel::GRAVITY_PARENT) {
                                $parents[] = $item;
                        }
                }
@@ -1198,13 +1364,13 @@ class Conversation
                        $parents[$i]['children'] = $this->sortItemChildren($parents[$i]['children']);
                }
 
-               if (!$this->pConfig->get(local_user(), 'system', 'no_smart_threading', 0)) {
+               if (!$this->pConfig->get($this->session->getLocalUserId(), 'system', 'no_smart_threading', 0)) {
                        foreach ($parents as $i => $parent) {
                                $parents[$i] = $this->smartFlattenConversation($parent);
                        }
                }
 
-               /// @TODO: Stop recusrsively adding all children back to the top level (!!!)
+               /// @TODO: Stop recursively adding all children back to the top level (!!!)
                /// However, this apparently ensures responses (likes, attendance) display (?!)
                foreach ($parents as $parent) {
                        if (count($parent['children'])) {
@@ -1223,7 +1389,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrFeaturedReceived(array $a, array $b)
+       private function sortThrFeaturedReceived(array $a, array $b): int
        {
                if ($b['featured'] && !$a['featured']) {
                        return 1;
@@ -1241,7 +1407,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrFeaturedCommented(array $a, array $b)
+       private function sortThrFeaturedCommented(array $a, array $b): int
        {
                if ($b['featured'] && !$a['featured']) {
                        return 1;
@@ -1259,7 +1425,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrReceived(array $a, array $b)
+       private function sortThrReceived(array $a, array $b): int
        {
                return strcmp($b['received'], $a['received']);
        }
@@ -1271,7 +1437,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrReceivedRev(array $a, array $b)
+       private function sortThrReceivedRev(array $a, array $b): int
        {
                return strcmp($a['received'], $b['received']);
        }
@@ -1283,7 +1449,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrCommented(array $a, array $b)
+       private function sortThrCommented(array $a, array $b): int
        {
                return strcmp($b['commented'], $a['commented']);
        }
@@ -1295,7 +1461,7 @@ class Conversation
         * @param array $b
         * @return int
         */
-       private function sortThrCreated(array $a, array $b)
+       private function sortThrCreated(array $a, array $b): int
        {
                return strcmp($b['created'], $a['created']);
        }