public function notifyNewDiscussion($DiscussionID) { if (!c('Vanilla.QueueNotifications')) { throw forbiddenException('NotifyNewDiscussion'); } if (!$this->Request->isPostBack()) { throw forbiddenException('GET'); } // Grab the discussion. $Discussion = $this->DiscussionModel->getID($DiscussionID); if (!$Discussion) { throw notFoundException('Discussion'); } if (val('Notified', $Discussion) != ActivityModel::SENT_PENDING) { die('Not pending'); } // Mark the notification as in progress. $this->DiscussionModel->setField($DiscussionID, 'Notified', ActivityModel::SENT_INPROGRESS); $discussionType = val('Type', $Discussion); if ($discussionType) { $Code = "HeadlineFormat.Discussion.{$discussionType}"; } else { $Code = 'HeadlineFormat.Discussion'; } $HeadlineFormat = t($Code, '{ActivityUserID,user} started a new discussion: <a href="{Url,html}">{Data.Name,text}</a>'); $Category = CategoryModel::categories(val('CategoryID', $Discussion)); $Activity = array('ActivityType' => 'Discussion', 'ActivityUserID' => $Discussion->InsertUserID, 'HeadlineFormat' => $HeadlineFormat, 'RecordType' => 'Discussion', 'RecordID' => $DiscussionID, 'Route' => DiscussionUrl($Discussion), 'Data' => array('Name' => $Discussion->Name, 'Category' => val('Name', $Category))); $ActivityModel = new ActivityModel(); $this->DiscussionModel->NotifyNewDiscussion($Discussion, $ActivityModel, $Activity); $ActivityModel->SaveQueue(); $this->DiscussionModel->setField($DiscussionID, 'Notified', ActivityModel::SENT_OK); die('OK'); }
/** * * * @throws Exception * @throws Gdn_UserException */ public function merge() { $this->permission('Garden.Settings.Manage'); // This must be a postback. if (!$this->Request->isAuthenticatedPostBack()) { throw forbiddenException('GET'); } $Validation = new Gdn_Validation(); $Validation->applyRule('OldUserID', 'ValidateRequired'); $Validation->applyRule('NewUserID', 'ValidateRequired'); if ($Validation->validate($this->Request->Post())) { $Result = Gdn::userModel()->merge($this->Request->post('OldUserID'), $this->Request->post('NewUserID')); $this->setData($Result); } else { $this->Form->setValidationResults($Validation->results()); } $this->render('Blank', 'Utility'); }
/** * Set user preference for sorting discussions. * * @param string $Target The target to redirect to. */ public function sort($Target = '') { deprecated("sort"); if (!Gdn::session()->isValid()) { throw permissionException(); } if (!$this->Request->isAuthenticatedPostBack()) { throw forbiddenException('GET'); } if ($Target) { redirect($Target); } // Send sorted discussions. $this->setData('Deprecated', true); $this->deliveryMethod(DELIVERY_METHOD_JSON); $this->render(); }
/** * Set user preference for sorting discussions. */ public function sort($Target = '') { if (!Gdn::session()->isValid()) { throw permissionException(); } if (!$this->Request->isAuthenticatedPostBack()) { throw forbiddenException('GET'); } // Get param $SortField = Gdn::request()->Post('DiscussionSort'); $SortField = 'd.' . stringBeginsWith($SortField, 'd.', true, true); // Use whitelist here too to keep database clean if (!in_array($SortField, DiscussionModel::AllowedSortFields())) { throw new Gdn_UserException("Unknown sort {$SortField}."); } // Set user pref Gdn::userModel()->SavePreference(Gdn::session()->UserID, 'Discussions.SortField', $SortField); if ($Target) { redirect($Target); } // Send sorted discussions. $this->deliveryMethod(DELIVERY_METHOD_JSON); $this->render(); }
/** * Form to ask for the destination of the move, confirmation and permission check. */ public function confirmDiscussionMoves($DiscussionID = null) { $Session = Gdn::session(); $this->Form = new Gdn_Form(); $DiscussionModel = new DiscussionModel(); $CategoryModel = new CategoryModel(); $this->title(t('Confirm')); if ($DiscussionID) { $CheckedDiscussions = (array) $DiscussionID; $ClearSelection = false; } else { $CheckedDiscussions = Gdn::userModel()->getAttribute($Session->User->UserID, 'CheckedDiscussions', array()); if (!is_array($CheckedDiscussions)) { $CheckedDiscussions = array(); } $ClearSelection = true; } $DiscussionIDs = $CheckedDiscussions; $CountCheckedDiscussions = count($DiscussionIDs); $this->setData('CountCheckedDiscussions', $CountCheckedDiscussions); // Check for edit permissions on each discussion $AllowedDiscussions = array(); $DiscussionData = $DiscussionModel->SQL->select('DiscussionID, Name, DateLastComment, CategoryID, CountComments')->from('Discussion')->whereIn('DiscussionID', $DiscussionIDs)->get(); $DiscussionData = Gdn_DataSet::Index($DiscussionData->resultArray(), array('DiscussionID')); foreach ($DiscussionData as $DiscussionID => $Discussion) { $Category = CategoryModel::categories($Discussion['CategoryID']); if ($Category && $Category['PermsDiscussionsEdit']) { $AllowedDiscussions[] = $DiscussionID; } } $this->setData('CountAllowed', count($AllowedDiscussions)); $CountNotAllowed = $CountCheckedDiscussions - count($AllowedDiscussions); $this->setData('CountNotAllowed', $CountNotAllowed); if ($this->Form->authenticatedPostBack()) { // Retrieve the category id $CategoryID = $this->Form->getFormValue('CategoryID'); $Category = CategoryModel::categories($CategoryID); $RedirectLink = $this->Form->getFormValue('RedirectLink'); // User must have add permission on the target category if (!$Category['PermsDiscussionsAdd']) { throw forbiddenException('@' . t('You do not have permission to add discussions to this category.')); } $AffectedCategories = array(); // Iterate and move. foreach ($AllowedDiscussions as $DiscussionID) { $Discussion = val($DiscussionID, $DiscussionData); // Create the shadow redirect. if ($RedirectLink) { $DiscussionModel->defineSchema(); $MaxNameLength = val('Length', $DiscussionModel->Schema->GetField('Name')); $RedirectDiscussion = array('Name' => SliceString(sprintf(t('Moved: %s'), $Discussion['Name']), $MaxNameLength), 'DateInserted' => $Discussion['DateLastComment'], 'Type' => 'redirect', 'CategoryID' => $Discussion['CategoryID'], 'Body' => formatString(t('This discussion has been <a href="{url,html}">moved</a>.'), array('url' => DiscussionUrl($Discussion))), 'Format' => 'Html', 'Closed' => true); // Pass a forced input formatter around this exception. if (c('Garden.ForceInputFormatter')) { $InputFormat = c('Garden.InputFormatter'); saveToConfig('Garden.InputFormatter', 'Html', false); } $RedirectID = $DiscussionModel->save($RedirectDiscussion); // Reset the input formatter if (c('Garden.ForceInputFormatter')) { saveToConfig('Garden.InputFormatter', $InputFormat, false); } if (!$RedirectID) { $this->Form->setValidationResults($DiscussionModel->validationResults()); break; } } $DiscussionModel->setField($DiscussionID, 'CategoryID', $CategoryID); if (!isset($AffectedCategories[$Discussion['CategoryID']])) { $AffectedCategories[$Discussion['CategoryID']] = array(-1, -$Discussion['CountComments']); } else { $AffectedCategories[$Discussion['CategoryID']][0] -= 1; $AffectedCategories[$Discussion['CategoryID']][1] -= $Discussion['CountComments']; } if (!isset($AffectedCategories[$CategoryID])) { $AffectedCategories[$CategoryID] = array(1, $Discussion['CountComments']); } else { $AffectedCategories[$CategoryID][0] += 1; $AffectedCategories[$CategoryID][1] += $Discussion['CountComments']; } } // Update recent posts and counts on all affected categories. foreach ($AffectedCategories as $CategoryID => $Counts) { $CategoryModel->SetRecentPost($CategoryID); $CategoryModel->SQL->update('Category')->set('CountDiscussions', 'CountDiscussions' . ($Counts[0] < 0 ? ' - ' : ' + ') . abs($Counts[0]), false)->set('CountComments', 'CountComments' . ($Counts[1] < 0 ? ' - ' : ' + ') . abs($Counts[1]), false)->where('CategoryID', $CategoryID)->put(); } // Clear selections. if ($ClearSelection) { Gdn::userModel()->saveAttribute($Session->UserID, 'CheckedDiscussions', false); ModerationController::InformCheckedDiscussions($this); } if ($this->Form->errorCount() == 0) { $this->jsonTarget('', '', 'Refresh'); } } $this->render(); }
/** * * * @throws Exception */ public function setHourOffset() { $Form = new Gdn_Form(); if ($Form->authenticatedPostBack()) { if (!Gdn::session()->isValid()) { throw permissionException('Garden.SignIn.Allow'); } $HourOffset = $Form->getFormValue('HourOffset'); Gdn::userModel()->setField(Gdn::session()->UserID, 'HourOffset', $HourOffset); $this->setData('Result', true); $this->setData('HourOffset', $HourOffset); $time = time(); $this->setData('UTCDateTime', gmdate('r', $time)); $this->setData('UserDateTime', gmdate('r', $time + $HourOffset * 3600)); } else { throw forbiddenException('GET'); } $this->render('Blank'); }
/** * Revoke an invitation. * * @since 2.0.0 * @param int $InvitationID Unique identifier. * @throws Exception Throws an exception when the invitation isn't found or the user doesn't have permission to delete it. */ public function uninvite($InvitationID) { $this->permission('Garden.SignIn.Allow'); if (!$this->Form->authenticatedPostBack()) { throw forbiddenException('GET'); } $InvitationModel = new InvitationModel(); $Session = Gdn::session(); try { $Valid = $InvitationModel->delete($InvitationID, $this->UserModel); if ($Valid) { $this->informMessage(t('The invitation was removed successfully.')); $this->jsonTarget(".js-invitation[data-id=\"{$InvitationID}\"]", '', 'SlideUp'); } } catch (Exception $ex) { $this->Form->addError(strip_tags($ex->getMessage())); } if ($this->Form->errorCount() == 0) { $this->render('Blank', 'Utility'); } }
/** * Enable or disable the use of categories in Vanilla. * * @param bool $enabled Whether or not to enable/disable categories. * @throws Exception Throws an exception if accessed through an invalid post back. */ public function enableCategories($enabled) { $this->permission('Garden.Settings.Manage'); if ($this->Form->authenticatedPostBack()) { $enabled = (bool) $enabled; saveToConfig('Vanilla.Categories.Use', $enabled); $this->setData('Enabled', $enabled); if ($this->deliveryType() !== DELIVERY_TYPE_DATA) { $this->RedirectUrl = url('/vanilla/settings/managecategories'); } } else { throw forbiddenException('GET'); } return $this->render('Blank', 'Utility', 'Dashboard'); }
/** * * * @throws Exception */ public function setHourOffset() { $Form = new Gdn_Form(); if ($Form->authenticatedPostBack()) { if (!Gdn::session()->isValid()) { throw permissionException('Garden.SignIn.Allow'); } $HourOffset = $Form->getFormValue('HourOffset'); Gdn::userModel()->setField(Gdn::session()->UserID, 'HourOffset', $HourOffset); // If we receive a time zone, only accept it if we can verify it as a valid identifier. $timeZone = $Form->getFormValue('TimeZone'); if (!empty($timeZone)) { try { $tz = new DateTimeZone($timeZone); Gdn::userModel()->saveAttribute(Gdn::session()->UserID, ['TimeZone' => $tz->getName(), 'SetTimeZone' => null]); } catch (\Exception $ex) { Logger::log(Logger::ERROR, $ex->getMessage(), ['timeZone' => $timeZone]); Gdn::userModel()->saveAttribute(Gdn::session()->UserID, ['TimeZone' => null, 'SetTimeZone' => $timeZone]); $timeZone = ''; } } elseif ($currentTimeZone = Gdn::session()->getAttribute('TimeZone')) { // Check to see if the current timezone agrees with the posted offset. try { $tz = new DateTimeZone($currentTimeZone); $currentHourOffset = $tz->getOffset(new DateTime()) / 3600; if ($currentHourOffset != $HourOffset) { // Clear out the current timezone or else it will override the browser's offset. Gdn::userModel()->saveAttribute(Gdn::session()->UserID, ['TimeZone' => null, 'SetTimeZone' => null]); } else { $timeZone = $tz->getName(); } } catch (Exception $ex) { Logger::log(Logger::ERROR, "Clearing out bad timezone: {timeZone}", ['timeZone' => $currentTimeZone]); // Clear out the bad timezone. Gdn::userModel()->saveAttribute(Gdn::session()->UserID, ['TimeZone' => null, 'SetTimeZone' => null]); } } $this->setData('Result', true); $this->setData('HourOffset', $HourOffset); $this->setData('TimeZone', $timeZone); $time = time(); $this->setData('UTCDateTime', gmdate('r', $time)); $this->setData('UserDateTime', gmdate('r', $time + $HourOffset * 3600)); } else { throw forbiddenException('GET'); } $this->render('Blank'); }