Fix opening draft conversations
[GitHub/WoltLab/com.woltlab.wcf.conversation.git] / files / lib / page / ConversationPage.class.php
index bde44f5f9c69c5af425b332321f0d5bfa951b0b8..fe520b14eeb17b1a1aa988972d7709994e786e88 100644 (file)
@@ -1,15 +1,18 @@
 <?php
+
 namespace wcf\page;
+
+use wcf\data\conversation\Conversation;
+use wcf\data\conversation\ConversationAction;
+use wcf\data\conversation\ConversationParticipantList;
 use wcf\data\conversation\label\ConversationLabel;
 use wcf\data\conversation\label\ConversationLabelList;
 use wcf\data\conversation\message\ConversationMessage;
 use wcf\data\conversation\message\ViewableConversationMessageList;
-use wcf\data\conversation\Conversation;
-use wcf\data\conversation\ConversationAction;
-use wcf\data\conversation\ConversationParticipantList;
 use wcf\data\conversation\ViewableConversation;
 use wcf\data\modification\log\ConversationLogModificationLogList;
 use wcf\data\smiley\SmileyCache;
+use wcf\data\user\UserProfile;
 use wcf\system\attachment\AttachmentHandler;
 use wcf\system\bbcode\BBCodeHandler;
 use wcf\system\exception\IllegalLinkException;
@@ -18,326 +21,412 @@ use wcf\system\message\quote\MessageQuoteManager;
 use wcf\system\page\PageLocationManager;
 use wcf\system\page\ParentPageLocation;
 use wcf\system\request\LinkHandler;
+use wcf\system\user\signature\SignatureCache;
 use wcf\system\WCF;
 use wcf\util\HeaderUtil;
 use wcf\util\StringUtil;
 
 /**
  * Shows a conversation.
- * 
- * @author     Marcel Werk
- * @copyright  2001-2018 WoltLab GmbH
- * @license    GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
- * @package    WoltLabSuite\Core\Page
- * 
- * @property   ViewableConversationMessageList         $objectList
+ *
+ * @author  Marcel Werk
+ * @copyright   2001-2019 WoltLab GmbH
+ * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
+ * @package WoltLabSuite\Core\Page
+ *
+ * @property    ViewableConversationMessageList $objectList
  */
-class ConversationPage extends MultipleLinkPage {
-       /**
-        * @inheritDoc
-        */
-       public $itemsPerPage = CONVERSATION_MESSAGES_PER_PAGE;
-       
-       /**
-        * @inheritDoc
-        */
-       public $sortOrder = 'ASC';
-       
-       /**
-        * @inheritDoc
-        */
-       public $objectListClassName = ViewableConversationMessageList::class;
-       
-       /**
-        * @inheritDoc
-        */
-       public $loginRequired = true;
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededModules = ['MODULE_CONVERSATION'];
-       
-       /**
-        * @inheritDoc
-        */
-       public $neededPermissions = ['user.conversation.canUseConversation'];
-       
-       /**
-        * conversation id
-        * @var integer
-        */
-       public $conversationID = 0;
-       
-       /**
-        * viewable conversation object
-        * @var ViewableConversation
-        */
-       public $conversation;
-       
-       /**
-        * conversation label list
-        * @var ConversationLabelList
-        */
-       public $labelList;
-       
-       /**
-        * message id
-        * @var integer
-        */
-       public $messageID = 0;
-       
-       /**
-        * conversation message object
-        * @var ConversationMessage
-        */
-       public $message;
-       
-       /**
-        * modification log list object
-        * @var ConversationLogModificationLogList
-        */
-       public $modificationLogList;
-       
-       /**
-        * list of participants
-        * @var ConversationParticipantList
-        */
-       public $participantList;
-       
-       /**
-        * @inheritDoc
-        */
-       public function readParameters() {
-               parent::readParameters();
-               
-               if (isset($_REQUEST['id'])) $this->conversationID = intval($_REQUEST['id']);
-               if (isset($_REQUEST['messageID'])) $this->messageID = intval($_REQUEST['messageID']);
-               if ($this->messageID) {
-                       $this->message = new ConversationMessage($this->messageID);
-                       if (!$this->message->messageID) {
-                               throw new IllegalLinkException();
-                       }
-                       $this->conversationID = $this->message->conversationID;
-               }
-               
-               $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
-               if ($this->conversation === null) {
-                       throw new IllegalLinkException();
-               }
-               if (!$this->conversation->canRead()) {
-                       throw new PermissionDeniedException();
-               }
-               
-               // load labels
-               $this->labelList = ConversationLabel::getLabelsByUser();
-               $this->conversation = ViewableConversation::getViewableConversation($this->conversation, $this->labelList);
-               
-               // messages per page
-               /** @noinspection PhpUndefinedFieldInspection */
-               if (WCF::getUser()->conversationMessagesPerPage) {
-                       /** @noinspection PhpUndefinedFieldInspection */
-                       $this->itemsPerPage = WCF::getUser()->conversationMessagesPerPage;
-               }
-               
-               $this->canonicalURL = LinkHandler::getInstance()->getLink('Conversation', [
-                       'object' => $this->conversation
-               ], ($this->pageNo ? 'pageNo=' . $this->pageNo : ''));
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       protected function initObjectList() {
-               parent::initObjectList();
-               
-               $this->objectList->getConditionBuilder()->add('conversation_message.conversationID = ?', [$this->conversation->conversationID]);
-               $this->objectList->setConversation($this->conversation->getDecoratedObject());
-               
-               // handle visibility filter
-               if ($this->conversation->joinedAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time >= ?', [$this->conversation->joinedAt]);
-               if ($this->conversation->leftAt > 0) $this->objectList->getConditionBuilder()->add('conversation_message.time <= ?', [$this->conversation->leftAt]);
-               
-               // handle jump to
-               if ($this->action == 'lastPost') $this->goToLastPost();
-               if ($this->action == 'firstNew') $this->goToFirstNewPost();
-               if ($this->messageID) $this->goToPost();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function readData() {
-               parent::readData();
-               
-               // add breadcrumbs
-               if ($this->conversation->isDraft) {
-                       // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
-                       PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList', -1, new ParentPageLocation(
-                               WCF::getLanguage()->get('wcf.conversation.folder.draft'),
-                               LinkHandler::getInstance()->getLink('ConversationList', ['filter' => 'draft'])
-                       ));
-               }
-               PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
-               
-               // update last visit time count
-               if ($this->conversation->isNew() && $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime) {
-                       $visitTime = $this->objectList->getMaxPostTime();
-                       if ($visitTime == $this->conversation->lastPostTime) $visitTime = TIME_NOW;
-                       $conversationAction = new ConversationAction([$this->conversation->getDecoratedObject()], 'markAsRead', ['visitTime' => $visitTime]);
-                       $conversationAction->executeAction();
-               }
-               
-               // get participants
-               $this->participantList = new ConversationParticipantList($this->conversationID, WCF::getUser()->userID, $this->conversation->userID == WCF::getUser()->userID);
-               $this->participantList->readObjects();
-               
-               // init quote objects
-               $messageIDs = [];
-               foreach ($this->objectList as $message) {
-                       $messageIDs[] = $message->messageID;
-               }
-               MessageQuoteManager::getInstance()->initObjects('com.woltlab.wcf.conversation.message', $messageIDs);
-               
-               // set attachment permissions
-               if ($this->objectList->getAttachmentList() !== null) {
-                       $this->objectList->getAttachmentList()->setPermissions([
-                               'canDownload' => true,
-                               'canViewPreview' => true
-                       ]);
-               }
-               
-               // get timeframe for modifications
-               $this->objectList->rewind();
-               $startTime = ($this->conversation->joinedAt ?: $this->objectList->current()->time);
-               $endTime = ($this->conversation->leftAt ?: TIME_NOW);
-               
-               $count = count($this->objectList);
-               if ($count > 1) {
-                       $this->objectList->seek($count - 1);
-                       if ($this->objectList->current()->time < $this->conversation->lastPostTime) {
-                               $sql = "SELECT          time
-                                       FROM            wcf".WCF_N."_conversation_message
-                                       WHERE           conversationID = ?
-                                                       AND time > ?
-                                       ORDER BY        time";
-                               $statement = WCF::getDB()->prepareStatement($sql, 1);
-                               $statement->execute([$this->conversationID, $this->objectList->current()->time]);
-                               $endTime = $statement->fetchSingleColumn() - 1;
-                       }
-               }
-               $this->objectList->rewind();
-               
-               // get invisible participants
-               $invisibleParticipantIDs = [];
-               if (WCF::getUser()->userID != $this->conversation->userID) {
-                       foreach ($this->participantList as $participant) {
-                               if ($participant->isInvisible) {
-                                       $invisibleParticipantIDs[] = $participant->userID;
-                               }
-                       }
-               }
-               
-               // load modification log entries
-               $this->modificationLogList = new ConversationLogModificationLogList($this->conversation->conversationID);
-               $this->modificationLogList->getConditionBuilder()->add("modification_log.time BETWEEN ? AND ?", [$startTime, $endTime]);
-               
-               if (!empty($invisibleParticipantIDs)) {
-                       $this->modificationLogList->getConditionBuilder()->add("(modification_log.action <> ? OR modification_log.userID NOT IN (?))", ['leave', $invisibleParticipantIDs]);
-               }
-               
-               $this->modificationLogList->readObjects();
-       }
-       
-       /**
-        * @inheritDoc
-        */
-       public function assignVariables() {
-               parent::assignVariables();
-               
-               MessageQuoteManager::getInstance()->assignVariables();
-               
-               $tmpHash = StringUtil::getRandomID();
-               $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $tmpHash, 0);
-               
-               WCF::getTPL()->assign([
-                       'attachmentHandler' => $attachmentHandler,
-                       'attachmentObjectID' => 0,
-                       'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
-                       'attachmentParentObjectID' => 0,
-                       'tmpHash' => $tmpHash,
-                       'attachmentList' => $this->objectList->getAttachmentList(),
-                       'labelList' => $this->labelList,
-                       'modificationLogList' => $this->modificationLogList,
-                       'sortOrder' => $this->sortOrder,
-                       'conversation' => $this->conversation,
-                       'conversationID' => $this->conversationID,
-                       'participants' => $this->participantList->getObjects(),
-                       'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies()
-               ]);
-               
-               BBCodeHandler::getInstance()->setDisallowedBBCodes(explode(',', WCF::getSession()->getPermission('user.message.disallowedBBCodes')));
-       }
-       
-       /**
-        * Calculates the position of a specific post in this conversation.
-        */
-       protected function goToPost() {
-               $conditionBuilder = clone $this->objectList->getConditionBuilder();
-               $conditionBuilder->add('time '.($this->sortOrder == 'ASC' ? '<=' : '>=').' ?', [$this->message->time]);
-               
-               $sql = "SELECT  COUNT(*) AS messages
-                       FROM    wcf".WCF_N."_conversation_message conversation_message
-                       ".$conditionBuilder;
-               $statement = WCF::getDB()->prepareStatement($sql);
-               $statement->execute($conditionBuilder->getParameters());
-               $row = $statement->fetchArray();
-               $this->pageNo = intval(ceil($row['messages'] / $this->itemsPerPage));
-       }
-       
-       /**
-        * Gets the id of the last post in this conversation and forwards the user to this post.
-        */
-       protected function goToLastPost() {
-               $sql = "SELECT          conversation_message.messageID
-                       FROM            wcf".WCF_N."_conversation_message conversation_message
-                       ".$this->objectList->getConditionBuilder()."
-                       ORDER BY        time ".($this->sortOrder == 'ASC' ? 'DESC' : 'ASC');
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute($this->objectList->getConditionBuilder()->getParameters());
-               $row = $statement->fetchArray();
-               HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
-                       'encodeTitle' => true,
-                       'object' => $this->conversation,
-                       'messageID' => $row['messageID']
-               ]).'#message'.$row['messageID']);
-               exit;
-       }
-       
-       /**
-        * Forwards the user to the first new message in this conversation.
-        */
-       protected function goToFirstNewPost() {
-               $conditionBuilder = clone $this->objectList->getConditionBuilder();
-               $conditionBuilder->add('time > ?', [$this->conversation->lastVisitTime]);
-               
-               $sql = "SELECT          conversation_message.messageID
-                       FROM            wcf".WCF_N."_conversation_message conversation_message
-                       ".$conditionBuilder."
-                       ORDER BY        time ASC";
-               $statement = WCF::getDB()->prepareStatement($sql, 1);
-               $statement->execute($conditionBuilder->getParameters());
-               $row = $statement->fetchArray();
-               if ($row !== false) {
-                       HeaderUtil::redirect(LinkHandler::getInstance()->getLink('Conversation', [
-                               'encodeTitle' => true,
-                               'object' => $this->conversation,
-                               'messageID' => $row['messageID']
-                       ]).'#message'.$row['messageID']);
-                       exit;
-               }
-               else {
-                       $this->goToLastPost();
-               }
-       }
+class ConversationPage extends MultipleLinkPage
+{
+    /**
+     * @inheritDoc
+     */
+    public $itemsPerPage = CONVERSATION_MESSAGES_PER_PAGE;
+
+    /**
+     * @inheritDoc
+     */
+    public $sortOrder = 'ASC';
+
+    /**
+     * @inheritDoc
+     */
+    public $objectListClassName = ViewableConversationMessageList::class;
+
+    /**
+     * @inheritDoc
+     */
+    public $loginRequired = true;
+
+    /**
+     * @inheritDoc
+     */
+    public $neededModules = ['MODULE_CONVERSATION'];
+
+    /**
+     * @inheritDoc
+     */
+    public $neededPermissions = ['user.conversation.canUseConversation'];
+
+    /**
+     * conversation id
+     * @var int
+     */
+    public $conversationID = 0;
+
+    /**
+     * viewable conversation object
+     * @var ViewableConversation
+     */
+    public $conversation;
+
+    /**
+     * conversation label list
+     * @var ConversationLabelList
+     */
+    public $labelList;
+
+    /**
+     * message id
+     * @var int
+     */
+    public $messageID = 0;
+
+    /**
+     * conversation message object
+     * @var ConversationMessage
+     */
+    public $message;
+
+    /**
+     * modification log list object
+     * @var ConversationLogModificationLogList
+     */
+    public $modificationLogList;
+
+    /**
+     * list of participants
+     * @var ConversationParticipantList
+     */
+    public $participantList;
+
+    /**
+     * @inheritDoc
+     */
+    public function readParameters()
+    {
+        parent::readParameters();
+
+        if (isset($_REQUEST['id'])) {
+            $this->conversationID = \intval($_REQUEST['id']);
+        }
+        if (isset($_REQUEST['messageID'])) {
+            $this->messageID = \intval($_REQUEST['messageID']);
+        }
+        if ($this->messageID) {
+            $this->message = new ConversationMessage($this->messageID);
+            if (!$this->message->messageID) {
+                throw new IllegalLinkException();
+            }
+            $this->conversationID = $this->message->conversationID;
+        }
+
+        $this->conversation = Conversation::getUserConversation($this->conversationID, WCF::getUser()->userID);
+        if ($this->conversation === null) {
+            throw new IllegalLinkException();
+        }
+        if (!$this->conversation->canRead()) {
+            throw new PermissionDeniedException();
+        }
+
+        // load labels
+        $this->labelList = ConversationLabel::getLabelsByUser();
+        $this->conversation = ViewableConversation::getViewableConversation($this->conversation, $this->labelList);
+
+        // messages per page
+        /** @noinspection PhpUndefinedFieldInspection */
+        if (WCF::getUser()->conversationMessagesPerPage) {
+            /** @noinspection PhpUndefinedFieldInspection */
+            $this->itemsPerPage = WCF::getUser()->conversationMessagesPerPage;
+        }
+
+        $this->canonicalURL = LinkHandler::getInstance()->getLink('Conversation', [
+            'object' => $this->conversation,
+        ], ($this->pageNo ? 'pageNo=' . $this->pageNo : ''));
+    }
+
+    /**
+     * @inheritDoc
+     */
+    protected function initObjectList()
+    {
+        parent::initObjectList();
+
+        $this->objectList->getConditionBuilder()
+            ->add('conversation_message.conversationID = ?', [$this->conversation->conversationID]);
+        $this->objectList->setConversation($this->conversation->getDecoratedObject());
+
+        // handle visibility filter
+        if ($this->conversation->joinedAt > 0) {
+            $this->objectList->getConditionBuilder()
+                ->add('conversation_message.time >= ?', [$this->conversation->joinedAt]);
+        }
+        if ($this->conversation->leftAt > 0) {
+            $this->objectList->getConditionBuilder()
+                ->add('conversation_message.time <= ?', [$this->conversation->leftAt]);
+        }
+
+        // handle jump to
+        if ($this->action == 'lastPost') {
+            $this->goToLastPost();
+        }
+        if ($this->action == 'firstNew') {
+            $this->goToFirstNewPost();
+        }
+        if ($this->messageID) {
+            $this->goToPost();
+        }
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function readData()
+    {
+        parent::readData();
+
+        // add breadcrumbs
+        if ($this->conversation->isDraft) {
+            // `-1` = pseudo object id to have to pages with identifier `com.woltlab.wcf.conversation.ConversationList`
+            PageLocationManager::getInstance()->addParentLocation(
+                'com.woltlab.wcf.conversation.ConversationList',
+                -1,
+                new ParentPageLocation(
+                    WCF::getLanguage()->get('wcf.conversation.folder.draft'),
+                    LinkHandler::getInstance()->getLink('ConversationList', ['filter' => 'draft'])
+                )
+            );
+        }
+        PageLocationManager::getInstance()->addParentLocation('com.woltlab.wcf.conversation.ConversationList');
+
+        // update last visit time count
+        if (
+            $this->conversation->isNew()
+            && (
+                $this->objectList->getMaxPostTime() > $this->conversation->lastVisitTime
+                || ($this->conversation->joinedAt && !\count($this->objectList))
+            )
+        ) {
+            $visitTime = $this->objectList->getMaxPostTime();
+            if ($visitTime == $this->conversation->lastPostTime) {
+                $visitTime = TIME_NOW;
+            }
+            $conversationAction = new ConversationAction(
+                [$this->conversation->getDecoratedObject()],
+                'markAsRead',
+                ['visitTime' => $visitTime]
+            );
+            $conversationAction->executeAction();
+        }
+
+        // get participants
+        $this->participantList = new ConversationParticipantList(
+            $this->conversationID,
+            WCF::getUser()->userID,
+            $this->conversation->userID == WCF::getUser()->userID
+        );
+        $this->participantList->readObjects();
+
+        // init quote objects
+        $messageIDs = [];
+        foreach ($this->objectList as $message) {
+            $messageIDs[] = $message->messageID;
+        }
+        MessageQuoteManager::getInstance()->initObjects('com.woltlab.wcf.conversation.message', $messageIDs);
+
+        $userIDs = [];
+        foreach ($this->objectList as $message) {
+            if ($message->userID) {
+                $userIDs[] = $message->userID;
+            }
+        }
+        $userIDs = \array_unique($userIDs);
+
+        // fetch special trophies
+        if (MODULE_TROPHY) {
+            if (!empty($userIDs)) {
+                UserProfile::prepareSpecialTrophies($userIDs);
+            }
+        }
+
+        if (MODULE_USER_SIGNATURE) {
+            if (!empty($userIDs)) {
+                SignatureCache::getInstance()->cacheUserSignature($userIDs);
+            }
+        }
+
+        // set attachment permissions
+        if ($this->objectList->getAttachmentList() !== null) {
+            $this->objectList->getAttachmentList()->setPermissions([
+                'canDownload' => true,
+                'canViewPreview' => true,
+            ]);
+        }
+
+        // get timeframe for modifications
+        $this->objectList->rewind();
+        $startTime = ($this->conversation->joinedAt ?: $this->objectList->current()->time);
+        $endTime = ($this->conversation->leftAt ?: TIME_NOW);
+
+        $count = \count($this->objectList);
+        if ($count > 1) {
+            $this->objectList->seek($count - 1);
+            if ($this->objectList->current()->time < $this->conversation->lastPostTime) {
+                $sql = "SELECT      time
+                        FROM        wcf" . WCF_N . "_conversation_message
+                        WHERE       conversationID = ?
+                                AND time > ?
+                        ORDER BY    time";
+                $statement = WCF::getDB()->prepareStatement($sql, 1);
+                $statement->execute([$this->conversationID, $this->objectList->current()->time]);
+                $endTime = $statement->fetchSingleColumn() - 1;
+            }
+        }
+        $this->objectList->rewind();
+
+        // get visible participants
+        $visibleParticipantIDs = [];
+        foreach ($this->participantList as $participant) {
+            /** @noinspection PhpUndefinedFieldInspection */
+            if (!$participant->isInvisible || WCF::getUser()->userID == $this->conversation->userID) {
+                $visibleParticipantIDs[] = $participant->userID;
+            }
+        }
+
+        // Drafts do not store their participants in conversation_to_user.
+        if ($this->conversation->isDraft) {
+            $visibleParticipantIDs[] = $this->conversation->userID;
+        }
+
+        // load modification log entries
+        $this->modificationLogList = new ConversationLogModificationLogList($this->conversation->conversationID);
+        $this->modificationLogList->getConditionBuilder()
+            ->add("modification_log.time BETWEEN ? AND ?", [$startTime, $endTime]);
+        $this->modificationLogList->getConditionBuilder()->add(
+            "modification_log.userID IN (?)",
+            [$visibleParticipantIDs]
+        );
+
+        $this->modificationLogList->readObjects();
+    }
+
+    /**
+     * @inheritDoc
+     */
+    public function assignVariables()
+    {
+        parent::assignVariables();
+
+        MessageQuoteManager::getInstance()->assignVariables();
+
+        $tmpHash = StringUtil::getRandomID();
+        $attachmentHandler = new AttachmentHandler('com.woltlab.wcf.conversation.message', 0, $tmpHash, 0);
+
+        WCF::getTPL()->assign([
+            'attachmentHandler' => $attachmentHandler,
+            'attachmentObjectID' => 0,
+            'attachmentObjectType' => 'com.woltlab.wcf.conversation.message',
+            'attachmentParentObjectID' => 0,
+            'tmpHash' => $tmpHash,
+            'attachmentList' => $this->objectList->getAttachmentList(),
+            'labelList' => $this->labelList,
+            'modificationLogList' => $this->modificationLogList,
+            'sortOrder' => $this->sortOrder,
+            'conversation' => $this->conversation,
+            'conversationID' => $this->conversationID,
+            'participants' => $this->participantList->getObjects(),
+            'defaultSmilies' => SmileyCache::getInstance()->getCategorySmilies(),
+        ]);
+
+        BBCodeHandler::getInstance()->setDisallowedBBCodes(\explode(
+            ',',
+            WCF::getSession()->getPermission('user.message.disallowedBBCodes')
+        ));
+    }
+
+    /**
+     * Calculates the position of a specific post in this conversation.
+     */
+    protected function goToPost()
+    {
+        $conditionBuilder = clone $this->objectList->getConditionBuilder();
+        $conditionBuilder->add('time ' . ($this->sortOrder == 'ASC' ? '<=' : '>=') . ' ?', [$this->message->time]);
+
+        $sql = "SELECT  COUNT(*) AS messages
+                FROM    wcf" . WCF_N . "_conversation_message conversation_message
+                " . $conditionBuilder;
+        $statement = WCF::getDB()->prepareStatement($sql);
+        $statement->execute($conditionBuilder->getParameters());
+        $row = $statement->fetchArray();
+        $this->pageNo = \intval(\ceil($row['messages'] / $this->itemsPerPage));
+    }
+
+    /**
+     * Gets the id of the last post in this conversation and forwards the user to this post.
+     */
+    protected function goToLastPost()
+    {
+        $sql = "SELECT      conversation_message.messageID
+                FROM        wcf" . WCF_N . "_conversation_message conversation_message
+                " . $this->objectList->getConditionBuilder() . "
+                ORDER BY    time " . ($this->sortOrder == 'ASC' ? 'DESC' : 'ASC');
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute($this->objectList->getConditionBuilder()->getParameters());
+        $row = $statement->fetchArray();
+        HeaderUtil::redirect(
+            LinkHandler::getInstance()->getLink(
+                'Conversation',
+                [
+                    'encodeTitle' => true,
+                    'object' => $this->conversation,
+                    'messageID' => $row['messageID'],
+                ]
+            ) . '#message' . $row['messageID']
+        );
+
+        exit;
+    }
+
+    /**
+     * Forwards the user to the first new message in this conversation.
+     */
+    protected function goToFirstNewPost()
+    {
+        $conditionBuilder = clone $this->objectList->getConditionBuilder();
+        $conditionBuilder->add('time > ?', [$this->conversation->lastVisitTime]);
+
+        $sql = "SELECT      conversation_message.messageID
+                FROM        wcf" . WCF_N . "_conversation_message conversation_message
+                " . $conditionBuilder . "
+                ORDER BY    time ASC";
+        $statement = WCF::getDB()->prepareStatement($sql, 1);
+        $statement->execute($conditionBuilder->getParameters());
+        $row = $statement->fetchArray();
+        if ($row !== false) {
+            HeaderUtil::redirect(
+                LinkHandler::getInstance()->getLink(
+                    'Conversation',
+                    [
+                        'encodeTitle' => true,
+                        'object' => $this->conversation,
+                        'messageID' => $row['messageID'],
+                    ]
+                ) . '#message' . $row['messageID']
+            );
+
+            exit;
+        } else {
+            $this->goToLastPost();
+        }
+    }
 }