<?php
namespace XF\Pub\Controller;
use XF\Mvc\ParameterBag;
use XF\Mvc\Reply\AbstractReply;
use function intval;
class Conversation extends AbstractController
{
protected function preDispatchController($action, ParameterBag $params)
{
$this->assertRegistrationRequired();
}
public function actionIndex(ParameterBag $params)
{
if ($params->conversation_id)
{
return $this->rerouteController(__CLASS__, 'view', $params);
}
$this->assertNotEmbeddedImageRequest();
$page = $this->filterPage($params->page);
$perPage = $this->options()->discussionsPerPage;
$this->assertCanonicalUrl($this->buildLink('conversations', null, ['page' => $page]));
$visitor = \XF::visitor();
$filters = $this->getConversationFilterInput();
$conversationRepo = $this->getConversationRepo();
$conversationFinder = $conversationRepo->findUserConversations($visitor)
->limitByPage($page, $perPage);
$this->applyConversationFilters($conversationFinder, $filters);
$totalConversations = $conversationFinder->total();
$this->assertValidPage($page, $perPage, $totalConversations, 'conversations');
$userConvs = $conversationFinder->fetch();
$starterFilter = !empty($filters['starter_id']) ? $this->em()->find('XF:User', $filters['starter_id']) : null;
$receiverFilter = !empty($filters['receiver_id']) ? $this->em()->find('XF:User', $filters['receiver_id']) : null;
$viewParams = [
'userConvs' => $userConvs,
'page' => $page,
'perPage' => $perPage,
'total' => $totalConversations,
'starterFilter' => $starterFilter,
'receiverFilter' => $receiverFilter,
'filters' => $filters
];
return $this->view('XF:Conversations\Listing', 'conversation_list', $viewParams);
}
protected function applyConversationFilters(\XF\Finder\ConversationUser $finder, array $filters)
{
if (!empty($filters['starter_id']))
{
$finder->where('Master.user_id', intval($filters['starter_id']));
}
if (!empty($filters['receiver_id']))
{
$finder->exists('Master.Recipients|' . intval($filters['receiver_id']));
}
if (!empty($filters['starred']))
{
$finder->where('is_starred', 1);
}
if (!empty($filters['unread']))
{
$finder->where('is_unread', 1);
}
}
protected function getConversationFilterInput()
{
$filters = [];
$input = $this->filter([
'starter_id' => 'uint',
'receiver_id' => 'uint',
'filter_type' => 'str',
'starter' => 'str',
'receiver' => 'str',
'starred' => 'bool',
'unread' => 'bool'
]);
if ($input['starter_id'])
{
$filters['starter_id'] = $input['starter_id'];
}
else if ($input['filter_type'] == 'started' && $input['starter'])
{
$user = $this->em()->findOne('XF:User', ['username' => $input['starter']]);
if ($user)
{
$filters['starter_id'] = $user->user_id;
}
}
if ($input['receiver_id'])
{
$filters['receiver_id'] = $input['receiver_id'];
}
else if ($input['filter_type'] == 'received' && $input['receiver'])
{
$user = $this->em()->findOne('XF:User', ['username' => $input['receiver']]);
if ($user)
{
$filters['receiver_id'] = $user->user_id;
}
}
if ($input['starred'])
{
$filters['starred'] = 1;
}
if ($input['unread'])
{
$filters['unread'] = 1;
}
return $filters;
}
public function actionFilters()
{
$filters = $this->getConversationFilterInput();
return $this->redirect($this->buildLink('conversations', null, $filters));
}
public function actionPopup()
{
$this->assertNotEmbeddedImageRequest();
$visitor = \XF::visitor();
$conversationRepo = $this->getConversationRepo();
$cutOff = \XF::$time - $this->options()->conversationPopupExpiryHours * 3600;
$conversations = $conversationRepo->getUserConversationsForPopup($visitor, 10, $cutOff, ['Master.LastMessageUser']);
$totalUnread = $conversationRepo->findUserConversationsForPopupList($visitor, true)->total();
if ($totalUnread != $visitor->conversations_unread)
{
$visitor->conversations_unread = $totalUnread;
$visitor->saveIfChanged();
}
$viewParams = [
'unreadConversations' => $conversations['unread'],
'readConversations' => $conversations['read']
];
return $this->view('XF:Conversations\Popup', 'conversations_popup', $viewParams);
}
public function actionView(ParameterBag $params)
{
$this->assertNotEmbeddedImageRequest();
$userConv = $this->assertViewableUserConversation($params->conversation_id,
['Master.DraftReplies|' . \XF::visitor()->user_id]
);
$conversation = $userConv->Master;
$page = $params->page;
$perPage = $this->options()->messagesPerPage;
$messageCount = $conversation->reply_count + 1;
$this->assertValidPage($page, $perPage, $messageCount, 'conversations', $conversation);
$this->assertCanonicalUrl($this->buildLink('conversations', $conversation, ['page' => $page]));
$conversationRepo = $this->getConversationRepo();
$conversationMessageRepo = $this->getConversationMessageRepo();
$messageList = $conversationMessageRepo->findMessagesForConversationView($conversation);
$messages = $messageList->limitByPage($page, $perPage)->fetch();
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentRepo->addAttachmentsToContent($messages, 'conversation_message');
/** @var \XF\Repository\UserAlert $userAlertRepo */
$userAlertRepo = $this->repository('XF:UserAlert');
$userAlertRepo->markUserAlertsReadForContent('conversation_message', $messages->keys());
/** @var \XF\Repository\Unfurl $unfurlRepo */
$unfurlRepo = $this->repository('XF:Unfurl');
$unfurlRepo->addUnfurlsToContent($messages, false);
$lastRead = $userConv->Recipient ? $userConv->Recipient->last_read_date : 0;
$lastMessage = $messages->last();
$conversationRepo->markUserConversationRead($userConv, $lastMessage->message_date);
$viewParams = [
'userConv' => $userConv,
'conversation' => $conversation,
'recipients' => $conversationRepo->findRecipientsForList($conversation)->fetch(),
'lastRead' => $lastRead,
'messages' => $messages,
'lastMessage' => $lastMessage,
'page' => $page,
'perPage' => $perPage,
'attachmentData' => $this->getReplyAttachmentData($conversation)
];
return $this->view('XF:Conversation\View', 'conversation_view', $viewParams);
}
public function actionUnread(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
$recipient = $userConv->Recipient;
if (!$recipient || !$recipient->last_read_date)
{
return $this->redirect($this->buildLink('conversations', $userConv));
}
$convMessageRepo = $this->getConversationMessageRepo();
$firstUnread = $convMessageRepo->getFirstUnreadMessageInConversation($userConv);
if (!$firstUnread || $firstUnread->message_id == $conversation->last_message_id)
{
$messagesBefore = $conversation->reply_count;
$messageId = $conversation->last_message_id;
}
else
{
$messagesBefore = $convMessageRepo->findEarlierMessages($conversation, $firstUnread)->total();
$messageId = $firstUnread->message_id;
}
$page = floor($messagesBefore / $this->options()->messagesPerPage) + 1;
return $this->redirect(
$this->buildLink('conversations', $conversation, ['page' => $page]) . '#convMessage-' . $messageId
);
}
public function actionLatest(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
$messagesBefore = $conversation->reply_count;
$messageId = $conversation->last_message_id;
$page = floor($messagesBefore / $this->options()->messagesPerPage) + 1;
return $this->redirect(
$this->buildLink('conversations', $conversation, ['page' => $page]) . '#convMessage-' . $messageId
);
}
/**
* @return \XF\Service\Conversation\Creator
*/
protected function setupConversationCreate()
{
$recipients = $this->filter('recipients', 'str');
$title = $this->filter('title', 'str');
$message = $this->plugin('XF:Editor')->fromInput('message');
$conversationLocked = $this->filter('conversation_locked', 'bool');
$options = $this->filter([
'open_invite' => 'bool'
]);
$options['conversation_open'] = !$conversationLocked;
$visitor = \XF::visitor();
/** @var \XF\Service\Conversation\Creator $creator */
$creator = $this->service('XF:Conversation\Creator', $visitor);
$creator->setOptions($options);
$creator->setRecipients($recipients);
$creator->setContent($title, $message);
$conversation = $creator->getConversation();
if ($conversation->canUploadAndManageAttachments())
{
$creator->setAttachmentHash($this->filter('attachment_hash', 'str'));
}
return $creator;
}
protected function finalizeConversationCreate(\XF\Service\Conversation\Creator $creator)
{
\XF\Draft::createFromKey('conversation')->delete();
}
public function actionAdd()
{
$visitor = \XF::visitor();
if (!$visitor->canStartConversation())
{
return $this->noPermission();
}
if ($this->isPost())
{
$creator = $this->setupConversationCreate();
if (!$creator->validate($errors))
{
return $this->error($errors);
}
$this->assertNotFlooding('conversation', $this->app->options()->floodCheckLengthDiscussion ?: null);
$conversation = $creator->save();
$this->finalizeConversationCreate($creator);
return $this->redirect($this->buildLink('conversations', $conversation));
}
else
{
$to = $this->filter('to', 'str');
$title = $this->filter('title', 'str');
$message = $this->filter('message', 'str');
if ($to !== '' && strpos($to, ',') === false)
{
/** @var \XF\Entity\User $toUser */
$toUser = $this->em()->findOne('XF:User', ['username' => $to]);
if (!$toUser)
{
return $this->notFound(\XF::phrase('requested_user_not_found'));
}
if ($visitor->user_id == $toUser->user_id)
{
return $this->noPermission(\XF::phrase('you_may_not_start_conversation_with_yourself'));
}
if (!$visitor->canStartConversationWith($toUser))
{
return $this->noPermission(\XF::phrase('you_may_not_start_conversation_with_x_because_of_their_privacy_settings', ['name' => $toUser->username]));
}
}
/** @var \XF\Entity\ConversationMaster $conversation */
$conversation = $this->em()->create('XF:ConversationMaster');
$draft = \XF\Draft::createFromKey('conversation');
if ($conversation->canUploadAndManageAttachments())
{
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentData = $attachmentRepo->getEditorData('conversation_message', null, $draft->attachment_hash);
}
else
{
$attachmentData = null;
}
$viewParams = [
'to' => $to ?: $draft->recipients,
'title' => $title ?: $draft->title,
'message' => $message ?: $draft->message,
'conversation' => $conversation,
'maxRecipients' => $conversation->getMaximumAllowedRecipients(),
'draft' => $draft,
'attachmentData' => $attachmentData
];
return $this->view('XF:Conversation\Add', 'conversation_add', $viewParams);
}
}
public function actionDraft(ParameterBag $params)
{
$this->assertDraftsEnabled();
$extraData = $this->filter([
'attachment_hash' => 'str'
]);
if ($params->conversation_id)
{
$conversation = $this->assertViewableUserConversation($params->conversation_id);
$draft = $conversation->Master->draft_reply;
}
else
{
$visitor = \XF::visitor();
if (!$visitor->canStartConversation())
{
return $this->noPermission();
}
$extraData = $extraData + $this->filter([
'recipients' => 'str',
'title' => 'str',
'open_invite' => 'bool',
'conversation_locked' => 'bool'
]);
$extraData['conversation_open'] = !$extraData['conversation_locked'];
unset($extraData['conversation_locked']);
$draft = \XF\Draft::createFromKey('conversation');
}
/** @var \XF\ControllerPlugin\Draft $draftPlugin */
$draftPlugin = $this->plugin('XF:Draft');
$draftReply = $draftPlugin->actionDraftMessage($draft, $extraData, 'message', $draftAction);
if ($draftAction == 'save')
{
$draftPlugin->refreshTempAttachments($extraData['attachment_hash']);
}
return $draftReply;
}
/**
* @param \XF\Entity\ConversationMaster $conversation
* @param \XF\Entity\ConversationUser $userConv
*
* @return \XF\Service\Conversation\Replier
*/
protected function setupConversationReply(\XF\Entity\ConversationMaster $conversation, \XF\Entity\ConversationUser $userConv)
{
$visitor = \XF::visitor();
$message = $this->plugin('XF:Editor')->fromInput('message');
/** @var \XF\Service\Conversation\Replier $replier */
$replier = $this->service('XF:Conversation\Replier', $conversation, $visitor);
$replier->setMessageContent($message);
if ($conversation->canUploadAndManageAttachments())
{
$replier->setAttachmentHash($this->filter('attachment_hash', 'str'));
}
return $replier;
}
protected function afterConversationReply(\XF\Service\Conversation\Replier $replier)
{
$conversation = $replier->getConversation();
$conversation->draft_reply->delete();
}
public function actionReply(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if (!$conversation->canReply())
{
return $this->noPermission();
}
$defaultMessage = '';
$forceAttachmentHash = null;
$quote = $this->filter('quote', 'uint');
if ($quote)
{
/** @var \XF\Entity\ConversationMessage $message */
$message = $this->em()->find('XF:ConversationMessage', $quote, 'User');
if ($message->conversation_id == $conversation->conversation_id && $message->canView())
{
$defaultMessage = $message->getQuoteWrapper(
$this->app->stringFormatter()->getBbCodeForQuote($message->message, 'conversation_message')
);
$forceAttachmentHash = '';
}
}
else
{
$defaultMessage = $conversation->draft_reply->message;
}
$viewParams = [
'conversation' => $conversation,
'attachmentData' => $this->getReplyAttachmentData($conversation, $forceAttachmentHash),
'defaultMessage' => $defaultMessage
];
return $this->view('XF:Conversation\Reply', 'conversation_reply', $viewParams);
}
public function actionAddReply(ParameterBag $params)
{
$this->assertPostOnly();
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if (!$conversation->canReply())
{
return $this->noPermission();
}
$replier = $this->setupConversationReply($conversation, $userConv);
if (!$replier->validate($errors))
{
return $this->error($errors);
}
$this->assertNotFlooding('conversation_message');
$message = $replier->save();
$this->afterConversationReply($replier);
if ($this->filter('_xfWithData', 'bool') && $this->request->exists('last_date') && $message->canView())
{
$convMessageRepo = $this->getConversationMessageRepo();
$limit = 3;
$lastDate = $this->filter('last_date', 'uint');
/** @var \XF\Mvc\Entity\Finder $messageList */
$messageList = $convMessageRepo->findNewestMessagesInConversation($conversation, $lastDate)->limit($limit + 1);
$messages = $messageList->fetch();
// We fetched one more post than needed, if more than $limit posts were returned,
// we can show the 'there are more posts' notice
if ($messages->count() > $limit)
{
$firstUnshownMessage = $messages->first();
// Remove the extra post
$messages = $messages->pop();
}
else
{
$firstUnshownMessage = null;
}
// put the posts into oldest-first order
$messages = $messages->reverse(true);
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentRepo->addAttachmentsToContent($messages, 'conversation_message');
$viewParams = [
'conversation' => $conversation,
'messages' => $messages,
'firstUnshownMessage' => $firstUnshownMessage
];
$view = $this->view('XF:Conversation\NewMessages', 'conversation_reply_new_messages', $viewParams);
$view->setJsonParam('lastDate', $messages->last()->message_date);
return $view;
}
else
{
return $this->redirect($this->buildLink('conversations/messages', $message));
}
}
public function actionAddPreview()
{
$visitor = \XF::visitor();
if (!$visitor->canStartConversation())
{
return $this->noPermission();
}
$creator = $this->setupConversationCreate();
if (!$creator->validate($errors) && isset($errors['message']))
{
return $this->error($errors);
}
$message = $creator->getMessage();
$conversation = $creator->getConversation();
$attachments = null;
$tempHash = $this->filter('attachment_hash', 'str');
if ($tempHash && $conversation->canUploadAndManageAttachments())
{
$attachRepo = $this->repository('XF:Attachment');
$attachments = $attachRepo->findAttachmentsByTempHash($tempHash)->fetch();
}
return $this->plugin('XF:BbCodePreview')->actionPreview(
$message->message, 'conversation_message', $message->User, $attachments
);
}
public function actionReplyPreview(ParameterBag $params)
{
$this->assertPostOnly();
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if (!$conversation->canReply())
{
return $this->noPermission();
}
$replier = $this->setupConversationReply($conversation, $userConv);
if (!$replier->validate($errors))
{
return $this->error($errors);
}
$message = $replier->getMessage();
$attachments = null;
$tempHash = $this->filter('attachment_hash', 'str');
if ($tempHash && $conversation->canUploadAndManageAttachments())
{
$attachRepo = $this->repository('XF:Attachment');
$attachments = $attachRepo->findAttachmentsByTempHash($tempHash)->fetch();
}
return $this->plugin('XF:BbCodePreview')->actionPreview(
$message->message, 'conversation_message', $message->User, $attachments
);
}
public function actionMultiQuote(ParameterBag $params)
{
$this->assertPostOnly();
/** @var \XF\ControllerPlugin\Quote $quotePlugin */
$quotePlugin = $this->plugin('XF:Quote');
$quotes = $this->filter('quotes', 'json-array');
if (!$quotes)
{
return $this->error(\XF::phrase('no_messages_selected'));
}
$quotes = $quotePlugin->prepareQuotes($quotes);
$messageFinder = $this->finder('XF:ConversationMessage');
$messages = $messageFinder
->with(['Conversation', 'User'])
->where('message_id', array_keys($quotes))
->order('message_date', 'DESC')
->fetch()
->filterViewable();
if ($this->request->exists('insert'))
{
$insertOrder = $this->filter('insert', 'array');
return $quotePlugin->actionMultiQuote($messages, $insertOrder, $quotes, 'conversation_message');
}
else
{
$viewParams = [
'quotes' => $quotes,
'messages' => $messages
];
return $this->view('XF:Conversation\MultiQuote', 'conversation_multi_quote', $viewParams);
}
}
/**
* @param \XF\Entity\ConversationMaster $conversation
*
* @return \XF\Service\Conversation\Editor
*/
protected function setupConversationEdit(\XF\Entity\ConversationMaster $conversation)
{
/** @var \XF\Service\Conversation\Editor $editor */
$editor = $this->service('XF:Conversation\Editor', $conversation);
$editor->setTitle($this->filter('title', 'str'));
$editor->setOpenInvite($this->filter('open_invite', 'bool'));
$editor->setConversationOpen(!$this->filter('conversation_locked', 'bool'));
return $editor;
}
public function actionEdit(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if (!$conversation->canEdit())
{
return $this->noPermission();
}
if ($this->isPost())
{
$editor = $this->setupConversationEdit($conversation);
if (!$editor->validate($errors))
{
return $this->error($errors);
}
$editor->save();
return $this->redirect($this->buildLink('conversations', $conversation));
}
else
{
$viewParams = [
'userConv' => $userConv,
'conversation' => $conversation
];
return $this->view('XF:Conversation\Edit', 'conversation_edit', $viewParams);
}
}
public function actionStar(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$wasStarred = $userConv->is_starred;
$redirect = $this->getDynamicRedirect(null, false);
if ($this->isPost())
{
if (!$wasStarred)
{
$userConv->is_starred = true;
$message = \XF::phrase('conversation_starred');
}
else
{
$userConv->is_starred = false;
$message = \XF::phrase('conversation_unstarred');
}
$userConv->save();
$reply = $this->redirect($redirect, $message);
$reply->setJsonParam('switchKey', $userConv->is_starred ? 'unstar' : 'star');
return $reply;
}
else
{
$viewParams = [
'userConv' => $userConv,
'conversation' => $userConv->Master,
'redirect' => $redirect,
'isStarred' => $wasStarred
];
return $this->view('XF:Conversation\Star', 'conversation_star', $viewParams);
}
}
public function actionMarkUnread(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$wasUnread = $userConv->is_unread;
$redirect = $this->getDynamicRedirect($this->buildLink('conversations'));
if ($this->isPost())
{
if (!$wasUnread)
{
$this->getConversationRepo()->markUserConversationUnread($userConv);
$message = \XF::phrase('conversation_marked_as_unread');
}
else
{
$this->getConversationRepo()->markUserConversationRead($userConv);
$message = \XF::phrase('conversation_marked_as_read');
}
$reply = $this->redirect($redirect, $message);
$reply->setJsonParam('switchKey', $userConv->is_unread ? 'read' : 'unread');
return $reply;
}
else
{
$viewParams = [
'userConv' => $userConv,
'conversation' => $userConv->Master,
'redirect' => $redirect,
'isUnread' => $wasUnread
];
return $this->view('XF:Conversation\MarkUnread', 'conversation_mark_unread', $viewParams);
}
}
public function actionInvite(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if (!$conversation->canInvite())
{
return $this->noPermission();
}
if ($this->isPost())
{
/** @var \XF\Service\Conversation\Inviter $inviter */
$inviter = $this->service('XF:Conversation\Inviter', $conversation, \XF::visitor());
$recipients = $this->filter('recipients', 'str');
$inviter->setRecipients($recipients);
if (!$inviter->validate($errors))
{
return $this->error($errors);
}
$inviter->save();
return $this->redirect($this->buildLink('conversations', $conversation));
}
else
{
$viewParams = [
'userConv' => $userConv,
'conversation' => $conversation,
'remainingRecipients' => $conversation->getRemainingRecipientsCount()
];
return $this->view('XF:Conversation\Invite', 'conversation_invite', $viewParams);
}
}
public function actionLeave(ParameterBag $params)
{
$userConv = $this->assertViewableUserConversation($params->conversation_id);
$conversation = $userConv->Master;
if ($this->isPost())
{
$recipientState = $this->filter('recipient_state', 'str');
// TODO: turn to service?
switch ($recipientState)
{
case 'deleted':
case 'deleted_ignored':
break;
default:
$recipientState = 'deleted';
}
$recipient = $userConv->Recipient;
if ($recipient)
{
$recipient->recipient_state = $recipientState;
$recipient->save();
}
$this->plugin('XF:InlineMod')->clearIdFromCookie('conversation', $conversation->conversation_id);
return $this->redirect($this->buildLink('conversations'));
}
else
{
$viewParams = [
'conversation' => $conversation
];
return $this->view('XF:Conversation\Leave', 'conversation_leave', $viewParams);
}
}
public function actionMessages(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
$conversation = $message->Conversation;
$conversationMessageRepo = $this->getConversationMessageRepo();
$redirectParams = [];
$earlierMessages = $conversationMessageRepo->findEarlierMessages($conversation, $message)->total();
$page = floor($earlierMessages / $this->options()->messagesPerPage) + 1;
if ($page > 1)
{
$redirectParams['page'] = $page;
}
return $this->redirectPermanently(
$this->buildLink('conversations', $conversation, $redirectParams) . '#convMessage-' . $message->message_id
);
}
public function actionMessagesReact(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
/** @var \XF\ControllerPlugin\Reaction $reactionPlugin */
$reactionPlugin = $this->plugin('XF:Reaction');
return $reactionPlugin->actionReactSimple($message, 'conversations/messages');
}
public function actionMessagesReactions(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
$breadcrumbs = [];
$breadcrumbs[] = [
'value' => $message->Conversation->title,
'href' => $this->buildLink('conversations', $message->Conversation)
];
$title = \XF::phrase('members_who_reacted_to_message_by_x', ['user' => $message->User ? $message->User->username : $message->username]);
/** @var \XF\ControllerPlugin\Reaction $reactionPlugin */
$reactionPlugin = $this->plugin('XF:Reaction');
return $reactionPlugin->actionReactions(
$message,
'conversations/messages/reactions',
$title, $breadcrumbs
);
}
public function actionMessagesQuote(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
if (!$message->Conversation->canReply())
{
return $this->noPermission();
}
return $this->plugin('XF:Quote')->actionQuote($message, 'conversation_message');
}
/**
* @param \XF\Entity\ConversationMessage $message
*
* @return \XF\Service\Conversation\MessageEditor
*/
protected function setupMessageEdit(\XF\Entity\ConversationMessage $conversationMessage)
{
$message = $this->plugin('XF:Editor')->fromInput('message');
/** @var \XF\Service\Conversation\MessageEditor $editor */
$editor = $this->service('XF:Conversation\MessageEditor', $conversationMessage);
$editor->setMessageContent($message);
$conversation = $conversationMessage->Conversation;
if ($conversation->canUploadAndManageAttachments())
{
$editor->setAttachmentHash($this->filter('attachment_hash', 'str'));
}
return $editor;
}
public function actionMessagesEdit(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
if (!$message->canEdit($error))
{
return $this->noPermission($error);
}
$conversation = $message->Conversation;
if ($this->isPost())
{
$editor = $this->setupMessageEdit($message);
if (!$editor->validate($errors))
{
return $this->error($errors);
}
$editor->save();
if ($this->filter('_xfWithData', 'bool') && $this->filter('_xfInlineEdit', 'bool'))
{
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentRepo->addAttachmentsToContent([
$message->message_id => $message
], 'conversation_message');
$viewParams = [
'conversation' => $conversation,
'message' => $message
];
$reply = $this->view('XF:Conversation\Message\EditNewMessage', 'conversation_message_edit_new_message', $viewParams);
$reply->setJsonParam('message', \XF::phrase('your_changes_have_been_saved'));
return $reply;
}
else
{
return $this->redirect($this->buildLink('conversations/messages', $message));
}
}
else
{
if ($conversation->canUploadAndManageAttachments())
{
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentData = $attachmentRepo->getEditorData('conversation_message', $message);
}
else
{
$attachmentData = null;
}
$viewParams = [
'conversation' => $conversation,
'message' => $message,
'attachmentData' => $attachmentData,
'quickEdit' => $this->filter('_xfWithData', 'bool')
];
return $this->view('XF:Conversation\Message\Edit', 'conversation_message_edit', $viewParams);
}
}
public function actionMessagesPreview(ParameterBag $params)
{
$this->assertPostOnly();
$message = $this->assertViewableMessage($params->message_id);
if (!$message->canEdit($error))
{
return $this->noPermission($error);
}
$editor = $this->setupMessageEdit($message);
if (!$editor->validate($errors))
{
return $this->error($errors);
}
$conversation = $message->Conversation;
$attachments = [];
$tempHash = $this->filter('attachment_hash', 'str');
if ($conversation->canUploadAndManageAttachments())
{
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
$attachmentData = $attachmentRepo->getEditorData('conversation_message', $message, $tempHash);
$attachments = $attachmentData['attachments'];
}
return $this->plugin('XF:BbCodePreview')->actionPreview(
$message->message, 'post', $message->User, $attachments, true
);
}
public function actionMessagesIp(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
/** @var \XF\ControllerPlugin\Ip $ipPlugin */
$ipPlugin = $this->plugin('XF:Ip');
return $ipPlugin->actionIp($message);
}
public function actionMessagesReport(ParameterBag $params)
{
$message = $this->assertViewableMessage($params->message_id);
if (!$message->canReport($error))
{
return $this->noPermission($error);
}
/** @var \XF\ControllerPlugin\Report $reportPlugin */
$reportPlugin = $this->plugin('XF:Report');
return $reportPlugin->actionReport(
'conversation_message', $message,
$this->buildLink('conversations/messages/report', $message),
$this->buildLink('conversations/messages', $message),
[
'extraViewParams' => [
'conversation' => $message->Conversation
]
]
);
}
/**
* @param $conversationId
* @param array $extraWith
*
* @return \XF\Entity\ConversationUser
*
* @throws \XF\Mvc\Reply\Exception
*/
protected function assertViewableUserConversation($conversationId, array $extraWith = [])
{
$visitor = \XF::visitor();
/** @var \XF\Finder\ConversationUser $finder */
$finder = $this->finder('XF:ConversationUser');
$finder->forUser($visitor, false);
$finder->where('conversation_id', $conversationId);
$finder->with($extraWith);
/** @var \XF\Entity\ConversationUser $conversation */
$conversation = $finder->fetchOne();
if (!$conversation || !$conversation->Master)
{
throw $this->exception($this->notFound(\XF::phrase('requested_conversation_not_found')));
}
return $conversation;
}
/**
* @param $messageId
* @param array $extraWith
*
* @return \XF\Entity\ConversationMessage
*
* @throws \XF\Mvc\Reply\Exception
*/
protected function assertViewableMessage($messageId, array $extraWith = [])
{
$extraWith[] = 'Conversation';
$visitor = \XF::visitor();
if ($visitor->user_id)
{
$extraWith[] = 'Conversation.Recipients|' . $visitor->user_id;
$extraWith[] = 'Conversation.Users|' . $visitor->user_id;
}
array_unique($extraWith);
/** @var \XF\Entity\ConversationMessage $message */
$message = $this->em()->find('XF:ConversationMessage', $messageId, $extraWith);
if (!$message)
{
throw $this->exception($this->notFound(\XF::phrase('requested_message_not_found')));
}
if (!$message->canView($error))
{
throw $this->exception($this->noPermission($error));
}
return $message;
}
protected function getReplyAttachmentData(\XF\Entity\ConversationMaster $conversation, $forceAttachmentHash = null)
{
if ($conversation->canUploadAndManageAttachments())
{
if ($forceAttachmentHash !== null)
{
$attachmentHash = $forceAttachmentHash;
}
else
{
$attachmentHash = $conversation->draft_reply->attachment_hash;
}
/** @var \XF\Repository\Attachment $attachmentRepo */
$attachmentRepo = $this->repository('XF:Attachment');
return $attachmentRepo->getEditorData('conversation_message', $conversation, $attachmentHash);
}
else
{
return null;
}
}
protected function canUpdateSessionActivity($action, ParameterBag $params, AbstractReply &$reply, &$viewState)
{
if (strtolower($action) == 'addreply')
{
$viewState = 'valid';
return true;
}
return parent::canUpdateSessionActivity($action, $params, $reply, $viewState);
}
/**
* @return \XF\Repository\Conversation
*/
protected function getConversationRepo()
{
return $this->repository('XF:Conversation');
}
/**
* @return \XF\Repository\ConversationMessage
*/
protected function getConversationMessageRepo()
{
return $this->repository('XF:ConversationMessage');
}
public function assertNotSecurityLocked($action)
{
switch (strtolower($action))
{
// mostly just so it doesn't error
case 'popup':
break;
default:
parent::assertNotSecurityLocked($action);
}
}
public function assertPolicyAcceptance($action)
{
switch (strtolower($action))
{
// mostly just so it doesn't error
case 'popup':
break;
default:
parent::assertPolicyAcceptance($action);
}
}
public static function getActivityDetails(array $activities)
{
return \XF::phrase('engaged_in_conversation');
}
}