Skip to content

Commit 86cf905

Browse files
authored
Merge pull request #86 from topcoder-platform/issues-475
Issues-475: Voting plugin
2 parents 322e0d2 + 8502e5a commit 86cf905

File tree

10 files changed

+602
-0
lines changed

10 files changed

+602
-0
lines changed

Voting/addon.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"key": "Voting",
3+
"name": "Voting",
4+
"description": "This plugin allows users to vote for comments and discussions.",
5+
"version": "1.0.0",
6+
"documentationUrl": "http://discussions.topcoder.com",
7+
"type": "addon",
8+
"priority": "100",
9+
"icon": "topcoder-logo.jpeg",
10+
"mobileFriendly": true,
11+
"hasLocale": false,
12+
"authors": [
13+
{
14+
"name": "Topcoder",
15+
"email": "[email protected]",
16+
"homepage": "https://topcoder.com"
17+
}
18+
],
19+
"require": {
20+
"vanilla": ">=3.0"
21+
}
22+
}

Voting/class.voting.plugin.php

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
<?php if (!defined('APPLICATION')) exit();
2+
3+
class VotingPlugin extends Gdn_Plugin {
4+
5+
/**
6+
* Add JS & CSS to the page.
7+
*/
8+
public function AddJsCss($Sender) {
9+
$Sender->AddCSSFile('voting.css', 'plugins/Voting');
10+
$Sender->AddJSFile('voting.js', 'plugins/Voting');
11+
}
12+
13+
public function addVotingBox($sender, $args) {
14+
$session = Gdn::Session();
15+
$object = $args['Object'];
16+
$VoteType = $args['Type'] == 'Discussion' ? 'votediscussion' : 'votecomment';
17+
$id = $args['Type'] == 'Discussion' ? val('DiscussionID', $object) : val('CommentID', $object);
18+
$score = val('Score', $object);
19+
$cssClass = '';
20+
$voteUpUrl = '/discussion/'.$VoteType.'/'.$id.'/voteup/'.$session->TransientKey().'/';
21+
$voteDownUrl = '/discussion/'.$VoteType.'/'.$id.'/votedown/'.$session->TransientKey().'/';
22+
if (!$session->IsValid()) {
23+
$voteUpUrl = Gdn::Authenticator()->SignInUrl($sender->SelfUrl);
24+
$voteDownUrl = $voteUpUrl;
25+
$cssClass = ' SignInPopup';
26+
}
27+
28+
if($args['Type'] == 'Discussion') {
29+
$discussionModel = new DiscussionModel();
30+
$currentUserVote = $discussionModel->GetUserScore($id, $session->UserID);
31+
} else {
32+
$commentModel = new CommentModel();
33+
$currentUserVote = $commentModel->GetUserScore($id, $session->UserID);
34+
}
35+
$cssClassVoteUp = $cssClassVoteDown = '';
36+
if($currentUserVote > 0) {
37+
$cssClassVoteUp = ' Voted';
38+
} else if($currentUserVote < 0){
39+
$cssClassVoteDown = ' Voted';
40+
}
41+
42+
$formattedScore = $this->formattedScore($score);
43+
echo '<span class="Voter">';
44+
echo Anchor(Wrap('Vote Up', 'span', array('class' => 'ArrowSprite SpriteUp'.$cssClassVoteUp , 'rel' => 'nofollow')), $voteUpUrl, 'VoteUp'.$cssClass);
45+
echo Wrap($formattedScore, 'span', array('class' => 'CountVoices'));
46+
echo Anchor(Wrap('Vote Down', 'span', array('class' => 'ArrowSprite SpriteDown'.$cssClassVoteDown, 'rel' => 'nofollow')), $voteDownUrl, 'VoteDown'.$cssClass);
47+
echo '</span>&nbsp;|&nbsp;';
48+
49+
}
50+
51+
private function formattedScore($score) {
52+
if(StringIsNullOrEmpty($score)) {
53+
$formattedScore = '0';
54+
} else {
55+
$formattedScore = $score <= 0 ? Gdn_Format::BigNumber($score):'+' . Gdn_Format::BigNumber($score);
56+
}
57+
58+
return $formattedScore;
59+
}
60+
61+
62+
public function discussionController_BeforeInlineDiscussionOptions_handler($sender, $args) {
63+
$this->addVotingBox($sender, $args);
64+
}
65+
66+
public function discussionController_BeforeInlineCommentOptions_handler($sender, $args) {
67+
$this->addVotingBox($sender, $args);
68+
}
69+
70+
public function postController_BeforeInlineCommentOptions_handler($sender, $args) {
71+
$this->addVotingBox($sender, $args);
72+
}
73+
74+
75+
/**
76+
* Add the files to discussions page
77+
*/
78+
public function discussionController_render_Before($sender) {
79+
$this->AddJsCss($sender);
80+
}
81+
82+
83+
/**
84+
* Increment/decrement comment scores
85+
*/
86+
public function discussionController_VoteComment_create($sender) {
87+
$CommentID = GetValue(0, $sender->RequestArgs, 0);
88+
$VoteType = GetValue(1, $sender->RequestArgs);
89+
$TransientKey = GetValue(2, $sender->RequestArgs);
90+
$Session = Gdn::Session();
91+
$FinalVote = 0;
92+
$Total = 0;
93+
if ($Session->IsValid() && $Session->ValidateTransientKey($TransientKey) && $CommentID > 0) {
94+
$CommentModel = new CommentModel();
95+
$OldUserVote = $CommentModel->GetUserScore($CommentID, $Session->UserID);
96+
switch ($VoteType) {
97+
case 'voteup':
98+
$NewUserVote = 1;
99+
break;
100+
case 'votedown':
101+
$NewUserVote = -1;
102+
break;
103+
default:
104+
$NewUserVote = 0;
105+
}
106+
$FinalVote = intval($OldUserVote) + intval($NewUserVote);
107+
if ($FinalVote == 2 || $FinalVote == -2) {
108+
// user cancelled a voice
109+
$FinalVote = 0;
110+
} else {
111+
$FinalVote = $NewUserVote;
112+
}
113+
114+
$Total = $CommentModel->SetUserScore($CommentID, $Session->UserID, $FinalVote);
115+
}
116+
$sender->DeliveryType(DELIVERY_TYPE_BOOL);
117+
$sender->SetJson('TotalScore', $this->formattedScore($Total));
118+
$sender->SetJson('FinalVote', $FinalVote);
119+
$sender->SetJson('VoteUpCssClass', $FinalVote > 0? 'Voted':'');
120+
$sender->SetJson('VoteDownCssClass', $FinalVote < 0? 'Voted':'');
121+
$sender->Render();
122+
}
123+
124+
/**
125+
* Increment/decrement discussion scores
126+
*/
127+
public function discussionController_VoteDiscussion_create($sender) {
128+
$DiscussionID = GetValue(0, $sender->RequestArgs, 0);
129+
$TransientKey = GetValue(1, $sender->RequestArgs);
130+
$VoteType = FALSE;
131+
if ($TransientKey == 'voteup' || $TransientKey == 'votedown') {
132+
$VoteType = $TransientKey;
133+
$TransientKey = GetValue(2, $sender->RequestArgs);
134+
}
135+
$Session = Gdn::Session();
136+
$NewUserVote = 0;
137+
$Total = 0;
138+
if ($Session->IsValid() && $Session->ValidateTransientKey($TransientKey) && $DiscussionID > 0) {
139+
$DiscussionModel = new DiscussionModel();
140+
$OldUserVote = $DiscussionModel->GetUserScore($DiscussionID, $Session->UserID);
141+
142+
switch ($VoteType) {
143+
case 'voteup':
144+
$NewUserVote = 1;
145+
break;
146+
case 'votedown':
147+
$NewUserVote = -1;
148+
break;
149+
default:
150+
$NewUserVote = 0;
151+
}
152+
153+
$FinalVote = intval($OldUserVote) + intval($NewUserVote);
154+
if ($FinalVote == 2 || $FinalVote == -2) {
155+
// user cancelled a voice
156+
$FinalVote = 0;
157+
} else {
158+
$FinalVote = $NewUserVote;
159+
}
160+
$Total = $DiscussionModel->SetUserScore($DiscussionID, $Session->UserID, $FinalVote);
161+
}
162+
$sender->DeliveryType(DELIVERY_TYPE_BOOL);
163+
$sender->SetJson('TotalScore', $this->formattedScore($Total));
164+
$sender->SetJson('FinalVote', $FinalVote);
165+
$sender->SetJson('VoteUpCssClass', $FinalVote > 0? 'Voted':'');
166+
$sender->SetJson('VoteDownCssClass', $FinalVote < 0? 'Voted':'');
167+
$sender->Render();
168+
}
169+
170+
/**
171+
* Grab the score field whenever the discussions are queried.
172+
*/
173+
public function DiscussionModel_AfterDiscussionSummaryQuery_Handler($Sender) {
174+
$Sender->SQL->Select('d.Score');
175+
}
176+
177+
/**
178+
* Add voting css to post controller.
179+
*/
180+
public function PostController_Render_Before($Sender) {
181+
$this->AddJsCss($Sender);
182+
}
183+
184+
public function Setup() {
185+
}
186+
187+
public function OnDisable() {
188+
}
189+
190+
public function dashboardNavModule_init_handler($sender) {
191+
/** @var DashboardNavModule $nav */
192+
$nav = $sender;
193+
$sort = -1;
194+
$nav->addGroupToSection('Moderation', t('Voting'), 'voting', '', ['after'=>'site'])
195+
->addLinkToSectionIf('Garden.Settings.Manage', 'Moderation', t('Discussions'), '/voting/discussions',
196+
'voting.discussions', '', $sort)
197+
->addLinkToSectionIf('Garden.Settings.Manage', 'Moderation', t('Comments'), '/voting/comments',
198+
'voting.comments', '', $sort);
199+
200+
}
201+
}
Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,167 @@
1+
<?php
2+
/**
3+
* View Voting Discussions/Comments
4+
*
5+
* @copyright 2009-2019 Vanilla Forums Inc.
6+
* @license GPL-2.0-only
7+
* @package Dashboard
8+
* @since 2.0
9+
*/
10+
11+
use Vanilla\Contracts\ConfigurationInterface;
12+
13+
/**
14+
* Handles /voting endpoint.
15+
*/
16+
class VotingController extends DashboardController {
17+
18+
/** @var ConfigurationInterface */
19+
private $config;
20+
21+
/** @var array Models to automatically instantiate. */
22+
public $Uses = ['Database', 'Form'];
23+
24+
/** @var Gdn_Form */
25+
public $Form;
26+
27+
public $discussionModel;
28+
29+
/**
30+
* Configure the controller.
31+
*/
32+
public function __construct(ConfigurationInterface $config = null) {
33+
$this->config = $config instanceof ConfigurationInterface ? $config : Gdn::getContainer()->get(ConfigurationInterface::class);
34+
$this->discussionModel = Gdn::getContainer()->get(DiscussionModel::class);
35+
parent::__construct();
36+
}
37+
38+
/**
39+
* Highlight menu path. Automatically run on every use.
40+
*/
41+
public function initialize() {
42+
parent::initialize();
43+
Gdn_Theme::section('Dashboard');
44+
if ($this->Menu) {
45+
$this->Menu->highlightRoute('/dashboard/settings');
46+
}
47+
$this->fireEvent('Init');
48+
}
49+
50+
/**
51+
* Discussion list.
52+
* @param string $page Page number.
53+
* @param string $sort
54+
* @throws Exception
55+
*/
56+
public function discussions($page = '', $sort = 'top') {
57+
$this->permission('Garden.Settings.Manage');
58+
59+
// Page setup
60+
$this->addJsFile('jquery.gardenmorepager.js');
61+
$this->title(t('Voting Discussions'));
62+
$this->setHighlightRoute('voting/discussions');
63+
Gdn_Theme::section('Moderation');
64+
65+
// Input Validation.
66+
list($offset, $limit) = offsetLimit($page, PagerModule::$DefaultPageSize);
67+
68+
$DiscussionModel = new DiscussionModel();
69+
$DiscussionModel->setSort($sort);
70+
71+
$where = ['Announce' => 'all', 'd.Score is not null' => ''];
72+
// Get Discussion Count
73+
$CountDiscussions = $DiscussionModel->getCount($where);
74+
75+
$this->setData('RecordCount', $CountDiscussions);
76+
if ($offset >= $CountDiscussions) {
77+
$offset = $CountDiscussions - $limit;
78+
}
79+
80+
// Get Discussions and Announcements
81+
$discussionData = $DiscussionModel->getWhereRecent($where, $limit, $offset);
82+
$this->setData('Discussions', $discussionData);
83+
84+
// Deliver json data if necessary
85+
if ($this->_DeliveryType != DELIVERY_TYPE_ALL && $this->_DeliveryMethod == DELIVERY_METHOD_XHTML) {
86+
$this->setJson('LessRow', $this->Pager->toString('less'));
87+
$this->setJson('MoreRow', $this->Pager->toString('more'));
88+
$this->View = 'discussions';
89+
}
90+
91+
$this->render();
92+
}
93+
94+
/**
95+
* Comment list.
96+
* @param string $page Page number.
97+
* @param string $sort
98+
* @throws Exception
99+
*/
100+
public function comments($page = '', $sort = 'top') {
101+
$this->permission('Garden.Settings.Manage');
102+
103+
// Page setup
104+
$this->addJsFile('jquery.gardenmorepager.js');
105+
$this->title(t('Voting Comments'));
106+
$this->setHighlightRoute('voting/comments');
107+
Gdn_Theme::section('Moderation');
108+
109+
// Input Validation.
110+
list($offset, $limit) = offsetLimit($page, PagerModule::$DefaultPageSize);
111+
112+
$CommentModel = new CommentModel();
113+
114+
switch (strtolower($sort)) {
115+
case 'top':
116+
$CommentModel->OrderBy(array('c.Score desc', 'c.CommentID desc'));
117+
break;
118+
default:
119+
$CommentModel->OrderBy(array('c.Score desc', 'c.CommentID desc'));
120+
break;
121+
}
122+
123+
$where = ['Score is not null' => ''];
124+
// Get Comment Count
125+
$CountComments = $CommentModel->getCount($where);
126+
127+
$this->setData('RecordCount', $CountComments);
128+
if ($offset >= $CountComments) {
129+
$offset = $CountComments - $limit;
130+
}
131+
132+
$data = $CommentModel->getWhere($where,'', '' , $limit, $offset);
133+
$this->setData('Comments', $data);
134+
135+
// Deliver json data if necessary
136+
if ($this->_DeliveryType != DELIVERY_TYPE_ALL && $this->_DeliveryMethod == DELIVERY_METHOD_XHTML) {
137+
$this->setJson('LessRow', $this->Pager->toString('less'));
138+
$this->setJson('MoreRow', $this->Pager->toString('more'));
139+
$this->View = 'discussions';
140+
}
141+
142+
$this->render();
143+
}
144+
145+
146+
/**
147+
* Build URL to order users by value passed.
148+
*/
149+
protected function _OrderDiscussionsUrl($field) {
150+
$get = Gdn::request()->get();
151+
$get['sort'] = $field;
152+
$get['Page'] = 'p1';
153+
return '/voting/discussions?'.http_build_query($get);
154+
}
155+
156+
/**
157+
* Build URL to order users by value passed.
158+
*/
159+
protected function _OrderCommentsUrl($field) {
160+
$get = Gdn::request()->get();
161+
$get['sort'] = $field;
162+
$get['Page'] = 'p1';
163+
return '/voting/comments?'.http_build_query($get);
164+
}
165+
166+
167+
}

0 commit comments

Comments
 (0)