public function SetWatch($Discussion, $Limit, $Offset, $TotalComments) { // Record the user's watch data $Session = Gdn::Session(); if ($Session->UserID > 0) { $CountWatch = $Limit + $Offset; if ($CountWatch > $TotalComments) { $CountWatch = $TotalComments; } if (is_numeric($Discussion->CountCommentWatch)) { // Update the watch data if ($CountWatch != $Discussion->CountCommentWatch && $CountWatch > $Discussion->CountCommentWatch) { // Only update the watch if there are new comments. $this->SQL->Put('UserDiscussion', array('CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime()), array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID)); } } else { // Make sure the discussion isn't archived. $ArchiveDate = Gdn::Config('Vanilla.Archive.Date'); if (!$ArchiveDate || Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($ArchiveDate)) { // Insert watch data $this->SQL->Insert('UserDiscussion', array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID, 'CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime())); } } } }
/** * Modify CountUnreadComments to account for DateAllViewed * * Required in DiscussionModel->Get() just before the return: * $this->EventArguments['Data'] = $Data; * $this->FireEvent('AfterAddColumns'); * @link http://vanillaforums.org/discussion/13227 */ function DiscussionModel_AfterAddColumns_Handler(&$Sender) { if (!C('Plugins.AllViewed.Enabled')) { return; } // Only for members $Session = Gdn::Session(); if (!$Session->IsValid()) { return; } // Recalculate New count with user's DateAllViewed $Sender->Data = GetValue('Data', $Sender->EventArguments, ''); $Result =& $Sender->Data->Result(); foreach ($Result as &$Discussion) { if (Gdn_Format::ToTimestamp($Discussion->DateLastComment) <= Gdn_Format::ToTimestamp($Session->User->DateAllViewed)) { $Discussion->CountUnreadComments = 0; // Covered by AllViewed } elseif ($Discussion->CountCommentWatch == 0) { // AllViewed used, but new comments since then $Discussion->CountCommentWatch = -1; // hack around "incomplete comment count" logic in WriteDiscussion $Discussion->CountUnreadComments = $Discussion->CountComments; } } }
/** * Checks to see if the user is spamming. Returns TRUE if the user is spamming. */ public function CheckForSpam($Type) { $Spam = FALSE; if (!in_array($Type, array('Comment', 'Discussion'))) { trigger_error(ErrorMessage(sprintf('Spam check type unknown: %s', $Type), 'VanillaModel', 'CheckForSpam'), E_USER_ERROR); } $Session = Gdn::Session(); $CountSpamCheck = $Session->GetAttribute('Count' . $Type . 'SpamCheck', 0); $DateSpamCheck = $Session->GetAttribute('Date' . $Type . 'SpamCheck', 0); $SecondsSinceSpamCheck = time() - Gdn_Format::ToTimestamp($DateSpamCheck); $SpamCount = Gdn::Config('Vanilla.' . $Type . '.SpamCount'); if (!is_numeric($SpamCount) || $SpamCount < 2) { $SpamCount = 2; } // 2 spam minimum $SpamTime = Gdn::Config('Vanilla.' . $Type . '.SpamTime'); if (!is_numeric($SpamTime) || $SpamTime < 0) { $SpamTime = 30; } // 30 second minimum spam span $SpamLock = Gdn::Config('Vanilla.' . $Type . '.SpamLock'); if (!is_numeric($SpamLock) || $SpamLock < 30) { $SpamLock = 30; } // 30 second minimum lockout // Definition: // Users cannot post more than $SpamCount comments within $SpamTime // seconds or their account will be locked for $SpamLock seconds. // Apply a spam lock if necessary $Attributes = array(); if ($SecondsSinceSpamCheck < $SpamLock && $CountSpamCheck >= $SpamCount && $DateSpamCheck !== FALSE) { // TODO: REMOVE DEBUGGING INFO AFTER THIS IS WORKING PROPERLY /* echo '<div>SecondsSinceSpamCheck: '.$SecondsSinceSpamCheck.'</div>'; echo '<div>SpamLock: '.$SpamLock.'</div>'; echo '<div>CountSpamCheck: '.$CountSpamCheck.'</div>'; echo '<div>SpamCount: '.$SpamCount.'</div>'; echo '<div>DateSpamCheck: '.$DateSpamCheck.'</div>'; echo '<div>SpamTime: '.$SpamTime.'</div>'; */ $Spam = TRUE; $this->Validation->AddValidationResult('Body', sprintf(T('You have posted %1$s times within %2$s seconds. A spam block is now in effect on your account. You must wait at least %3$s seconds before attempting to post again.'), $SpamCount, $SpamTime, $SpamLock)); // Update the 'waiting period' every time they try to post again $Attributes['Date' . $Type . 'SpamCheck'] = Gdn_Format::ToDateTime(); } else { if ($SecondsSinceSpamCheck > $SpamTime) { $Attributes['Count' . $Type . 'SpamCheck'] = 1; $Attributes['Date' . $Type . 'SpamCheck'] = Gdn_Format::ToDateTime(); } else { $Attributes['Count' . $Type . 'SpamCheck'] = $CountSpamCheck + 1; } } // Update the user profile after every comment $UserModel = Gdn::UserModel(); $UserModel->SaveAttribute($Session->UserID, $Attributes); return $Spam; }
/** * Modify CountUnreadComments to account for DateAllViewed * * Required in DiscussionModel->Get() just before the return: * $this->EventArguments['Data'] = $Data; * $this->FireEvent('AfterAddColumns'); * @link http://vanillaforums.org/discussion/13227 */ function DiscussionModel_AfterAddColumns_Handler(&$Sender) { if (!C('Plugins.AllViewed.Enabled')) return; // Only for members $Session = Gdn::Session(); if(!$Session->IsValid()) return; // Recalculate New count with user's DateAllViewed $Sender->Data = GetValue('Data', $Sender->EventArguments, ''); $Result = &$Sender->Data->Result(); $DateAllViewed = Gdn_Format::ToTimestamp($Session->User->DateAllViewed); foreach($Result as &$Discussion) { if ($DateAllViewed != 0) { // Only if they've used AllViewed if (Gdn_Format::ToTimestamp($Discussion->DateLastComment) <= $DateAllViewed) $Discussion->CountUnreadComments = 0; // Covered by AllViewed elseif ($Discussion->DateLastViewed == $DateAllViewed) // AllViewed used since last "real" view, but new comments since then $Discussion->CountUnreadComments = $this->GetCommentCountSince($Discussion->DiscussionID, $DateAllViewed); } } }
protected function DrawEdited($Sender) { $Record = $Sender->Data('Discussion'); if (!$Record) { $Record = $Sender->Data('Record'); } if (!$Record) { return; } $PermissionCategoryID = GetValue('PermissionCategoryID', $Record); $Data = $Record; $RecordType = 'discussion'; $RecordID = GetValue('DiscussionID', $Data); // But override if comment if (isset($Sender->EventArguments['Comment']) || GetValue('RecordType', $Record) == 'comment') { $Data = $Sender->EventArguments['Comment']; $RecordType = 'comment'; $RecordID = GetValue('CommentID', $Data); } $UserCanEdit = Gdn::Session()->CheckPermission('Vanilla.' . ucfirst($RecordType) . 's.Edit', TRUE, 'Category', $PermissionCategoryID); if (is_null($Data->DateUpdated)) { return; } if (Gdn_Format::ToTimestamp($Data->DateUpdated) <= Gdn_Format::ToTimestamp($Data->DateInserted)) { return; } $SourceUserID = $Data->InsertUserID; $UpdatedUserID = $Data->UpdateUserID; $UserData = Gdn::UserModel()->GetID($UpdatedUserID); $Edited = array('EditUser' => GetValue('Name', $UserData, T('Unknown User')), 'EditDate' => Gdn_Format::Date($Data->DateUpdated, 'html'), 'EditLogUrl' => Url("/log/record/{$RecordType}/{$RecordID}"), 'EditWord' => 'at'); $DateUpdateTime = Gdn_Format::ToTimestamp($Data->DateUpdated); if (date('ymd', $DateUpdateTime) != date('ymd')) { $Edited['EditWord'] = 'on'; } $Format = T('PostEdited.Plain', 'Post edited by {EditUser} {EditWord} {EditDate}'); if ($UserCanEdit) { $Format = T('PostEdited.Log', 'Post edited by {EditUser} {EditWord} {EditDate} (<a href="{EditLogUrl}">log</a>)'); } $Display = '<div class="PostEdited">' . FormatString($Format, $Edited) . '</div>'; echo $Display; }
public function Activity($UserReference = '', $Username = '', $UserID = '') { $this->Permission('Garden.Profiles.View'); $this->GetUserInfo($UserReference, $Username, $UserID); $this->SetTabView('Activity'); $this->ActivityModel = new ActivityModel(); $Session = Gdn::Session(); $Comment = $this->Form->GetFormValue('Comment'); if ($Session->UserID > 0 && $this->Form->AuthenticatedPostBack() && !StringIsNullOrEmpty($Comment)) { $Comment = substr($Comment, 0, 1000); // Limit to 1000 characters... // Update About if necessary $ActivityType = 'WallComment'; $SendNotification = TRUE; if ($Session->UserID == $this->User->UserID) { $SendNotification = FALSE; $this->UserModel->SaveAbout($Session->UserID, $Comment); $this->User->About = $Comment; $this->SetJson('UserData', $this->FetchView('user')); $ActivityType = 'AboutUpdate'; } $NewActivityID = $this->ActivityModel->Add($Session->UserID, $ActivityType, $Comment, $this->User->UserID, '', '/profile/' . $this->User->UserID . '/' . Gdn_Format::Url($this->User->Name), $SendNotification); if ($this->_DeliveryType === DELIVERY_TYPE_ALL) { Redirect('dashboard/profile/' . $UserReference); } else { // Load just the single new comment $this->HideActivity = TRUE; $this->ActivityData = $this->ActivityModel->GetWhere('ActivityID', $NewActivityID); $this->View = 'activities'; $this->ControllerName = 'activity'; } } else { $this->ProfileUserID = $this->User->UserID; $this->ActivityData = $this->ActivityModel->Get($this->User->UserID); if ($this->ActivityData->NumRows() > 0) { $ActivityData = $this->ActivityData->Result(); $ActivityIDs = ConsolidateArrayValuesByKey($ActivityData, 'ActivityID'); $LastActivity = $this->ActivityData->FirstRow(); $LastModifiedDate = Gdn_Format::ToTimestamp($this->User->DateUpdated); $LastActivityDate = Gdn_Format::ToTimestamp($LastActivity->DateInserted); if ($LastModifiedDate < $LastActivityDate) { $LastModifiedDate = $LastActivityDate; } // Make sure to only query this page if the user has no new activity since the requesting browser last saw it. $this->SetLastModified($LastModifiedDate); $this->CommentData = $this->ActivityModel->GetComments($ActivityIDs); } else { $this->CommentData = FALSE; } } // Set the canonical Url. if (is_numeric($this->User->Name) || Gdn_Format::Url($this->User->Name) != strtolower($this->User->Name)) { $this->CanonicalUrl(Url('profile/' . $this->User->UserID . '/' . Gdn_Format::Url($this->User->Name), TRUE)); } else { $this->CanonicalUrl(Url('profile/' . strtolower($this->User->Name), TRUE)); } $this->Render(); }
/** * Authenticates the user with the provided Authenticator class. * * @param int $UserID The UserID to start the session with. * @param bool $SetIdentity Whether or not to set the identity (cookie) or make this a one request session. */ public function Start($UserID = FALSE, $SetIdentity = TRUE) { if (!Gdn::Config('Garden.Installed')) { return; } // Retrieve the authenticated UserID from the Authenticator module. $UserModel = Gdn::Authenticator()->GetUserModel(); $this->UserID = $UserID ? $UserID : Gdn::Authenticator()->GetIdentity(); $this->User = FALSE; // Now retrieve user information if ($this->UserID > 0) { // Instantiate a UserModel to get session info $this->User = $UserModel->GetSession($this->UserID); if ($this->User) { if ($UserID && $SetIdentity) { Gdn::Authenticator()->SetIdentity($UserID); } if (Gdn::Authenticator()->ReturningUser($this->User)) { $UserModel->UpdateLastVisit($this->UserID, $this->User->Attributes, $this->User->Attributes['HourOffset']); } $UserModel->EventArguments['User'] =& $this->User; $UserModel->FireEvent('AfterGetSession'); $this->_Permissions = Gdn_Format::Unserialize($this->User->Permissions); $this->_Preferences = Gdn_Format::Unserialize($this->User->Preferences); $this->_Attributes = Gdn_Format::Unserialize($this->User->Attributes); $this->_TransientKey = is_array($this->_Attributes) ? ArrayValue('TransientKey', $this->_Attributes) : FALSE; if ($this->_TransientKey === FALSE) { $this->_TransientKey = $UserModel->SetTransientKey($this->UserID); } // If the user hasn't been active in the session-time, update their date last active $SessionLength = Gdn::Config('Garden.Session.Length', '15 minutes'); if (Gdn_Format::ToTimestamp($this->User->DateLastActive) < strtotime($SessionLength . ' ago')) { $UserModel->Save(array('UserID' => $this->UserID, 'DateLastActive' => Gdn_Format::ToDateTime())); } } else { $this->UserID = 0; $this->User = FALSE; if ($SetIdentity) { Gdn::Authenticator()->SetIdentity(NULL); } } } // Load guest permissions if necessary if ($this->UserID == 0) { $this->_Permissions = Gdn_Format::Unserialize($UserModel->DefinePermissions(0)); } }
public function GetID($DiscussionID) { $Session = Gdn::Session(); $this->FireEvent('BeforeGetID'); $Data = $this->SQL->Select('d.*')->Select('ca.Name', '', 'Category')->Select('ca.UrlCode', '', 'CategoryUrlCode')->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('iup.Name', '', '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')->Join('Photo iup', 'iu.PhotoID = iup.PhotoID', 'left')->Join('Comment lc', 'd.LastCommentID = lc.CommentID', 'left')->Join('User lcu', 'lc.InsertUserID = lcu.UserID', 'left')->Where('d.DiscussionID', $DiscussionID)->Get()->FirstRow(); if ($Data && Gdn_Format::ToTimestamp($Data->DateLastComment) <= Gdn_Format::ToTimestamp(Gdn::Config('Vanilla.Archive.Date', 0))) { $Data->Closed = '1'; } return $Data; }
/** * Record the user's watch data. * * @since 2.0.0 * @access public * * @param object $Discussion Discussion being watched. * @param int $Limit Max number to get. * @param int $Offset Number to skip. * @param int $TotalComments Total in entire discussion (hard limit). */ public function SetWatch($Discussion, $Limit, $Offset, $TotalComments) { $NewComment = FALSE; $Session = Gdn::Session(); if ($Session->UserID > 0) { $CountWatch = $Limit + $Offset + 1; // Include the first comment (in the discussion table) in the count. if ($CountWatch > $TotalComments) { $CountWatch = $TotalComments; $NewComment = TRUE; } if (is_numeric($Discussion->CountCommentWatch)) { if (isset($Discussion->DateLastViewed)) { $NewComment |= Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($Discussion->DateLastViewed); } // Update the watch data. if ($NewComment || $CountWatch > $Discussion->CountCommentWatch) { // Only update the watch if there are new comments. $this->SQL->Put('UserDiscussion', array('CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime()), array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID)); } } else { // Make sure the discussion isn't archived. $ArchiveDate = Gdn::Config('Vanilla.Archive.Date'); if (!$ArchiveDate || Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($ArchiveDate)) { // Insert watch data $this->SQL->Insert('UserDiscussion', array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID, 'CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime())); } } } }
?> <li id="<?php echo 'Bookmark_' . $Discussion->DiscussionID; ?> "> <strong><?php echo Anchor($Discussion->Name, '/discussion/' . $Discussion->DiscussionID . '/' . Gdn_Format::Url($Discussion->Name) . ($Discussion->CountCommentWatch > 0 ? '/#Item_' . $Discussion->CountCommentWatch : ''), 'DiscussionLink'); ?> </strong> <div class="Meta"> <?php echo '<span>' . $Discussion->CountComments . '</span>'; $CountUnreadComments = $Discussion->CountComments - $Discussion->CountCommentWatch; // Logic for incomplete comment count. if ($Discussion->CountCommentWatch == 0 && ($DateLastViewed = GetValue('DateLastViewed', $Discussion))) { if (Gdn_Format::ToTimestamp($DateLastViewed) >= Gdn_Format::ToTimestamp($Discussion->LastDate)) { $CountUnreadComments = 0; $Discussion->CountCommentWatch = $Discussion->CountComments; } else { $CountUnreadComments = ''; } } if ($CountUnreadComments > 0 || $CountUnreadComments === '') { echo '<strong>' . trim(sprintf('%s new', $CountUnreadComments)) . '</strong>'; } $Last = new stdClass(); $Last->UserID = $Discussion->LastUserID; $Last->Name = $Discussion->LastName; echo '<span>' . Gdn_Format::Date($Discussion->LastDate) . ' ' . UserAnchor($Last) . '</span>'; ?> </div>
/** * Show activity feed for this user. * * @since 2.0.0 * @access public * @param mixed $UserReference Unique identifier, possible ID or username. * @param string $Username Username. * @param int $UserID Unique ID. * @param int $Offset How many to skip (for paging). */ public function Activity($UserReference = '', $Username = '', $UserID = '', $Page = '') { $this->Permission('Garden.Profiles.View'); $this->EditMode(FALSE); // Object setup $Session = Gdn::Session(); $this->ActivityModel = new ActivityModel(); // Calculate offset. list($Offset, $Limit) = OffsetLimit($Page, 30); // Get user, tab, and comment $this->GetUserInfo($UserReference, $Username, $UserID); $UserID = $this->User->UserID; $Username = $this->User->Name; $this->_SetBreadcrumbs(T('Activity'), UserUrl($this->User, '', 'activity')); $this->SetTabView('Activity'); $Comment = $this->Form->GetFormValue('Comment'); // Load data to display $this->ProfileUserID = $this->User->UserID; $Limit = 30; $NotifyUserIDs = array(ActivityModel::NOTIFY_PUBLIC); if (Gdn::Session()->CheckPermission('Garden.Moderation.Manage')) { $NotifyUserIDs[] = ActivityModel::NOTIFY_MODS; } $Activities = $this->ActivityModel->GetWhere(array('ActivityUserID' => $UserID, 'NotifyUserID' => $NotifyUserIDs), $Offset, $Limit)->ResultArray(); $this->ActivityModel->JoinComments($Activities); $this->SetData('Activities', $Activities); if (count($Activities) > 0) { $LastActivity = $Activities[0]; $LastModifiedDate = Gdn_Format::ToTimestamp($this->User->DateUpdated); $LastActivityDate = Gdn_Format::ToTimestamp($LastActivity['DateInserted']); if ($LastModifiedDate < $LastActivityDate) { $LastModifiedDate = $LastActivityDate; } // Make sure to only query this page if the user has no new activity since the requesting browser last saw it. $this->SetLastModified($LastModifiedDate); } // Set the canonical Url. if (is_numeric($this->User->Name) || Gdn_Format::Url($this->User->Name) != strtolower($this->User->Name)) { $this->CanonicalUrl(Url('profile/' . $this->User->UserID . '/' . Gdn_Format::Url($this->User->Name), TRUE)); } else { $this->CanonicalUrl(Url('profile/' . strtolower($this->User->Name), TRUE)); } $this->Render(); }
/** * Display custom fields on Profile. */ public function UserInfoModule_OnBasicInfo_Handler($Sender) { try { // Get the custom fields $ProfileFields = Gdn::UserModel()->GetMeta($Sender->User->UserID, 'Profile.%', 'Profile.'); // Import from CustomProfileFields if available if (!count($ProfileFields) && is_object($Sender->User) && C('Plugins.CustomProfileFields.SuggestedFields', FALSE)) { $ProfileFields = Gdn::UserModel()->GetAttribute($Sender->User->UserID, 'CustomProfileFields', FALSE); if ($ProfileFields) { // Migrate to UserMeta & delete original Gdn::UserModel()->SetMeta($Sender->User->UserID, $ProfileFields, 'Profile.'); Gdn::UserModel()->SaveAttribute($Sender->User->UserID, 'CustomProfileFields', FALSE); } } // Send them off for magic formatting $ProfileFields = $this->ParseSpecialFields($ProfileFields); // Get all field data, error check $AllFields = $this->GetProfileFields(); if (!is_array($AllFields) || !is_array($ProfileFields)) { return; } // DateOfBirth is special case that core won't handle // Hack it in here instead if (C('ProfileExtender.Fields.DateOfBirth.OnProfile')) { // Do not use Gdn_Format::Date because it shifts to local timezone $ProfileFields['DateOfBirth'] = date(T('Birthday Format', 'F j, Y'), Gdn_Format::ToTimestamp($Sender->User->DateOfBirth)); $AllFields['DateOfBirth'] = array('Label' => T('Birthday'), 'OnProfile' => TRUE); } // Display all non-hidden fields $ProfileFields = array_reverse($ProfileFields); foreach ($ProfileFields as $Name => $Value) { if (!$Value) { continue; } if (!GetValue('OnProfile', $AllFields[$Name])) { continue; } // Non-magic fields must be plain text, but we'll auto-link if (!in_array($Name, $this->MagicLabels)) { $Value = Gdn_Format::Links(Gdn_Format::Text($Value)); } echo ' <dt class="ProfileExtend Profile' . Gdn_Format::AlphaNumeric($Name) . '">' . Gdn_Format::Text($AllFields[$Name]['Label']) . '</dt> '; echo ' <dd class="ProfileExtend Profile' . Gdn_Format::AlphaNumeric($Name) . '">' . Gdn_Format::Html($Value) . '</dd> '; } } catch (Exception $ex) { // No errors } }
protected static function CalculateData(&$Data) { foreach ($Data as &$Category) { $Category['CountAllDiscussions'] = $Category['CountDiscussions']; $Category['CountAllComments'] = $Category['CountComments']; $Category['Url'] = '/categories/'.rawurlencode($Category['UrlCode']); // Calculate the following field. $Following = !((bool)GetValue('Archived', $Category) || (bool)GetValue('Unfollow', $Category)); $Category['Following'] = $Following; // Calculate the read field. if (isset($Category['DateLastComment'])) { $DateMarkedRead = GetValue('UserDateMarkedRead', $Category); if (!$DateMarkedRead) $DateMarkedRead = GetValue('DateMarkedRead', $Category); if ($DateMarkedRead || !GetValue('DateLastComment', $Category)) $Category['Read'] = Gdn_Format::ToTimestamp($DateMarkedRead) >= Gdn_Format::ToTimestamp($Category['DateLastComment']); else $Category['Read'] = FALSE; } $Category['Children'] = array(); } $Keys = array_reverse(array_keys($Data)); foreach ($Keys as $Key) { $Cat = $Data[$Key]; $ParentID = $Cat['ParentCategoryID']; if (isset($Data[$ParentID])) { $Data[$ParentID]['CountAllDiscussions'] += $Cat['CountAllDiscussions']; $Data[$ParentID]['CountAllComments'] += $Cat['CountAllComments']; $Data[$ParentID]['Children'][] =& $Data[$Key]; if ($ParentID != $Cat['CategoryID']) $Data[$Key]['Parent'] =& $Data[$ParentID]; } } }
/** * Signin process that multiple authentication methods. * * @access public * @since 2.0.0 * @author Tim Gunter * * @param string $Method * @param array $Arg1 * @return string Rendered XHTML template. */ public function SignIn($Method = FALSE, $Arg1 = FALSE) { $this->AddJsFile('entry.js'); $this->SetData('Title', T('Sign In')); $this->Form->AddHidden('Target', $this->Target()); $this->Form->AddHidden('ClientHour', date('Y-m-d H:00')); // Use the server's current hour as a default. // Additional signin methods are set up with plugins. $Methods = array(); $this->SetData('MainFormArgs', array($Arg1)); $this->SetData('Methods', $Methods); $this->SetData('FormUrl', Url('entry/signin')); $this->FireEvent('SignIn'); if ($this->Form->IsPostBack()) { $this->Form->ValidateRule('Email', 'ValidateRequired', sprintf(T('%s is required.'), T('Email/Username'))); $this->Form->ValidateRule('Password', 'ValidateRequired'); // Check the user. if ($this->Form->ErrorCount() == 0) { $Email = $this->Form->GetFormValue('Email'); $User = Gdn::UserModel()->GetByEmail($Email); if (!$User) { $User = Gdn::UserModel()->GetByUsername($Email); } if (!$User) { $this->Form->AddError('ErrorCredentials'); } else { $ClientHour = $this->Form->GetFormValue('ClientHour'); $HourOffset = Gdn_Format::ToTimestamp($ClientHour) - time(); $HourOffset = round($HourOffset / 3600); // Check the password. $PasswordHash = new Gdn_PasswordHash(); if ($PasswordHash->CheckPassword($this->Form->GetFormValue('Password'), GetValue('Password', $User), GetValue('HashMethod', $User))) { Gdn::Session()->Start(GetValue('UserID', $User), TRUE, (bool) $this->Form->GetFormValue('RememberMe')); if (!Gdn::Session()->CheckPermission('Garden.SignIn.Allow')) { $this->Form->AddError('ErrorPermission'); Gdn::Session()->End(); } else { if ($HourOffset != Gdn::Session()->User->HourOffset) { Gdn::UserModel()->SetProperty(Gdn::Session()->UserID, 'HourOffset', $HourOffset); } $this->_SetRedirect(); } } else { $this->Form->AddError('ErrorCredentials'); } } } } else { if ($Target = $this->Request->Get('Target')) { $this->Form->AddHidden('Target', $Target); } $this->Form->SetValue('RememberMe', TRUE); } return $this->Render(); }
/** * 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; }
public static function LogChange($Operation, $RecordType, $NewData, $OldData = NULL) { $RecordID = isset($NewData['RecordID']) ? $NewData['RecordID'] : $NewData[$RecordType . 'ID']; // Grab the record from the DB. if ($OldData == NULL) { $OldData = Gdn::SQL()->GetWhere($RecordType, array($RecordType . 'ID' => $RecordID))->ResultArray(); } elseif (!isset($OldData[0])) { $OldData = array($OldData); } foreach ($OldData as $Row) { // Don't log the change if it's right after an insert. if (isset($Row['DateInserted']) && time() - Gdn_Format::ToTimestamp($Row['DateInserted']) < C('Garden.Log.FloodControl', 20) * 60) { continue; } $Row['_New'] = $NewData; self::Insert($Operation, $RecordType, $Row); } }
public function Calculate(&$Discussion) { $ArchiveTimestamp = Gdn_Format::ToTimestamp(Gdn::Config('Vanilla.Archive.Date', 0)); // Fix up output $Discussion->Name = Gdn_Format::Text($Discussion->Name); $Discussion->Attributes = @unserialize($Discussion->Attributes); $Discussion->Url = DiscussionUrl($Discussion); $Discussion->Tags = $this->FormatTags($Discussion->Tags); // Join in the category. $Category = CategoryModel::Categories($Discussion->CategoryID); if (!$Category) { $Category = FALSE; } $Discussion->Category = $Category['Name']; $Discussion->CategoryUrlCode = $Category['UrlCode']; $Discussion->PermissionCategoryID = $Category['PermissionCategoryID']; // Add some legacy calculated columns. if (!property_exists($Discussion, 'FirstUserID')) { $Discussion->FirstUserID = $Discussion->InsertUserID; $Discussion->FirstDate = $Discussion->DateInserted; $Discussion->LastUserID = $Discussion->LastCommentUserID; $Discussion->LastDate = $Discussion->DateLastComment; } // Add the columns from UserDiscussion if they don't exist. if (!property_exists($Discussion, 'CountCommentWatch')) { $Discussion->WatchUserID = NULL; $Discussion->DateLastViewed = NULL; $Discussion->Dismissed = 0; $Discussion->Bookmarked = 0; $Discussion->CountCommentWatch = NULL; } // Allow for discussions to be archived 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; } // Allow for discussions to just be new. } elseif ($Discussion->CountCommentWatch === NULL) { $Discussion->CountUnreadComments = TRUE; } else { $Discussion->CountUnreadComments = $Discussion->CountComments - $Discussion->CountCommentWatch; } if (!property_exists($Discussion, 'Read')) { $Discussion->Read = !(bool) $Discussion->CountUnreadComments; if ($Category && !is_null($Category['DateMarkedRead'])) { // If the category was marked explicitly read at some point, see if that applies here if ($Category['DateMarkedRead'] > $Discussion->DateLastComment) { $Discussion->Read = TRUE; } if ($Discussion->Read) { $Discussion->CountUnreadComments = 0; } } } // 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 : NULL; if ($Discussion->LastUserID == NULL) { $Discussion->LastUserID = $Discussion->InsertUserID; $Discussion->LastDate = $Discussion->DateInserted; } $this->EventArguments['Discussion'] = $Discussion; $this->FireEvent('SetCalculatedFields'); }
function WriteDiscussion($Discussion, &$Sender, &$Session, $Alt) { $CssClass = 'Item'; $CssClass .= $Discussion->Bookmarked == '1' ? ' Bookmarked' : ''; $CssClass .= $Alt . ' '; $CssClass .= $Discussion->Announce == '1' ? ' Announcement' : ''; $CssClass .= $Discussion->InsertUserID == $Session->UserID ? ' Mine' : ''; $CountUnreadComments = 0; if (is_numeric($Discussion->CountUnreadComments)) { $CountUnreadComments = $Discussion->CountUnreadComments; } // Logic for incomplete comment count. if ($Discussion->CountCommentWatch == 0 && ($DateLastViewed = GetValue('DateLastViewed', $Discussion))) { if (Gdn_Format::ToTimestamp($DateLastViewed) >= Gdn_Format::ToTimestamp($Discussion->LastDate)) { $CountUnreadComments = 0; $Discussion->CountCommentWatch = $Discussion->CountComments; } else { $CountUnreadComments = ''; } } $CssClass .= $CountUnreadComments > 0 && $Session->IsValid() ? ' New' : ''; $Sender->EventArguments['Discussion'] =& $Discussion; $First = UserBuilder($Discussion, 'First'); $Last = UserBuilder($Discussion, 'Last'); $DiscussionName = Gdn_Format::Text($Discussion->Name); if ($DiscussionName == '') { $DiscussionName = T('Blank Discussion Topic'); } static $FirstDiscussion = TRUE; if (!$FirstDiscussion) { $Sender->FireEvent('BetweenDiscussion'); } else { $FirstDiscussion = FALSE; } ?> <li class="<?php echo $CssClass; ?> "> <?php $Sender->FireEvent('BeforeDiscussionContent'); WriteOptions($Discussion, $Sender, $Session); ?> <div class="ItemContent Discussion"> <?php echo Anchor($DiscussionName, '/discussion/' . $Discussion->DiscussionID . '/' . Gdn_Format::Url($Discussion->Name) . ($Discussion->CountCommentWatch > 0 && C('Vanilla.Comments.AutoOffset') ? '/#Item_' . $Discussion->CountCommentWatch : ''), 'Title'); ?> <?php $Sender->FireEvent('AfterDiscussionTitle'); ?> <div class="Meta"> <?php if ($Discussion->Announce == '1') { ?> <span class="Announcement"><?php echo T('Announcement'); ?> </span> <?php } ?> <?php if ($Discussion->Closed == '1') { ?> <span class="Closed"><?php echo T('Closed'); ?> </span> <?php } ?> <span class="CommentCount"><?php printf(Plural($Discussion->CountComments, '%s comment', '%s comments'), $Discussion->CountComments); ?> </span> <?php if ($Session->IsValid()) { if ($CountUnreadComments == $Discussion->CountComments) { echo '<strong>' . T('New') . '</strong>'; } else { if ($CountUnreadComments > 0) { echo '<strong>' . Plural($CountUnreadComments, '%s New', '%s New Plural') . '</strong>'; } } } if ($Discussion->LastCommentID != '') { echo '<span class="LastCommentBy">' . sprintf(T('Most recent by %1$s'), UserAnchor($Last)) . '</span>'; echo '<span class="LastCommentDate">' . Gdn_Format::Date($Discussion->LastDate) . '</span>'; } else { echo '<span class="LastCommentBy">' . sprintf(T('Started by %1$s'), UserAnchor($First)) . '</span>'; echo '<span class="LastCommentDate">' . Gdn_Format::Date($Discussion->FirstDate) . '</span>'; } if (C('Vanilla.Categories.Use')) { echo Wrap(Anchor($Discussion->Category, '/categories/' . $Discussion->CategoryUrlCode, 'Category')); } $Sender->FireEvent('DiscussionMeta'); ?> </div> </div> </li> <?php }
/** * Record the user's watch data. * * @since 2.0.0 * @access public * * @param object $Discussion Discussion being watched. * @param int $Limit Max number to get. * @param int $Offset Number to skip. * @param int $TotalComments Total in entire discussion (hard limit). */ public function SetWatch($Discussion, $Limit, $Offset, $TotalComments) { $Session = Gdn::Session(); if ($Session->UserID > 0) { $CountWatch = $Limit + $Offset + 1; // Include the first comment (in the discussion table) in the count. if ($CountWatch > $TotalComments) $CountWatch = $TotalComments; if (is_numeric($Discussion->CountCommentWatch)) { $NewComment = FALSE; if (isset($Discussion->DateLastViewed)) $NewComment |= Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($Discussion->DateLastViewed); // Update the watch data. if ($NewComment || ($CountWatch != $Discussion->CountCommentWatch && $CountWatch > $Discussion->CountCommentWatch)) { // Only update the watch if there are new comments. $this->SQL->Put( 'UserDiscussion', array( 'CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime() ), array( 'UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID ) ); } } else { // Make sure the discussion isn't archived. $ArchiveDate = Gdn::Config('Vanilla.Archive.Date'); if(!$ArchiveDate || (Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($ArchiveDate))) { // Insert watch data $this->SQL->Insert( 'UserDiscussion', array( 'UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID, 'CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime() ) ); } } // decho('Limit: '.$Limit.'; Offset: '.$Offset.'; CountComments: '.$TotalComments); // decho('Setting Watch Records for Discussion '.$Discussion->DiscussionID.' to CountWatch: '.$CountWatch.' Limit: '.$Limit.' Offset: '.$Offset.' TotalComments ' . $TotalComments); // die(); } }
/** * Modify CountUnreadComments to account for DateAllViewed. * * Required in DiscussionModel->Get() just before the return: * $this->EventArguments['Data'] = $Data; * $this->FireEvent('AfterAddColumns'); * @link http://vanillaforums.org/discussion/13227 * @since 1.0 * @access public */ public function DiscussionModel_SetCalculatedFields_Handler($Sender) { // Only for members if (!Gdn::Session()->IsValid()) { return; } // Recalculate New count with each category's DateMarkedRead $Discussion =& $Sender->EventArguments['Discussion']; $Category = CategoryModel::Categories($Discussion->CategoryID); $CategoryLastDate = Gdn_Format::ToTimestamp($Category["DateMarkedRead"]); if ($CategoryLastDate != 0) { $this->CheckDiscussionDate($Discussion, $CategoryLastDate); } }
/** * Set the user's timezone (hour offset). * * @since 2.0.0 * @access public * @param string $ClientDate Client-reported datetime. * @param string $TransientKey Security token. */ public function SetClientHour($ClientDate = '', $TransientKey = '') { $this->_DeliveryType = DELIVERY_TYPE_BOOL; $Session = Gdn::Session(); $Success = FALSE; $ClientTimestamp = Gdn_Format::ToTimestamp($ClientDate); if (is_numeric($ClientTimestamp) && $Session->UserID > 0 && $Session->ValidateTransientKey($TransientKey)) { $UserModel = Gdn::UserModel(); $HourOffset = $ClientTimestamp - time(); $HourOffset = round($HourOffset / 3600); $UserModel->SetField($Session->UserID, 'HourOffset', $HourOffset); $Success = TRUE; } $this->Render(); }
/** * Invitation-only registration. Requires code. * * @param int $InvitationCode * @since 2.0.0 */ public function RegisterInvitation($InvitationCode = 0) { $this->Form->SetModel($this->UserModel); // Define gender dropdown options $this->GenderOptions = array('u' => T('Unspecified'), 'm' => T('Male'), 'f' => T('Female')); if (!$this->Form->IsPostBack()) { $this->Form->SetValue('InvitationCode', $InvitationCode); } $InvitationModel = new InvitationModel(); // Look for the invitation. $Invitation = $InvitationModel->GetWhere(array('Code' => $this->Form->GetValue('InvitationCode')))->FirstRow(DATASET_TYPE_ARRAY); if (!$Invitation) { $this->Form->AddError('Invitation not found.', 'Code'); } else { if ($Expires = GetValue('DateExpires', $Invitation)) { $Expires = Gdn_Format::ToTimestamp($Expires); if ($Expires <= time()) { } } } $this->Form->AddHidden('ClientHour', date('Y-m-d H:00')); // Use the server's current hour as a default $this->Form->AddHidden('Target', $this->Target()); Gdn::UserModel()->AddPasswordStrength($this); if ($this->Form->IsPostBack() === TRUE) { $this->InvitationCode = $this->Form->GetValue('InvitationCode'); // Add validation rules that are not enforced by the model $this->UserModel->DefineSchema(); $this->UserModel->Validation->ApplyRule('Name', 'Username', $this->UsernameError); $this->UserModel->Validation->ApplyRule('TermsOfService', 'Required', T('You must agree to the terms of service.')); $this->UserModel->Validation->ApplyRule('Password', 'Required'); $this->UserModel->Validation->ApplyRule('Password', 'Strength'); $this->UserModel->Validation->ApplyRule('Password', 'Match'); // $this->UserModel->Validation->ApplyRule('DateOfBirth', 'MinimumAge'); $this->FireEvent('RegisterValidation'); try { $Values = $this->Form->FormValues(); unset($Values['Roles']); $AuthUserID = $this->UserModel->Register($Values, array('Method' => 'Invitation')); if (!$AuthUserID) { $this->Form->SetValidationResults($this->UserModel->ValidationResults()); } else { // The user has been created successfully, so sign in now. Gdn::Session()->Start($AuthUserID); if ($this->Form->GetFormValue('RememberMe')) { Gdn::Authenticator()->SetIdentity($AuthUserID, TRUE); } $this->FireEvent('RegistrationSuccessful'); // ... and redirect them appropriately $Route = $this->RedirectTo(); if ($this->_DeliveryType != DELIVERY_TYPE_ALL) { $this->RedirectUrl = Url($Route); } else { if ($Route !== FALSE) { Redirect($Route); } } } } catch (Exception $Ex) { $this->Form->AddError($Ex); } } else { // Set some form defaults. if ($Name = GetValue('Name', $Invitation)) { $this->Form->SetValue('Name', $Name); } $this->InvitationCode = $InvitationCode; } // Make sure that the hour offset for new users gets defined when their account is created $this->AddJsFile('entry.js'); $this->Render(); }
/** * Checks to see if the user is spamming. Returns TRUE if the user is spamming. * * Users cannot post more than $SpamCount comments within $SpamTime * seconds or their account will be locked for $SpamLock seconds. * * @since 2.0.0 * @access public * @todo Remove debugging info if/when this is working correctly. * * @param string $Type Valid values are 'Comment' or 'Discussion'. * @return bool Whether spam check is positive (TRUE = spammer). */ public function CheckForSpam($Type) { $Session = Gdn::Session(); // If spam checking is disabled or user is an admin, skip $SpamCheckEnabled = GetValue('SpamCheck', $this, TRUE); if ($SpamCheckEnabled === FALSE || $Session->User->Admin || $Session->CheckPermission('Garden.Moderation.Manage')) { return FALSE; } $Spam = FALSE; // Validate $Type if (!in_array($Type, array('Comment', 'Discussion'))) { trigger_error(ErrorMessage(sprintf('Spam check type unknown: %s', $Type), 'VanillaModel', 'CheckForSpam'), E_USER_ERROR); } $CountSpamCheck = $Session->GetAttribute('Count' . $Type . 'SpamCheck', 0); $DateSpamCheck = $Session->GetAttribute('Date' . $Type . 'SpamCheck', 0); $SecondsSinceSpamCheck = time() - Gdn_Format::ToTimestamp($DateSpamCheck); // Get spam config settings $SpamCount = Gdn::Config('Vanilla.' . $Type . '.SpamCount'); if (!is_numeric($SpamCount) || $SpamCount < 1) { $SpamCount = 1; } // 1 spam minimum $SpamTime = Gdn::Config('Vanilla.' . $Type . '.SpamTime'); if (!is_numeric($SpamTime) || $SpamTime < 30) { $SpamTime = 30; } // 30 second minimum spam span $SpamLock = Gdn::Config('Vanilla.' . $Type . '.SpamLock'); if (!is_numeric($SpamLock) || $SpamLock < 60) { $SpamLock = 60; } // 60 second minimum lockout // Apply a spam lock if necessary $Attributes = array(); if ($SecondsSinceSpamCheck < $SpamLock && $CountSpamCheck >= $SpamCount && $DateSpamCheck !== FALSE) { // TODO: REMOVE DEBUGGING INFO AFTER THIS IS WORKING PROPERLY /* echo '<div>SecondsSinceSpamCheck: '.$SecondsSinceSpamCheck.'</div>'; echo '<div>SpamLock: '.$SpamLock.'</div>'; echo '<div>CountSpamCheck: '.$CountSpamCheck.'</div>'; echo '<div>SpamCount: '.$SpamCount.'</div>'; echo '<div>DateSpamCheck: '.$DateSpamCheck.'</div>'; echo '<div>SpamTime: '.$SpamTime.'</div>'; */ $Spam = TRUE; $this->Validation->AddValidationResult('Body', '@' . sprintf(T('You have posted %1$s times within %2$s seconds. A spam block is now in effect on your account. You must wait at least %3$s seconds before attempting to post again.'), $SpamCount, $SpamTime, $SpamLock)); // Update the 'waiting period' every time they try to post again $Attributes['Date' . $Type . 'SpamCheck'] = Gdn_Format::ToDateTime(); } else { if ($SecondsSinceSpamCheck > $SpamTime) { $Attributes['Count' . $Type . 'SpamCheck'] = 1; $Attributes['Date' . $Type . 'SpamCheck'] = Gdn_Format::ToDateTime(); } else { $Attributes['Count' . $Type . 'SpamCheck'] = $CountSpamCheck + 1; } } // Update the user profile after every comment $UserModel = Gdn::UserModel(); if ($Session->UserID) { $UserModel->SaveAttribute($Session->UserID, $Attributes); } return $Spam; }
/** * Show activity feed for this user. * * @since 2.0.0 * @access public * @param mixed $UserReference Unique identifier, possible ID or username. * @param string $Username Username. * @param int $UserID Unique ID. * @param int $Offset How many to skip (for paging). */ public function Activity($UserReference = '', $Username = '', $UserID = '', $Offset = '0') { $this->Permission('Garden.Profiles.View'); // Object setup $Session = Gdn::Session(); $this->ActivityModel = new ActivityModel(); // Calculate offset $Offset = is_numeric($Offset) ? $Offset : 0; if ($Offset < 0) { $Offset = 0; } // Get user, tab, and comment $this->GetUserInfo($UserReference, $Username, $UserID); $this->SetTabView('Activity'); $Comment = $this->Form->GetFormValue('Comment'); if ($Session->UserID > 0 && $this->Form->AuthenticatedPostBack() && !StringIsNullOrEmpty($Comment) && CheckPermission('Garden.Profiles.Edit')) { // Active user has submitted a comment $Comment = substr($Comment, 0, 1000); // Limit to 1000 characters... // Update About if necessary. $SendNotification = TRUE; if ($Session->UserID == $this->User->UserID) { $SendNotification = FALSE; $this->UserModel->SaveAbout($Session->UserID, $Comment); $this->User->About = $Comment; $this->SetJson('UserData', $this->FetchView('user')); $ActivityUserID = $Session->UserID; $RegardingUserID = $ActivityUserID; $ActivityType = 'AboutUpdate'; } else { $ActivityUserID = $this->User->UserID; $RegardingUserID = $Session->UserID; $ActivityType = 'WallPost'; } // Create activity entry $NewActivityID = $this->ActivityModel->Add($ActivityUserID, $ActivityType, $Comment, $RegardingUserID, '', '/profile/' . $this->ProfileUrl(), FALSE); // @todo Add a notification too. if ($this->_DeliveryType === DELIVERY_TYPE_ALL) { Redirect('dashboard/profile/' . $this->ProfileUrl()); } else { // Load just the single new comment $this->HideActivity = TRUE; $this->ActivityData = $this->ActivityModel->GetWhere('ActivityID', $NewActivityID); $this->View = 'activities'; $this->ControllerName = 'activity'; } } else { // Load data to display $this->ProfileUserID = $this->User->UserID; $Limit = 50; $this->SetData('ActivityData', $this->ActivityModel->Get($this->User->UserID, $Offset, $Limit), TRUE); $TotalRecords = $this->ActivityModel->GetCount($this->User->UserID); if ($this->ActivityData->NumRows() > 0) { $ActivityData = $this->ActivityData->Result(); $ActivityIDs = ConsolidateArrayValuesByKey($ActivityData, 'ActivityID'); $LastActivity = $this->ActivityData->FirstRow(); $LastModifiedDate = Gdn_Format::ToTimestamp($this->User->DateUpdated); $LastActivityDate = Gdn_Format::ToTimestamp($LastActivity->DateInserted); if ($LastModifiedDate < $LastActivityDate) { $LastModifiedDate = $LastActivityDate; } // Make sure to only query this page if the user has no new activity since the requesting browser last saw it. $this->SetLastModified($LastModifiedDate); $this->CommentData = $this->ActivityModel->GetComments($ActivityIDs); } else { $this->CommentData = FALSE; } // Build a pager $PagerFactory = new Gdn_PagerFactory(); $this->Pager = $PagerFactory->GetPager('MorePager', $this); $this->Pager->MoreCode = 'More'; $this->Pager->LessCode = 'Newer Activity'; $this->Pager->ClientID = 'Pager'; $this->Pager->Configure($Offset, $Limit, $TotalRecords, 'profile/activity/' . $this->User->UserID . '/' . Gdn_Format::Url($this->User->Name) . '/' . $this->User->UserID . '/%1$s/'); // Deliver json data if necessary if ($this->_DeliveryType != DELIVERY_TYPE_ALL) { $this->SetJson('LessRow', $this->Pager->ToString('less')); $this->SetJson('MoreRow', $this->Pager->ToString('more')); if ($Offset > 0) { $this->View = 'activities'; $this->ControllerName = 'Activity'; } } } // Set the canonical Url. if (is_numeric($this->User->Name) || Gdn_Format::Url($this->User->Name) != strtolower($this->User->Name)) { $this->CanonicalUrl(Url('profile/' . $this->User->UserID . '/' . Gdn_Format::Url($this->User->Name), TRUE)); } else { $this->CanonicalUrl(Url('profile/' . strtolower($this->User->Name), TRUE)); } $this->Render(); }
/** * Modifies category data before it is returned. * * Adds CountAllDiscussions column to each category representing the sum of * discussions within this category as well as all subcategories. * * @since 2.0.17 * @access public * * @param object $Data SQL result. */ public static function AddCategoryColumns($Data) { $Result =& $Data->Result(); $Result2 = $Result; foreach ($Result as &$Category) { if (!property_exists($Category, 'CountAllDiscussions')) { $Category->CountAllDiscussions = $Category->CountDiscussions; } if (!property_exists($Category, 'CountAllComments')) { $Category->CountAllComments = $Category->CountComments; } // Calculate the following field. $Following = !((bool) GetValue('Archived', $Category) || (bool) GetValue('Unfollow', $Category)); $Category->Following = $Following; $DateMarkedRead = GetValue('DateMarkedRead', $Category); $UserDateMarkedRead = GetValue('UserDateMarkedRead', $Category); if (!$DateMarkedRead) { $DateMarkedRead = $UserDateMarkedRead; } elseif ($UserDateMarkedRead && Gdn_Format::ToTimestamp($UserDateMarkedRead) > Gdn_Format::ToTimeStamp($DateMarkedRead)) { $DateMarkedRead = $UserDateMarkedRead; } // Set appropriate Last* columns. SetValue('LastTitle', $Category, GetValue('LastDiscussionTitle', $Category, NULL)); $LastDateInserted = GetValue('LastDateInserted', $Category, NULL); if (GetValue('LastCommentUserID', $Category) == NULL) { SetValue('LastCommentUserID', $Category, GetValue('LastDiscussionUserID', $Category, NULL)); SetValue('DateLastComment', $Category, GetValue('DateLastDiscussion', $Category, NULL)); SetValue('LastUserID', $Category, GetValue('LastDiscussionUserID', $Category, NULL)); $LastDiscussion = ArrayTranslate($Category, array('LastDiscussionID' => 'DiscussionID', 'CategoryID' => 'CategoryID', 'LastTitle' => 'Name')); SetValue('LastUrl', $Category, DiscussionUrl($LastDiscussion, FALSE, '//') . '#latest'); if (is_null($LastDateInserted)) { SetValue('LastDateInserted', $Category, GetValue('DateLastDiscussion', $Category, NULL)); } } else { $LastDiscussion = ArrayTranslate($Category, array('LastDiscussionID' => 'DiscussionID', 'CategoryID' => 'CategoryID', 'LastTitle' => 'Name')); SetValue('LastUserID', $Category, GetValue('LastCommentUserID', $Category, NULL)); SetValue('LastUrl', $Category, DiscussionUrl($LastDiscussion, FALSE, '//') . '#latest'); if (is_null($LastDateInserted)) { SetValue('LastDateInserted', $Category, GetValue('DateLastComment', $Category, NULL)); } } $LastDateInserted = GetValue('LastDateInserted', $Category, NULL); if ($DateMarkedRead) { if ($LastDateInserted) { $Category->Read = Gdn_Format::ToTimestamp($DateMarkedRead) >= Gdn_Format::ToTimestamp($LastDateInserted); } else { $Category->Read = TRUE; } } else { $Category->Read = FALSE; } foreach ($Result2 as $Category2) { if ($Category2->TreeLeft > $Category->TreeLeft && $Category2->TreeRight < $Category->TreeRight) { $Category->CountAllDiscussions += $Category2->CountDiscussions; $Category->CountAllComments += $Category2->CountComments; } } } }
/** * Updates visit level information such as date last active and the user's ip address. * * @param int $UserID * @param string|int|float $ClientHour */ function UpdateVisit($UserID, $ClientHour = FALSE) { $UserID = (int) $UserID; if (!$UserID) { throw new Exception('A valid User ID is required.'); } $User = Gdn::UserModel()->GetID($UserID, DATASET_TYPE_ARRAY); $Fields = array(); if (Gdn_Format::ToTimestamp($User['DateLastActive']) < strtotime('5 minutes ago')) { // We only update the last active date once every 5 minutes to cut down on DB activity. $Fields['DateLastActive'] = Gdn_Format::ToDateTime(); } // Update session level information if necessary. if ($UserID == Gdn::Session()->UserID) { $IP = Gdn::Request()->IpAddress(); $Fields['LastIPAddress'] = $IP; if (Gdn::Session()->NewVisit()) { $Fields['CountVisits'] = GetValue('CountVisits', $User, 0) + 1; } } // Generate the AllIPs field. $AllIPs = GetValue('AllIPAddresses', $User, array()); if (is_string($AllIPs)) { $AllIPs = explode(',', $AllIPs); SetValue('AllIPAddresses', $User, $AllIPs); } if (!is_array($AllIPs)) { $AllIPs = array(); } if ($IP = GetValue('InsertIPAddress', $User)) { $AllIPs[] = ForceIPv4($IP); } if ($IP = GetValue('LastIPAddress', $User)) { $AllIPs[] = $IP; } $AllIPs = array_unique($AllIPs); sort($AllIPs); $Fields['AllIPAddresses'] = $AllIPs; // Set the hour offset based on the client's clock. if (is_numeric($ClientHour) && $ClientHour >= 0 && $ClientHour < 24) { $HourOffset = $ClientHour - date('G', time()); $Fields['HourOffset'] = $HourOffset; } // See if the fields have changed. $Set = array(); foreach ($Fields as $Name => $Value) { if (GetValue($Name, $User) != $Value) { $Set[$Name] = $Value; } } if (!empty($Set)) { $this->EventArguments['Fields'] =& $Set; $this->FireEvent('UpdateVisit'); $this->SetField($UserID, $Set); } if ($User['LastIPAddress'] != $Fields['LastIPAddress']) { $User = $this->GetID($UserID, DATASET_TYPE_ARRAY); if (!BanModel::CheckUser($User, NULL, TRUE, $Bans)) { $BanModel = new BanModel(); $Ban = array_pop($Bans); $BanModel->SaveUser($User, TRUE, $Ban); $BanModel->SetCounts($Ban); } } }
/** * Modify CountUnreadComments to account for DateAllViewed * * Required in DiscussionModel->Get() just before the return: * $this->EventArguments['Data'] = $Data; * $this->FireEvent('AfterAddColumns'); * @link http://vanillaforums.org/discussion/13227 * @since 1.0 * @access public */ public function DiscussionModel_AfterAddColumns_Handler($Sender) { // Only for members $Session = Gdn::Session(); if (!$Session->IsValid()) { return; } // Recalculate New count with user's DateAllViewed $DateAllViewed = Gdn_Format::ToTimestamp($Session->User->DateAllViewed); foreach ($Sender->EventArguments['Data']->Result() as $Discussion) { if ($DateAllViewed != 0) { // Only if they've used AllViewed if (Gdn_Format::ToTimestamp($Discussion->DateInserted) > $DateAllViewed) { // Discussion is newer than last 'AllViewed' click continue; } if (Gdn_Format::ToTimestamp($Discussion->DateLastComment) <= $DateAllViewed) { // Covered by AllViewed $Discussion->CountUnreadComments = 0; } elseif (Gdn_Format::ToTimestamp($Discussion->DateLastViewed) == $DateAllViewed || !$Discussion->DateLastViewed) { // User clicked AllViewed // Discussion is older than click // Last comment is newer than click // No UserDiscussion record found OR UserDiscussion was set by AllViewed $Discussion->CountUnreadComments = $this->GetCommentCountSince($Discussion->DiscussionID, $DateAllViewed); } } } }
/** * Record the user's watch data. * * @since 2.0.0 * @access public * * @param object $Discussion Discussion being watched. * @param int $Limit Max number to get. * @param int $Offset Number to skip. * @param int $TotalComments Total in entire discussion (hard limit). */ public function SetWatch($Discussion, $Limit, $Offset, $TotalComments) { $NewComments = FALSE; $Session = Gdn::Session(); if ($Session->UserID > 0) { // Max comments we could have seen $CountWatch = $Limit * ($Offset + 1); if ($CountWatch > $TotalComments) { $CountWatch = $TotalComments; } // This dicussion looks familiar... if (is_numeric($Discussion->CountCommentWatch)) { if (isset($Discussion->DateLastViewed)) { $NewComments |= Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($Discussion->DateLastViewed); } if ($TotalComments > $Discussion->CountCommentWatch) { $NewComments |= TRUE; } // Update the watch data. if ($NewComments) { // Only update the watch if there are new comments. $this->SQL->Put('UserDiscussion', array('CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime()), array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID)); } } else { // Make sure the discussion isn't archived. $ArchiveDate = C('Vanilla.Archive.Date', FALSE); if (!$ArchiveDate || Gdn_Format::ToTimestamp($Discussion->DateLastComment) > Gdn_Format::ToTimestamp($ArchiveDate)) { $NewComments = TRUE; // Insert watch data. $this->SQL->Options('Ignore', TRUE); $this->SQL->Insert('UserDiscussion', array('UserID' => $Session->UserID, 'DiscussionID' => $Discussion->DiscussionID, 'CountComments' => $CountWatch, 'DateLastViewed' => Gdn_Format::ToDateTime())); } } /** * Fuzzy way of trying to automatically mark a cateogyr read again * if the user reads all the comments on the first few pages. */ // If this discussion is in a category that has been marked read, // check if reading this thread causes it to be completely read again $CategoryID = GetValue('CategoryID', $Discussion); if ($CategoryID) { $Category = CategoryModel::Categories($CategoryID); if ($Category) { $DateMarkedRead = GetValue('DateMarkedRead', $Category); if ($DateMarkedRead) { // Fuzzy way of looking back about 2 pages into the past $LookBackCount = C('Vanilla.Discussions.PerPage', 50) * 2; // Find all discussions with content from after DateMarkedRead $DiscussionModel = new DiscussionModel(); $Discussions = $DiscussionModel->Get(0, 101, array('CategoryID' => $CategoryID, 'DateLastComment>' => $DateMarkedRead)); unset($DiscussionModel); // Abort if we get back as many as we asked for, meaning a // lot has happened. $NumDiscussions = $Discussions->NumRows(); if ($NumDiscussions <= $LookBackCount) { // Loop over these and see if any are still unread $MarkAsRead = TRUE; while ($Discussion = $Discussions->NextRow(DATASET_TYPE_ARRAY)) { if ($Discussion['Read']) { continue; } $MarkAsRead = FALSE; break; } // Mark this category read if all the new content is read if ($MarkAsRead) { $CategoryModel = new CategoryModel(); $CategoryModel->SaveUserTree($CategoryID, array('DateMarkedRead' => Gdn_Format::ToDateTime())); unset($CategoryModel); } } } } } } }
/** * Join message counts into the discussion list. * @param DiscussionModel $Sender * @param array $Args */ public function DiscussionModel_AfterAddColumns_Handler($Sender, $Args) { if (!Gdn::Session()->UserID) { return; } $Data = $Args['Data']; $Result =& $Data->Result(); // Gather the discussion IDs. $DiscusisonIDs = array(); foreach ($Result as $Row) { $DiscusisonIDs[] = GetValue('DiscussionID', $Row); } // Grab all of the whispers associated to the discussions being looked at. $Sql = Gdn::SQL()->Select('c.DiscussionID')->Select('c.CountMessages', 'sum', 'CountMessages')->Select('c.DateUpdated', 'max', 'DateLastMessage')->From('Conversation c')->WhereIn('c.DiscussionID', $DiscusisonIDs)->GroupBy('c.DiscussionID'); if (!Gdn::Session()->CheckPermission('Conversations.Moderation.Manage')) { $Sql->Join('UserConversation uc', 'c.ConversationID = uc.ConversationID')->Where('uc.UserID', Gdn::Session()->UserID); } $Conversations = $Sql->Get()->ResultArray(); $Conversations = Gdn_DataSet::Index($Conversations, 'DiscussionID'); foreach ($Result as &$Row) { $DiscusisonID = GetValue('DiscussionID', $Row); $CRow = GetValue($DiscusisonID, $Conversations); if (!$CRow) { continue; } $DateLastViewed = GetValue('DateLastViewed', $Row); $DateLastMessage = $CRow['DateLastMessage']; $NewWhispers = Gdn_Format::ToTimestamp($DateLastViewed) < Gdn_Format::ToTimestamp($DateLastMessage); SetValue('CountWhispers', $Row, $CRow['CountMessages']); SetValue('DateLastWhisper', $Row, $DateLastMessage); SetValue('NewWhispers', $Row, $NewWhispers); } }
/** * Signin process that multiple authentication methods. * * @access public * @since 2.0.0 * @author Tim Gunter * * @param string $Method * @param array $Arg1 * @return string Rendered XHTML template. */ public function SignIn($Method = FALSE, $Arg1 = FALSE) { $this->AddJsFile('entry.js'); $this->SetData('Title', T('Sign In')); $this->Form->AddHidden('Target', $this->Target()); $this->Form->AddHidden('ClientHour', date('Y-m-d H:00')); // Use the server's current hour as a default. // Additional signin methods are set up with plugins. $Methods = array(); $this->SetData('Methods', $Methods); $this->SetData('FormUrl', Url('entry/signin')); $this->FireEvent('SignIn'); if ($this->Form->IsPostBack()) { $this->Form->ValidateRule('Email', 'ValidateRequired', sprintf(T('%s is required.'), T(UserModel::SigninLabelCode()))); $this->Form->ValidateRule('Password', 'ValidateRequired'); // Check the user. if ($this->Form->ErrorCount() == 0) { $Email = $this->Form->GetFormValue('Email'); $User = Gdn::UserModel()->GetByEmail($Email); if (!$User) { $User = Gdn::UserModel()->GetByUsername($Email); } if (!$User) { $this->Form->AddError('@' . sprintf(T('User not found.'), strtolower(T(UserModel::SigninLabelCode())))); } else { $ClientHour = $this->Form->GetFormValue('ClientHour'); $HourOffset = Gdn_Format::ToTimestamp($ClientHour) - time(); $HourOffset = round($HourOffset / 3600); // Check the password. $PasswordHash = new Gdn_PasswordHash(); $Password = $this->Form->GetFormValue('Password'); try { $PasswordChecked = $PasswordHash->CheckPassword($Password, GetValue('Password', $User), GetValue('HashMethod', $User)); } catch (Gdn_UserException $Ex) { $this->Form->AddError($Ex); $this->Render(); return; } if ($PasswordChecked) { // Update weak passwords $HashMethod = GetValue('HashMethod', $User); if ($PasswordHash->Weak || $HashMethod && strcasecmp($HashMethod, 'Vanilla') != 0) { $Pw = $PasswordHash->HashPassword($Password); Gdn::UserModel()->SetField(GetValue('UserID', $User), array('Password' => $Pw, 'HashMethod' => 'Vanilla')); } Gdn::Session()->Start(GetValue('UserID', $User), TRUE, (bool) $this->Form->GetFormValue('RememberMe')); if (!Gdn::Session()->CheckPermission('Garden.SignIn.Allow')) { $this->Form->AddError('ErrorPermission'); Gdn::Session()->End(); } else { if ($HourOffset != Gdn::Session()->User->HourOffset) { Gdn::UserModel()->SetProperty(Gdn::Session()->UserID, 'HourOffset', $HourOffset); } Gdn::UserModel()->FireEvent('AfterSignIn'); $this->_SetRedirect(); } } else { $this->Form->AddError('Invalid password.'); } } } } else { if ($Target = $this->Request->Get('Target')) { $this->Form->AddHidden('Target', $Target); } $this->Form->SetValue('RememberMe', TRUE); } return $this->Render(); }