Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Tree: 4a6921fbeb
Fetching contributors…

Cannot retrieve contributors at this time

1908 lines (1626 sloc) 68.447 kB
<?php if (!defined('APPLICATION')) exit();
/*
Copyright 2008, 2009 Vanilla Forums Inc.
This file is part of Garden.
Garden is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
Garden is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along with Garden. If not, see <http://www.gnu.org/licenses/>.
Contact Vanilla Forums Inc. at support [at] vanillaforums [dot] com
*/
/**
* Discussion Model
*
* @package Vanilla
*/
/**
* Manages discussions.
*
* @since 2.0.0
* @package Vanilla
*/
class DiscussionModel extends VanillaModel {
/**
* @var array
*/
protected static $_CategoryPermissions = NULL;
public $Watching = FALSE;
/**
* Class constructor. Defines the related database table name.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
parent::__construct('Discussion');
}
public function Counts($Column, $From = FALSE, $To = FALSE, $Max = FALSE) {
$Result = array('Complete' => TRUE);
switch ($Column) {
case 'CountComments':
$this->Database->Query(DBAModel::GetCountSQL('count', 'Discussion', 'Comment'));
break;
case 'FirstCommentID':
$this->Database->Query(DBAModel::GetCountSQL('min', 'Discussion', 'Comment', $Column));
break;
case 'LastCommentID':
$this->Database->Query(DBAModel::GetCountSQL('max', 'Discussion', 'Comment', $Column));
break;
case 'DateLastComment':
$this->Database->Query(DBAModel::GetCountSQL('max', 'Discussion', 'Comment', $Column, 'DateInserted'));
$this->SQL
->Update('Discussion')
->Set('DateLastComment', 'DateInserted', FALSE, FALSE)
->Where('DateLastComment', NULL)
->Put();
break;
case 'LastCommentUserID':
if (!$Max) {
// Get the range for this update.
$DBAModel = new DBAModel();
list($Min, $Max) = $DBAModel->PrimaryKeyRange('Discussion');
if (!$From) {
$From = $Min;
$To = $Min + DBAModel::$ChunkSize - 1;
}
}
$this->SQL
->Update('Discussion d')
->Join('Comment c', 'c.CommentID = d.LastCommentID')
->Set('d.LastCommentUserID', 'c.InsertUserID', FALSE, FALSE)
->Where('d.DiscussionID >=', $From)
->Where('d.DiscussionID <=', $To)
->Put();
$Result['Complete'] = $To >= $Max;
$Percent = round($To * 100 / $Max);
if ($Percent > 100 || $Result['Complete'])
$Result['Percent'] = '100%';
else
$Result['Percent'] = $Percent.'%';
$From = $To + 1;
$To = $From + DBAModel::$ChunkSize - 1;
$Result['Args']['From'] = $From;
$Result['Args']['To'] = $To;
$Result['Args']['Max'] = $Max;
break;
default:
throw new Gdn_UserException("Unknown column $Column");
}
return $Result;
}
/**
* Builds base SQL query for discussion data.
*
* Events: AfterDiscussionSummaryQuery.
*
* @since 2.0.0
* @access public
*
* @param array $AdditionalFields Allows selection of additional fields as Alias=>Table.Fieldname.
*/
public function DiscussionSummaryQuery($AdditionalFields = array(), $Join = TRUE) {
// Verify permissions (restricting by category if necessary)
if ($this->Watching)
$Perms = CategoryModel::CategoryWatch();
else
$Perms = self::CategoryPermissions();
if($Perms !== TRUE) {
$this->SQL->WhereIn('d.CategoryID', $Perms);
}
// Buid main query
$this->SQL
->Select('d.*')
->Select('d.InsertUserID', '', 'FirstUserID')
->Select('d.DateInserted', '', 'FirstDate')
->Select('d.DateLastComment', '', 'LastDate')
->Select('d.LastCommentUserID', '', 'LastUserID')
->From('Discussion d');
if ($Join) {
$this->SQL
->Select('iu.Name', '', 'FirstName') // <-- Need these for rss!
->Select('iu.Photo', '', 'FirstPhoto')
->Select('iu.Email', '', 'FirstEmail')
->Join('User iu', 'd.InsertUserID = iu.UserID', 'left') // First comment author is also the discussion insertuserid
->Select('lcu.Name', '', 'LastName')
->Select('lcu.Photo', '', 'LastPhoto')
->Select('lcu.Email', '', 'LastEmail')
->Join('User lcu', 'd.LastCommentUserID = lcu.UserID', 'left') // Last comment user
->Select('ca.Name', '', 'Category')
->Select('ca.UrlCode', '', 'CategoryUrlCode')
->Select('ca.PermissionCategoryID')
->Join('Category ca', 'd.CategoryID = ca.CategoryID', 'left'); // Category
}
// Add any additional fields that were requested
if(is_array($AdditionalFields)) {
foreach($AdditionalFields as $Alias => $Field) {
// See if a new table needs to be joined to the query.
$TableAlias = explode('.', $Field);
$TableAlias = $TableAlias[0];
if(array_key_exists($TableAlias, $Tables)) {
$Join = $Tables[$TableAlias];
$this->SQL->Join($Join[0], $Join[1]);
unset($Tables[$TableAlias]);
}
// Select the field.
$this->SQL->Select($Field, '', is_numeric($Alias) ? '' : $Alias);
}
}
$this->FireEvent('AfterDiscussionSummaryQuery');
}
/**
* Gets the data for multiple discussions based on the given criteria.
*
* Sorts results based on config options Vanilla.Discussions.SortField
* and Vanilla.Discussions.SortDirection.
* Events: BeforeGet, AfterAddColumns.
*
* @since 2.0.0
* @access public
*
* @param int $Offset Number of discussions to skip.
* @param int $Limit Max number of discussions to return.
* @param array $Wheres SQL conditions.
* @param array $AdditionalFields Allows selection of additional fields as Alias=>Table.Fieldname.
* @return Gdn_DataSet SQL result.
*/
public function Get($Offset = '0', $Limit = '', $Wheres = '', $AdditionalFields = NULL) {
if ($Limit == '')
$Limit = Gdn::Config('Vanilla.Discussions.PerPage', 50);
$Offset = !is_numeric($Offset) || $Offset < 0 ? 0 : $Offset;
$Session = Gdn::Session();
$UserID = $Session->UserID > 0 ? $Session->UserID : 0;
$this->DiscussionSummaryQuery($AdditionalFields, FALSE);
if ($UserID > 0) {
$this->SQL
->Select('w.UserID', '', 'WatchUserID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$UserID, 'left');
} else {
$this->SQL
->Select('0', '', 'WatchUserID')
->Select('now()', '', 'DateLastViewed')
->Select('0', '', 'Dismissed')
->Select('0', '', 'Bookmarked')
->Select('0', '', 'CountCommentWatch')
->Select('d.Announce','','IsAnnounce');
}
$this->AddArchiveWhere($this->SQL);
if ($Offset !== FALSE && $Limit !== FALSE)
$this->SQL->Limit($Limit, $Offset);
$this->EventArguments['SortField'] = C('Vanilla.Discussions.SortField', 'd.DateLastComment');
$this->EventArguments['SortDirection'] = C('Vanilla.Discussions.SortDirection', 'desc');
$this->EventArguments['Wheres'] = &$Wheres;
$this->FireEvent('BeforeGet'); // @see 'BeforeGetCount' for consistency in results vs. counts
$IncludeAnnouncements = FALSE;
if (strtolower(GetValue('Announce', $Wheres)) == 'all') {
$IncludeAnnouncements = TRUE;
unset($Wheres['Announce']);
}
if (is_array($Wheres))
$this->SQL->Where($Wheres);
// Get sorting options from config
$SortField = $this->EventArguments['SortField'];
if (!in_array($SortField, array('d.DiscussionID', 'd.DateLastComment', 'd.DateInserted')))
$SortField = 'd.DateLastComment';
$SortDirection = $this->EventArguments['SortDirection'];
if ($SortDirection != 'asc')
$SortDirection = 'desc';
$this->SQL->OrderBy($SortField, $SortDirection);
// Set range and fetch
$Data = $this->SQL->Get();
// If not looking at discussions filtered by bookmarks or user, filter announcements out.
if (!$IncludeAnnouncements) {
if (!isset($Wheres['w.Bookmarked']) && !isset($Wheres['d.InsertUserID']))
$this->RemoveAnnouncements($Data);
}
// Change discussions returned based on additional criteria
$this->AddDiscussionColumns($Data);
// Join in the users.
Gdn::UserModel()->JoinUsers($Data, array('FirstUserID', 'LastUserID'));
CategoryModel::JoinCategories($Data);
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Data);
// Prep and fire event
$this->EventArguments['Data'] = $Data;
$this->FireEvent('AfterAddColumns');
return $Data;
}
/**
* Gets the data for multiple unread discussions based on the given criteria.
*
* Sorts results based on config options Vanilla.Discussions.SortField
* and Vanilla.Discussions.SortDirection.
* Events: BeforeGet, AfterAddColumns.
*
* @since 2.0.0
* @access public
*
* @param int $Offset Number of discussions to skip.
* @param int $Limit Max number of discussions to return.
* @param array $Wheres SQL conditions.
* @param array $AdditionalFields Allows selection of additional fields as Alias=>Table.Fieldname.
* @return Gdn_DataSet SQL result.
*/
public function GetUnread($Offset = '0', $Limit = '', $Wheres = '', $AdditionalFields = NULL) {
if ($Limit == '')
$Limit = Gdn::Config('Vanilla.Discussions.PerPage', 50);
$Offset = !is_numeric($Offset) || $Offset < 0 ? 0 : $Offset;
$Session = Gdn::Session();
$UserID = $Session->UserID > 0 ? $Session->UserID : 0;
$this->DiscussionSummaryQuery($AdditionalFields, FALSE);
if ($UserID > 0) {
$this->SQL
->Select('w.UserID', '', 'WatchUserID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$UserID, 'left')
//->BeginWhereGroup()
//->Where('w.DateLastViewed', NULL)
//->OrWhere('d.DateLastComment >', 'w.DateLastViewed')
//->EndWhereGroup()
->Where('d.CountComments >', 'COALESCE(w.CountComments, 0)', TRUE, FALSE);
} else {
$this->SQL
->Select('0', '', 'WatchUserID')
->Select('now()', '', 'DateLastViewed')
->Select('0', '', 'Dismissed')
->Select('0', '', 'Bookmarked')
->Select('0', '', 'CountCommentWatch')
->Select('d.Announce','','IsAnnounce');
}
$this->AddArchiveWhere($this->SQL);
$this->SQL->Limit($Limit, $Offset);
$this->EventArguments['SortField'] = C('Vanilla.Discussions.SortField', 'd.DateLastComment');
$this->EventArguments['SortDirection'] = C('Vanilla.Discussions.SortDirection', 'desc');
$this->EventArguments['Wheres'] = &$Wheres;
$this->FireEvent('BeforeGetUnread'); // @see 'BeforeGetCount' for consistency in results vs. counts
$IncludeAnnouncements = FALSE;
if (strtolower(GetValue('Announce', $Wheres)) == 'all') {
$IncludeAnnouncements = TRUE;
unset($Wheres['Announce']);
}
if (is_array($Wheres))
$this->SQL->Where($Wheres);
// Get sorting options from config
$SortField = $this->EventArguments['SortField'];
if (!in_array($SortField, array('d.DiscussionID', 'd.DateLastComment', 'd.DateInserted')))
$SortField = 'd.DateLastComment';
$SortDirection = $this->EventArguments['SortDirection'];
if ($SortDirection != 'asc')
$SortDirection = 'desc';
$this->SQL->OrderBy($SortField, $SortDirection);
// Set range and fetch
$Data = $this->SQL->Get();
// If not looking at discussions filtered by bookmarks or user, filter announcements out.
if (!$IncludeAnnouncements) {
if (!isset($Wheres['w.Bookmarked']) && !isset($Wheres['d.InsertUserID']))
$this->RemoveAnnouncements($Data);
}
// Change discussions returned based on additional criteria
$this->AddDiscussionColumns($Data);
// Join in the users.
Gdn::UserModel()->JoinUsers($Data, array('FirstUserID', 'LastUserID'));
CategoryModel::JoinCategories($Data);
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Data);
// Prep and fire event
$this->EventArguments['Data'] = $Data;
$this->FireEvent('AfterAddColumns');
return $Data;
}
/**
* Removes undismissed announcements from the data.
*
* @since 2.0.0
* @access public
*
* @param object $Data SQL result.
*/
public function RemoveAnnouncements($Data) {
$Result =& $Data->Result();
$Unset = FALSE;
foreach($Result as $Key => &$Discussion) {
if (isset($this->_AnnouncementIDs)) {
if (in_array($Discussion->DiscussionID, $this->_AnnouncementIDs)) {
unset($Result[$Key]);
$Unset = TRUE;
}
} elseif ($Discussion->Announce == 1 && $Discussion->Dismissed == 0) {
// Unset discussions that are announced and not dismissed
unset($Result[$Key]);
$Unset = TRUE;
}
}
if ($Unset) {
// Make sure the discussions are still in order for json encoding.
$Result = array_values($Result);
}
}
public function AddDenormalizedViews(&$Discussions) {
if ($Discussions instanceof Gdn_DataSet) {
$Result = $Discussions->Result();
foreach($Result as &$Discussion) {
$CacheKey = "QueryCache.Discussion.{$Discussion->DiscussionID}.CountViews";
$CacheViews = Gdn::Cache()->Get($CacheKey);
if ($CacheViews !== Gdn_Cache::CACHEOP_FAILURE)
$Discussion->CountViews = $CacheViews;
}
} else {
if (isset($Discussions->DiscussionID)) {
$Discussion = $Discussions;
$CacheKey = "QueryCache.Discussion.{$Discussion->DiscussionID}.CountViews";
$CacheViews = Gdn::Cache()->Get($CacheKey);
if ($CacheViews !== Gdn_Cache::CACHEOP_FAILURE)
$Discussion->CountViews = $CacheViews;
}
}
}
/**
* Modifies discussion data before it is returned.
*
* Takes archiving into account and fixes inaccurate comment counts.
*
* @since 2.0.0
* @access public
*
* @param object $Data SQL result.
*/
public function AddDiscussionColumns($Data) {
// Change discussions based on archiving.
$ArchiveTimestamp = Gdn_Format::ToTimestamp(Gdn::Config('Vanilla.Archive.Date', 0));
$Result = &$Data->Result();
foreach($Result as &$Discussion) {
$CategoryID = $Discussion->CategoryID;
$Category = CategoryModel::Categories($CategoryID);
$Discussion->Name = Gdn_Format::Text($Discussion->Name);
$Discussion->Url = DiscussionUrl($Discussion);
if($Discussion->DateLastComment && Gdn_Format::ToTimestamp($Discussion->DateLastComment) <= $ArchiveTimestamp) {
$Discussion->Closed = '1';
if ($Discussion->CountCommentWatch) {
$Discussion->CountUnreadComments = $Discussion->CountComments - $Discussion->CountCommentWatch;
} else {
$Discussion->CountUnreadComments = 0;
}
} elseif ($Discussion->CountCommentWatch === NULL) {
// Allow for discussions to just be new.
$Discussion->CountUnreadComments = TRUE;
} else {
$Discussion->CountUnreadComments = $Discussion->CountComments - $Discussion->CountCommentWatch;
}
$Discussion->Read = !(bool)$Discussion->CountUnreadComments;
if ($Category)
$Discussion->Read |= $Category['DateMarkedRead'] > $Discussion->DateLastComment;
// Logic for incomplete comment count.
if ($Discussion->CountCommentWatch == 0 && $DateLastViewed = GetValue('DateLastViewed', $Discussion)) {
$Discussion->CountUnreadComments = TRUE;
if (Gdn_Format::ToTimestamp($DateLastViewed) >= Gdn_Format::ToTimestamp($Discussion->LastDate)) {
$Discussion->CountCommentWatch = $Discussion->CountComments;
$Discussion->CountUnreadComments = 0;
}
}
if ($Discussion->CountUnreadComments === NULL)
$Discussion->CountUnreadComments = 0;
elseif ($Discussion->CountUnreadComments < 0)
$Discussion->CountUnreadComments = 0;
$Discussion->CountCommentWatch = is_numeric($Discussion->CountCommentWatch) ? $Discussion->CountCommentWatch : 0;
if ($Discussion->LastUserID == NULL) {
$Discussion->LastUserID = $Discussion->InsertUserID;
$Discussion->LastDate = $Discussion->DateInserted;
}
}
}
/**
* Add SQL Where to account for archive date.
*
* @since 2.0.0
* @access public
*
* @param object $Sql Gdn_SQLDriver
*/
public function AddArchiveWhere($Sql = NULL) {
if(is_null($Sql))
$Sql = $this->SQL;
$Exclude = Gdn::Config('Vanilla.Archive.Exclude');
if($Exclude) {
$ArchiveDate = Gdn::Config('Vanilla.Archive.Date');
if($ArchiveDate) {
$Sql->Where('d.DateLastComment >', $ArchiveDate);
}
}
}
/**
* Gets announced discussions.
*
* @since 2.0.0
* @access public
*
* @param array $Wheres SQL conditions.
* @return object SQL result.
*/
public function GetAnnouncements($Wheres = '') {
$Session = Gdn::Session();
$Limit = Gdn::Config('Vanilla.Discussions.PerPage', 50);
$Offset = 0;
$UserID = $Session->UserID > 0 ? $Session->UserID : 0;
// Get the discussion IDs of the announcements.
$CacheKey = 'Announcements';
$AnnouncementIDs = $this->SQL
->Cache($CacheKey)
->Select('d.DiscussionID')
->From('Discussion d')
->Where('d.Announce >', '0')->Get()->ResultArray();
$AnnouncementIDs = ConsolidateArrayValuesByKey($AnnouncementIDs, 'DiscussionID');
// Short circuit querying when there are no announcements.
if (count($AnnouncementIDs) == 0)
return new Gdn_DataSet();
$this->DiscussionSummaryQuery(array(), FALSE);
if (!empty($Wheres))
$this->SQL->Where($Wheres);
if ($UserID) {
$this->SQL->Select('w.UserID', '', 'WatchUserID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$UserID, 'left');
} else {
// Don't join in the user table when we are a guest.
$this->SQL->Select('null as WatchUserID, null as DateLastViewed, null as Dismissed, null as Bookmarked, null as CountCommentWatch');
}
// Add conditions passed.
// if (is_array($Wheres))
// $this->SQL->Where($Wheres);
//
// $this->SQL
// ->Where('d.Announce', '1');
$this->SQL->WhereIn('d.DiscussionID', $AnnouncementIDs);
// If we aren't viewing announcements in a category then only show global announcements.
if (!$Wheres) {
$this->SQL->Where('d.Announce', 1);
} else {
$this->SQL->Where('d.Announce >', 0);
}
// If we allow users to dismiss discussions, skip ones this user dismissed
if (C('Vanilla.Discussions.Dismiss', 1) && $UserID) {
$this->SQL
->Where('coalesce(w.Dismissed, \'0\')', '0', FALSE);
}
$this->SQL
->OrderBy('d.DateLastComment', 'desc')
->Limit($Limit, $Offset);
$Data = $this->SQL->Get();
// Save the announcements that were fetched for later removal.
$AnnouncementIDs = array();
foreach ($Data as $Row) {
$AnnouncementIDs[] = GetValue('DiscussionID', $Row);
}
$this->_AnnouncementIDs = $AnnouncementIDs;
$this->AddDiscussionColumns($Data);
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Data);
Gdn::UserModel()->JoinUsers($Data, array('FirstUserID', 'LastUserID'));
CategoryModel::JoinCategories($Data);
// Prep and fire event
$this->EventArguments['Data'] = $Data;
$this->FireEvent('AfterAddColumns');
return $Data;
}
/**
* Gets all users who have bookmarked the specified discussion.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID to find bookmarks for.
* @return object SQL result.
*/
public function GetBookmarkUsers($DiscussionID) {
return $this->SQL
->Select('UserID')
->From('UserDiscussion')
->Where('DiscussionID', $DiscussionID)
->Where('Bookmarked', '1')
->Get();
}
/**
*
* Get discussions for a user.
*
* @since 2.1
* @access public
*
* @param int $UserID Which user to get discussions for.
* @param int $Limit Max number to get.
* @param int $Offset Number to skip.
* @param int $LastDiscussionID A hint for quicker paging.
* @return Gdn_DataSet SQL results.
*/
public function GetByUser($UserID, $Limit, $Offset, $LastDiscussionID = FALSE) {
$Perms = DiscussionModel::CategoryPermissions();
if (is_array($Perms) && empty($Perms)) {
return new Gdn_DataSet(array());
}
// The point of this query is to select from one comment table, but filter and sort on another.
// This puts the paging into an index scan rather than a table scan.
$this->SQL
->Select('d2.*')
->Select('d2.InsertUserID', '', 'FirstUserID')
->Select('d2.DateInserted', '', 'FirstDate')
->Select('d2.DateLastComment', '', 'LastDate')
->Select('d2.LastCommentUserID', '', 'LastUserID')
->From('Discussion d')
->Join('Discussion d2', 'd.DiscussionID = d2.DiscussionID')
->Where('d.InsertUserID', $UserID)
->OrderBy('d.DiscussionID', 'desc');
// Join in the watch data.
if ($UserID > 0) {
$this->SQL
->Select('w.UserID', '', 'WatchUserID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Join('UserDiscussion w', 'd2.DiscussionID = w.DiscussionID and w.UserID = '.$UserID, 'left');
} else {
$this->SQL
->Select('0', '', 'WatchUserID')
->Select('now()', '', 'DateLastViewed')
->Select('0', '', 'Dismissed')
->Select('0', '', 'Bookmarked')
->Select('0', '', 'CountCommentWatch')
->Select('d.Announce','','IsAnnounce');
}
if ($LastDiscussionID) {
// The last comment id from the last page was given and can be used as a hint to speed up the query.
$this->SQL
->Where('d.DiscussionID <', $LastDiscussionID)
->Limit($Limit);
} else {
$this->SQL->Limit($Limit, $Offset);
}
$Data = $this->SQL->Get();
$Result =& $Data->Result();
$this->LastDiscussionCount = $Data->NumRows();
$this->LastDiscussionID = $Result[count($Result) - 1]->DiscussionID;
// Now that we have th comments we can filter out the ones we don't have permission to.
if ($Perms !== TRUE) {
$Remove = array();
foreach ($Data->Result() as $Index => $Row) {
if (!in_array($Row->CategoryID, $Perms))
$Remove[] = $Index;
}
if (count($Remove) > 0) {
foreach ($Remove as $Index) {
unset($Result[$Index]);
}
$Result = array_values($Result);
}
}
// Change discussions returned based on additional criteria
$this->AddDiscussionColumns($Data);
// Join in the users.
Gdn::UserModel()->JoinUsers($Data, array('FirstUserID', 'LastUserID'));
CategoryModel::JoinCategories($Data);
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Data);
return $Data;
}
/**
* Identify current user's category permissions and set as local array.
*
* @since 2.0.0
* @access public
*
* @param bool $Escape Prepends category IDs with @
* @return array Protected local _CategoryPermissions
*/
public static function CategoryPermissions($Escape = FALSE) {
if(is_null(self::$_CategoryPermissions)) {
$Session = Gdn::Session();
if((is_object($Session->User) && $Session->User->Admin)) {
self::$_CategoryPermissions = TRUE;
} elseif(C('Garden.Permissions.Disabled.Category')) {
if($Session->CheckPermission('Vanilla.Discussions.View'))
self::$_CategoryPermissions = TRUE;
else
self::$_CategoryPermissions = array(); // no permission
} else {
$SQL = Gdn::SQL();
$Categories = CategoryModel::Categories();
$IDs = array();
foreach ($Categories as $ID => $Category) {
if ($Category['PermsDiscussionsView']) {
$IDs[] = $ID;
}
}
// Check to see if the user has permission to all categories. This is for speed.
$CategoryCount = count($Categories);
if (count($IDs) == $CategoryCount)
self::$_CategoryPermissions = TRUE;
else {
self::$_CategoryPermissions = array();
foreach($IDs as $ID) {
self::$_CategoryPermissions[] = ($Escape ? '@' : '').$ID;
}
}
}
}
return self::$_CategoryPermissions;
}
public function FetchPageInfo($Url) {
$PageInfo = FetchPageInfo($Url);
$Title = GetValue('Title', $PageInfo, '');
if ($Title == '')
$Title = FormatString(T('Undefined discussion subject.'), array('Url' => $Url));
else {
if ($Strip = C('Vanilla.Embed.StripPrefix'))
$Title = StringBeginsWith($Title, $Strip, TRUE, TRUE);
if ($Strip = C('Vanilla.Embed.StripSuffix'))
$Title = StringEndsWith($Title, $Strip, TRUE, TRUE);
}
$Title = trim($Title);
$Description = GetValue('Description', $PageInfo, '');
$Images = GetValue('Images', $PageInfo, array());
$Body = FormatString(T('EmbeddedDiscussionFormat'), array(
'Title' => $Title,
'Excerpt' => $Description,
'Image' => (count($Images) > 0 ? Img(GetValue(0, $Images), array('class' => 'LeftAlign')) : ''),
'Url' => $Url
));
if ($Body == '')
$Body = $ForeignUrl;
if ($Body == '')
$Body = FormatString(T('EmbeddedNoBodyFormat.'), array('Url' => $Url));
$Result = array(
'Name' => $Title,
'Body' => $Body,
'Format' => 'Html');
return $Result;
}
/**
* Count how many discussions match the given criteria.
*
* @since 2.0.0
* @access public
*
* @param array $Wheres SQL conditions.
* @param bool $ForceNoAnnouncements Not used.
* @return int Number of discussions.
*/
public function GetCount($Wheres = '', $ForceNoAnnouncements = FALSE) {
if (is_array($Wheres) && count($Wheres) == 0)
$Wheres = '';
// Check permission and limit to categories as necessary
if ($this->Watching)
$Perms = CategoryModel::CategoryWatch();
else
$Perms = self::CategoryPermissions();
if (!$Wheres || (count($Wheres) == 1 && isset($Wheres['d.CategoryID']))) {
// Grab the counts from the faster category cache.
if (isset($Wheres['d.CategoryID'])) {
$CategoryIDs = (array)$Wheres['d.CategoryID'];
if ($Perms === FALSE)
$CategoryIDs = array();
elseif (is_array($Perms))
$CategoryIDs = array_intersect($CategoryIDs, $Perms);
if (count($CategoryIDs) == 0) {
return 0;
} else {
$Perms = $CategoryIDs;
}
}
$Categories = CategoryModel::Categories();
$Count = 0;
foreach ($Categories as $Cat) {
if (is_array($Perms) && !in_array($Cat['CategoryID'], $Perms))
continue;
$Count += (int)$Cat['CountDiscussions'];
}
return $Count;
}
if ($Perms !== TRUE) {
$this->SQL->WhereIn('c.CategoryID', $Perms);
}
$this->EventArguments['Wheres'] = &$Wheres;
$this->FireEvent('BeforeGetCount'); // @see 'BeforeGet' for consistency in count vs. results
$this->SQL
->Select('d.DiscussionID', 'count', 'CountDiscussions')
->From('Discussion d')
->Join('Category c', 'd.CategoryID = c.CategoryID')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.Gdn::Session()->UserID, 'left')
->Where($Wheres);
$Result = $this->SQL
->Get()
->FirstRow()
->CountDiscussions;
return $Result;
}
/**
* Count how many discussions match the given criteria.
*
* @since 2.0.0
* @access public
*
* @param array $Wheres SQL conditions.
* @param bool $ForceNoAnnouncements Not used.
* @return int Number of discussions.
*/
public function GetUnreadCount($Wheres = '', $ForceNoAnnouncements = FALSE) {
if (is_array($Wheres) && count($Wheres) == 0)
$Wheres = '';
// Check permission and limit to categories as necessary
if ($this->Watching)
$Perms = CategoryModel::CategoryWatch();
else
$Perms = self::CategoryPermissions();
if (!$Wheres || (count($Wheres) == 1 && isset($Wheres['d.CategoryID']))) {
// Grab the counts from the faster category cache.
if (isset($Wheres['d.CategoryID'])) {
$CategoryIDs = (array)$Wheres['d.CategoryID'];
if ($Perms === FALSE)
$CategoryIDs = array();
elseif (is_array($Perms))
$CategoryIDs = array_intersect($CategoryIDs, $Perms);
if (count($CategoryIDs) == 0) {
return 0;
} else {
$Perms = $CategoryIDs;
}
}
$Categories = CategoryModel::Categories();
$Count = 0;
foreach ($Categories as $Cat) {
if (is_array($Perms) && !in_array($Cat['CategoryID'], $Perms))
continue;
$Count += (int)$Cat['CountDiscussions'];
}
return $Count;
}
if ($Perms !== TRUE) {
$this->SQL->WhereIn('c.CategoryID', $Perms);
}
$this->EventArguments['Wheres'] = &$Wheres;
$this->FireEvent('BeforeGetUnreadCount'); // @see 'BeforeGet' for consistency in count vs. results
$this->SQL
->Select('d.DiscussionID', 'count', 'CountDiscussions')
->From('Discussion d')
->Join('Category c', 'd.CategoryID = c.CategoryID')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.Gdn::Session()->UserID, 'left')
//->BeginWhereGroup()
//->Where('w.DateLastViewed', NULL)
//->OrWhere('d.DateLastComment >', 'w.DateLastViewed')
//->EndWhereGroup()
->Where('d.CountComments >', 'COALESCE(w.CountComments, 0)', TRUE, FALSE)
->Where($Wheres);
$Result = $this->SQL
->Get()
->FirstRow()
->CountDiscussions;
return $Result;
}
/**
* Get data for a single discussion by ForeignID.
*
* @since 2.0.18
* @access public
*
* @param int $ForeignID Foreign ID of discussion to get.
* @return object SQL result.
*/
public function GetForeignID($ForeignID, $Type = '') {
$Hash = strlen($ForeignID) > 32 ? md5($ForeignID) : $ForeignID;
$Session = Gdn::Session();
$this->FireEvent('BeforeGetForeignID');
$this->SQL
->Select('d.*')
->Select('ca.Name', '', 'Category')
->Select('ca.UrlCode', '', 'CategoryUrlCode')
->Select('ca.PermissionCategoryID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Select('d.DateLastComment', '', 'LastDate')
->Select('d.LastCommentUserID', '', 'LastUserID')
->Select('lcu.Name', '', 'LastName')
->Select('iu.Name', '', 'InsertName')
->Select('iu.Photo', '', 'InsertPhoto')
->From('Discussion d')
->Join('Category ca', 'd.CategoryID = ca.CategoryID', 'left')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$Session->UserID, 'left')
->Join('User iu', 'd.InsertUserID = iu.UserID', 'left') // Insert user
->Join('Comment lc', 'd.LastCommentID = lc.CommentID', 'left') // Last comment
->Join('User lcu', 'lc.InsertUserID = lcu.UserID', 'left') // Last comment user
->Where('d.ForeignID', $Hash);
if ($Type != '')
$this->SQL->Where('d.Type', $Type);
$Discussion = $this->SQL
->Get()
->FirstRow();
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Discussion);
return $Discussion;
}
/**
* Get data for a single discussion by ID.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion to get.
* @return object SQL result.
*/
public function GetID($DiscussionID) {
$Session = Gdn::Session();
$this->FireEvent('BeforeGetID');
$Data = $this->SQL
->Select('d.*')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Select('d.DateLastComment', '', 'LastDate')
->Select('d.LastCommentUserID', '', 'LastUserID')
->From('Discussion d')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$Session->UserID, 'left')
->Where('d.DiscussionID', $DiscussionID)
->Get()
->FirstRow();
if (!$Data)
return $Data;
$Data->Name = Gdn_Format::Text($Data->Name);
$Data->Attributes = @unserialize($Data->Attributes);
$Data->Url = DiscussionUrl($Data);
$Data->Tags = $this->FormatTags($Data->Tags);
// Join in the category.
$Category = CategoryModel::Categories($Data->CategoryID);
if (!$Category) $Category = FALSE;
$Data->Category = $Category['Name'];
$Data->CategoryUrlCode = $Category['UrlCode'];
$Data->PermissionCategoryID = $Category['PermissionCategoryID'];
// Join in the users.
$Data = array($Data);
Gdn::UserModel()->JoinUsers($Data, array('LastUserID', 'InsertUserID'));
CategoryModel::JoinCategories($Data);
$Data = $Data[0];
// ->Select('lcu.Name', '', 'LastName')
// ->Select('iu.Name', '', 'InsertName')
// ->Select('iu.Photo', '', 'InsertPhoto')
// ->Select('iu.Email', '', 'InsertEmail')
// ->Join('User iu', 'd.InsertUserID = iu.UserID', 'left') // Insert user
// ->Join('Comment lc', 'd.LastCommentID = lc.CommentID', 'left') // Last comment
// ->Join('User lcu', 'lc.InsertUserID = lcu.UserID', 'left') // Last comment user
// Close if older than archive date
if (
$Data
&& $Data->DateLastComment
&& Gdn_Format::ToTimestamp($Data->DateLastComment) <= Gdn_Format::ToTimestamp(Gdn::Config('Vanilla.Archive.Date', 0))
) {
$Data->Closed = '1';
}
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Data);
return $Data;
}
/**
* Get discussions that have IDs in the provided array.
*
* @since 2.0.18
* @access public
*
* @param array $DiscussionIDs Array of DiscussionIDs to get.
* @return object SQL result.
*/
public function GetIn($DiscussionIDs) {
$Session = Gdn::Session();
$this->FireEvent('BeforeGetIn');
$Result = $this->SQL
->Select('d.*')
->Select('ca.Name', '', 'Category')
->Select('ca.UrlCode', '', 'CategoryUrlCode')
->Select('ca.PermissionCategoryID')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Select('d.DateLastComment', '', 'LastDate')
->Select('d.LastCommentUserID', '', 'LastUserID')
->Select('lcu.Name', '', 'LastName')
->Select('iu.Name', '', 'InsertName')
->Select('iu.Photo', '', 'InsertPhoto')
->From('Discussion d')
->Join('Category ca', 'd.CategoryID = ca.CategoryID', 'left')
->Join('UserDiscussion w', 'd.DiscussionID = w.DiscussionID and w.UserID = '.$Session->UserID, 'left')
->Join('User iu', 'd.InsertUserID = iu.UserID', 'left') // Insert user
->Join('Comment lc', 'd.LastCommentID = lc.CommentID', 'left') // Last comment
->Join('User lcu', 'lc.InsertUserID = lcu.UserID', 'left') // Last comment user
->WhereIn('d.DiscussionID', $DiscussionIDs)
->Get();
// Spliting views off to side table. Aggregate cached keys here.
if (C('Vanilla.Views.Denormalize', FALSE))
$this->AddDenormalizedViews($Result);
return $Result;
}
public static function GetViewsFallback($DiscussionID) {
// Not found. Check main table.
$Views = GetValue('CountViews', Gdn::SQL()
->Select('CountViews')
->From('Discussion')
->Where('DiscussionID', $DiscussionID)
->Get()->FirstRow(DATASET_TYPE_ARRAY), NULL);
// Found. Insert into denormalized table and return.
if (!is_null($Views))
return $Views;
return NULL;
}
/**
* Marks the specified announcement as dismissed by the specified user.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion being affected.
* @param int $UserID Unique ID of the user being affected.
*/
public function DismissAnnouncement($DiscussionID, $UserID) {
$Count = $this->SQL
->Select('UserID')
->From('UserDiscussion')
->Where('DiscussionID', $DiscussionID)
->Where('UserID', $UserID)
->Get()
->NumRows();
$CountComments = $this->SQL
->Select('CountComments')
->From('Discussion')
->Where('DiscussionID', $DiscussionID)
->Get()
->FirstRow()
->CountComments;
if ($Count > 0) {
$this->SQL
->Update('UserDiscussion')
->Set('CountComments', $CountComments)
->Set('DateLastViewed', Gdn_Format::ToDateTime())
->Set('Dismissed', '1')
->Where('DiscussionID', $DiscussionID)
->Where('UserID', $UserID)
->Put();
} else {
$this->SQL->Options('Ignore', TRUE);
$this->SQL->Insert(
'UserDiscussion',
array(
'UserID' => $UserID,
'DiscussionID' => $DiscussionID,
'CountComments' => $CountComments,
'DateLastViewed' => Gdn_Format::ToDateTime(),
'Dismissed' => '1'
)
);
}
}
/**
* Inserts or updates the discussion via form values.
*
* Events: BeforeSaveDiscussion, AfterSaveDiscussion.
*
* @since 2.0.0
* @access public
*
* @param array $FormPostValues Data sent from the form model.
* @return int $DiscussionID Unique ID of the discussion.
*/
public function Save($FormPostValues) {
$Session = Gdn::Session();
// Define the primary key in this model's table.
$this->DefineSchema();
// Add & apply any extra validation rules:
$this->Validation->ApplyRule('Body', 'Required');
$MaxCommentLength = Gdn::Config('Vanilla.Comment.MaxLength');
if (is_numeric($MaxCommentLength) && $MaxCommentLength > 0) {
$this->Validation->SetSchemaProperty('Body', 'Length', $MaxCommentLength);
$this->Validation->ApplyRule('Body', 'Length');
}
// Get the DiscussionID from the form so we know if we are inserting or updating.
$DiscussionID = ArrayValue('DiscussionID', $FormPostValues, '');
// See if there is a source ID.
if (GetValue('SourceID', $FormPostValues)) {
$DiscussionID = $this->SQL->GetWhere('Discussion', ArrayTranslate($FormPostValues, array('Source', 'SourceID')))->Value('DiscussionID');
if ($DiscussionID)
$FormPostValues['DiscussionID'] = $DiscussionID;
} elseif (GetValue('ForeignID', $FormPostValues)) {
$DiscussionID = $this->SQL->GetWhere('Discussion', array('ForeignID' => $FormPostValues['ForeignID']))->Value('DiscussionID');
if ($DiscussionID)
$FormPostValues['DiscussionID'] = $DiscussionID;
}
$Insert = $DiscussionID == '' ? TRUE : FALSE;
$this->EventArguments['Insert'] = $Insert;
if ($Insert) {
unset($FormPostValues['DiscussionID']);
// If no categoryid is defined, grab the first available.
if (!GetValue('CategoryID', $FormPostValues) && !C('Vanilla.Categories.Use')) {
$FormPostValues['CategoryID'] = GetValue('CategoryID', CategoryModel::DefaultCategory(), -1);
}
$this->AddInsertFields($FormPostValues);
// $FormPostValues['LastCommentUserID'] = $Session->UserID;
$FormPostValues['DateLastComment'] = Gdn_Format::ToDateTime();
}
// Add the update fields because this table's default sort is by DateUpdated (see $this->Get()).
$this->AddUpdateFields($FormPostValues);
// Set checkbox values to zero if they were unchecked
if (ArrayValue('Announce', $FormPostValues, '') === FALSE)
$FormPostValues['Announce'] = 0;
if (ArrayValue('Closed', $FormPostValues, '') === FALSE)
$FormPostValues['Closed'] = 0;
if (ArrayValue('Sink', $FormPostValues, '') === FALSE)
$FormPostValues['Sink'] = 0;
// Prep and fire event
$this->EventArguments['FormPostValues'] = &$FormPostValues;
$this->EventArguments['DiscussionID'] = $DiscussionID;
$this->FireEvent('BeforeSaveDiscussion');
// Validate the form posted values
$this->Validate($FormPostValues, $Insert);
$ValidationResults = $this->ValidationResults();
// If the body is not required, remove it's validation errors.
$BodyRequired = C('Vanilla.DiscussionBody.Required', TRUE);
if (!$BodyRequired && array_key_exists('Body', $ValidationResults))
unset($ValidationResults['Body']);
if (count($ValidationResults) == 0) {
// If the post is new and it validates, make sure the user isn't spamming
if (!$Insert || !$this->CheckForSpam('Discussion')) {
// Get all fields on the form that relate to the schema
$Fields = $this->Validation->SchemaValidationFields();
// Get DiscussionID if one was sent
$DiscussionID = intval(ArrayValue('DiscussionID', $Fields, 0));
// Remove the primary key from the fields for saving
$Fields = RemoveKeyFromArray($Fields, 'DiscussionID');
$StoredCategoryID = FALSE;
if ($DiscussionID > 0) {
// Updating
$Stored = $this->GetID($DiscussionID, DATASET_TYPE_ARRAY);
// Clear the cache if necessary.
if (GetValue('Announce', $Stored) != GetValue('Announce', $Fields)) {
$CacheKeys = array('Announcements');
$this->SQL->Cache($CacheKeys);
}
$this->SQL->Put($this->Name, $Fields, array($this->PrimaryKey => $DiscussionID));
SetValue('DiscussionID', $Fields, $DiscussionID);
LogModel::LogChange('Edit', 'Discussion', (array)$Fields, $Stored);
if (GetValue('CategoryID', $Stored) != GetValue('CategoryID', $Fields))
$StoredCategoryID = GetValue('CategoryID', $Stored);
} else {
// Inserting.
if (!GetValue('Format', $Fields))
$Fields['Format'] = C('Garden.InputFormatter', '');
// Clear the cache if necessary.
if (GetValue('Announce', $Fields)) {
$CacheKeys = array('Announcements');
$this->SQL->Cache($CacheKeys);
}
// Check for spam
$Spam = SpamModel::IsSpam('Discussion', $Fields);
if ($Spam)
return SPAM;
// Check for approval
$ApprovalRequired = CheckRestriction('Vanilla.Approval.Require');
if ($ApprovalRequired) {
LogModel::Insert('Moderate', 'Discussion', $Fields);
return UNAPPROVED;
}
// Create discussion
$DiscussionID = $this->SQL->Insert($this->Name, $Fields);
$Fields['DiscussionID'] = $DiscussionID;
// Update the cache.
if ($DiscussionID && Gdn::Cache()->ActiveEnabled()) {
$CategoryCache = array(
'LastDiscussionID' => $DiscussionID,
'LastCommentID' => NULL,
'LastTitle' => Gdn_Format::Text($Fields['Name']), // kluge so JoinUsers doesn't wipe this out.
'LastUserID' => $Fields['InsertUserID'],
'LastDateInserted' => $Fields['DateInserted'],
'LastUrl' => DiscussionUrl($Fields)
);
CategoryModel::SetCache($Fields['CategoryID'], $CategoryCache);
}
// Assign the new DiscussionID to the comment before saving.
$FormPostValues['IsNewDiscussion'] = TRUE;
$FormPostValues['DiscussionID'] = $DiscussionID;
// Notify users of mentions.
$DiscussionName = ArrayValue('Name', $Fields, '');
$Story = ArrayValue('Body', $Fields, '');
$NotifiedUsers = array();
$UserModel = Gdn::UserModel();
$ActivityModel = new ActivityModel();
if (GetValue('Type', $FormPostValues))
$Code = 'HeadlineFormat.Discussion.'.$FormPostValues['Type'];
else
$Code = 'HeadlineFormat.Discussion';
$HeadlineFormat = T($Code, '{ActivityUserID,user} started a new discussion: <a href="{Url,html}">{Data.Name,text}</a>');
$Category = CategoryModel::Categories(GetValue('CategoryID', $Fields));
$Activity = array(
'ActivityType' => 'Discussion',
'ActivityUserID' => $Fields['InsertUserID'],
'HeadlineFormat' => $HeadlineFormat,
'RecordType' => 'Discussion',
'RecordID' => $DiscussionID,
'Route' => DiscussionUrl($Fields),
'Data' => array(
'Name' => $DiscussionName,
'Category' => GetValue('Name', $Category)
)
);
// Allow simple fulltext notifications
if (C('Vanilla.Activity.ShowDiscussionBody', FALSE))
$Activity['Story'] = $Story;
// Notify all of the users that were mentioned in the discussion.
$Usernames = array_merge(GetMentions($DiscussionName), GetMentions($Story));
$Usernames = array_unique($Usernames);
foreach ($Usernames as $Username) {
$User = $UserModel->GetByUsername($Username);
if (!$User)
continue;
// Check user can still see the discussion.
if (!$UserModel->GetCategoryViewPermission($User->UserID, GetValue('CategoryID', $Fields)))
continue;
$Activity['HeadlineFormat'] = T('HeadlineFormat.Mention', '{ActivityUserID,user} mentioned you in <a href="{Url,html}">{Data.Name,text}</a>');
$Activity['NotifyUserID'] = GetValue('UserID', $User);
$ActivityModel->Queue($Activity, 'Mention');
}
// Notify everyone that has advanced notifications.
try {
$Fields['DiscussionID'] = $DiscussionID;
$this->NotifyNewDiscussion($Fields, $ActivityModel, $Activity);
} catch(Exception $Ex) {
throw $Ex;
}
// Throw an event for users to add their own events.
$this->EventArguments['Discussion'] = $Fields;
$this->EventArguments['Activity'] = $Activity;
$this->EventArguments['NotifiedUsers'] = $NotifiedUsers;
$this->EventArguments['MentionedUsers'] = $Usernames;
$this->EventArguments['ActivityModel'] = $ActivityModel;
$this->FireEvent('BeforeNotification');
// Send all notifications.
$ActivityModel->SaveQueue();
}
// Get CategoryID of this discussion
$Discussion = $this->GetID($DiscussionID, DATASET_TYPE_ARRAY);
$CategoryID = GetValue('CategoryID', $Discussion, FALSE);
// Update discussion counter for affected categories
$this->UpdateDiscussionCount($CategoryID, $Insert ? $Discussion : FALSE);
if ($StoredCategoryID)
$this->UpdateDiscussionCount($StoredCategoryID);
// Fire an event that the discussion was saved.
$this->EventArguments['FormPostValues'] = $FormPostValues;
$this->EventArguments['Fields'] = $Fields;
$this->EventArguments['DiscussionID'] = $DiscussionID;
$this->FireEvent('AfterSaveDiscussion');
}
}
return $DiscussionID;
}
/**
*
* @param type $Discussion
* @param type $NotifiedUsers
* @param ActivityModel $ActivityModel
*/
public function NotifyNewDiscussion($Discussion, $ActivityModel, $Activity) {
if (is_numeric($Discussion)) {
$Discussion = $this->GetID($Discussion);
}
$CategoryID = GetValue('CategoryID', $Discussion);
// Figure out the category that governs this notification preference.
$i = 0;
$Category = CategoryModel::Categories($CategoryID);
if (!$Category)
return;
while ($Category['Depth'] > 2 && $i < 20) {
if (!$Category || $Category['Archived'])
return;
$i++;
$Category = CategoryModel::Categories($Category['ParentCategoryID']);
}
// Grab all of the users that need to be notified.
$Data = $this->SQL
->WhereIn('Name', array('Preferences.Email.NewDiscussion.'.$Category['CategoryID'], 'Preferences.Popup.NewDiscussion.'.$Category['CategoryID']))
->Get('UserMeta')->ResultArray();
// decho($Data, 'Data');
$NotifyUsers = array();
foreach ($Data as $Row) {
if (!$Row['Value'])
continue;
$UserID = $Row['UserID'];
$Name = $Row['Name'];
if (strpos($Name, '.Email.') !== FALSE) {
$NotifyUsers[$UserID]['Emailed'] = ActivityModel::SENT_PENDING;
} elseif (strpos($Name, '.Popup.') !== FALSE) {
$NotifyUsers[$UserID]['Notified'] = ActivityModel::SENT_PENDING;
}
}
// decho($NotifyUsers);
$InsertUserID = GetValue('InsertUserID', $Discussion);
foreach ($NotifyUsers as $UserID => $Prefs) {
if ($UserID == $InsertUserID)
continue;
$Activity['NotifyUserID'] = $UserID;
$Activity['Emailed'] = GetValue('Emailed', $Prefs, FALSE);
$Activity['Notified'] = GetValue('Notified', $Prefs, FALSE);
$ActivityModel->Queue($Activity);
// decho($Activity, 'die');
}
// die();
}
/**
* Updates the CountDiscussions value on the category based on the CategoryID
* being saved.
*
* @since 2.0.0
* @access public
*
* @param int $CategoryID Unique ID of category we are updating.
*/
public function UpdateDiscussionCount($CategoryID, $Discussion = FALSE) {
$DiscussionID = GetValue('DiscussionID', $Discussion, FALSE);
if (strcasecmp($CategoryID, 'All') == 0) {
$Exclude = (bool)Gdn::Config('Vanilla.Archive.Exclude');
$ArchiveDate = Gdn::Config('Vanilla.Archive.Date');
$Params = array();
$Where = '';
if($Exclude && $ArchiveDate) {
$Where = 'where d.DateLastComment > :ArchiveDate';
$Params[':ArchiveDate'] = $ArchiveDate;
}
// Update all categories.
$Sql = "update :_Category c
left join (
select
d.CategoryID,
coalesce(count(d.DiscussionID), 0) as CountDiscussions,
coalesce(sum(d.CountComments), 0) as CountComments
from :_Discussion d
$Where
group by d.CategoryID
) d
on c.CategoryID = d.CategoryID
set
c.CountDiscussions = coalesce(d.CountDiscussions, 0),
c.CountComments = coalesce(d.CountComments, 0)";
$Sql = str_replace(':_', $this->Database->DatabasePrefix, $Sql);
$this->Database->Query($Sql, $Params, 'DiscussionModel_UpdateDiscussionCount');
} elseif (is_numeric($CategoryID)) {
$this->SQL
->Select('d.DiscussionID', 'count', 'CountDiscussions')
->Select('d.CountComments', 'sum', 'CountComments')
->From('Discussion d')
->Where('d.CategoryID', $CategoryID);
$this->AddArchiveWhere();
$Data = $this->SQL->Get()->FirstRow();
$CountDiscussions = (int)GetValue('CountDiscussions', $Data, 0);
$CountComments = (int)GetValue('CountComments', $Data, 0);
$CacheAmendment = array(
'CountDiscussions' => $CountDiscussions,
'CountComments' => $CountComments
);
if ($DiscussionID) {
$CacheAmendment = array_merge($CacheAmendment, array(
'LastDiscussionID' => $DiscussionID,
'LastCommentID' => NULL,
'LastDateInserted' => GetValue('DateInserted', $Discussion)
));
}
$CategoryModel = new CategoryModel();
$CategoryModel->SetField($CategoryID, $CacheAmendment);
}
}
/**
* Update and get bookmark count for the specified user.
*
* @since 2.0.0
* @access public
*
* @param int $UserID Unique ID of user to update.
* @return int Total number of bookmarks user has.
*/
public function SetUserBookmarkCount($UserID) {
$Count = $this->UserBookmarkCount($UserID);
$this->SQL
->Update('User')
->Set('CountBookmarks', $Count)
->Where('UserID', $UserID)
->Put();
return $Count;
}
/**
* Updates a discussion field.
*
* By default, this toggles the specified between '1' and '0'. If $ForceValue
* is provided, the field is set to this value instead. An example use is
* announcing and unannouncing a discussion.
*
* @param int $DiscussionID Unique ID of discussion being updated.
* @param string $Property Name of field to be updated.
* @param mixed $ForceValue If set, overrides toggle behavior with this value.
* @return mixed Value that was ultimately set for the field.
*/
public function SetProperty($DiscussionID, $Property, $ForceValue = NULL) {
if ($ForceValue !== NULL) {
$Value = $ForceValue;
} else {
$Value = '1';
$Discussion = $this->GetID($DiscussionID);
$Value = ($Discussion->$Property == '1' ? '0' : '1');
}
$this->SQL
->Update('Discussion')
->Set($Property, $Value)
->Where('DiscussionID', $DiscussionID)
->Put();
return $Value;
}
/**
* Sets the discussion score for specified user.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion to update.
* @param int $UserID Unique ID of user setting score.
* @param int $Score New score for discussion.
* @return int Total score.
*/
public function SetUserScore($DiscussionID, $UserID, $Score) {
// Insert or update the UserDiscussion row
$this->SQL->Replace(
'UserDiscussion',
array('Score' => $Score),
array('DiscussionID' => $DiscussionID, 'UserID' => $UserID)
);
// Get the total new score
$TotalScore = $this->SQL->Select('Score', 'sum', 'TotalScore')
->From('UserDiscussion')
->Where('DiscussionID', $DiscussionID)
->Get()
->FirstRow()
->TotalScore;
// Update the Discussion's cached version
$this->SQL->Update('Discussion')
->Set('Score', $TotalScore)
->Where('DiscussionID', $DiscussionID)
->Put();
return $TotalScore;
}
/**
* Gets the discussion score for specified user.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion getting score for.
* @param int $UserID Unique ID of user whose score we're getting.
* @return int Total score.
*/
public function GetUserScore($DiscussionID, $UserID) {
$Data = $this->SQL->Select('Score')
->From('UserDiscussion')
->Where('DiscussionID', $DiscussionID)
->Where('UserID', $UserID)
->Get()
->FirstRow();
return $Data ? $Data->Score : 0;
}
/**
* Increments view count for the specified discussion.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion to get +1 view.
*/
public function AddView($DiscussionID) {
if (C('Vanilla.Views.Denormalize', FALSE) && Gdn::Cache()->ActiveEnabled()) {
$CacheKey = "QueryCache.Discussion.{$DiscussionID}.CountViews";
// Increment. If not success, create key.
$Views = Gdn::Cache()->Increment($CacheKey);
if ($Views === Gdn_Cache::CACHEOP_FAILURE) {
$Views = $this->GetWhere(array('DiscussionID' => $DiscussionID))->Value('CountViews', 0);
Gdn::Cache()->Store($CacheKey, $Views);
}
// Every X views, writeback to Discussions
if (($Views % C('Vanilla.Views.DenormalizeWriteback', 100)) == 0) {
$this->SetField($DiscussionID, 'CountViews', $Views);
}
} else {
$this->SQL
->Update('Discussion')
->Set('CountViews', 'CountViews + 1', FALSE)
->Where('DiscussionID', $DiscussionID)
->Put();
}
}
/**
* Bookmarks (or unbookmarks) a discussion for specified user.
*
* Events: AfterBookmarkDiscussion.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion to (un)bookmark.
* @param int $UserID Unique ID of user doing the (un)bookmarking.
* @param object $Discussion Discussion data.
* @return bool Current state of the bookmark (TRUE for bookmarked, FALSE for unbookmarked).
*/
public function BookmarkDiscussion($DiscussionID, $UserID, &$Discussion = NULL) {
$State = '1';
$DiscussionData = $this->SQL
->Select('d.*')
->Select('w.DateLastViewed, w.Dismissed, w.Bookmarked')
->Select('w.CountComments', '', 'CountCommentWatch')
->Select('w.UserID', '', 'WatchUserID')
->Select('d.DateLastComment', '', 'LastDate')
->Select('d.LastCommentUserID', '', 'LastUserID')
->Select('lcu.Name', '', 'LastName')
->From('Discussion d')
->Join('UserDiscussion w', "d.DiscussionID = w.DiscussionID and w.UserID = $UserID", 'left')
->Join('User lcu', 'd.LastCommentUserID = lcu.UserID', 'left') // Last comment user
->Where('d.DiscussionID', $DiscussionID)
->Get();
$this->AddDiscussionColumns($DiscussionData);
$Discussion = $DiscussionData->FirstRow();
if ($Discussion->WatchUserID == '') {
$this->SQL->Options('Ignore', TRUE);
$this->SQL
->Insert('UserDiscussion', array(
'UserID' => $UserID,
'DiscussionID' => $DiscussionID,
'Bookmarked' => $State
));
$Discussion->Bookmarked = TRUE;
} else {
$State = ($Discussion->Bookmarked == '1' ? '0' : '1');
$this->SQL
->Update('UserDiscussion')
->Set('Bookmarked', $State)
->Where('UserID', $UserID)
->Where('DiscussionID', $DiscussionID)
->Put();
$Discussion->Bookmarked = $State;
}
// Update the cached bookmark count on the discussion
$BookmarkCount = $this->BookmarkCount($DiscussionID);
$this->SQL->Update('Discussion')
->Set('CountBookmarks', $BookmarkCount)
->Where('DiscussionID', $DiscussionID)
->Put();
$this->CountDiscussionBookmarks = $BookmarkCount;
// Prep and fire event
$this->EventArguments['Discussion'] = $Discussion;
$this->EventArguments['State'] = $State;
$this->FireEvent('AfterBookmarkDiscussion');
return $State == '1' ? TRUE : FALSE;
}
/**
* Gets number of bookmarks specified discussion has (all users).
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion for which to tally bookmarks.
* @return int Total number of bookmarks.
*/
public function BookmarkCount($DiscussionID) {
$Data = $this->SQL
->Select('DiscussionID', 'count', 'Count')
->From('UserDiscussion')
->Where('DiscussionID', $DiscussionID)
->Where('Bookmarked', '1')
->Get()
->FirstRow();
return $Data !== FALSE ? $Data->Count : 0;
}
/**
* Gets number of bookmarks specified user has.
*
* @since 2.0.0
* @access public
*
* @param int $UserID Unique ID of user for which to tally bookmarks.
* @return int Total number of bookmarks.
*/
public function UserBookmarkCount($UserID) {
$Data = $this->SQL
->Select('ud.DiscussionID', 'count', 'Count')
->From('UserDiscussion ud')
->Join('Discussion d', 'd.DiscussionID = ud.DiscussionID')
->Where('ud.UserID', $UserID)
->Where('ud.Bookmarked', '1')
->Get()
->FirstRow();
return $Data !== FALSE ? $Data->Count : 0;
}
/**
* Delete a discussion. Update and/or delete all related data.
*
* Events: DeleteDiscussion.
*
* @since 2.0.0
* @access public
*
* @param int $DiscussionID Unique ID of discussion to delete.
* @return bool Always returns TRUE.
*/
public function Delete($DiscussionID, $Options = array()) {
// Retrieve the users who have bookmarked this discussion.
$BookmarkData = $this->GetBookmarkUsers($DiscussionID);
$Data = $this->SQL
->Select('*')
->From('Discussion')
->Where('DiscussionID', $DiscussionID)
->Get()->FirstRow(DATASET_TYPE_ARRAY);
$UserID = FALSE;
$CategoryID = FALSE;
if ($Data) {
$UserID = $Data['InsertUserID'];
$CategoryID = $Data['CategoryID'];
}
// Prep and fire event
$this->EventArguments['DiscussionID'] = $DiscussionID;
$this->FireEvent('DeleteDiscussion');
// Execute deletion of discussion and related bits
$this->SQL->Delete('Draft', array('DiscussionID' => $DiscussionID));
$Log = GetValue('Log', $Options, TRUE);
$LogOptions = GetValue('LogOptions', $Options, array());
if ($Log === TRUE)
$Log = 'Delete';
LogModel::BeginTransaction();
// Log all of the comment deletes.
$Comments = $this->SQL->GetWhere('Comment', array('DiscussionID' => $DiscussionID))->ResultArray();
if (count($Comments) > 0 && count($Comments) < 50) {
// A smaller number of comments should just be stored with the record.
$Data['_Data']['Comment'] = $Comments;
LogModel::Insert($Log, 'Discussion', $Data, $LogOptions);
} else {
LogModel::Insert($Log, 'Discussion', $Data, $LogOptions);
foreach ($Comments as $Comment) {
LogModel::Insert($Log, 'Comment', $Comment, $LogOptions);
}
}
LogModel::EndTransaction();
$this->SQL->Delete('Comment', array('DiscussionID' => $DiscussionID));
$this->SQL->Delete('Discussion', array('DiscussionID' => $DiscussionID));
$this->SQL->Delete('UserDiscussion', array('DiscussionID' => $DiscussionID));
$this->UpdateDiscussionCount($CategoryID);
// Get the user's discussion count
$CountDiscussions = $this->SQL
->Select('DiscussionID', 'count', 'CountDiscussions')
->From('Discussion')
->Where('InsertUserID', $UserID)
->Get()->Value('CountDiscussions', 0);
// Save the count to the user table
$this->SQL
->Update('User')
->Set('CountDiscussions', $CountDiscussions)
->Where('UserID', $UserID)
->Put();
// Update bookmark counts for users who had bookmarked this discussion
foreach ($BookmarkData->Result() as $User) {
$this->SetUserBookmarkCount($User->UserID);
}
return TRUE;
}
/**
* Convert tags from stored format to user-presentable format.
*
* @since 2.1
* @access protected
*
* @param string Serialized array.
* @return string Comma-separated tags.
*/
protected function FormatTags($Tags) {
// Don't bother if there aren't any tags
if (!$Tags)
return '';
// Get the array
$TagsArray = Gdn_Format::Unserialize($Tags);
// Compensate for deprecated space-separated format
if (is_string($TagsArray) && $TagsArray == $Tags)
$TagsArray = explode(' ', $Tags);
// Safe format
$TagsArray = Gdn_Format::Text($TagsArray);
// Send back an comma-separated string
return implode(',', $TagsArray);
}
}
Jump to Line
Something went wrong with that request. Please try again.