<?php
namespace XF\Entity;
use XF\BbCode\RenderableContentInterface;
use XF\Mvc\Entity\Entity;
use XF\Mvc\Entity\Structure;
/**
* COLUMNS
* @property int|null $post_id
* @property int $thread_id
* @property int $user_id
* @property string $username
* @property int $post_date
* @property string $message
* @property int $ip_id
* @property string $message_state
* @property int $attach_count
* @property int $warning_id
* @property string $warning_message
* @property int $position
* @property array $type_data
* @property int $last_edit_date
* @property int $last_edit_user_id
* @property int $edit_count
* @property array|null $embed_metadata
* @property int $reaction_score
* @property array $reactions_
* @property array $reaction_users_
* @property int $vote_score
* @property int $vote_count
*
* GETTERS
* @property mixed $Unfurls
* @property mixed $is_question_solution
* @property mixed $reactions
* @property mixed $reaction_users
* @property mixed $vote_score_short
*
* RELATIONS
* @property \XF\Entity\Thread $Thread
* @property \XF\Entity\User $User
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\Attachment[] $Attachments
* @property \XF\Entity\DeletionLog $DeletionLog
* @property \XF\Entity\ApprovalQueue $ApprovalQueue
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\ReactionContent[] $Reactions
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\BookmarkItem[] $Bookmarks
* @property \XF\Mvc\Entity\AbstractCollection|\XF\Entity\ContentVote[] $ContentVotes
*/
class Post extends Entity implements LinkableInterface, QuotableInterface, RenderableContentInterface
{
use ReactionTrait, BookmarkTrait, ContentVoteTrait;
public function canView(&$error = null)
{
if (!$this->Thread || !$this->Thread->canView($error))
{
return false;
}
$visitor = \XF::visitor();
$nodeId = $this->Thread->node_id;
if ($this->message_state == 'moderated')
{
if (
!$visitor->hasNodePermission($nodeId, 'viewModerated')
&& (!$visitor->user_id || $visitor->user_id != $this->user_id)
)
{
$error = \XF::phraseDeferred('requested_post_not_found');
return false;
}
}
else if ($this->message_state == 'deleted')
{
if (!$visitor->hasNodePermission($nodeId, 'viewDeleted'))
{
$error = \XF::phraseDeferred('requested_post_not_found');
return false;
}
}
return true;
}
public function canEdit(&$error = null)
{
$thread = $this->Thread;
$visitor = \XF::visitor();
if (!$visitor->user_id || !$thread)
{
return false;
}
if (!$thread->discussion_open && !$thread->canLockUnlock())
{
$error = \XF::phraseDeferred('you_may_not_perform_this_action_because_discussion_is_closed');
return false;
}
$nodeId = $thread->node_id;
if ($visitor->hasNodePermission($nodeId, 'editAnyPost'))
{
return true;
}
if ($this->user_id == $visitor->user_id && $visitor->hasNodePermission($nodeId, 'editOwnPost'))
{
$editLimit = $visitor->hasNodePermission($nodeId, 'editOwnPostTimeLimit');
if ($editLimit != -1 && (!$editLimit || $this->post_date < \XF::$time - 60 * $editLimit))
{
$error = \XF::phraseDeferred('message_edit_time_limit_expired', ['minutes' => $editLimit]);
return false;
}
if (!$thread->Forum || !$thread->Forum->allow_posting)
{
$error = \XF::phraseDeferred('you_may_not_perform_this_action_because_forum_does_not_allow_posting');
return false;
}
return true;
}
return false;
}
public function canEditSilently(&$error = null)
{
$thread = $this->Thread;
$visitor = \XF::visitor();
if (!$visitor->user_id || !$thread)
{
return false;
}
$nodeId = $thread->node_id;
if ($visitor->hasNodePermission($nodeId, 'editAnyPost'))
{
return true;
}
return false;
}
public function canUseInlineModeration(&$error = null)
{
return $this->Thread->canUseInlineModeration($error);
}
public function canViewHistory(&$error = null)
{
$visitor = \XF::visitor();
if (!$visitor->user_id)
{
return false;
}
if (!$this->app()->options()->editHistory['enabled'])
{
return false;
}
if ($visitor->hasNodePermission($this->Thread->node_id, 'editAnyPost'))
{
return true;
}
return false;
}
public function canDelete($type = 'soft', &$error = null)
{
$thread = $this->Thread;
$visitor = \XF::visitor();
if (!$visitor->user_id || !$thread)
{
return false;
}
$nodeId = $thread->node_id;
if ($type != 'soft' && !$visitor->hasNodePermission($nodeId, 'hardDeleteAnyPost'))
{
return false;
}
if (!$thread->discussion_open && !$thread->canLockUnlock())
{
$error = \XF::phraseDeferred('you_may_not_perform_this_action_because_discussion_is_closed');
return false;
}
if ($this->isFirstPost())
{
return $thread->canDelete($type, $error);
}
if ($visitor->hasNodePermission($nodeId, 'deleteAnyPost'))
{
return true;
}
if ($this->user_id == $visitor->user_id && $visitor->hasNodePermission($nodeId, 'deleteOwnPost'))
{
$editLimit = $visitor->hasNodePermission($nodeId, 'editOwnPostTimeLimit');
if ($editLimit != -1 && (!$editLimit || $this->post_date < \XF::$time - 60 * $editLimit))
{
$error = \XF::phraseDeferred('message_edit_time_limit_expired', ['minutes' => $editLimit]);
return false;
}
if (!$thread->Forum || !$thread->Forum->allow_posting)
{
$error = \XF::phraseDeferred('you_may_not_perform_this_action_because_forum_does_not_allow_posting');
return false;
}
return true;
}
return false;
}
public function canUndelete(&$error = null)
{
$thread = $this->Thread;
$visitor = \XF::visitor();
if (!$visitor->user_id || !$thread)
{
return false;
}
return $visitor->hasNodePermission($thread->node_id, 'undelete');
}
public function canApproveUnapprove(&$error = null)
{
if (!$this->Thread)
{
return false;
}
return $this->Thread->canApproveUnapprove();
}
public function canWarn(&$error = null)
{
$visitor = \XF::visitor();
if (!$this->user_id
|| !$visitor->user_id
|| $this->user_id == $visitor->user_id
|| !$visitor->hasNodePermission($this->Thread->node_id, 'warn')
)
{
return false;
}
if ($this->warning_id)
{
$error = \XF::phraseDeferred('user_has_already_been_warned_for_this_content');
return false;
}
return ($this->User && $this->User->isWarnable());
}
public function canMove(&$error = null)
{
return $this->Thread->canMove($error);
}
public function canCopy(&$error = null)
{
return $this->Thread->canCopy($error);
}
public function canMerge(&$error = null)
{
return $this->Thread->canMerge($error);
}
public function canReport(&$error = null, User $asUser = null)
{
$asUser = $asUser ?: \XF::visitor();
return $asUser->canReport($error);
}
public function canReact(&$error = null)
{
$visitor = \XF::visitor();
if (!$visitor->user_id)
{
return false;
}
if ($this->message_state != 'visible')
{
return false;
}
if ($this->user_id == $visitor->user_id)
{
$error = \XF::phraseDeferred('reacting_to_your_own_content_is_considered_cheating');
return false;
}
if (!$this->Thread)
{
return false;
}
return $visitor->hasNodePermission($this->Thread->node_id, 'react');
}
protected function canBookmarkContent(&$error = null)
{
return $this->isVisible();
}
public function isContentVotingSupported(): bool
{
$thread = $this->Thread;
return ($thread && $thread->TypeHandler->isPostVotingSupported($thread, $this));
}
public function isContentDownvoteSupported(): bool
{
$thread = $this->Thread;
return ($thread && $thread->TypeHandler->isPostDownvoteSupported($thread, $this));
}
protected function canVoteOnContentInternal(&$error = null): bool
{
if (!$this->isVisible())
{
return false;
}
$thread = $this->Thread;
return ($thread && $thread->TypeHandler->canVoteOnPost($thread, $this, $error));
}
public function canDownvoteContent(&$error = null): bool
{
$thread = $this->Thread;
return ($thread && $thread->TypeHandler->canDownvotePost($thread, $this, $error));
}
public function canCleanSpam()
{
return (\XF::visitor()->canCleanSpam() && $this->User && $this->User->isPossibleSpammer());
}
public function canSendModeratorActionAlert()
{
$visitor = \XF::visitor();
if (!$visitor->user_id || $visitor->user_id == $this->user_id)
{
return false;
}
if ($this->message_state != 'visible')
{
return false;
}
return true;
}
public function canMarkAsQuestionSolution(&$error = null): bool
{
if (!$this->isVisible())
{
return false;
}
$thread = $this->Thread;
$typeHandler = $thread->TypeHandler;
if (!($typeHandler instanceof \XF\ThreadType\Question))
{
return false;
}
return $typeHandler->canMarkPostAsSolution($thread, $this, $error);
}
public function isQuestionSolution()
{
$thread = $this->Thread;
$typeHandler = $thread->TypeHandler;
if (!($typeHandler instanceof \XF\ThreadType\Question))
{
return false;
}
return $typeHandler->isPostSolution($thread, $this, $error);
}
public function isVisible()
{
return (
$this->message_state == 'visible'
&& $this->Thread
&& $this->Thread->discussion_state == 'visible'
);
}
public function isFirstPost()
{
$thread = $this->Thread;
if (!$thread)
{
return false;
}
if ($this->post_id == $thread->first_post_id)
{
return true;
}
// this can be called during an insert where the thread hasn't actually been updated yet
// just assume it's the first post
if (!$thread->thread_id)
{
return true;
}
if (!$thread->first_post_id && $this->post_date == $thread->post_date)
{
return true;
}
return false;
}
public function isLastPost()
{
$thread = $this->Thread;
if (!$thread)
{
return false;
}
return ($this->post_id == $thread->last_post_id);
}
public function isUnread()
{
if (!$this->Thread)
{
return false;
}
$readDate = $this->Thread->getVisitorReadDate();
if ($readDate === null)
{
return false;
}
return $readDate < $this->post_date;
}
public function isAttachmentEmbedded($attachmentId)
{
if (!$this->embed_metadata)
{
return false;
}
if ($attachmentId instanceof Attachment)
{
$attachmentId = $attachmentId->attachment_id;
}
return isset($this->embed_metadata['attachments'][$attachmentId]);
}
public function isIgnored()
{
return \XF::visitor()->isIgnoring($this->user_id);
}
public function getQuoteWrapper($inner)
{
return '[QUOTE="'
. ($this->User ? $this->User->username : $this->username)
. ', post: ' . $this->post_id
. ($this->User ? ", member: $this->user_id" : '')
. '"]'
. "\n" . $inner . "\n"
. "[/QUOTE]\n";
}
public function getBbCodeRenderOptions($context, $type)
{
return [
'entity' => $this,
'user' => $this->User,
'attachments' => $this->attach_count ? $this->Attachments : [],
'viewAttachments' => $this->Thread ? $this->Thread->canViewAttachments() : false,
'unfurls' => $this->Unfurls ?: []
];
}
public function getUnfurls()
{
return $this->_getterCache['Unfurls'] ?? [];
}
public function setUnfurls($unfurls)
{
$this->_getterCache['Unfurls'] = $unfurls;
}
protected function _postSave()
{
$visibilityChange = $this->isStateChanged('message_state', 'visible');
$approvalChange = $this->isStateChanged('message_state', 'moderated');
$deletionChange = $this->isStateChanged('message_state', 'deleted');
if ($this->isUpdate())
{
if ($visibilityChange == 'enter')
{
$this->postMadeVisible();
if ($approvalChange)
{
$this->submitHamData();
}
}
else if ($visibilityChange == 'leave')
{
$this->postHidden();
}
if ($deletionChange == 'leave' && $this->DeletionLog)
{
$this->DeletionLog->delete();
}
if ($approvalChange == 'leave' && $this->ApprovalQueue)
{
$this->ApprovalQueue->delete();
}
}
else
{
// insert
if ($this->message_state == 'visible')
{
$this->postInsertedVisible();
}
}
if ($approvalChange == 'enter')
{
$approvalQueue = $this->getRelationOrDefault('ApprovalQueue', false);
$approvalQueue->content_date = $this->post_date;
$approvalQueue->save();
}
else if ($deletionChange == 'enter' && !$this->DeletionLog)
{
$delLog = $this->getRelationOrDefault('DeletionLog', false);
$delLog->setFromVisitor();
$delLog->save();
}
$this->updateThreadRecord();
$thread = $this->Thread;
if ($this->isUpdate() && $this->isFirstPost())
{
if ($this->isChanged('reaction_score'))
{
$thread->first_post_reaction_score = $this->reaction_score;
}
if ($this->isChanged('reactions'))
{
$thread->first_post_reactions = $this->reactions;
}
$thread->save();
}
if ($thread)
{
$thread->TypeHandler->onPostSave($thread, $this);
}
if ($this->isUpdate() && $this->getOption('log_moderator'))
{
$this->app()->logger()->logModeratorChanges('post', $this);
}
$this->_postSaveBookmarks();
}
protected function updateThreadRecord()
{
if (!$this->Thread || !$this->Thread->exists())
{
// inserting a thread, don't try to write to it
return;
}
$visibilityChange = $this->isStateChanged('message_state', 'visible');
if ($visibilityChange == 'enter')
{
$this->Thread->postAdded($this);
$this->Thread->save();
}
else if ($visibilityChange == 'leave')
{
$this->Thread->postRemoved($this);
$this->Thread->save();
}
}
protected function adjustUserMessageCountIfNeeded($amount)
{
if ($this->user_id
&& $this->User
&& !empty($this->Thread->Forum->count_messages)
&& $this->Thread->discussion_state == 'visible'
)
{
$this->User->fastUpdate('message_count', max(0, $this->User->message_count + $amount));
}
}
protected function adjustThreadUserPostCount($amount)
{
if ($this->user_id)
{
$db = $this->db();
if ($amount > 0)
{
$db->insert('xf_thread_user_post', [
'thread_id' => $this->thread_id,
'user_id' => $this->user_id,
'post_count' => $amount
], false, 'post_count = post_count + VALUES(post_count)');
}
else
{
$existingValue = $db->fetchOne("
SELECT post_count
FROM xf_thread_user_post
WHERE thread_id = ?
AND user_id = ?
", [$this->thread_id, $this->user_id]);
if ($existingValue !== null)
{
$newValue = $existingValue + $amount;
if ($newValue <= 0)
{
$this->db()->delete('xf_thread_user_post',
'thread_id = ? AND user_id = ?', [$this->thread_id, $this->user_id]
);
}
else
{
$this->db()->update('xf_thread_user_post',
['post_count' => $newValue],
'thread_id = ? AND user_id = ?', [$this->thread_id, $this->user_id]
);
}
}
}
}
}
protected function postInsertedVisible()
{
$this->adjustUserMessageCountIfNeeded(1);
$this->adjustThreadUserPostCount(1);
}
protected function postMadeVisible()
{
if ($this->isChanged('position'))
{
// if we've updated the position, we need to trust what we had is accurate...
$basePosition = $this->getExistingValue('position');
}
else
{
// ...otherwise, we should always double check the DB for the latest position since this function won't
// update cached entities
$basePosition = $this->db()->fetchOne("
SELECT position
FROM xf_post
WHERE post_id = ?
", $this->post_id);
if ($basePosition === null || $basePosition === false)
{
$basePosition = $this->getExistingValue('position');
}
// also, since we haven't changed the position yet, we need to update that
$this->fastUpdate('position', $basePosition + 1);
}
$this->db()->query("
UPDATE xf_post
SET position = position + 1
WHERE thread_id = ?
AND (
position > ?
OR (position = ? AND post_date > ?)
)
AND post_id <> ?
", [$this->thread_id, $basePosition, $basePosition, $this->post_date, $this->post_id]);
$this->adjustUserMessageCountIfNeeded(1);
$this->adjustThreadUserPostCount(1);
}
protected function postHidden($hardDelete = false)
{
if ($hardDelete || $this->isChanged('position'))
{
// if we've deleted the post or updated the position, we need to trust what we had is accurate...
$basePosition = $this->getExistingValue('position');
}
else
{
// ...otherwise, we should always double check the DB for the latest position since this function won't
// update cached entities
$basePosition = $this->db()->fetchOne("
SELECT position
FROM xf_post
WHERE post_id = ?
", $this->post_id);
if ($basePosition === null || $basePosition === false)
{
$basePosition = $this->getExistingValue('position');
}
// also, since we haven't changed the position yet, we need to update that
$this->fastUpdate('position', $basePosition - 1);
}
$this->db()->query("
UPDATE xf_post
SET position = IF(position > 0, position - 1, 0)
WHERE thread_id = ?
AND position >= ?
AND post_id <> ?
", [$this->thread_id, $basePosition, $this->post_id]);
$this->adjustUserMessageCountIfNeeded(-1);
$this->adjustThreadUserPostCount(-1);
/** @var \XF\Repository\UserAlert $alertRepo */
$alertRepo = $this->repository('XF:UserAlert');
$alertRepo->fastDeleteAlertsForContent('post', $this->post_id);
}
protected function submitHamData()
{
/** @var \XF\Spam\ContentChecker $submitter */
$submitter = $this->app()->container('spam.contentHamSubmitter');
$submitter->submitHam('post', $this->post_id);
}
protected function _preDelete()
{
// if we're deleting multiple posts, the position value we base the position recalc on in postHidden
// will be from when the entity was originally loaded, rather than what is in the database.
// we therefore need to check what the expected position is before the record is gone and ensure we use that.
$expectedPosition = $this->db()->fetchOne('SELECT position FROM xf_post WHERE post_id = ?', $this->post_id);
if ($expectedPosition != $this->position)
{
$this->setAsSaved('position', $expectedPosition);
}
}
protected function _postDelete()
{
if ($this->message_state == 'visible')
{
$this->postHidden(true);
}
$thread = $this->Thread;
if ($thread && $this->message_state == 'visible')
{
$thread->postRemoved($this);
$thread->save();
}
if ($this->message_state == 'deleted' && $this->DeletionLog)
{
$this->DeletionLog->delete();
}
if ($this->message_state == 'moderated' && $this->ApprovalQueue)
{
$this->ApprovalQueue->delete();
}
if ($thread)
{
$thread->TypeHandler->onPostDelete($thread, $this);
}
if ($this->getOption('log_moderator'))
{
$this->app()->logger()->logModeratorAction('post', $this, 'delete_hard');
}
$this->db()->delete('xf_edit_history', 'content_type = ? AND content_id = ?', ['post', $this->post_id]);
/** @var \XF\Repository\Attachment $attachRepo */
$attachRepo = $this->repository('XF:Attachment');
$attachRepo->fastDeleteContentAttachments('post', $this->post_id);
$this->_postDeleteBookmarks();
}
public function softDelete($reason = '', User $byUser = null)
{
$byUser = $byUser ?: \XF::visitor();
$thread = $this->Thread;
if ($this->isFirstPost())
{
return $thread->softDelete($reason, $byUser);
}
else
{
$db = $this->db();
$db->beginTransaction();
$rawPost = $db->fetchRow("
SELECT *
FROM xf_post
WHERE post_id = ?
FOR UPDATE
", $this->post_id);
if ($rawPost['message_state'] == 'deleted')
{
$db->commit();
return false;
}
$this->message_state = 'deleted';
/** @var \XF\Entity\DeletionLog $deletionLog */
$deletionLog = $this->getRelationOrDefault('DeletionLog');
$deletionLog->setFromUser($byUser);
$deletionLog->delete_reason = $reason;
$this->save(true, false);
$db->commit();
return true;
}
}
/**
* @param \XF\Api\Result\EntityResult $result
* @param int $verbosity
* @param array $options
*
* @api-out str $username
* @api-out bool $is_first_post
* @api-out bool $is_last_post
* @api-out bool $is_unread <cond> If accessing as a user, true if this post is unread
* @api-out str $message_parsed HTML parsed version of the message contents.
* @api-out bool $can_edit
* @api-out bool $can_soft_delete
* @api-out bool $can_hard_delete
* @api-out bool $can_react
* @api-out bool $can_view_attachments
* @api-out string $view_url
* @api-out Thread $Thread <cond> If requested by context, the thread this post is part of.
* @api-out Attachment[] $Attachments <cond> Attachments to this post, if it has any.
* @api-see XF\Entity\ReactionTrait::addReactionStateToApiResult
* @api-see XF\Entity\ContentVoteTrait::addContentVoteToApiResult
*/
protected function setupApiResultData(
\XF\Api\Result\EntityResult $result, $verbosity = self::VERBOSITY_NORMAL, array $options = []
)
{
$result->username = $this->User ? $this->User->username : $this->username;
if (!empty($options['with_thread']))
{
$result->includeRelation('Thread');
}
if ($this->attach_count)
{
// note that we allow viewing of thumbs and metadata, regardless of permissions, when viewing the
// content an attachment is connected to
$result->includeRelation('Attachments');
}
$result->message_parsed = $this->app()->bbCode()->render($this->message, 'apiHtml', 'post:api', $this);
$this->addReactionStateToApiResult($result);
$this->addContentVoteToApiResult($result);
$result->is_first_post = $this->isFirstPost();
$result->is_last_post = $this->isLastPost();
if (\XF::visitor()->user_id)
{
$result->is_unread = $this->isUnread();
}
$result->can_edit = $this->canEdit();
$result->can_soft_delete = $this->canDelete();
$result->can_hard_delete = $this->canDelete('hard');
$result->can_react = $this->canReact();
// this is repeated, mostly because attachments are post associated, even if the permission comes from above
$result->can_view_attachments = $this->Thread->canViewAttachments();
$result->view_url = $this->getContentUrl(true);
}
public function getContentUrl(bool $canonical = false, array $extraParams = [], $hash = null)
{
$route = $canonical ? 'canonical:posts' : 'posts';
return $this->app()->router('public')->buildLink($route, $this, $extraParams, $hash);
}
public function getContentPublicRoute()
{
return 'posts';
}
public function getContentTitle(string $context = '')
{
// in some situations, referencing the first post is akin
if ($this->isFirstPost())
{
return $this->Thread->getContentTitle($context);
}
return \XF::phrase('post_in_thread_x', ['title' => $this->Thread->title]);
}
public static function getStructure(Structure $structure)
{
$structure->table = 'xf_post';
$structure->shortName = 'XF:Post';
$structure->contentType = 'post';
$structure->primaryKey = 'post_id';
$structure->columns = [
'post_id' => ['type' => self::UINT, 'autoIncrement' => true, 'nullable' => true],
'thread_id' => ['type' => self::UINT, 'required' => true, 'api' => true],
'user_id' => ['type' => self::UINT, 'required' => true, 'api' => true],
'username' => ['type' => self::STR, 'maxLength' => 50,
'required' => 'please_enter_valid_name', 'api' => true
],
'post_date' => ['type' => self::UINT, 'required' => true, 'default' => \XF::$time, 'api' => true],
'message' => ['type' => self::STR,
'required' => 'please_enter_valid_message', 'api' => true
],
'ip_id' => ['type' => self::UINT, 'default' => 0],
'message_state' => ['type' => self::STR, 'default' => 'visible',
'allowedValues' => ['visible', 'moderated', 'deleted'], 'api' => true
],
'attach_count' => ['type' => self::UINT, 'max' => 65535, 'forced' => true, 'default' => 0, 'api' => true],
'warning_id' => ['type' => self::UINT, 'default' => 0],
'warning_message' => ['type' => self::STR, 'default' => '', 'maxLength' => 255, 'api' => true],
'position' => ['type' => self::UINT, 'forced' => true, 'api' => true],
'type_data' => ['type' => self::JSON_ARRAY, 'default' => []],
'last_edit_date' => ['type' => self::UINT, 'default' => 0, 'api' => true],
'last_edit_user_id' => ['type' => self::UINT, 'default' => 0],
'edit_count' => ['type' => self::UINT, 'forced' => true, 'default' => 0],
'embed_metadata' => ['type' => self::JSON_ARRAY, 'nullable' => true, 'default' => null]
];
$structure->behaviors = [
'XF:ContentVotable' => ['stateField' => 'message_state'],
'XF:Reactable' => ['stateField' => 'message_state'],
'XF:Indexable' => [
'checkForUpdates' => ['message', 'user_id', 'thread_id', 'post_date', 'message_state']
],
'XF:NewsFeedPublishable' => [
'usernameField' => 'username',
'dateField' => 'post_date'
]
];
$structure->getters = [
'Unfurls' => true,
'is_question_solution' => ['getter' => 'isQuestionSolution', 'cache' => false],
];
$structure->relations = [
'Thread' => [
'entity' => 'XF:Thread',
'type' => self::TO_ONE,
'conditions' => 'thread_id',
'primary' => true,
'with' => ['Forum', 'Forum.Node']
],
'User' => [
'entity' => 'XF:User',
'type' => self::TO_ONE,
'conditions' => 'user_id',
'primary' => true,
'api' => true
],
'Attachments' => [
'entity' => 'XF:Attachment',
'type' => self::TO_MANY,
'conditions' => [
['content_type', '=', 'post'],
['content_id', '=', '$post_id']
],
'with' => 'Data',
'order' => 'attach_date'
],
'DeletionLog' => [
'entity' => 'XF:DeletionLog',
'type' => self::TO_ONE,
'conditions' => [
['content_type', '=', 'post'],
['content_id', '=', '$post_id']
],
'primary' => true
],
'ApprovalQueue' => [
'entity' => 'XF:ApprovalQueue',
'type' => self::TO_ONE,
'conditions' => [
['content_type', '=', 'post'],
['content_id', '=', '$post_id']
],
'primary' => true
]
];
$structure->options = [
'log_moderator' => true
];
$structure->withAliases = [
'full' => [
'User',
'User.Option',
'User.Profile',
'User.Privacy',
'User.PermissionCombination', // to determine if links are trusted
function()
{
if (\XF::options()->showMessageOnlineStatus)
{
return 'User.Activity';
}
return null;
},
function()
{
$userId = \XF::visitor()->user_id;
if ($userId)
{
return [
'Reactions|' . $userId,
'Bookmarks|' . $userId
];
}
return null;
}
],
'api' => [
'User',
'User.api',
function($withParams)
{
if (!empty($withParams['thread']))
{
return ['Thread.api'];
}
return null;
},
function()
{
$userId = \XF::visitor()->user_id;
if ($userId)
{
return [
'Reactions|' . $userId,
'ContentVotes|' . $userId
];
}
return null;
}
]
];
static::addReactableStructureElements($structure);
static::addBookmarkableStructureElements($structure);
static::addVotableStructureElements($structure);
return $structure;
}
}