Файл: library/XenForo/DataWriter/Discussion.php
Строк: 1169
<?php
/**
* Data writer for templates.
*
* @package XenForo_Discussion
*/
abstract class XenForo_DataWriter_Discussion extends XenForo_DataWriter
{
/**
* Gets the object that represents the definition of this type of discussion.
*
* @return XenForo_Discussion_Definition_Abstract
*/
abstract public function getDiscussionDefinition();
/**
* Gets the object that represents the definition of the message within this discussion.
*
* @return XenForo_DiscussionMessage_Definition_Abstract
*/
abstract public function getDiscussionMessageDefinition();
/**
* Get information about the last message in this discussion. It is expected to either
* be an an empty array (or false) or contain standard discussion message fields.
*
* @return array|false
*/
abstract protected function _getLastMessageInDiscussion();
/**
* Gets simple information about all messages in this discussion. Fields are assumed
* to be the standard discussion message fields, not including the actual message unless
* specifically requested.
*
* @param boolean $includeMessage If true, includes the message contents
*
* @return array Format: [discussion message id] => info
*/
abstract protected function _getMessagesInDiscussionSimple($includeMessage = false);
/**
* Rebuilds counters and position lists for this discussion.
*
* @return boolean True if the results are valid; false otherwise (if false, discussion can be removed)
*/
abstract public function rebuildDiscussion();
/**
* Gets the IDs of all messages in this discussion. Designed to be overridden.
*
* @return array
*/
protected function _getMessageIdsInDiscussion()
{
return array_keys($this->_getDiscussionMessages(false));
}
/**
* Option to control whether a first message is required on insert of a new discussion.
* An example of this is requiring the first post when creating a thread. Generally,
* this will remain at the default, but certain applications will need to create a
* discussion before the child messages. Default is true.
*
* @var string
*/
const OPTION_REQUIRE_INSERT_FIRST_MESSAGE = 'requireInsertFirstMessage';
/**
* Option that controls whether the data in this discussion should be indexed for
* search. If this value is set inconsistently for the same discussion (and messages within),
* data might be orphaned in the search index. Defaults to true.
*
* @var string
*/
const OPTION_INDEX_FOR_SEARCH = 'indexForSearch';
/**
* Option that controls whether the posting user's message count will be
* changed by posting this message. Defaults to true.
*
* @var string
*/
const OPTION_CHANGE_USER_MESSAGE_COUNT = 'changeUserMessageCount';
/**
* Option that controls what to do with the case of discussion titles. Defaults
* to option value.
*
* @var string
*/
const OPTION_ADJUST_TITLE_CASE = 'adjustTitleCase';
/**
* Controls whether the container (eg, forum) data is updated. Defaults to true.
*
* @var string
*/
const OPTION_UPDATE_CONTAINER = 'updateContainer';
/**
* Option that controls whether this should be published in the news feed. Defaults to true.
*
* @var string
*/
const OPTION_PUBLISH_FEED = 'publishFeed';
/**
* Controls whether the title will automatically be trimmed to fix the max length
*/
const OPTION_TRIM_TITLE = 'trimTitle';
/**
* 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 discussion has a parent container item.
* Must overload {@see _getContainerDataWriter} if set to true.
*
* @var boolean
*/
protected $_hasParentContainer = true;
/**
* Data about the discussion's definition.
*
* @var XenForo_DiscussionMessage_Definition_Abstract
*/
protected $_discussionDefinition = null;
/**
* Data about the definition of messages within.
*
* @var XenForo_DiscussionMessage_Definition_Abstract
*/
protected $_messageDefinition = null;
/**
* Data writer for the first message in this discussion.
*
* @var XenForo_DataWriter_DiscussionMessage|null
*/
protected $_firstMessageDw = null;
/**
* Constructor.
*
* @param constant Error handler. See {@link ERROR_EXCEPTION} and related.
* @param array|null Dependency injector. Array keys available: db, cache.
*/
public function __construct($errorHandler = self::ERROR_EXCEPTION, array $inject = null)
{
$this->_discussionDefinition = $this->getDiscussionDefinition();
$config = $this->_discussionDefinition->getDiscussionConfiguration();
$this->_hasParentContainer = $config['hasParentContainer'];
$this->_defaultChangeUserMessageCount = $config['changeUserMessageCount'];
$this->_messageDefinition = $this->getDiscussionMessageDefinition();
parent::__construct($errorHandler, $inject);
}
/**
* Gets the fields that are defined for the table. See parent for explanation.
*
* @return array
*/
protected function _getCommonFields()
{
$structure = $this->_discussionDefinition->getDiscussionStructure();
return array(
$structure['table'] => array(
$structure['key'] => array('type' => self::TYPE_UINT, 'autoIncrement' => true),
$structure['container'] => array('type' => self::TYPE_UINT, 'required' => true),
'title' => array('type' => self::TYPE_STRING, 'required' => true, 'maxLength' => 100,
'verification' => array('$this', '_verifyTitle'), 'requiredError' => 'please_enter_valid_title'
),
'reply_count' => array('type' => self::TYPE_UINT_FORCED, 'default' => 0),
'view_count' => array('type' => self::TYPE_UINT_FORCED, 'default' => 0),
'user_id' => array('type' => self::TYPE_UINT, 'required' => true),
'username' => array('type' => self::TYPE_STRING, 'required' => true, 'maxLength' => 50),
'post_date' => array('type' => self::TYPE_UINT, 'default' => 0),
'sticky' => array('type' => self::TYPE_BOOLEAN, 'default' => 0),
'discussion_state' => array('type' => self::TYPE_STRING, 'default' => 'visible',
'allowedValues' => array('visible', 'moderated', 'deleted')
),
'discussion_open' => array('type' => self::TYPE_BOOLEAN, 'default' => 1),
'discussion_type' => array('type' => self::TYPE_STRING, 'default' => '', 'maxLength' => 25),
'first_post_id' => array('type' => self::TYPE_UINT, 'default' => 0),
'last_post_date' => array('type' => self::TYPE_UINT, 'default' => 0),
'last_post_id' => array('type' => self::TYPE_UINT, 'default' => 0),
'last_post_user_id' => array('type' => self::TYPE_UINT, 'default' => 0),
'last_post_username' => array('type' => self::TYPE_STRING, 'default' => '', 'maxLength' => 50),
)
);
}
/**
* Gets SQL condition to update the existing record.
*
* @return string
*/
protected function _getUpdateCondition($tableName)
{
$keyName = $this->getDiscussionKeyName();
return $keyName . ' = ' . $this->_db->quote($this->getExisting($keyName));
}
/**
* Gets the default set of options for this data writer.
*
* @return array
*/
protected function _getDefaultOptions()
{
return array(
self::OPTION_REQUIRE_INSERT_FIRST_MESSAGE => true,
self::OPTION_INDEX_FOR_SEARCH => true,
self::OPTION_CHANGE_USER_MESSAGE_COUNT => $this->_defaultChangeUserMessageCount,
self::OPTION_ADJUST_TITLE_CASE => XenForo_Application::get('options')->adjustTitleCase,
self::OPTION_UPDATE_CONTAINER => true,
self::OPTION_PUBLISH_FEED => true,
self::OPTION_TRIM_TITLE => false
);
}
/**
* Gets a data writer that represents the first message. This is
* primarily used for inserts, but may also be used for updates.
*
* @return XenForo_DataWriter_DiscussionMessage
*/
public function getFirstMessageDw()
{
if (!$this->_firstMessageDw)
{
$this->_firstMessageDw = $this->_discussionDefinition->getFirstMessageDataWriter(
$this->get('first_post_id'), $this->_errorHandler
);
$this->_firstMessageDw->setDiscussionDataWriter($this, $this->isInsert());
}
return $this->_firstMessageDw;
}
/**
* Verifies that the discussion title is valid
*
* @param string
*
* @return boolean
*/
protected function _verifyTitle(&$title)
{
// TODO: send these to callbacks to allow hookability?
switch ($this->getOption(self::OPTION_ADJUST_TITLE_CASE))
{
case 'ucfirst': // sentence case
$title = utf8_ucfirst(utf8_strtolower($title));
break;
case 'ucwords': // title case
$title = utf8_ucwords(utf8_strtolower($title));
break;
}
if ($this->getOption(self::OPTION_TRIM_TITLE))
{
$table = reset($this->_fields);
$title = XenForo_Helper_String::wholeWordTrim($title, $table['title']['maxLength'] - 5);
}
return true;
}
/**
* Generic Discussion Message Pre Save handler
*/
protected final function _preSave()
{
if ($this->isInsert() && $this->getOption(self::OPTION_REQUIRE_INSERT_FIRST_MESSAGE) && !$this->_firstMessageDw)
{
throw new XenForo_Exception('A discussion insert was attempted without the required first message.');
}
if ($this->isInsert() && !$this->isChanged('discussion_state'))
{
$this->set('discussion_state', 'visible');
}
$this->_setDynamicFieldDefaults();
$this->_discussionPreSave();
if ($this->_firstMessageDw)
{
$this->_syncFirstMessageDw();
$this->_preSaveFirstMessageDw();
}
}
/**
* Synchronizes the first message DW with data set in this discussions before saving.
* By default, this assumes that fields with matching names are the same between tables.
*/
protected function _syncFirstMessageDw()
{
if ($this->isInsert())
{
// this will be corrected in post-save (before the message is inserted)
$this->_firstMessageDw->set($this->_firstMessageDw->getContainerKeyName(), 0);
}
foreach ($this->_newData AS $table => $newData)
{
foreach ($newData AS $field => $value)
{
$this->_firstMessageDw->set($field, $value, '', array('ignoreInvalidFields' => true));
}
}
}
/**
* Validate that the first message DW is saveable and merge any errors into this DW.
*/
protected function _preSaveFirstMessageDw()
{
$messageDw = $this->_firstMessageDw;
$messageDw->preSave();
$firstMessageErrors = $messageDw->getErrors();
if ($firstMessageErrors)
{
$this->_errors = array_merge($this->_errors, $firstMessageErrors);
}
}
/**
* Sets the pre-save defaults for fields with dynamic default values.
*/
protected function _setDynamicFieldDefaults()
{
if (!$this->get('post_date'))
{
$this->set('post_date', XenForo_Application::$time);
}
if (!$this->get('last_post_date'))
{
$this->set('last_post_date', $this->get('post_date'));
$this->set('last_post_user_id', $this->get('user_id'));
$this->set('last_post_username', $this->get('username'));
}
}
/**
* Designed to be overridden by child classes
*/
protected function _discussionPreSave()
{
}
/**
* Cache of the messages in this discussion so they're only retrieved when needed
*
* @var array
*/
protected $_messageCache = array();
/**
* Gets all messages in this discussion, in order
*
* @param bool $includeMessage
* @return array
*/
protected function _getDiscussionMessages($includeMessage = false)
{
$cacheKey = 'messages' . ($includeMessage ? '-message' : '');
if (!isset($this->_messageCache[$cacheKey]))
{
$this->_messageCache[$cacheKey] = $this->_getMessagesInDiscussionSimple($includeMessage);
}
return $this->_messageCache[$cacheKey];
}
protected function _getDiscussionMessageIds()
{
if (!isset($this->_messageCache['messageIds']))
{
if (isset($this->_messageCache['messages']))
{
$this->_messageCache['messageIds'] = array_keys($this->_messageCache['messages']);
}
else
{
$this->_messageCache['messageIds'] = $this->_getMessageIdsInDiscussion();
}
}
return $this->_messageCache['messageIds'];
}
/**
* Generic Discussion Message Post Save handler
*/
protected final function _postSave()
{
if ($this->_firstMessageDw)
{
$this->_saveFirstMessageDw();
}
if ($this->_hasParentContainer && $this->getOption(self::OPTION_UPDATE_CONTAINER))
{
$this->_updateContainerPostSave();
}
$this->_updateDeletionLog();
$this->_updateModerationQueue();
$this->_submitSpamLog();
if ($this->isChanged('discussion_state') && $this->isUpdate())
{
if ($this->getOption(self::OPTION_CHANGE_USER_MESSAGE_COUNT))
{
$this->_updateUserMessageCount();
}
$this->_updateUserLikeCount();
}
if ($this->getOption(self::OPTION_INDEX_FOR_SEARCH))
{
$this->_indexForSearch();
}
$this->_discussionPostSave();
}
/**
* Saves the first message DW and merges and required data from it back to this
* (eg, first post ID).
*/
protected function _saveFirstMessageDw()
{
$messageDw = $this->_firstMessageDw;
if ($this->isInsert())
{
$messageDw->setOption(XenForo_DataWriter_DiscussionMessage::OPTION_UPDATE_PARENT_DISCUSSION, false);
$discussionId = $this->get($this->getDiscussionKeyName());
$messageDw->set($messageDw->getContainerKeyName(), $discussionId, '', array('setAfterPreSave' => true));
}
if ($messageDw->hasChanges())
{
// must clear out DW, as the message will try to save it and possibly cause conflicts
$messageDw->setDiscussionDataWriter(null, $this->isInsert());
$messageDw->save();
}
if ($this->isInsert())
{
$messageId = $messageDw->getDiscussionMessageId();
// note: it is assumed that the other last post info will have been handled by this DW
$toUpdate = array(
'first_post_id' => $messageId,
'last_post_id' => $messageId
);
$keyName = $this->getDiscussionKeyName();
$condition = $keyName . ' = ' . $this->_db->quote($this->get($keyName));
$this->_db->update($this->getDiscussionTableName(), $toUpdate, $condition);
$this->bulkSet($toUpdate, array('setAfterPreSave' => true));
}
if ($this->getOption(self::OPTION_PUBLISH_FEED))
{
$this->_publishToNewsFeed();
}
}
/**
* Updates the necessary data in the container.
*/
protected function _updateContainerPostSave()
{
$containerKey = $this->getContainerKeyName();
if ($this->isUpdate() && $this->isChanged($containerKey))
{
// this is a move. move is like: inserting into new container...
$newContainerDw = $this->_discussionDefinition->getContainerDataWriter($this->get($containerKey), $this->_errorHandler);
if ($newContainerDw)
{
$newContainerDw->updateCountersAfterDiscussionSave($this, true);
if ($newContainerDw->hasChanges())
{
$newContainerDw->save();
}
}
// ...and deleting from old container
$oldContainerDw = $this->_discussionDefinition->getContainerDataWriter($this->getExisting($containerKey), $this->_errorHandler);
if ($oldContainerDw)
{
$oldContainerDw->updateCountersAfterDiscussionDelete($this);
if ($oldContainerDw->hasChanges())
{
$oldContainerDw->save();
}
}
}
else
{
$containerDw = $this->_discussionDefinition->getContainerDataWriter($this->get($containerKey), $this->_errorHandler);
if ($containerDw)
{
$containerDw->updateCountersAfterDiscussionSave($this);
if ($containerDw->hasChanges())
{
$containerDw->save();
}
}
}
}
/**
* Updates the deletion log if necessary.
*/
protected function _updateDeletionLog()
{
if (!$this->isChanged('discussion_state'))
{
return;
}
if ($this->get('discussion_state') == 'deleted')
{
$reason = $this->getExtraData(self::DATA_DELETE_REASON);
$this->getModelFromCache('XenForo_Model_DeletionLog')->logDeletion(
$this->getContentType(), $this->getDiscussionId(), $reason
);
}
else if ($this->getExisting('discussion_state') == 'deleted')
{
$this->getModelFromCache('XenForo_Model_DeletionLog')->removeDeletionLog(
$this->getContentType(), $this->getDiscussionId()
);
}
}
/**
* Updates the moderation queue if necessary.
*/
protected function _updateModerationQueue()
{
if (!$this->isChanged('discussion_state'))
{
return;
}
if ($this->get('discussion_state') == 'moderated')
{
$this->getModelFromCache('XenForo_Model_ModerationQueue')->insertIntoModerationQueue(
$this->getContentType(), $this->getDiscussionId(), $this->get('post_date')
);
}
else if ($this->getExisting('discussion_state') == 'moderated')
{
$this->getModelFromCache('XenForo_Model_ModerationQueue')->deleteFromModerationQueue(
$this->getContentType(), $this->getDiscussionId()
);
}
}
protected function _submitSpamLog()
{
if ($this->getExisting('discussion_state') == 'moderated' && $this->get('discussion_state') == 'visible')
{
/** @var $spamModel XenForo_Model_SpamPrevention */
$spamModel = $this->getModelFromCache('XenForo_Model_SpamPrevention');
$spamModel->submitHamCommentData($this->getContentType(), $this->getDiscussionId());
}
}
/**
* Updates the search index for this discussion.
*/
protected function _indexForSearch()
{
if ($this->get('discussion_state') == 'visible')
{
if ($this->getExisting('discussion_state') != 'visible')
{
$this->_insertIntoSearchIndex();
}
else if ($this->_needsSearchIndexUpdate())
{
$this->_updateSearchIndexTitle();
}
}
else if ($this->isUpdate() && $this->get('discussion_state') != 'visible' && $this->getExisting('discussion_state') == 'visible')
{
$this->_deleteFromSearchIndex();
}
}
/**
* Returns true if the changes made require the search index to be updated.
*
* @return boolean
*/
protected function _needsSearchIndexUpdate()
{
return $this->isChanged('title') || $this->isChanged($this->getContainerKeyName());
}
/**
* Inserts a record in the search index for this discussion.
*/
protected function _insertIntoSearchIndex()
{
$discussion = $this->getMergedData();
$indexer = new XenForo_Search_Indexer();
$discussionHandler = $this->_discussionDefinition->getSearchDataHandler();
if ($discussionHandler)
{
$discussionHandler->insertIntoIndex($indexer, $discussion);
}
if ($this->isUpdate())
{
XenForo_Application::defer('SearchIndexPartial', array(
'contentType' => $this->getDiscussionMessageDefinition()->getContentType(),
'contentIds' => $this->_getDiscussionMessageIds()
));
}
}
/**
* Updates the title in the search index for this discussion.
*/
protected function _updateSearchIndexTitle()
{
$indexer = new XenForo_Search_Indexer();
$mergedData = $this->getMergedData();
$discussionHandler = $this->_discussionDefinition->getSearchDataHandler();
if ($discussionHandler)
{
$discussionHandler->insertIntoIndex($indexer, $mergedData);
}
if ($this->isUpdate())
{
$messageHandler = $this->_messageDefinition->getSearchDataHandler();
if ($messageHandler && $firstMessageDw = $this->getFirstMessageDw())
{
if ($firstMessageDw->getMergedData())
{
$messageHandler->insertIntoIndex($indexer, $firstMessageDw->getMergedData(), $mergedData);
}
}
}
}
/**
* Deletes this discussion from the search index.
*/
protected function _deleteFromSearchIndex()
{
$discussion = $this->getMergedData();
$indexer = new XenForo_Search_Indexer();
$discussionHandler = $this->_discussionDefinition->getSearchDataHandler();
if ($discussionHandler)
{
$discussionHandler->deleteFromIndex($indexer, $discussion);
}
$messageHandler = $this->_messageDefinition->getSearchDataHandler();
if ($messageHandler)
{
$messageHandler->deleteFromIndex($indexer, $this->_getDiscussionMessageIds());
}
}
/**
* Designed to be overridden by child classes
*/
protected function _discussionPostSave()
{
}
/**
* Generic discussion pre-delete handler.
*/
protected final function _preDelete()
{
$this->_discussionPreDelete();
}
/**
* Designed to be overridden by child classes
*/
protected function _discussionPreDelete()
{
}
/**
* Generic discussion post-delete handler.
*/
protected final function _postDelete()
{
if ($this->_hasParentContainer && $this->getOption(self::OPTION_UPDATE_CONTAINER))
{
$this->_updateContainerPostDelete();
}
$this->getModelFromCache('XenForo_Model_DeletionLog')->removeDeletionLog(
$this->getContentType(), $this->getDiscussionId()
);
$this->getModelFromCache('XenForo_Model_ModerationQueue')->deleteFromModerationQueue(
$this->getContentType(), $this->getDiscussionId()
);
$this->_deleteDiscussionMessages();
if ($this->getOption(self::OPTION_CHANGE_USER_MESSAGE_COUNT))
{
$this->_updateUserMessageCount(true);
}
if ($this->getOption(self::OPTION_INDEX_FOR_SEARCH))
{
$this->_deleteFromSearchIndex();
}
$this->_discussionPostDelete();
$this->_deleteFromNewsFeed();
}
/**
* Updates the user message count for all the messages in
* this discussion.
*
* @param boolean $isDelete True if discussion is being deleted
* @param boolean $containerCountStateChange If the discussion is moving from a container that counts messages towards user totals to one that does not or vice versa, set the counting state of the destination container
*/
protected function _updateUserMessageCount($isDelete = false, $forceUpdateType = null)
{
if (!is_null($forceUpdateType))
{
$updateType = $forceUpdateType;
}
else
{
$newState = $this->get('discussion_state');
$oldState = $this->getExisting('discussion_state');
if ($newState == 'visible' && $oldState != 'visible')
{
$updateType = 'add';
}
else if ($oldState == 'visible' && ($newState != 'visible' || $isDelete))
{
$updateType = 'subtract';
}
else
{
return;
}
}
$users = $this->_getUserMessageCountAdjustments();
foreach ($users AS $userId => $modify)
{
if ($updateType == 'add')
{
$this->_db->query('
UPDATE xf_user
SET message_count = message_count + ?
WHERE user_id = ?
', array($modify, $userId));
}
else
{
$this->_db->query('
UPDATE xf_user
SET message_count = IF(message_count > ?, message_count - ?, 0)
WHERE user_id = ?
', array($modify, $modify, $userId));
}
}
}
protected function _getUserMessageCountAdjustments()
{
$users = array();
foreach ($this->_getDiscussionMessages(false) AS $message)
{
if ($message['message_state'] == 'visible' && $message['user_id'])
{
if (isset($users[$message['user_id']]))
{
$users[$message['user_id']]++;
}
else
{
$users[$message['user_id']] = 1;
}
}
}
return $users;
}
/**
* Updates the user like count for all the messages in
* this discussion.
*
* @param boolean $isDelete True if discussion is being deleted
*/
protected function _updateUserLikeCount($isDelete = false)
{
if ($this->get('discussion_state') == 'visible'
&& $this->getExisting('discussion_state') != 'visible'
)
{
$updateType = 'add';
}
else if ($this->getExisting('discussion_state') == 'visible'
&& ($this->get('discussion_state') != 'visible' || $isDelete)
)
{
$updateType = 'subtract';
}
else
{
return;
}
$users = $this->_getUserLikeCountAdjustments();
foreach ($users AS $userId => $modify)
{
if ($updateType == 'add')
{
$this->_db->query('
UPDATE xf_user
SET like_count = like_count + ?
WHERE user_id = ?
', array($modify, $userId));
}
else
{
$this->_db->query('
UPDATE xf_user
SET like_count = IF(like_count > ?, like_count - ?, 0)
WHERE user_id = ?
', array($modify, $modify, $userId));
}
}
}
protected function _getUserLikeCountAdjustments()
{
$users = array();
foreach ($this->_getDiscussionMessages(false) AS $message)
{
if ($message['likes'] && $message['message_state'] == 'visible' && $message['user_id'])
{
if (isset($users[$message['user_id']]))
{
$users[$message['user_id']] += $message['likes'];
}
else
{
$users[$message['user_id']] = $message['likes'];
}
}
}
return $users;
}
/**
* Update container information after the main record has been deleted.
*/
protected function _updateContainerPostDelete()
{
$containerDw = $this->_discussionDefinition->getContainerDataWriter($this->get($this->getContainerKeyName()), $this->_errorHandler);
if ($containerDw)
{
$containerDw->updateCountersAfterDiscussionDelete($this);
if ($containerDw->hasChanges())
{
$containerDw->save();
}
}
}
/**
* Deletes all messages in this discussion.
*/
protected function _deleteDiscussionMessages()
{
$messages = $this->_getDiscussionMessages(false);
if (!$messages)
{
return;
}
$messageIds = array_keys($messages);
$messageStructure = $this->_messageDefinition->getMessageStructure();
$messageContentType = $this->_messageDefinition->getContentType();
$this->_db->delete($messageStructure['table'],
"$messageStructure[key] IN (" . $this->_db->quote($messageIds) . ')'
);
$this->getModelFromCache('XenForo_Model_Attachment')->deleteAttachmentsFromContentIds(
$messageContentType, $messageIds
);
$this->getModelFromCache('XenForo_Model_DeletionLog')->removeDeletionLog(
$messageContentType, $messageIds
);
$this->getModelFromCache('XenForo_Model_ModerationQueue')->deleteFromModerationQueue(
$messageContentType, $messageIds
);
$this->getModelFromCache('XenForo_Model_EditHistory')->deleteEditHistoryForContent(
$messageContentType, $messageIds
);
$this->getModelFromCache('XenForo_Model_BbCode')->deleteBbCodeParseCacheForContent(
$messageContentType, $messageIds
);
$visibleMessageIds = array();
$nonVisibleMessageIds = array();
foreach ($messages AS $messageId => $message)
{
if (empty($message['message_state']) || $message['message_state'] == 'visible')
{
$visibleMessageIds[] = $messageId;
}
else
{
$nonVisibleMessageIds[] = $messageId;
}
}
$this->getModelFromCache('XenForo_Model_Like')->deleteContentLikes(
$messageContentType, $visibleMessageIds, ($this->get('discussion_state') == 'visible')
);
$this->getModelFromCache('XenForo_Model_Like')->deleteContentLikes(
$messageContentType, $visibleMessageIds, false
);
}
/**
* Designed to be overridden by child classes
*/
protected function _discussionPostDelete()
{
}
/**
* Updates denormalized counters, based on changes made to the provided
* discussion message, after the message has been saved.
*
* @param XenForo_DataWriter_DiscussionMessage $messageDw
*/
public function updateCountersAfterMessageSave(XenForo_DataWriter_DiscussionMessage $messageDw)
{
if ($messageDw->get('message_state') == 'visible' && $messageDw->get('post_date') > $this->get('last_post_date'))
{
$this->set('last_post_date', $messageDw->get('post_date'));
$this->set('last_post_id', $messageDw->getDiscussionMessageId());
$this->set('last_post_user_id', $messageDw->get('user_id'));
$this->set('last_post_username', $messageDw->get('username'));
}
if ($messageDw->get('message_state') == 'visible' && $messageDw->getExisting('message_state') != 'visible')
{
$this->set('reply_count', $this->get('reply_count') + 1);
}
else if ($messageDw->getExisting('message_state') == 'visible' && $messageDw->get('message_state') != 'visible')
{
$this->set('reply_count', $this->get('reply_count') - 1);
if ($messageDw->getDiscussionMessageId() == $this->get('last_post_id'))
{
$this->updateLastPost();
}
}
}
/**
* Updates denormalized counters. Used after a message has been deleted.
*
* @param XenForo_DataWriter_DiscussionMessage $messageDw
* @param boolean $deleteIfFirstMessage If true and message if first, delete discussion
*
* @return string State changes to discussion: delete means remove discussion; firstDelete means first message was removed but still valid
*/
public function updateCountersAfterMessageDelete(XenForo_DataWriter_DiscussionMessage $messageDw, $deleteIfFirstMessage = true)
{
$messageId = $messageDw->getDiscussionMessageId();
if ($messageId == $this->get('first_post_id'))
{
if (!$deleteIfFirstMessage && $this->rebuildDiscussion())
{
return 'firstDelete';
}
else
{
return 'delete';
}
}
if ($messageId == $this->get('last_post_id'))
{
$this->updateLastPost();
}
if ($messageDw->get('message_state') == 'visible')
{
$this->set('reply_count', $this->get('reply_count') - 1);
}
return '';
}
/**
* Updates the value of the last post for this discussion.
*/
public function updateLastPost()
{
$lastPost = $this->_getLastMessageInDiscussion();
if ($lastPost)
{
$messageStructure = $this->_messageDefinition->getMessageStructure();
$this->set('last_post_id', $lastPost[$messageStructure['key']]);
$this->set('last_post_date', $lastPost['post_date']);
$this->set('last_post_user_id', $lastPost['user_id']);
$this->set('last_post_username', $lastPost['username']);
}
else
{
$this->set('last_post_id', $this->get('first_post_id'));
$this->set('last_post_date', $this->get('post_date'));
$this->set('last_post_user_id', $this->get('user_id'));
$this->set('last_post_username', $this->get('username'));
}
}
/**
* Gets the current value of the discussion ID for this discussion.
*
* @return integer
*/
public function getDiscussionId()
{
return $this->get($this->getDiscussionKeyName());
}
/**
* 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->getDiscussionId(),
($this->isUpdate() ? 'update' : 'insert')
);
}
/**
* Removes an already published news feed item
*/
protected function _deleteFromNewsFeed()
{
$this->_getNewsFeedModel()->delete(
$this->getContentType(),
$this->getDiscussionId()
);
}
/**
* The name of the table that holds the discussion data.
*
* @return string
*/
public function getDiscussionTableName()
{
return $this->_discussionDefinition->getDiscussionTableName();
}
/**
* The name of the discussion table's primary key. This must be an auto increment field.
*
* @return string
*/
public function getDiscussionKeyName()
{
return $this->_discussionDefinition->getDiscussionKeyName();
}
/**
* Gets the name of the field that represents the discussion's container.
* This must be an integer field.
*
* @return string
*/
public function getContainerKeyName()
{
return $this->_discussionDefinition->getContainerKeyName();
}
/**
* Gets the content type for tables that contain multiple data types together.
*
* @return string
*/
public function getContentType()
{
return $this->_discussionDefinition->getContentType();
}
/**
* Gets the discussion from the update marked with "for update" to ensure that position
* counters are maintained correctly.
*
* @return array|false
*/
public function getDiscussionForUpdate()
{
if ($this->isUpdate())
{
return $this->_discussionDefinition->getDiscussionForUpdate($this->_db, $this->getDiscussionId());
}
else
{
return false;
}
}
}