'Q&A', 'Description' => "Users may designate a discussion as a Question and then officially accept one or more of the comments as the answer.", 'Version' => '1.2.1', 'RequiredApplications' => array('Vanilla' => '2.0.18'), 'MobileFriendly' => TRUE, 'Author' => 'Todd Burry', 'AuthorEmail' => 'todd@vanillaforums.com', 'AuthorUrl' => 'http://www.vanillaforums.org/profile/todd' ); /** * Adds Question & Answer format to Vanilla. * * You can set Plugins.QnA.UseBigButtons = TRUE in config to separate 'New Discussion' * and 'Ask Question' into "separate" forms each with own big button in Panel. */ class QnAPlugin extends Gdn_Plugin { /// PROPERTIES /// protected $Reactions = FALSE; protected $Badges = FALSE; /// METHODS /// public function __construct() { parent::__construct(); if (Gdn::PluginManager()->CheckPlugin('Reactions') && C('Plugins.QnA.Reactions', TRUE)) { $this->Reactions = TRUE; } if (Gdn::ApplicationManager()->CheckApplication('Reputation') && C('Plugins.QnA.Badges', TRUE)) { $this->Badges = TRUE; } } public function Setup() { $this->Structure(); } public function Structure() { Gdn::Structure() ->Table('Discussion'); $QnAExists = Gdn::Structure()->ColumnExists('QnA'); $DateAcceptedExists = Gdn::Structure()->ColumnExists('DateAccepted'); Gdn::Structure() ->Column('QnA', array('Unanswered', 'Answered', 'Accepted', 'Rejected'), NULL) ->Column('DateAccepted', 'datetime', TRUE) // The ->Column('DateOfAnswer', 'datetime', TRUE) // The time to answer an accepted question. ->Set(); Gdn::Structure() ->Table('Comment') ->Column('QnA', array('Accepted', 'Rejected'), NULL) ->Column('DateAccepted', 'datetime', TRUE) ->Column('AcceptedUserID', 'int', TRUE) ->Set(); Gdn::Structure() ->Table('User') ->Column('CountAcceptedAnswers', 'int', '0') ->Set(); Gdn::SQL()->Replace( 'ActivityType', array('AllowComments' => '0', 'RouteCode' => 'question', 'Notify' => '1', 'Public' => '0', 'ProfileHeadline' => '', 'FullHeadline' => ''), array('Name' => 'QuestionAnswer'), TRUE); Gdn::SQL()->Replace( 'ActivityType', array('AllowComments' => '0', 'RouteCode' => 'answer', 'Notify' => '1', 'Public' => '0', 'ProfileHeadline' => '', 'FullHeadline' => ''), array('Name' => 'AnswerAccepted'), TRUE); if ($QnAExists && !$DateAcceptedExists) { // Default the date accepted to the accepted answer's date. $Px = Gdn::Database()->DatabasePrefix; $Sql = "update {$Px}Discussion d set DateAccepted = (select min(c.DateInserted) from {$Px}Comment c where c.DiscussionID = d.DiscussionID and c.QnA = 'Accepted')"; Gdn::SQL()->Query($Sql, 'update'); Gdn::SQL()->Update('Discussion') ->Set('DateOfAnswer', 'DateAccepted', FALSE, FALSE) ->Put(); Gdn::SQL()->Update('Comment c') ->Join('Discussion d', 'c.CommentID = d.DiscussionID') ->Set('c.DateAccepted', 'c.DateInserted', FALSE, FALSE) ->Set('c.AcceptedUserID', 'd.InsertUserID', FALSE, FALSE) ->Where('c.QnA', 'Accepted') ->Where('c.DateAccepted', NULL) ->Put(); } $this->StructureReactions(); $this->StructureBadges(); } /** * Define all of the structure related to badges. */ public function StructureBadges() { // Define 'Answer' badges if (!$this->Badges || !class_exists('BadgeModel')) return; $BadgeModel = new BadgeModel(); // Answer Counts $BadgeModel->Define(array( 'Name' => 'First Answer', 'Slug' => 'answer', 'Type' => 'UserCount', 'Body' => 'Answering questions is a great way to show your support for a community!', 'Photo' => 'http://badges.vni.la/100/answer.png', 'Points' => 2, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 1, 'Class' => 'Answerer', 'Level' => 1, 'CanDelete' => 0 )); $BadgeModel->Define(array( 'Name' => '5 Answers', 'Slug' => 'answer-5', 'Type' => 'UserCount', 'Body' => 'Your willingness to share knowledge has definitely been noticed.', 'Photo' => 'http://badges.vni.la/100/answer-2.png', 'Points' => 3, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 5, 'Class' => 'Answerer', 'Level' => 2, 'CanDelete' => 0 )); $BadgeModel->Define(array( 'Name' => '25 Answers', 'Slug' => 'answer-25', 'Type' => 'UserCount', 'Body' => 'Looks like you’re starting to make a name for yourself as someone who knows the score!', 'Photo' => 'http://badges.vni.la/100/answer-3.png', 'Points' => 5, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 25, 'Class' => 'Answerer', 'Level' => 3, 'CanDelete' => 0 )); $BadgeModel->Define(array( 'Name' => '50 Answers', 'Slug' => 'answer-50', 'Type' => 'UserCount', 'Body' => 'Why use Google when we could just ask you?', 'Photo' => 'http://badges.vni.la/100/answer-4.png', 'Points' => 10, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 50, 'Class' => 'Answerer', 'Level' => 4, 'CanDelete' => 0 )); $BadgeModel->Define(array( 'Name' => '100 Answers', 'Slug' => 'answer-100', 'Type' => 'UserCount', 'Body' => 'Admit it, you read Wikipedia in your spare time.', 'Photo' => 'http://badges.vni.la/100/answer-5.png', 'Points' => 15, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 100, 'Class' => 'Answerer', 'Level' => 5, 'CanDelete' => 0 )); $BadgeModel->Define(array( 'Name' => '250 Answers', 'Slug' => 'answer-250', 'Type' => 'UserCount', 'Body' => 'Is there *anything* you don’t know?', 'Photo' => 'http://badges.vni.la/100/answer-6.png', 'Points' => 20, 'Attributes' => array('Column' => 'CountAcceptedAnswers'), 'Threshold' => 250, 'Class' => 'Answerer', 'Level' => 6, 'CanDelete' => 0 )); } /** * Define all of the structure releated to reactions. * @return type */ public function StructureReactions() { // Define 'Accept' reaction if (!$this->Reactions) return; $Rm = new ReactionModel(); if (Gdn::Structure()->Table('ReactionType')->ColumnExists('Hidden')) { // AcceptAnswer $Rm->DefineReactionType(array('UrlCode' => 'AcceptAnswer', 'Name' => 'Accept Answer', 'Sort' => 0, 'Class' => 'Good', 'IncrementColumn' => 'Score', 'IncrementValue' => 5, 'Points' => 3, 'Permission' => 'Garden.Curation.Manage', 'Hidden' => 1, 'Description' => "When someone correctly answers a question, they are rewarded with this reaction.")); } Gdn::Structure()->Reset(); } /// EVENTS /// public function Base_AddonEnabled_Handler($Sender, $Args) { switch (strtolower($Args['AddonName'])) { case 'reactions': $this->Reactions = TRUE; $this->StructureReactions(); break; case 'reputation': $this->Badges = TRUE; $this->StructureBadges(); break; } } public function Base_BeforeCommentDisplay_Handler($Sender, $Args) { $QnA = GetValueR('Comment.QnA', $Args); if ($QnA && isset($Args['CssClass'])) { $Args['CssClass'] = ConcatSep(' ', $Args['CssClass'], "QnA-Item-$QnA"); } } public function Base_DiscussionTypes_Handler($Sender, $Args) { $Args['Types']['Question'] = array( 'Singular' => 'Question', 'Plural' => 'Questions', 'AddUrl' => '/post/question', 'AddText' => 'Ask a Question' ); } /** * * @param Gdn_Controller $Sender * @param array $Args */ // public function Base_AfterReactions_Handler($Sender, $Args) { // // public function Base_CommentOptions_Handler($Sender, $Args) { // $Discussion = GetValue('Discussion', $Args); // $Comment = GetValue('Comment', $Args); // // if (!$Comment) // return; // // $CommentID = GetValue('CommentID', $Comment); // if (!is_numeric($CommentID)) // return; // // if (!$Discussion) { // static $DiscussionModel = NULL; // if ($DiscussionModel === NULL) // $DiscussionModel = new DiscussionModel(); // $Discussion = $DiscussionModel->GetID(GetValue('DiscussionID', $Comment)); // } // // if (!$Discussion || strtolower(GetValue('Type', $Discussion)) != 'question') // return; // // // Check permissions. // $CanAccept = Gdn::Session()->CheckPermission('Garden.Moderation.Manage'); // $CanAccept |= Gdn::Session()->UserID == GetValue('InsertUserID', $Discussion) && Gdn::Session()->UserID != GetValue('InsertUserID', $Comment); // // if (!$CanAccept) // return; // // $QnA = GetValue('QnA', $Comment); // if ($QnA) // return; // // // Write the links. // $Types = GetValue('ReactionTypes', $Sender->EventArguments); // if ($Types) // echo Bullet(); // // $Query = http_build_query(array('commentid' => $CommentID, 'tkey' => Gdn::Session()->TransientKey())); // echo Anchor(Sprite('ReactAccept', 'ReactSprite').T('Accept', 'Accept'), '/discussion/qna/accept?'.$Query, array('class' => 'React QnA-Yes', 'title' => T('Accept this answer.'))); // echo Anchor(Sprite('ReactReject', 'ReactSprite').T('Reject', 'Reject'), '/discussion/qna/reject?'.$Query, array('class' => 'React QnA-No', 'title' => T('Reject this answer.'))); // // static $InformMessage = TRUE; // // if ($InformMessage && Gdn::Session()->UserID == GetValue('InsertUserID', $Discussion) && in_array(GetValue('QnA', $Discussion), array('', 'Answered'))) { // $Sender->InformMessage(T('Click accept or reject beside an answer.'), 'Dismissable'); // $InformMessage = FALSE; // } // } public function Base_CommentInfo_Handler($Sender, $Args) { $Type = GetValue('Type', $Args); if ($Type != 'Comment') return; $QnA = GetValueR('Comment.QnA', $Args); if ($QnA && ($QnA == 'Accepted' || Gdn::Session()->CheckPermission('Garden.Moderation.Manage'))) { $Title = T("QnA $QnA Answer", "$QnA Answer"); echo ' '.$Title.' '; } } public function DiscussionController_CommentOptions_Handler($Sender, $Args) { $Comment = $Args['Comment']; if (!$Comment) return; $Discussion = Gdn::Controller()->Data('Discussion'); if (GetValue('Type', $Discussion) != 'Question') return; if (!Gdn::Session()->CheckPermission('Vanilla.Discussions.Edit', TRUE, 'Category', $Discussion->PermissionCategoryID)) return; $Args['CommentOptions']['QnA'] = array('Label' => T('Q&A').'...', 'Url' => '/discussion/qnaoptions?commentid='.$Comment->CommentID, 'Class' => 'Popup'); } public function Base_DiscussionOptions_Handler($Sender, $Args) { $Discussion = $Args['Discussion']; if (!Gdn::Session()->CheckPermission('Vanilla.Discussions.Edit', TRUE, 'Category', $Discussion->PermissionCategoryID)) return; if (isset($Args['DiscussionOptions'])) { $Args['DiscussionOptions']['QnA'] = array('Label' => T('Q&A').'...', 'Url' => '/discussion/qnaoptions?discussionid='.$Discussion->DiscussionID, 'Class' => 'Popup'); } elseif (isset($Sender->Options)) { $Sender->Options .= '
  • '.Anchor(T('Q&A').'...', '/discussion/qnaoptions?discussionid='.$Discussion->DiscussionID, 'Popup QnAOptions') . '
  • '; } } public function CommentModel_BeforeNotification_Handler($Sender, $Args) { $ActivityModel = $Args['ActivityModel']; $Comment = (array)$Args['Comment']; $CommentID = $Comment['CommentID']; $Discussion = (array)$Args['Discussion']; if ($Comment['InsertUserID'] == $Discussion['InsertUserID']) return; if (strtolower($Discussion['Type']) != 'question') return; if (!C('Plugins.QnA.Notifications', TRUE)) return; $HeadlineFormat = T('HeadlingFormat.Answer', '{ActivityUserID,user} answered your question: {Data.Name,text}'); $Activity = array( 'ActivityType' => 'Comment', 'ActivityUserID' => $Comment['InsertUserID'], 'NotifyUserID' => $Discussion['InsertUserID'], 'HeadlineFormat' => $HeadlineFormat, 'RecordType' => 'Comment', 'RecordID' => $CommentID, 'Route' => "/discussion/comment/$CommentID#Comment_$CommentID", 'Emailed' => ActivityModel::SENT_PENDING, 'Notified' => ActivityModel::SENT_PENDING, 'Data' => array( 'Name' => GetValue('Name', $Discussion) ) ); $ActivityModel->Queue($Activity); } /** * @param CommentModel $Sender * @param array $Args */ public function CommentModel_BeforeUpdateCommentCount_Handler($Sender, $Args) { $Discussion =& $Args['Discussion']; // Mark the question as answered. if (strtolower($Discussion['Type']) == 'question' && !$Discussion['Sink'] && !in_array($Discussion['QnA'], array('Answered', 'Accepted')) && $Discussion['InsertUserID'] != Gdn::Session()->UserID) { $Sender->SQL->Set('QnA', 'Answered'); } } public function DiscussionController_BeforeDiscussionRender_Handler($Sender, $Args) { if ($Sender->Data('Discussion.QnA')) $Sender->CssClass .= ' Question'; if (strcasecmp($Sender->Data('Discussion.QnA'), 'Accepted') != 0) return; // Find the accepted answer(s) to the question. $CommentModel = new CommentModel(); $Answers = $CommentModel->GetWhere(array('DiscussionID' => $Sender->Data('Discussion.DiscussionID'), 'Qna' => 'Accepted'))->Result(); if (class_exists('ReplyModel')) { $ReplyModel = new ReplyModel(); $Discussion = NULL; $ReplyModel->JoinReplies($Discussion, $Answers); } $Sender->SetData('Answers', $Answers); // Remove the accepted answers from the comments. if (isset($Sender->Data['Comments'])) { $Comments = $Sender->Data['Comments']->Result(); $Comments = array_filter($Comments, function($Row) { return strcasecmp(GetValue('QnA', $Row), 'accepted'); }); $Sender->Data['Comments'] = new Gdn_DataSet(array_values($Comments)); } } /** * Write the accept/reject buttons. * @staticvar null $DiscussionModel * @staticvar boolean $InformMessage * @param type $Sender * @param type $Args * @return type */ public function DiscussionController_AfterCommentBody_Handler($Sender, $Args) { $Discussion = $Sender->Data('Discussion'); $Comment = GetValue('Comment', $Args); if (!$Comment) return; $CommentID = GetValue('CommentID', $Comment); if (!is_numeric($CommentID)) return; if (!$Discussion) { static $DiscussionModel = NULL; if ($DiscussionModel === NULL) $DiscussionModel = new DiscussionModel(); $Discussion = $DiscussionModel->GetID(GetValue('DiscussionID', $Comment)); } if (!$Discussion || strtolower(GetValue('Type', $Discussion)) != 'question') return; // Check permissions. $CanAccept = Gdn::Session()->CheckPermission('Garden.Moderation.Manage'); $CanAccept |= Gdn::Session()->UserID == GetValue('InsertUserID', $Discussion) && Gdn::Session()->UserID != GetValue('InsertUserID', $Comment); if (!$CanAccept) return; $QnA = GetValue('QnA', $Comment); if ($QnA) return; // Write the links. // $Types = GetValue('ReactionTypes', $Sender->EventArguments); // if ($Types) // echo Bullet(); $Query = http_build_query(array('commentid' => $CommentID, 'tkey' => Gdn::Session()->TransientKey())); echo '
    '; // echo ''.T('Feedback').''; echo ''.T('Did this answer the question?').' '; echo ''; echo Anchor(T('Yes'), '/discussion/qna/accept?'.$Query, array('class' => 'React QnA-Yes', 'title' => T('Accept this answer.'))); echo ' '.Bullet().' '; echo Anchor(T('No'), '/discussion/qna/reject?'.$Query, array('class' => 'React QnA-No', 'title' => T('Reject this answer.'))); echo ''; echo '
    '; // static $InformMessage = TRUE; // // if ($InformMessage && Gdn::Session()->UserID == GetValue('InsertUserID', $Discussion) && in_array(GetValue('QnA', $Discussion), array('', 'Answered'))) { // $Sender->InformMessage(T('Click accept or reject beside an answer.'), 'Dismissable'); // $InformMessage = FALSE; // } } /** * * @param DiscussionController $Sender * @param type $Args * @return type */ public function DiscussionController_AfterDiscussion_Handler($Sender, $Args) { if ($Sender->Data('Answers')) include $Sender->FetchViewLocation('Answers', '', 'plugins/QnA'); } /** * * @param DiscussionController $Sender * @param array $Args */ public function DiscussionController_QnA_Create($Sender, $Args = array()) { $Comment = Gdn::SQL()->GetWhere('Comment', array('CommentID' => $Sender->Request->Get('commentid')))->FirstRow(DATASET_TYPE_ARRAY); if (!$Comment) throw NotFoundException('Comment'); $Discussion = Gdn::SQL()->GetWhere('Discussion', array('DiscussionID' => $Comment['DiscussionID']))->FirstRow(DATASET_TYPE_ARRAY); // Check for permission. if (!(Gdn::Session()->UserID == GetValue('InsertUserID', $Discussion) || Gdn::Session()->CheckPermission('Garden.Moderation.Manage'))) { throw PermissionException('Garden.Moderation.Manage'); } if (!Gdn::Session()->ValidateTransientKey($Sender->Request->Get('tkey'))) throw PermissionException(); switch ($Args[0]) { case 'accept': $QnA = 'Accepted'; break; case 'reject': $QnA = 'Rejected'; break; } if (isset($QnA)) { $DiscussionSet = array('QnA' => $QnA); $CommentSet = array('QnA' => $QnA); if ($QnA == 'Accepted') { $CommentSet['DateAccepted'] = Gdn_Format::ToDateTime(); $CommentSet['AcceptedUserID'] = Gdn::Session()->UserID; if (!$Discussion['DateAccepted']) { $DiscussionSet['DateAccepted'] = Gdn_Format::ToDateTime(); $DiscussionSet['DateOfAnswer'] = $Comment['DateInserted']; } } // Update the comment. Gdn::SQL()->Put('Comment', $CommentSet, array('CommentID' => $Comment['CommentID'])); // Update the discussion. if ($Discussion['QnA'] != $QnA && (!$Discussion['QnA'] || in_array($Discussion['QnA'], array('Unanswered', 'Answered', 'Rejected')))) Gdn::SQL()->Put( 'Discussion', $DiscussionSet, array('DiscussionID' => $Comment['DiscussionID'])); // Determine QnA change if ($Comment['QnA'] != $QnA) { $Change = 0; switch ($QnA) { case 'Rejected': $Change = -1; if ($Comment['QnA'] != 'Accepted') $Change = 0; break; case 'Accepted': $Change = 1; break; default: if ($Comment['QnA'] == 'Rejected') $Change = 0; if ($Comment['QnA'] == 'Accepted') $Change = -1; break; } } // Apply change effects if ($Change) { // Update the user $UserID = GetValue('InsertUserID', $Comment); $this->RecalculateUserQnA($UserID); // Update reactions if ($this->Reactions) { include_once(Gdn::Controller()->FetchViewLocation('reaction_functions', '', 'plugins/Reactions')); $Rm = new ReactionModel(); // If there's change, reactions will take care of it $Rm->React('Comment', $Comment['CommentID'], 'AcceptAnswer'); } } // Record the activity. if ($QnA == 'Accepted') { AddActivity( Gdn::Session()->UserID, 'AnswerAccepted', Anchor(Gdn_Format::Text($Discussion['Name']), "/discussion/{$Discussion['DiscussionID']}/".Gdn_Format::Url($Discussion['Name'])), $Comment['InsertUserID'], "/discussion/comment/{$Comment['CommentID']}/#Comment_{$Comment['CommentID']}", TRUE ); } } Redirect("/discussion/comment/{$Comment['CommentID']}#Comment_{$Comment['CommentID']}"); } public function DiscussionController_QnAOptions_Create($Sender, $DiscussionID = '', $CommentID = '') { if ($DiscussionID) $this->_DiscussionOptions($Sender, $DiscussionID); elseif ($CommentID) $this->_CommentOptions($Sender, $CommentID); } public function RecalculateDiscussionQnA($Discussion) { // Find comments in this discussion with a QnA value. $Set = array(); $Row = Gdn::SQL()->GetWhere('Comment', array('DiscussionID' => GetValue('DiscussionID', $Discussion), 'QnA is not null' => ''), 'QnA, DateAccepted', 'asc', 1)->FirstRow(DATASET_TYPE_ARRAY); if (!$Row) { if (GetValue('CountComments', $Discussion) > 0) $Set['QnA'] = 'Unanswered'; else $Set['QnA'] = 'Answered'; $Set['DateAccepted'] = NULL; $Set['DateOfAnswer'] = NULL; } elseif ($Row['QnA'] == 'Accepted') { $Set['QnA'] = 'Accepted'; $Set['DateAccepted'] = $Row['DateAccepted']; $Set['DateOfAnswer'] = $Row['DateInserted']; } elseif ($Row['QnA'] == 'Rejected') { $Set['QnA'] = 'Rejected'; $Set['DateAccepted'] = NULL; $Set['DateOfAnswer'] = NULL; } Gdn::Controller()->DiscussionModel->SetField(GetValue('DiscussionID', $Discussion), $Set); } public function RecalculateUserQnA($UserID) { $CountAcceptedAnswers = Gdn::SQL()->GetCount('Comment', array('InsertUserID' => $UserID, 'QnA' => 'Accepted')); Gdn::UserModel()->SetField($UserID, 'CountAcceptedAnswers', $CountAcceptedAnswers); } public function _CommentOptions($Sender, $CommentID) { $Sender->Form = new Gdn_Form(); $Comment = $Sender->CommentModel->GetID($CommentID, DATASET_TYPE_ARRAY); if (!$Comment) throw NotFoundException('Comment'); $Discussion = $Sender->DiscussionModel->GetID(GetValue('DiscussionID', $Comment)); $Sender->Permission('Vanilla.Discussions.Edit', TRUE, 'Category', GetValue('PermissionCategoryID', $Discussion)); if ($Sender->Form->IsPostBack()) { $QnA = $Sender->Form->GetFormValue('QnA'); if (!$QnA) $QnA = NULL; $CurrentQnA = GetValue('QnA', $Comment); // ->Column('DateAccepted', 'datetime', TRUE) // ->Column('AcceptedUserID', 'int', TRUE) if ($CurrentQnA != $QnA) { $Set = array('QnA' => $QnA); if ($QnA == 'Accepted') { $Set['DateAccepted'] = Gdn_Format::ToDateTime(); $Set['AcceptedUserID'] = Gdn::Session()->UserID; } else { $Set['DateAccepted'] = NULL; $Set['AcceptedUserID'] = NULL; } $Sender->CommentModel->SetField($CommentID, $Set); $Sender->Form->SetValidationResults($Sender->CommentModel->ValidationResults()); // Determine QnA change if ($Comment['QnA'] != $QnA) { $Change = 0; switch ($QnA) { case 'Rejected': $Change = -1; if ($Comment['QnA'] != 'Accepted') $Change = 0; break; case 'Accepted': $Change = 1; break; default: if ($Comment['QnA'] == 'Rejected') $Change = 0; if ($Comment['QnA'] == 'Accepted') $Change = -1; break; } } // Apply change effects if ($Change) { // Update the user $UserID = GetValue('InsertUserID', $Comment); $this->RecalculateUserQnA($UserID); // Update reactions if ($this->Reactions) { include_once(Gdn::Controller()->FetchViewLocation('reaction_functions', '', 'plugins/Reactions')); $Rm = new ReactionModel(); // If there's change, reactions will take care of it $Rm->React('Comment', $Comment['CommentID'], 'AcceptAnswer'); } } } // Recalculate the Q&A status of the discussion. $this->RecalculateDiscussionQnA($Discussion); Gdn::Controller()->JsonTarget('', '', 'Refresh'); } else { $Sender->Form->SetData($Comment); } $Sender->SetData('Comment', $Comment); $Sender->SetData('Discussion', $Discussion); $Sender->SetData('_QnAs', array('Accepted' => T('Yes'), 'Rejected' => T('No'), '' => T("Don't know"))); $Sender->SetData('Title', T('Q&A Options')); $Sender->Render('CommentOptions', '', 'plugins/QnA'); } protected function _DiscussionOptions($Sender, $DiscussionID) { $Sender->Form = new Gdn_Form(); $Discussion = $Sender->DiscussionModel->GetID($DiscussionID); if (!$Discussion) throw NotFoundException('Discussion'); // Both '' and 'Discussion' denote a discussion type of discussion. if (!GetValue('Type', $Discussion)) SetValue('Type', $Discussion, 'Discussion'); if ($Sender->Form->IsPostBack()) { $Sender->DiscussionModel->SetField($DiscussionID, 'Type', $Sender->Form->GetFormValue('Type')); // $Form = new Gdn_Form(); $Sender->Form->SetValidationResults($Sender->DiscussionModel->ValidationResults()); // if ($Sender->DeliveryType() == DELIVERY_TYPE_ALL || $Redirect) // $Sender->RedirectUrl = Gdn::Controller()->Request->PathAndQuery(); Gdn::Controller()->JsonTarget('', '', 'Refresh'); } else { $Sender->Form->SetData($Discussion); } $Sender->SetData('Discussion', $Discussion); $Sender->SetData('_Types', array('Question' => '@'.T('Question Type', 'Question'), 'Discussion' => '@'.T('Discussion Type', 'Discussion'))); $Sender->SetData('Title', T('Q&A Options')); $Sender->Render('DiscussionOptions', '', 'plugins/QnA'); } public function DiscussionModel_BeforeGet_Handler($Sender, $Args) { $Unanswered = Gdn::Controller()->ClassName == 'DiscussionsController' && Gdn::Controller()->RequestMethod == 'unanswered'; if ($Unanswered) { $Args['Wheres']['Type'] = 'Question'; $Sender->SQL->WhereIn('d.QnA', array('Unanswered', 'Rejected')); Gdn::Controller()->Title('Unanswered Questions'); } elseif ($QnA = Gdn::Request()->Get('qna')) { $Args['Wheres']['QnA'] = $QnA; } } /** * * @param DiscussionModel $Sender * @param array $Args */ public function DiscussionModel_BeforeSaveDiscussion_Handler($Sender, $Args) { // $Sender->Validation->ApplyRule('Type', 'Required', T('Choose whether you want to ask a question or start a discussion.')); $Post =& $Args['FormPostValues']; if ($Args['Insert'] && GetValue('Type', $Post) == 'Question') { $Post['QnA'] = 'Unanswered'; } } /* New Html method of adding to discussion filters */ public function Base_AfterDiscussionFilters_Handler($Sender) { $Count = Gdn::Cache()->Get('QnA-UnansweredCount'); if ($Count === Gdn_Cache::CACHEOP_FAILURE) $Count = ' '; else $Count = ' '.$Count.''; echo '
  • ' .Anchor(Sprite('SpUnansweredQuestions').' '.T('Unanswered').$Count, '/discussions/unanswered', 'UnansweredQuestions') .'
  • '; } /* Old Html method of adding to discussion filters */ public function DiscussionsController_AfterDiscussionTabs_Handler($Sender, $Args) { if (StringEndsWith(Gdn::Request()->Path(), '/unanswered', TRUE)) $CssClass = ' class="Active"'; else $CssClass = ''; $Count = Gdn::Cache()->Get('QnA-UnansweredCount'); if ($Count === Gdn_Cache::CACHEOP_FAILURE) $Count = ' '; else $Count = ' '.$Count.''; echo ''.T('Unanswered Questions', 'Unanswered').$Count.''; } /** * @param DiscussionsController $Sender * @param array $Args */ public function DiscussionsController_Unanswered_Create($Sender, $Args = array()) { $Sender->View = 'Index'; $Sender->SetData('_PagerUrl', 'discussions/unanswered/{Page}'); $Sender->Index(GetValue(0, $Args, 'p1')); $this->InUnanswered = TRUE; } /** * * @param DiscussionsController $Sender * @param type $Args */ public function DiscussionsController_Unanswered_Render($Sender, $Args) { $Sender->SetData('CountDiscussions', FALSE); // Add 'Ask a Question' button if using BigButtons. $this->_AddNewQuestionModule($Sender); // Remove announcements that aren't questions... $Sender->Data('Announcements')->Result(); $Announcements = array(); foreach ($Sender->Data('Announcements') as $i => $Row) { if (GetValue('Type', $Row) == 'Question') $Announcements[] = $Row; } Trace($Announcements); $Sender->SetData('Announcements', $Announcements); $Sender->AnnounceData = $Announcements; } /** * @param DiscussionsController $Sender * @param array $Args */ public function DiscussionsController_UnansweredCount_Create($Sender, $Args = array()) { Gdn::SQL()->WhereIn('QnA', array('Unanswered', 'Rejected')); $Count = Gdn::SQL()->GetCount('Discussion', array('Type' => 'Question')); Gdn::Cache()->Store('QnA-UnansweredCount', $Count, array(Gdn_Cache::FEATURE_EXPIRY => 15 * 60)); $Sender->SetData('UnansweredCount', $Count); $Sender->SetData('_Value', $Count); $Sender->Render('Value', 'Utility', 'Dashboard'); } public function Base_BeforeDiscussionMeta_Handler($Sender, $Args) { $Discussion = $Args['Discussion']; if (strtolower(GetValue('Type', $Discussion)) != 'question') return; $QnA = GetValue('QnA', $Discussion); $Title = ''; switch ($QnA) { case '': case 'Unanswered': case 'Rejected': $Text = 'Question'; $QnA = 'Question'; break; case 'Answered': $Text = 'Answered'; if (GetValue('InsertUserID', $Discussion) == Gdn::Session()->UserID) { $QnA = 'Answered'; $Title = ' title="'.T("Someone's answered your question. You need to accept/reject the answer.").'"'; } break; case 'Accepted': $Text = 'Answered'; $Title = ' title="'.T("This question's answer has been accepted.").'"'; break; default: $QnA = FALSE; } if ($QnA) { echo ' '.T("Q&A $QnA", $Text).' '; } } /** * @param Gdn_Controller $Sender * @param array $Args */ public function NotificationsController_BeforeInformNotifications_Handler($Sender, $Args) { $Path = trim($Sender->Request->GetValue('Path'), '/'); if (preg_match('`^(vanilla/)?discussion[^s]`i', $Path)) return; // Check to see if the user has answered questions. $Count = Gdn::SQL()->GetCount('Discussion', array('Type' => 'Question', 'InsertUserID' => Gdn::Session()->UserID, 'QnA' => 'Answered')); if ($Count > 0) { $Sender->InformMessage(FormatString(T("You've asked questions that have now been answered", "You've asked questions that now have answers. Make sure you accept/reject the answers.")), 'Dismissable'); } } /** * Add 'Ask a Question' button if using BigButtons. */ public function CategoriesController_Render_Before($Sender) { $this->_AddNewQuestionModule($Sender); } /** * Add 'Ask a Question' button if using BigButtons. */ public function DiscussionController_Render_Before($Sender) { $this->_AddNewQuestionModule($Sender); if ($Sender->Data('Discussion.Type') == 'Question') { $Sender->SetData('_CommentsHeader', T('Answers')); } } /** * Add the "new question" option to the new discussion button group dropdown. */ // public function Base_BeforeNewDiscussionButton_Handler($Sender) { // $NewDiscussionModule = &$Sender->EventArguments['NewDiscussionModule']; // // $Category = Gdn::Controller()->Data('Category.UrlCode'); // if ($Category) // $Category = '/'.rawurlencode($Category); // else // $Category = ''; // // $NewDiscussionModule->AddButton(T('Ask a Question'), 'post/question'.$Category); // } /** * Add the question form to vanilla's post page. */ public function PostController_AfterForms_Handler($Sender) { $Forms = $Sender->Data('Forms'); $Forms[] = array('Name' => 'Question', 'Label' => Sprite('SpQuestion').T('Ask a Question'), 'Url' => 'post/question'); $Sender->SetData('Forms', $Forms); } /** * Create the new question method on post controller. */ public function PostController_Question_Create($Sender, $CategoryUrlCode = '') { // Create & call PostController->Discussion() $Sender->View = PATH_PLUGINS.'/QnA/views/post.php'; $Sender->SetData('Type', 'Question'); $Sender->Discussion($CategoryUrlCode); } /** * Override the PostController->Discussion() method before render to use our view instead. */ public function PostController_BeforeDiscussionRender_Handler($Sender) { // Override if we are looking at the question url. if ($Sender->RequestMethod == 'question') { $Sender->Form->AddHidden('Type', 'Question'); $Sender->Title(T('Ask a Question')); $Sender->SetData('Breadcrumbs', array(array('Name' => $Sender->Data('Title'), 'Url' => '/post/question'))); } } /** * Add 'New Question Form' location to Messages. */ public function MessageController_AfterGetLocationData_Handler($Sender, $Args) { $Args['ControllerData']['Vanilla/Post/Question'] = T('New Question Form'); } private function _AddNewQuestionModule($Sender) { $CategoryID = $Sender->CategoryID; if(C('Plugins.QnA.CategoryID') != $CategoryID) { return; } if(C('Plugins.QnA.UseBigButtons')) { $QuestionModule = new NewQuestionModule($Sender, 'plugins/QnA'); $Sender->AddModule($QuestionModule); } } }