Файл: library/XenForo/DataWriter/DiscussionMessage.php
Строк: 1197
<?php
/**
* Data writer for discussion.
*
* @package XenForo_Discussion
*/
abstract class XenForo_DataWriter_DiscussionMessage extends XenForo_DataWriter
{
/**
* Gets the object that represents the definition of this type of message.
*
* @return XenForo_DiscussionMessage_Definition_Abstract
*/
abstract public function getDiscussionMessageDefinition();
/**
* Meta-option for discussion message submissions that are via automated means.
* Some dynamic checks are skipped (eg, message length and flood check), and
* other data is ignored (eg, IP address).
*/
const OPTION_IS_AUTOMATED = 'isAutomated';
/**
* Option that controls whether an IP address should be recorded for this message.
* Defaults to true.
*
* @var string
*/
const OPTION_SET_IP_ADDRESS = 'setIpAddress';
/**
* Option that controls whether data in the parent discussion should be updated,
* including reply counts and last post info. Defaults to true.
*
* @var string
*/
const OPTION_UPDATE_PARENT_DISCUSSION = 'updateParentDiscussion';
/**
* Option that controls whether the data in this message should be indexed for
* search. If this value is set inconsistently for the same message, data might
* be orphaned in the search index.
*
* @var string
*/
const OPTION_INDEX_FOR_SEARCH = 'indexForSearch';
/**
* Option that controls the maximum number of characters that are allowed in
* a message.
*
* @var string
*/
const OPTION_MAX_MESSAGE_LENGTH = 'maxMessageLength';
/**
* Maximum number of images allowed in a message.
*
* @var string
*/
const OPTION_MAX_IMAGES = 'maxImages';
/**
* Maximum pieces of media allowed in a message.
*
* @var string
*/
const OPTION_MAX_MEDIA = 'maxMedia';
/**
* Option that controls whether the posting user's message count will be
* changed by posting this message.
*
* @var string
*/
const OPTION_CHANGE_USER_MESSAGE_COUNT = 'changeUserMessageCount';
/**
* Option that controls whether a guest user name is checked to confirm that it
* doesn't conflict with a valid user. Defaults to true.
*
* @var string
*/
const OPTION_VERIFY_GUEST_USERNAME = 'verifyGuestUsername';
/**
* Option that controls whether this should be published in the news feed. Defaults to true.
*
* @var string
*/
const OPTION_PUBLISH_FEED = 'publishFeed';
/**
* Option that controls whether the discussion is removed if deleting the first message.
* Note that if the discussion only has the first message and it's deleted, it will always be removed.
*
* @var string
*/
const OPTION_DELETE_DISCUSSION_FIRST_MESSAGE = 'deleteDiscussionFirstMessage';
/**
* Option that controls whether edits are logged via the edit date, if supported. Defaults to true.
*
* @var string
*/
const OPTION_UPDATE_EDIT_DATE = 'logEditDate';
/**
* Option that controls how long (in seconds) the delay is before an edit log will be added (if supported/enabled).
*
* @var string
*/
const OPTION_EDIT_DATE_DELAY = 'editDateDelay';
const OPTION_LOG_EDIT = 'logEdit';
/**
* Holds the temporary hash used to pull attachments and associate them with this message.
*
* @var string
*/
const DATA_ATTACHMENT_HASH = 'attachmentHash';
/**
* Holds the reason for soft deletion.
*
* @var string
*/
const DATA_DELETE_REASON = 'deleteReason';
/**
* Default value for the change user message count option.
*
* @var boolean
*/
protected $_defaultChangeUserMessageCount = true;
/**
* Identifies if a message has a parent discussion item.
* Must overload {@see getDiscussionDataWriter()} if set to true.
*
* @var boolean
*/
protected $_hasParentDiscussion = true;
/**
* Data about the message's definition.
*
* @var XenForo_DiscussionMessage_Definition_Abstract
*/
protected $_messageDefinition = null;
/**
* Reflects important changes to the parent discussion. Current values:
* * [empty] - no change of value
* * delete - discussion has been deleted
* * firstDelete - first message has been deleted, restructure has been carried out
*
* @var string
*/
protected $_discussionChange = '';
/**
* The discussion data writer.
*
* @var XenForo_DataWriter_Discussion
*/
protected $_discussionDw = null;
/**
* The insert/update mode of the discussion data writer
*
* @var string insert|update
*/
protected $_discussionMode = null;
/**
* Controls whether edit history/logging is supported by this content type.
*
* @var boolean
*/
protected $_supportEditLog = false;
/**
* Constructor.
*
* @param mixed $errorHandler Error handler. See {@link ERROR_EXCEPTION} and related.
* @param array|null $inject Dependency injector. Array keys available: db, cache.
*/
public function __construct($errorHandler = self::ERROR_EXCEPTION, array $inject = null)
{
$this->_messageDefinition = $this->getDiscussionMessageDefinition();
$config = $this->_messageDefinition->getMessageConfiguration();
$this->_hasParentDiscussion = $config['hasParentDiscussion'];
$this->_defaultChangeUserMessageCount = $config['changeUserMessageCount'];
parent::__construct($errorHandler, $inject);
}
/**
* Gets the fields that are defined for the table. See parent for explanation.
*
* @return array
*/
protected function _getCommonFields()
{
$structure = $this->_messageDefinition->getMessageStructure();
$fields = array(
$structure['table'] => array(
$structure['key'] => array('type' => self::TYPE_UINT, 'autoIncrement' => true),
$structure['container'] => array('type' => self::TYPE_UINT, 'required' => true),
'user_id' => array('type' => self::TYPE_UINT, 'required' => true),
'username' => array('type' => self::TYPE_STRING, 'required' => true, 'maxLength' => 50,
'requiredError' => 'please_enter_valid_name'
),
'post_date' => array('type' => self::TYPE_UINT, 'required' => true, 'default' => XenForo_Application::$time),
'message' => array('type' => self::TYPE_STRING, 'required' => true,
'requiredError' => 'please_enter_valid_message'
),
'ip_id' => array('type' => self::TYPE_UINT, 'default' => 0),
'message_state' => array('type' => self::TYPE_STRING, 'default' => 'visible',
'allowedValues' => array('visible', 'moderated', 'deleted')
),
'attach_count' => array('type' => self::TYPE_UINT_FORCED, 'default' => 0, 'max' => 65535),
'likes' => array('type' => self::TYPE_UINT_FORCED, 'default' => 0),
'like_users' => array('type' => self::TYPE_SERIALIZED, 'default' => 'a:0:{}'),
'warning_id' => array('type' => self::TYPE_UINT, 'default' => 0),
'warning_message' => array('type' => self::TYPE_STRING, 'default' => ''),
)
);
if ($this->_hasParentDiscussion)
{
$fields[$structure['table']]['position'] = array('type' => self::TYPE_UINT_FORCED);
}
if ($this->_supportEditLog)
{
$fields[$structure['table']]['last_edit_date'] = array('type' => self::TYPE_UINT, 'default' => 0);
$fields[$structure['table']]['last_edit_user_id'] = array('type' => self::TYPE_UINT, 'default' => 0);
$fields[$structure['table']]['edit_count'] = array('type' => self::TYPE_UINT_FORCED, 'default' => 0);
}
return $fields;
}
/**
* Gets SQL condition to update the existing record.
*
* @return string
*/
protected function _getUpdateCondition($tableName)
{
$keyName = $this->getDiscussionMessageKeyName();
return $keyName . ' = ' . $this->_db->quote($this->getExisting($keyName));
}
/**
* Gets the default set of options for this data writer.
*
* @return array
*/
protected function _getDefaultOptions()
{
$options = XenForo_Application::get('options');
return array(
self::OPTION_SET_IP_ADDRESS => true,
self::OPTION_UPDATE_PARENT_DISCUSSION => true,
self::OPTION_INDEX_FOR_SEARCH => true,
self::OPTION_MAX_MESSAGE_LENGTH => $options->messageMaxLength,
self::OPTION_MAX_IMAGES => $options->messageMaxImages,
self::OPTION_MAX_MEDIA => $options->messageMaxMedia,
self::OPTION_CHANGE_USER_MESSAGE_COUNT => $this->_defaultChangeUserMessageCount,
self::OPTION_VERIFY_GUEST_USERNAME => true,
self::OPTION_PUBLISH_FEED => true,
self::OPTION_DELETE_DISCUSSION_FIRST_MESSAGE => true,
self::OPTION_UPDATE_EDIT_DATE => true,
self::OPTION_LOG_EDIT => $options->editHistory['enabled'],
self::OPTION_EDIT_DATE_DELAY => $options->editLogDisplay['enabled'] ? $options->editLogDisplay['delay'] * 60 : -1
);
// note: OPTION_IS_AUTOMATED is not here, see setOption()
}
/**
* Sets an option. If the IS_AUTOMATED option is specified, other options are
* set instead.
*
* @param string $name
* @param mixed $value
*/
public function setOption($name, $value)
{
if ($name === self::OPTION_IS_AUTOMATED)
{
if ($value)
{
parent::setOption(self::OPTION_SET_IP_ADDRESS, false);
parent::setOption(self::OPTION_MAX_MESSAGE_LENGTH, 0);
parent::setOption(self::OPTION_MAX_IMAGES, 0);
parent::setOption(self::OPTION_MAX_MEDIA, 0);
}
}
else
{
parent::setOption($name, $value);
}
}
/**
* Generic discussion message pre-save handler.
*/
protected final function _preSave()
{
if ($this->isInsert()
&& !$this->get('user_id') && $this->isChanged('username')
&& $this->getOption(self::OPTION_VERIFY_GUEST_USERNAME)
)
{
$userDw = XenForo_DataWriter::create('XenForo_DataWriter_User', XenForo_DataWriter::ERROR_ARRAY);
$userDw->set('username', $this->get('username'));
$userErrors = $userDw->getErrors();
if ($userErrors)
{
$this->error(reset($userErrors), 'username');
}
}
if ($this->isInsert() && !$this->isChanged('message_state'))
{
$this->set('message_state', 'visible');
}
if ($this->isInsert() && !$this->isChanged('post_date'))
{
$this->set('post_date', XenForo_Application::$time);
}
if ($this->isChanged('message'))
{
$this->_checkMessageValidity();
}
if ($this->_hasParentDiscussion)
{
$this->_checkFirstMessageState();
if (!$this->isChanged('position'))
{
$this->_setPosition();
}
}
if ($this->_supportEditLog)
{
if ($this->isUpdate() && $this->isChanged('message'))
{
if ($this->getOption(self::OPTION_UPDATE_EDIT_DATE)
&& !$this->isChanged('last_edit_date')
&& $this->getOption(self::OPTION_EDIT_DATE_DELAY) >= 0
)
{
if ($this->get('post_date') + $this->getOption(self::OPTION_EDIT_DATE_DELAY) <= XenForo_Application::$time)
{
$this->set('last_edit_date', XenForo_Application::$time);
if (!$this->isChanged('last_edit_user_id'))
{
$this->set('last_edit_user_id', XenForo_Visitor::getUserId());
}
}
}
if (!$this->isChanged('edit_count'))
{
$this->set('edit_count', $this->get('edit_count') + 1);
}
}
if ($this->isChanged('edit_count') && $this->get('edit_count') == 0)
{
$this->set('last_edit_date', 0);
}
if (!$this->get('last_edit_date'))
{
$this->set('last_edit_user_id', 0);
}
}
$this->_messagePreSave();
}
/**
* Check that the contents of the message are valid, based on length, images, etc.
*/
protected function _checkMessageValidity()
{
$message = $this->get('message');
$maxLength = $this->getOption(self::OPTION_MAX_MESSAGE_LENGTH);
if ($maxLength && utf8_strlen($message) > $maxLength)
{
$this->error(new XenForo_Phrase('please_enter_message_with_no_more_than_x_characters', array('count' => $maxLength)), 'message');
}
else
{
$maxImages = $this->getOption(self::OPTION_MAX_IMAGES);
$maxMedia = $this->getOption(self::OPTION_MAX_MEDIA);
if ($maxImages || $maxMedia)
{
$formatter = XenForo_BbCode_Formatter_Base::create('ImageCount', false);
$parser = XenForo_BbCode_Parser::create($formatter);
$parser->render($message);
if ($maxImages && $formatter->getImageCount() > $maxImages)
{
$this->error(new XenForo_Phrase('please_enter_message_with_no_more_than_x_images', array('count' => $maxImages)), 'message');
}
if ($maxMedia && $formatter->getMediaCount() > $maxMedia)
{
$this->error(new XenForo_Phrase('please_enter_message_with_no_more_than_x_media', array('count' => $maxMedia)), 'message');
}
}
}
}
/**
* Sets the message's position within the discussion.
*/
protected function _setPosition()
{
if ($this->isInsert() || $this->isChanged('message_state'))
{
$discussionDw = $this->getDiscussionDataWriter();
if ($this->isInsert())
{
if (!$discussionDw || $discussionDw->isInsert())
{
// likely the first message
$this->set('position', 0);
}
else
{
if ($this->get('post_date') < $discussionDw->get('last_post_date'))
{
// TODO: this doesn't deal with inserting a message in the middle of a discussion
throw new XenForo_Exception('Cannot insert a message in the middle of a discussion.');
}
$discussion = $discussionDw->getDiscussionForUpdate();
$replyCount = ($discussion && isset($discussion['reply_count'])
? $discussion['reply_count']
: $discussionDw->get('reply_count')
);
if ($this->get('message_state') == 'visible')
{
$this->set('position', $replyCount + 1);
}
else
{
$this->set('position', $replyCount);
}
}
}
else
{
// updated the state on an existing message -- need to slot in
if ($this->get('message_state') == 'visible' && $this->getExisting('message_state') != 'visible')
{
$this->set('position', $this->get('position') + 1);
}
else if ($this->get('message_state') != 'visible' && $this->getExisting('message_state') == 'visible')
{
$this->set('position', $this->get('position') - 1);
}
}
}
}
/**
* Checks that the first message has a valid state. Normally,
* the first message must always be visible. If a non-visible
* first message is required, change the discussion.
*/
protected function _checkFirstMessageState()
{
if ($this->get('message_state') != 'visible')
{
if ($this->isUpdate() && $this->get('position') > 0)
{
// position > 0 means this isn't the first post
return;
}
$discussionDw = $this->getDiscussionDataWriter();
if (!$discussionDw)
{
// debugging message, no need for phrasing
$this->error('The first message of a discussion must be visible.');
}
else if ($discussionDw->isInsert() || $discussionDw->get('first_post_id') == $this->getDiscussionMessageId())
{
// treat first message state as the discussion state
$discussionDw->set('discussion_state', $this->get('message_state'));
$this->set('message_state', 'visible');
// in case we're deleting the discussion, push the reason up
$discussionDw->setExtraData(XenForo_DataWriter_Discussion::DATA_DELETE_REASON,
$this->getExtraData(self::DATA_DELETE_REASON)
);
}
}
}
/**
* Designed to be overridden by child classes
*/
protected function _messagePreSave()
{
}
/**
* Generic discussion message post-save handler.
*/
protected final function _postSave()
{
if ($this->isInsert() && $this->getOption(self::OPTION_SET_IP_ADDRESS) && !$this->get('ip_id'))
{
$this->_updateIpData();
}
$attachmentHash = $this->getExtraData(self::DATA_ATTACHMENT_HASH);
if ($attachmentHash)
{
$this->_associateAttachments($attachmentHash);
}
if ($this->_hasParentDiscussion && $this->getOption(self::OPTION_UPDATE_PARENT_DISCUSSION))
{
$this->_updateDiscussionPostSave();
}
if ($this->_hasParentDiscussion && $this->isUpdate() && $this->isChanged('message_state'))
{
$this->_updateMessagePositionList();
}
$this->_updateDeletionLog();
$this->_updateModerationQueue();
$this->_submitSpamLog();
if ($this->get('user_id') && $this->isChanged('message_state')
)
{
if ($this->getOption(self::OPTION_CHANGE_USER_MESSAGE_COUNT))
{
$this->_updateUserMessageCount();
}
if ($this->get('likes'))
{
$this->_updateUserLikeCount();
}
}
if ($this->_supportEditLog
&& $this->getOption(self::OPTION_LOG_EDIT)
&& $this->isUpdate() && $this->isChanged('edit_count') && $this->get('edit_count'))
{
$this->_insertEditHistory();
}
if ($this->isUpdate() && $this->isChanged('message'))
{
$this->getModelFromCache('XenForo_Model_BbCode')->deleteBbCodeParseCacheForContent(
$this->getContentType(), $this->getDiscussionMessageId()
);
}
if ($this->getOption(self::OPTION_INDEX_FOR_SEARCH))
{
$this->_indexForSearch();
}
$this->_publishAndNotify();
$this->_messagePostSave();
$this->_saveDiscussionDataWriter();
}
/**
* Updates the discussion container info.
*/
protected function _updateDiscussionPostSave()
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw && $discussionDw->isUpdate())
{
$discussionDw->updateCountersAfterMessageSave($this);
}
}
/**
* Updates the position list based on state changes.
*/
protected function _updateMessagePositionList()
{
if ($this->get('message_state') == 'visible' && $this->getExisting('message_state') != 'visible')
{
$this->_adjustPositionListForInsert();
}
else if ($this->get('message_state') != 'visible' && $this->getExisting('message_state') == 'visible')
{
$this->_adjustPositionListForRemoval();
}
}
/**
* Upates the IP data.
*/
protected function _updateIpData()
{
if (!empty($this->_extraData['ipAddress']))
{
$ipAddress = $this->_extraData['ipAddress'];
}
else
{
$ipAddress = null;
}
$ipId = XenForo_Model_Ip::log(
$this->get('user_id'), $this->getContentType(), $this->getDiscussionMessageId(), 'insert', $ipAddress
);
$this->set('ip_id', $ipId, '', array('setAfterPreSave' => true));
// TODO: ideally, this can be consolidated with other post-save message updates (see associateAttachments)
$this->_db->update($this->getDiscussionMessageTableName(), array(
'ip_id' => $ipId
), $this->getDiscussionMessageKeyName() . ' = ' . $this->_db->quote($this->getDiscussionMessageId()));
}
/**
* Associates attachments with this message.
*
* @param string $attachmentHash
*/
protected function _associateAttachments($attachmentHash)
{
$rows = $this->_db->update('xf_attachment', array(
'content_type' => $this->getContentType(),
'content_id' => $this->getDiscussionMessageId(),
'temp_hash' => '',
'unassociated' => 0
), 'temp_hash = ' . $this->_db->quote($attachmentHash));
if ($rows)
{
// TODO: ideally, this can be consolidated with other post-save message updates (see updateIpData)
$this->set('attach_count', $this->get('attach_count') + $rows, '', array('setAfterPreSave' => true));
$this->_db->update($this->getDiscussionMessageTableName(), array(
'attach_count' => $this->get('attach_count')
), $this->getDiscussionMessageKeyName() . ' = ' . $this->_db->quote($this->getDiscussionMessageId()));
}
}
/**
* Updates the deletion log if necessary.
*/
protected function _updateDeletionLog()
{
if (!$this->isChanged('message_state'))
{
return;
}
if ($this->get('message_state') == 'deleted')
{
$reason = $this->getExtraData(self::DATA_DELETE_REASON);
$this->getModelFromCache('XenForo_Model_DeletionLog')->logDeletion(
$this->getContentType(), $this->getDiscussionMessageId(), $reason
);
}
else if ($this->getExisting('message_state') == 'deleted')
{
$this->getModelFromCache('XenForo_Model_DeletionLog')->removeDeletionLog(
$this->getContentType(), $this->getDiscussionMessageId()
);
}
}
/**
* Updates the moderation queue if necessary.
*/
protected function _updateModerationQueue()
{
if (!$this->isChanged('message_state'))
{
return;
}
if ($this->get('message_state') == 'moderated' )
{
$this->getModelFromCache('XenForo_Model_ModerationQueue')->insertIntoModerationQueue(
$this->getContentType(), $this->getDiscussionMessageId(), $this->get('post_date')
);
}
else if ($this->getExisting('message_state') == 'moderated')
{
$this->getModelFromCache('XenForo_Model_ModerationQueue')->deleteFromModerationQueue(
$this->getContentType(), $this->getDiscussionMessageId()
);
}
}
protected function _submitSpamLog()
{
if ($this->getExisting('message_state') == 'moderated' && $this->get('message_state') == 'visible')
{
/** @var $spamModel XenForo_Model_SpamPrevention */
$spamModel = $this->getModelFromCache('XenForo_Model_SpamPrevention');
$spamModel->submitHamCommentData($this->getContentType(), $this->getDiscussionMessageId());
}
}
/**
* Updates the search index for this message.
*/
protected function _indexForSearch()
{
if ($this->get('message_state') == 'visible')
{
if ($this->getExisting('message_state') != 'visible' || $this->isChanged('message'))
{
$this->_insertOrUpdateSearchIndex();
}
}
else if ($this->isUpdate() && $this->get('message_state') != 'visible' && $this->getExisting('message_state') == 'visible')
{
$this->_deleteFromSearchIndex();
}
}
/**
* Inserts or updates a record in the search index for this message.
*/
protected function _insertOrUpdateSearchIndex()
{
$dataHandler = $this->_messageDefinition->getSearchDataHandler();
if (!$dataHandler)
{
return;
}
$indexer = new XenForo_Search_Indexer();
$dataHandler->insertIntoIndex($indexer, $this->getMergedData(), $this->getDiscussionData());
}
/**
* Inserts a record for the edit history.
*/
protected function _insertEditHistory()
{
$historyDw = XenForo_DataWriter::create('XenForo_DataWriter_EditHistory', XenForo_DataWriter::ERROR_SILENT);
$historyDw->bulkSet(array(
'content_type' => $this->getContentType(),
'content_id' => $this->getDiscussionMessageId(),
'edit_user_id' => XenForo_Visitor::getUserId(),
'old_text' => $this->getExisting('message')
));
$historyDw->save();
}
/**
* Designed to be overridden by child classes
*/
protected function _messagePostSave()
{
}
/**
* Generic discussion message pre-delete handler.
*/
protected final function _preDelete()
{
$this->_messagePreDelete();
}
/**
* Designed to be overridden by child classes
*/
protected function _messagePreDelete()
{
}
/**
* Generic discussion message post-delete handler.
*/
protected final function _postDelete()
{
if ($this->_hasParentDiscussion && $this->getOption(self::OPTION_UPDATE_PARENT_DISCUSSION))
{
$this->_updateDiscussionPostDelete();
}
// firstDelete would trigger this
if (!$this->discussionDeleted())
{
if ($this->_hasParentDiscussion)
{
$this->_adjustPositionListForRemoval();
}
}
$this->getModelFromCache('XenForo_Model_DeletionLog')->removeDeletionLog(
$this->getContentType(), $this->getDiscussionMessageId()
);
$this->getModelFromCache('XenForo_Model_ModerationQueue')->deleteFromModerationQueue(
$this->getContentType(), $this->getDiscussionMessageId()
);
$this->getModelFromCache('XenForo_Model_EditHistory')->deleteEditHistoryForContent(
$this->getContentType(), $this->getDiscussionMessageId()
);
$this->getModelFromCache('XenForo_Model_BbCode')->deleteBbCodeParseCacheForContent(
$this->getContentType(), $this->getDiscussionMessageId()
);
if ($this->get('attach_count'))
{
$this->_deleteAttachments();
}
if ($this->get('user_id'))
{
if ($this->getOption(self::OPTION_CHANGE_USER_MESSAGE_COUNT))
{
$this->_updateUserMessageCount(true);
}
}
if ($this->getOption(self::OPTION_INDEX_FOR_SEARCH))
{
$this->_deleteFromSearchIndex();
}
if ($this->get('likes'))
{
$this->_deleteLikes();
}
$this->_messagePostDelete();
$this->_deleteFromNewsFeed();
if (!$this->discussionDeleted())
{
$this->_saveDiscussionDataWriter();
}
}
/**
* Updates data in the discussion after the message is deleted.
* This may cause the entire discussion to be deleted.
*/
protected function _updateDiscussionPostDelete()
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw)
{
$deleteIfFirst = $this->getOption(self::OPTION_DELETE_DISCUSSION_FIRST_MESSAGE);
$discussionChange = $discussionDw->updateCountersAfterMessageDelete($this, $deleteIfFirst);
$this->_discussionChange = $discussionChange;
if ($discussionChange == 'delete')
{
$discussionDw->delete();
}
}
}
/**
* Deletes the attachments associated with this message.
*/
protected function _deleteAttachments()
{
$this->getModelFromCache('XenForo_Model_Attachment')->deleteAttachmentsFromContentIds(
$this->getContentType(),
array($this->getDiscussionMessageId())
);
}
/**
* Updates the user message count.
*
* @param boolean $isDelete True if hard deleting the message
*/
protected function _updateUserMessageCount($isDelete = false)
{
if ($this->_hasParentDiscussion)
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw && $discussionDw->get('discussion_state') != 'visible')
{
return;
}
}
if ($this->getExisting('message_state') == 'visible'
&& ($this->get('message_state') != 'visible' || $isDelete)
)
{
$this->_db->query('
UPDATE xf_user
SET message_count = IF(message_count > 0, message_count - 1, 0)
WHERE user_id = ?
', $this->get('user_id'));
}
else if ($this->get('message_state') == 'visible' && $this->getExisting('message_state') != 'visible')
{
$this->_db->query('
UPDATE xf_user
SET message_count = message_count + 1
WHERE user_id = ?
', $this->get('user_id'));
}
}
/**
* Updates the user like count.
*
* @param boolean $isDelete True if hard deleting the message
*/
protected function _updateUserLikeCount($isDelete = false)
{
$likes = $this->get('likes');
if (!$likes)
{
return;
}
if ($this->_hasParentDiscussion)
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw && $discussionDw->get('discussion_state') != 'visible')
{
return;
}
}
if ($this->getExisting('message_state') == 'visible'
&& ($this->get('message_state') != 'visible' || $isDelete)
)
{
$this->_db->query('
UPDATE xf_user
SET like_count = IF(like_count > ?, like_count - ?, 0)
WHERE user_id = ?
', array($likes, $likes, $this->get('user_id')));
}
else if ($this->get('message_state') == 'visible' && $this->getExisting('message_state') != 'visible')
{
$this->_db->query('
UPDATE xf_user
SET like_count = like_count + ?
WHERE user_id = ?
', array($likes, $this->get('user_id')));
}
}
/**
* Deletes this message from the search index.
*/
protected function _deleteFromSearchIndex()
{
$dataHandler = $this->_messageDefinition->getSearchDataHandler();
if (!$dataHandler)
{
return;
}
$indexer = new XenForo_Search_Indexer();
$dataHandler->deleteFromIndex($indexer, $this->getMergedData());
}
/**
* Delete all like entries for content.
*/
protected function _deleteLikes()
{
$updateUserLikeCounter = ($this->get('message_state') == 'visible');
if ($updateUserLikeCounter && $this->_hasParentDiscussion)
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw && $discussionDw->get('discussion_state') != 'visible')
{
$updateUserLikeCounter = false;
}
}
$this->getModelFromCache('XenForo_Model_Like')->deleteContentLikes(
$this->getContentType(), $this->getDiscussionMessageId(), $updateUserLikeCounter
);
}
/**
* Designed to be overridden by child classes
*/
protected function _messagePostDelete()
{
}
/**
* Adjust the position list surrounding this message, when this message
* has been put from a position that "counts" (removed or hidden).
*/
protected function _adjustPositionListForInsert()
{
if ($this->get('message_state') != 'visible')
{
// only renumber if becoming visible
return;
}
$containerKey = $this->getContainerKeyName();
$containerCondition = "$containerKey = " . $this->get($containerKey);
$positionQuoted = $this->_db->quote($this->getExisting('position'));
$postDateQuoted = $this->_db->quote($this->get('post_date'));
$messageKeyCondition = $this->getDiscussionMessageKeyName() . ' <> ' . $this->_db->quote($this->getDiscussionMessageId());
$this->_db->query("
UPDATE " . $this->getDiscussionMessageTableName() . "
SET position = position + 1
WHERE $containerCondition
AND (position > $positionQuoted
OR (position = $positionQuoted AND post_date > $postDateQuoted)
)
AND $messageKeyCondition
");
}
/**
* Adjust the position list surrounding this message, when this message
* has been removed from a position that "counts" (removed or hidden).
*/
protected function _adjustPositionListForRemoval()
{
if ($this->getExisting('message_state') != 'visible')
{
// no need to renumber after removal something that didn't count
return;
}
$containerKey = $this->getContainerKeyName();
$containerCondition = "$containerKey = " . $this->get($containerKey);
$messageKeyCondition = $this->getDiscussionMessageKeyName() . ' <> ' . $this->_db->quote($this->getDiscussionMessageId());
$this->_db->query('
UPDATE ' . $this->getDiscussionMessageTableName() . '
SET position = IF(position > 0, position - 1, 0)
WHERE ' . $containerCondition . '
AND position >= ?
AND ' . $messageKeyCondition . '
', $this->getExisting('position'));
}
/**
* Gets the discussion data writer. Note that if the container value changes,
* this cache will not be removed.
*
* @return XenForo_DataWriter_Discussion|false
*/
public function getDiscussionDataWriter()
{
if (!$this->_hasParentDiscussion)
{
return false;
}
if ($this->_discussionDw === null)
{
$containerId = $this->get($this->getContainerKeyName());
if (!$containerId)
{
$this->_discussionDw = false;
}
else
{
$this->_discussionDw = $this->_messageDefinition->getDiscussionDataWriter($containerId, $this->_errorHandler);
if ($this->_discussionDw && $this->_discussionMode === null)
{
$this->_discussionMode = 'update';
}
}
}
return $this->_discussionDw;
}
/**
* Sets the data writer for the discussion this message is in--or will be in.
*
* @param XenForo_DataWriter_Discussion|null $discussionDw
* @param boolean True if $discussionDataWriter->isInsert()
*/
public function setDiscussionDataWriter(XenForo_DataWriter_Discussion $discussionDw = null, $isInsert = null)
{
$this->_discussionDw = $discussionDw;
if ($isInsert !== null)
{
$this->_discussionMode = ($isInsert ? 'insert' : 'update');
}
}
/**
* Saves the discussion data writer if it exists and has changed.
*/
protected function _saveDiscussionDataWriter()
{
if ($this->_discussionDw && $this->_discussionDw->hasChanges())
{
$this->_discussionDw->save();
}
}
/**
* Gets the data about the discussion this message is in. This may use the
* discussion data writer, or some other source if needed.
*
* @return array|null
*/
public function getDiscussionData()
{
if ($this->_hasParentDiscussion)
{
$discussionDw = $this->getDiscussionDataWriter();
if ($discussionDw)
{
return $discussionDw->getMergedData();
}
}
return null;
}
/**
* Returns true if the parent discussion has been deleted (instead of this).
*
* @return boolean
*/
public function discussionDeleted()
{
return ($this->_discussionChange == 'delete');
}
/**
* Gets the current value of the discussion message ID for this message.
*
* @return integer
*/
public function getDiscussionMessageId()
{
return $this->get($this->getDiscussionMessageKeyName());
}
/**
* Returns true if this is the first message in a newly inserted discussion
*
* @return boolean
*/
public function isDiscussionFirstMessage()
{
return ($this->_discussionMode == 'insert');
}
/**
* Called during post-save, handles publishing to news feed
* and sending notifications
*/
protected function _publishAndNotify()
{
if ($this->isInsert() && !$this->isDiscussionFirstMessage() && $this->getOption(self::OPTION_PUBLISH_FEED))
{
$this->_publishToNewsFeed();
}
}
/**
* Publishes an insert or update event to the news feed
*/
protected function _publishToNewsFeed()
{
$this->_getNewsFeedModel()->publish(
$this->get('user_id'),
$this->get('username'),
$this->getContentType(),
$this->getDiscussionMessageId(),
($this->isUpdate() ? 'update' : 'insert')
);
}
/**
* Removes an already published news feed item
*/
protected function _deleteFromNewsFeed()
{
$this->_getNewsFeedModel()->delete(
$this->getContentType(),
$this->getDiscussionMessageId()
);
}
/**
* The name of the table that holds this type of discussion message.
*
* @return string
*/
public function getDiscussionMessageTableName()
{
return $this->_messageDefinition->getMessageTableName();
}
/**
* The name of the discussion message primary key. Must be an auto increment column.
*
* @return string
*/
public function getDiscussionMessageKeyName()
{
return $this->_messageDefinition->getMessageKeyName();
}
/**
* Gets the field name of the container this message belongs to. This may
* be a discussion (eg, thread) or something more general (a user for profile posts).
*
* @return string
*/
public function getContainerKeyName()
{
return $this->_messageDefinition->getContainerKeyName();
}
/**
* Gets the content type for tables that contain multiple data types together.
*
* @return string
*/
public function getContentType()
{
return $this->_messageDefinition->getContentType();
}
}