View Issue Details

IDProjectCategoryView StatusLast Update
0000668mantisbtfeaturepublic2017-09-13 08:02
ReporterjkorabAssigned To 
PrioritynormalSeverityfeatureReproducibilityunable to reproduce
Status newResolutionopen 
Product Version0.15.0 
Target VersionFixed in Version 
Summary0000668: Voting for bugs appears to be unavailable
Description

View bugs presents a vote field which suggests that multiple users can vote for a bug to indicate that it too is happening to them. This feature does not appear anywhere else though, making this function impossible.

Tagspatch

Relationships

has duplicate 0000697 closedprescience New idea voting booth 
has duplicate 0003717 closedgrangeway Allow for polls 
has duplicate 0005867 closedgrangeway Vote for issue 
has duplicate 0007707 closedvboctor Vote and Validation Feature 
has duplicate 0008907 closedvboctor Limit maximum sponsorship with $g_maximum_sponsorship_amount in lieu of voting 
has duplicate 0003624 closedvboctor Add Counter, so that there is a column in "view_all_bug_page.php" that shows the hits per bug 
has duplicate 0006326 closedgrangeway Inheritance of news to subprojects 
has duplicate 0005551 closedgiallu Assign point contingents for users / user groups and start programming when certain threshold has been collected. 
related to 0004143 closedgrangeway Add ability to limit total sponsorship fund by access level. 

Activities

prescience

prescience

2001-07-06 11:53

reporter   ~0000979

Correct, I've disabled it for the time being. It'll get re-enabled eventaully, probably with some added niceties.

rep

rep

2001-08-15 06:53

reporter   ~0001145

Look forward to it.
Wish I could vote for this request!

legolas

legolas

2001-08-15 11:03

reporter   ~0001146

hmm i'wonder what is it good for? bugs are to be corrected not to be voted for:_)

myplacedk

myplacedk

2001-08-15 11:26

reporter   ~0001147

Read the docs! :)
http://mantisbt.sourceforge.net/mantis/documentation.html#add_vote

awalsh

awalsh

2002-03-15 13:19

reporter   ~0002108

The ability for developers to establish relative priority (wrt users) of bugs/enhancements is great.

However, will users be able to increase the tally by simply pressing "Vote for this Bug"? This takes the need for a duplicate report away..

Tracking who votes would also be useful. You don't want an artificial tally created by a single vigilant user.:-)

lantic

lantic

2004-07-20 07:23

reporter   ~0006172

There's a high degree of cross-over between this feature & sponsorship but obviously within the same company true sponsorship isn't really an option as it would quickly become becomes non-sensical if users can sponsor issues with unlimited "funds". So perhaps a limit could be set on the amount of votes/funds each user gets per project based on their access level.

For instance:
Viewer : $10
Reporter : $20
Updater : $30
Developer : $50
Manager : $100

This would enable managers to have a greater say then reporters over what should get fixed but still give the non-developers a voice (provided they club together) when working in environment where paying each other wouldn't make sense.

What do you think?

lantic

lantic

2004-07-20 07:27

reporter   ~0006173

May make 0003890 redundant, if sponsorship can be made to work without money.

bblan

bblan

2004-07-20 07:58

reporter   ~0006174

from a reporter point of view, what's the difference between Priority and Votes?
Aren't they a little bit similar?
Can votes influence priority (programmaticaly I mean)? Many votes could raise the issue priority.

lantic

lantic

2004-07-20 08:34

reporter   ~0006175

Priority can often only reflect the opionion of the person setting it. As such votes/sponsorship may give better overall view of what users want. So yes, potentially priority could be updated by the votes/sponsorship for a given issue.

I've added a issue 0004143 to better reflect the concept of limiting sponsorship for non-fiscal environments.

lantic

lantic

2004-08-02 14:35

reporter   ~0006561

See previous discussion on issue 0004143. There does appear to be a fair degree of support for a true voting system making 0004143 somewhat redundant.

lantic

lantic

2004-08-03 16:10

reporter   ~0006606

Last edited: 2004-08-03 16:12

Voting Implementation Thoughts...

User Interaction:

  1. User is allocated a set number votes by a manager/administrator. (Should this be global or per project?)

  2. User can then vote for issues from the issue view page. Something similar to the following might be the best approach:
    Label: Total votes assigned to this issue.
    Label: Total users voting for this issue.
    Label: Vote ranking. (Should this be global or per project?)
    Input field: Vote count to assign to this issue.
    Button: "Vote" [for this issue.]
    Button: "Withdraw votes" [from this issue.]

  3. Add facility to order the view issues page by vote count.

Database Changes Required:

  1. Add votes_allocated & votes_exercised int(7) fields to the mantis_user_table. Used to reflect the total number of votes allocated & currently exercised by a user.

  2. Add a mantis_voting_table to the database with the following structure:
    id - unique index for vote entry
    bug_id - associated bug index
    user_id - associated user index
    votes - number of votes exercised by the user for this bug

  3. Add a vote_total column to mantis_bug_table for quick lookup.

Issues:

  1. How to handle a reduction of a users "votes_allocated" below their existing "votes_exercised" if changed by an admin user?

Ideally it would be best to attempt to scale the number of votes allocated to each issue by their new total allocation, still reducing the users voting power but preserving their voting priorities.

For instance, say a user had an initial allocation of 100 votes & had them exercised on 2 issues 60:40. Then assume an admin reduced the users allocation to just 50 votes, the users votes for the 2 issues would become 30:20.

  1. Do votes get automatically freed up as soon as a bug is closed?

edited on: 08-03-04 16:12

sgrund

sgrund

2004-08-04 01:09

reporter   ~0006616

just my 2cents:

  • User Interaction:
    1) User is allocated a set number votes by a manager/administrator. (Should this be global or per project?)

per project

2)Label: Vote ranking. (Should this be global or per project?)

per project

  • Issues:
  1. How to handle a reduction of a users "votes_allocated" below their existing "votes_exercised" if changed by an admin user?

I think the easiest aproach is: reduction takes place when "votes_allocated" where automaticali reduced because of closing issues.
Example: user has 100 votes and all are 'placed'. Now he is reduced to 50. One issue (voted 60) is resolved => now he have 10 votes free.

  1. Do votes get automatically freed up as soon as a bug is closed?

quote from bluetooth in 0004143:
Points get assigned to issues until they reach status X (e.g. assigned), until status X is reached, users can re-allocate their points.

Points are freed and given back to the user once the issue reaches status Y (probably resolved).

status X and status Y should be customizable by manager (per project)

razortt

razortt

2004-09-23 19:02

reporter   ~0007726

has the voting implementation been put on the back burner?

Our team uses Mantis and would love to have the ability to gauge users priorities. Sgrund's 2c sound great.

lantic

lantic

2004-09-24 04:34

reporter   ~0007731

I'm planning to create an implementation of voting. In fact I've already started it. My only issue is lack of time at the moment, so I can't provide a good idea of ETA.

minibbjd

minibbjd

2004-09-24 07:09

reporter   ~0007732

Last edited: 2004-09-24 07:27

I agree that voting is really, really much needed and should be given priority over other new features. Actually, I just came here to ask for an ETA when I saw razortt already did. :-)

I second sgrund's comments. Here are some more:

-Maybe obvious, but we should be able to sort by total number of votes.
-Maybe even sorting by "votes per user for this issue" (and showing this value) would make sense. (Having ten people vote for an issue with one point should be more of a priority than one user voting with 10 points.) EDIT: That doesn't make sense. "Votes per user" wouldn't give this info, but "Number of users who voted for this issue" would.
-Being able to give additional voting points to users who participated in the discussion of an issue as a gratification at the time we close/resolve bugs would be cool!

edited on: 09-24-04 07:10

edited on: 09-24-04 07:27

stevemagruder

stevemagruder

2004-10-06 13:50

reporter   ~0007937

I vote for a voting feature not unlike that in Bugzilla. If I could have voted, I wouldn't have needed to create a "me too" note like this one. :)

cornchips

cornchips

2005-08-29 22:20

reporter   ~0011311

I would like to second the request for voting

xstaindx

xstaindx

2005-12-29 13:47

reporter   ~0011851

Last edited: 2005-12-29 13:49

I would like to see voting implemented. is there any update on its status?

johnwebbcole

johnwebbcole

2006-09-25 09:18

reporter   ~0013502

+1 for voting. I have been asked several times by managers if we could open issues up for voting, to let users decide which features should be implemented first. Is this something that is being looked at for 1.1?

John

c_schmitz

c_schmitz

2007-03-10 17:16

reporter   ~0014166

ok.. just put $100 on it. This money will go from the PHPSurveyor project at http://www.phpsurveyor.org to the Mantis project as soon as this feature is implemented in a stable release.

vboctor

vboctor

2007-03-24 19:50

manager   ~0014239

I've created a requirements page on the wiki for this feature. Let me know your thoughts.

http://www.mantisbt.org/wiki/doku.php/mantisbt:issue_voting_requirements

I think we should start by allowing users to give a thumbs up or thumbs down for a feature. Show the users votes on the issue view page, show the totals in the View Issues page. Initially also provide no limit on the number of total votes, but only allow one vote per issue.

c_schmitz

c_schmitz

2007-08-28 13:13

reporter   ~0015549

Well.. even if we are now LimeSurvey ( http://www.limesurvey.org ) the 100$ are still put for this feature.

Can you give me feedback when you expect this feature to be available?
The Wiki page looks fine and I am totally fine with the way you want to implement it.

ingorenner

ingorenner

2008-02-19 17:54

reporter   ~0017114

someone working on this or has a solution already? We'd really need that...

CADbloke

CADbloke

2008-02-19 19:34

reporter   ~0017115

Despite my lengthy request-entry in the Wiki I'd be happy to see this implemented in a simpler form.

An alternative is to set $g_sponsorship_currency to "Beers" (legal tender in my part of the world) and fiddle some page layouts to change to the word "sponsor" to "vote" to use sponsorship as a voting system. This wouldn't be as tidy as voting but it is one way of conveying support for bug-fix.

While not wanting to hijack this report, is there anywhere you can tell how many users are monitoring a bug? That would also give some idea of how many people want it fixed.

ingorenner

ingorenner

2008-02-20 05:14

reporter   ~0017118

sorry, but this is a quite lousy idea... you're abusing a feature that is not meant to do that... other than that, just imagine someone being in the need for both, having sponsoring and voting at the same time...

the concept in the wikie seems realy thought through though

c_schmitz

c_schmitz

2008-02-20 05:41

reporter   ~0017119

Last edited: 2008-02-20 05:43

I put the total money for the sponsorship on this issue almost a year ago. (100$). First money for this was bidden in 2006.
Nothing was implemented since then.

This bug is the one with the second highest sponsorship on this whole system.

That leads to the question why offer a sponsorship feature in this bug tracker at all if it doesn't lead to any work done.

This is seriously a lousy treatment of possible sponsors and I am considering to pull the sponsorship at all since we get less and less dependant on such a feature.

So.. whats up?

giallu

giallu

2008-02-20 07:38

reporter   ~0017123

It's on my radar but with a low priority so any patch is really, really welcome.

xstaindx

xstaindx

2008-02-20 11:46

reporter   ~0017129

I gave up on an implementation date for this issue, it and many other problems with mantis led me to switch to Atlassian JIRA http://www.atlassian.com (Free for Open Source Projects)

I no longer have any intention on paying (Sponsering) for the implementation for this issue in Mantis as I no longer use it - please remove my sponsorship.

Thank You.

giallu

giallu

2008-02-20 12:27

reporter   ~0017130

@xstaindx
I'm not even sure the sponsorship was really paid for any bugs in this tracker so rest assured I will not be after you when this will be implemented.

Unlike JIRA, we have no paid programmers to work full time on mantis so we give out the code for free to everybody in the hope we get some contributions from parties willing to see a new feature or a bug fixed.

This usually works, it's a pity t did not for you

cornchips

cornchips

2008-03-01 02:30

reporter   ~0017228

Last edited: 2008-03-01 02:41

After reading http://www.mantisbt.org/wiki/doku.php/mantisbt:issue_voting_requirements

I'd be willing to code this up and submit a patch, unless it is already being worked on by someone else?

The current code in svn trunk does not seem to indicate any start of a vote api, can i assume no one has got to it just yet?

giallu

giallu

2008-03-01 02:53

reporter   ~0017229

@cornchips: right. Feel free to contact me here, on irc://irc.freenode.org/#mantis or on mantisbt-dev mailing list for anything else you may need to code the patch.

Please be sure to follow
http://www.mantisbt.org/wiki/doku.php/mantisbt:coding_guidelines
http://www.mantisbt.org/wiki/doku.php/mantisbt:howto_submit_patches

for a quicker integration

Thank you very much in advance

cornchips

cornchips

2008-03-01 19:49

reporter   ~0017236

OK, I've started coding, about half the api and gui done. Will contact you on IRC when I have some questions.

vboctor

vboctor

2008-03-02 00:34

manager   ~0017237

Thanks cornchips. I've done some updates to the Wiki page. Please check the revision history of the page. It has been a while since I wrote the requirements, so I added some details to keep the feature consistent with other features that have been implemented since then.

vboctor

vboctor

2008-03-09 03:25

manager   ~0017299

Last edited: 2008-03-09 03:26

Check this out. This is a site that is using by Ubuntu to allow users to vote on ideas. This is similar to what we are aiming by this feature:
http://brainstorm.ubuntu.com/

The interesting take aways from the above Ubuntu website are:

  1. Ability to vote on issues from the summary view.
  2. Ability to sort issues by popularity based on a certain time window (today, this week, this month, ever).
  3. Ability to focus on a specific category.
  4. Preview of the problem description with a link including the number of comments, which allows user to read more details.

Would be interesting if we can integrate some of these ideas in our Mantis Voting implementation.

cornchips

cornchips

2008-03-09 21:08

reporter   ~0017302

I like it. I especially like the idea of a view that has say just the Top X number of issues, with the full description of the issue shown to the user. One issue I have always had with the summary view is it's great for dev's but can be confusing for reporters - I would think a brainstorm view provides a clean simple view for most reporters to look at (and would possibly prevent duplicates being filed).

vboctor

vboctor

2008-03-26 17:28

manager   ~0017463

Here is another example of a user feedback / brainstorming feature:
http://example.uservoice.com/

@cornchips, what are you up to with the implementation? Will you be able to provide a patch with your implementation soon? From memory, you were done with all the core features.

vboctor

vboctor

2008-03-26 17:40

manager   ~0017464

One more:
http://www.fevote.com/facebook

cornchips

cornchips

2008-03-26 18:39

reporter   ~0017467

Still here, I got stuck in one area of the code and havn't had time to pick it up again, however I have time to finish it now, so i would expect I should be able to submit the patch sometime before april 6th.

2008-04-06 05:34

 

voting for r5156.txt (49,873 bytes)
Index: D:/Internet/mantis/bug_view_advanced_page.php
===================================================================
--- D:/Internet/mantis/bug_view_advanced_page.php	(revision 5156)
+++ D:/Internet/mantis/bug_view_advanced_page.php	(working copy)
@@ -556,6 +556,9 @@
 
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+	
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
 
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
Index: D:/Internet/mantis/lang/strings_english.txt
===================================================================
--- D:/Internet/mantis/lang/strings_english.txt	(revision 5156)
+++ D:/Internet/mantis/lang/strings_english.txt	(working copy)
@@ -637,6 +637,7 @@
 
 # bug_vote_add.php
 $s_vote_added_msg = 'Vote has been added...';
+$s_vote_removed_msg = 'Vote has been removed...';
 
 # bugnote_add.php
 $s_bugnote_added_msg = 'Note added...';
@@ -1508,6 +1509,21 @@
 $s_update_columns_as_my_default = 'Update Columns as Default for All Projects';
 $s_reset_columns_configuration = 'Reset Columns Configuration';
 
+# Voting
+$s_vote_cast_button = 'Cast Vote:';
+$s_vote_delete_button = 'Delete My Vote';
+$s_bugvote_added = 'Vote Added';
+$s_bugvote_deleted = 'Vote Deleted';
+$s_votes_positive = 'Votes Positive';
+$s_votes_negative = 'Votes Negative';
+$s_voted_by = 'Voted By';
+$s_vote_balance = 'Vote Balance';
+$s_vote_num_voters = '# Voters';
+$s_votes_remain = 'votes remaining';
+$s_votes_used = 'votes used';
+$s_voting_this_issue = 'Users voting for this issue';
+$s_votes_num_voters = 'Number of Voters';
+
 # mind mapping
 $s_mindmap = 'Mindmap';
 $s_freemind_export = 'Freemind Export';
Index: D:/Internet/mantis/bug_view_page.php
===================================================================
--- D:/Internet/mantis/bug_view_page.php	(revision 5156)
+++ D:/Internet/mantis/bug_view_page.php	(working copy)
@@ -436,6 +436,9 @@
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
 
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
+	
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
 
Index: D:/Internet/mantis/view_all_set.php
===================================================================
--- D:/Internet/mantis/view_all_set.php	(revision 5156)
+++ D:/Internet/mantis/view_all_set.php	(working copy)
@@ -190,6 +190,14 @@
 		$f_user_monitor = gpc_get_string( 'user_monitor', META_FILTER_ANY );
 		$f_user_monitor = array( $f_user_monitor );
 	}
+	
+	$f_user_votes = array();
+	if ( is_array( gpc_get( 'user_votes', null ) ) ) {
+		$f_user_votes = gpc_get_string_array( 'user_votes', META_FILTER_ANY );
+	} else {
+		$f_user_votes = gpc_get_string( 'user_votes', META_FILTER_ANY );
+		$f_user_votes = array( $f_user_votes );
+	}
 
 	# these are only single values, even when doing advanced filtering
 	$f_per_page				= gpc_get_int( 'per_page', -1 );
@@ -420,6 +428,7 @@
 				$t_setting_arr['target_version'] = $f_target_version;
 				$t_setting_arr['show_priority'] = $f_show_priority;
 				$t_setting_arr['user_monitor'] = $f_user_monitor;
+				$t_setting_arr['user_votes'] = $f_user_votes;
 				$t_setting_arr['view_state'] = $f_view_state;
 				$t_setting_arr['custom_fields'] = $f_custom_fields_data;
 				$t_setting_arr['sticky_issues'] = $f_sticky_issues;
@@ -472,6 +481,7 @@
 				$t_setting_arr['fixed_in_version']	= array( META_FILTER_ANY );
 				$t_setting_arr['target_version']	= array( META_FILTER_ANY );
 				$t_setting_arr['user_monitor'] 		= array( META_FILTER_ANY );
+				$t_setting_arr['user_votes'] 		= array( META_FILTER_ANY );
 				$t_setting_arr['relationship_type'] = -1;
 				$t_setting_arr['relationship_bug'] = 0;
 
Index: D:/Internet/mantis/bug_vote_delete.php
===================================================================
--- D:/Internet/mantis/bug_vote_delete.php	(revision 0)
+++ D:/Internet/mantis/bug_vote_delete.php	(revision 0)
@@ -0,0 +1,18 @@
+<?php
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if (config_get( 'voting_place_vote_threshold' ) != true){die('voting disabled');}
+
+$f_bug_id		= gpc_get_int( 'bug_id' );
+$t_user_id  = auth_get_current_user_id();
+
+access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+vote_delete($f_bug_id, $t_user_id);
+
+print_successful_redirect_to_bug($f_bug_id);
+
+?>
\ No newline at end of file
Index: D:/Internet/mantis/css/default.css
===================================================================
--- D:/Internet/mantis/css/default.css	(revision 5156)
+++ D:/Internet/mantis/css/default.css	(working copy)
@@ -162,3 +162,11 @@
 
 .progress400				{ position: relative; width: 400px; border: 1px solid #d7d7d7; margin-top: 1em; margin-bottom: 1em; padding: 1px; }
 .progress400 .bar			{ display: block; position: relative; background: #6bba70; text-align: center; font-weight: normal; color: #333; height: 2em; line-height: 2em; }
+
+.votesNegative, .votesPositive, .votesBalance{color:#fff; padding: 2px;}
+.votesPositive{background-color: #18592E;}
+.votesNegative{background-color: #C33039;}
+.votesBalance{background-color: #000080;}
+.voteRow{float: right;}
+.votesBalance.positiveBalance{background-color: #008000 !important;}
+.votesBalance.negativeBalance{background-color: #FF0000 !important;}
Index: D:/Internet/mantis/admin/schema.php
===================================================================
--- D:/Internet/mantis/admin/schema.php	(revision 5156)
+++ D:/Internet/mantis/admin/schema.php	(working copy)
@@ -404,3 +404,16 @@
 	" ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_project_version_table' ), "
 	obsolete		L		NOTNULL DEFAULT \" '0' \"" ) );
+
+# first version of voting
+$upgrade[] = Array( 'CreateTableSQL', Array( db_get_table( 'mantis_bug_votes_table' ), "
+	issue_id		I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	user_id			I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	weight			I		NOTNULL DEFAULT '0'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
+$upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "
+	votes_positive		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_negative		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_num_voters	I		UNSIGNED NOTNULL DEFAULT '0'
+	" ) );
+$upgrade[] = Array('CreateIndexSQL',Array('idx_votes_num_voters',db_get_table('mantis_bug_table'),'votes_num_voters'));
\ No newline at end of file
Index: D:/Internet/mantis/config_defaults_inc.php
===================================================================
--- D:/Internet/mantis/config_defaults_inc.php	(revision 5156)
+++ D:/Internet/mantis/config_defaults_inc.php	(working copy)
@@ -529,22 +529,22 @@
 	# resolution, fixed_in_version, view_state, os, os_build, build (for product build), platform, version, date_submitted, attachment,
 	# category, sponsorship_total, severity, status, last_updated, summary, bugnotes_count, description,
 	# steps_to_reproduce, additional_information
-	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 	
 	# The default columns to be included in the Print Issues Page.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 
 	# The default columns to be included in the CSV export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# The default columns to be included in the Excel export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# --- show projects when in All Projects mode ---
 	$g_show_bug_project_links	= ON;
@@ -1419,6 +1419,7 @@
 	$g_db_table['mantis_config_table']					= '%db_table_prefix%_config%db_table_suffix%';
 	$g_db_table['mantis_database_table']				= '%db_table_prefix%_database%db_table_suffix%';
 	$g_db_table['mantis_email_table']					= '%db_table_prefix%_email%db_table_suffix%';
+	$g_db_table['mantis_bug_votes_table']			= '%db_table_prefix%_bug_votes%db_table_suffix%';
 
 	###########################
 	# Mantis Enum Strings
@@ -1985,6 +1986,36 @@
 
 	# management threshold.
 	$g_manage_plugin_threshold = ADMINISTRATOR;
+	
+	#############################
+	# Voting System
+	#############################
+  
+	# enable or disable the whole voting feature.
+	$g_voting_enabled = ON; 
+	
+	# access level required for users to vote on issues.
+	$g_voting_place_vote_threshold = REPORTER; 
+	
+	# access level required for users to view the users who voted and their votes.
+	$g_voting_view_user_votes_threshold = DEVELOPER;
+	
+	# default number of votes allowed per user
+	$g_voting_default_num_votes = 10; # votes can be set for all user levels as an integer
+	$g_voting_default_num_votes = array( DEVELOPER => 25 , REPORTER => 10 ); # or you can set votes by user type, if a level is not specified then it will use the next lowest level available
+	
+	# default voting weights and thier labels, value needs to be integer, while key is a string eg: '+10 (Highly desired)'
+	$g_voting_weight_options = array('+1'=>1, '+2'=>2, '+5'=>5, '+10'=>10, '-1'=>1, '-2'=>-2, '-5'=>-5, '-10'=>-10);
+	
+	# the maximum weight a user at a given level may use in a single vote
+	$g_voting_max_vote_weight = 5; #max vote weight can be an integer 
+	$g_voting_max_vote_weight = array( DEVELOPER => 10 , REPORTER => 5 ); # or set by user type, eg: even though a reporter may have 10 votes, they may only use up to weight 5 in a single vote
+	
+	# voting weight that should be initially selected when casting a vote, usually the minimum positive vote
+	$g_voting_weight_default = 1;
+	
+	# whether you get your votes counted per project or globally, if ON then you will get $g_voting_default_num_votes per project, if it is OFF your votes are spread across all projects  
+	$g_voting_per_project = ON;
 
 	#############################
 	# Mind mapping
@@ -1995,4 +2026,4 @@
 	# Enables or disables the mind mapping features including ability to export Freemind files and 
 	# in browser view of generated mindmaps.
 	$g_mindmap_enabled = ON;
-?>
\ No newline at end of file
+?>
Index: D:/Internet/mantis/bug_vote_list_view_inc.php
===================================================================
--- D:/Internet/mantis/bug_vote_list_view_inc.php	(revision 0)
+++ D:/Internet/mantis/bug_vote_list_view_inc.php	(revision 0)
@@ -0,0 +1,146 @@
+<?php
+# This include file prints out the list of users that have voted for the current
+# bug.	$f_bug_id must be set to the bug id
+
+require_once( $t_core_path . 'vote_api.php' );
+require_once( $t_core_path . 'collapse_api.php' );
+
+$t_core_path = config_get( 'core_path' );
+$t_voting_enabled = vote_is_enabled();
+$t_current_user_id = auth_get_current_user_id();
+
+#
+# Determine whether the voting section should be shown.
+#
+
+if ($t_voting_enabled) {
+	
+	$t_votes = vote_get_issue_votes( $f_bug_id );
+
+	$t_votes_exist = count( $t_votes ) > 0;
+	$t_can_view_vote_details = vote_can_view_vote_details($f_bug_id, $t_current_user_id);
+	$t_can_vote = vote_can_vote($f_bug_id, $t_current_user_id);
+
+	$t_show_votes = $t_votes_exist || $t_can_vote;
+	
+	$t_total_positive = bug_get_field( $f_bug_id, 'votes_positive' );
+	$t_total_negative = bug_get_field( $f_bug_id, 'votes_negative' );
+	$t_total_votes = $t_total_positive - $t_total_negative;
+	
+	$t_total_voters = bug_get_field( $f_bug_id, 'votes_num_voters' );
+	
+	$t_button_text = lang_get('vote_cast_button');
+	$t_bug_id = string_attribute( $f_bug_id );
+	
+	$t_voting_weight_options = config_get( 'voting_weight_options' );
+	asort($t_voting_weight_options);
+	$t_voting_weight_default = config_get( 'voting_weight_default' );
+	
+	$t_max_votes = vote_max_votes( $t_current_user_id );
+	$t_used_votes = vote_used_votes( $t_current_user_id );
+	$t_issue_project = bug_get_field( $f_bug_id, 'project_id');
+	$t_available_votes = vote_available_votes( $t_current_user_id, $t_issue_project );
+	$t_voting_max_vote_weight = vote_max_weight( $t_current_user_id, $t_issue_project );
+	
+} else {
+	$t_show_votes = false;
+}
+?>
+<?php if ( $t_show_votes ) { # Voting Box	?>
+
+<a name="votings" id="votings"></a>
+<br />
+
+<?php collapse_open( 'voting' );?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title" colspan="2">
+		<?php collapse_icon( 'voting' ); ?>
+		<?php echo lang_get('voting_this_issue') ?> 
+	</td>
+	</tr>
+
+	<tr class="row-1">
+		<td class="category" width="15%">Vote on issue</td>
+		<td>
+		<?php
+		if ( $t_can_vote ) {
+			if (vote_exists($f_bug_id, $t_current_user_id)) { #show 'remove my vote' button
+									
+						html_button( 'bug_vote_delete.php',
+									 lang_get( 'vote_delete_button' ),
+									 array( 'bug_id' => $f_bug_id, 'action' => 'DELETE' ) );
+			
+			}
+			else {  # show 'add vote' button
+			?>
+				
+			<form method="post" action="bug_vote_add.php">
+			<?php if ($t_available_votes>0){ ?>
+			<input type="submit" class="button" value="<?php echo $t_button_text ;?>" />
+			<select name="vote_weight">
+			<?php 
+			foreach($t_voting_weight_options as $t_option_key => $t_option_value){
+				$t_vote_cost = ($t_option_value>0)?$t_option_value:-$t_option_value; #normalise the weight
+				if ($t_voting_max_vote_weight >= $t_vote_cost && $t_vote_cost != 0){
+			?>
+				<option value="<?php echo $t_option_value?>"<?php echo($t_voting_weight_default==$t_option_value)?' selected':'' ?>><?php echo $t_option_key?></option>
+			<?php }} ?>
+			</select>
+			
+			<? } # available_votes>0 ?>
+			
+			(<?php echo $t_used_votes . '/' . $t_max_votes ?> <?php echo lang_get('votes_used')?>, <?php echo $t_available_votes ?> <?php echo lang_get('votes_remain')?>)
+			<input type="hidden" name="bug_id" value="<?php echo $t_bug_id; ?>" />
+			</form>		
+		<? 
+			} #end vote_exists
+		} #end can_vote 
+		?>
+		</td>
+	</tr>
+	<?php if ( $t_votes_exist ) {	?>
+	<tr>
+	<td class="category" width="15%">Summary</td>
+	<td>
+		<?php echo lang_get('votes_positive') ?> = <?php echo $t_total_positive;?><br>
+		<?php echo lang_get('votes_negative') ?> = <?php echo $t_total_negative;?><br>
+		<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes; ?><br>
+		<?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters; ?>
+		
+	</td>
+	</tr>
+	
+	<?php if ($t_can_view_vote_details){ ?>
+	<tr class="row-2">
+		<td class="category" width="15%">Voters List</td>
+		<td>
+		<?php	foreach($t_votes as $userVote){ ?>
+			<div class="userVote">
+				<?php echo user_get_name($userVote['user_id']) ?> <?php echo ($userVote['weight']>=1)?'+'.$userVote['weight']:$userVote['weight'] ?>
+			</div>
+		<?php } ?>
+		</td>
+	</tr>
+	<?php
+		} #end view_vote_details 
+	} #end votes_exist
+	?>
+</table>
+
+<?php collapse_closed( 'voting' ); ?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title">
+		<?php collapse_icon( 'voting' );	?>
+		<?php echo lang_get('voting_this_issue') ?> <span style="font-weight: normal;">(<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes ?>, <?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters ?>)</span>
+		</td>
+	</tr>
+</table>
+
+<?php	
+	collapse_end( 'voting' );
+} # If voting enabled
+?>
Index: D:/Internet/mantis/core/html_api.php
===================================================================
--- D:/Internet/mantis/core/html_api.php	(revision 5156)
+++ D:/Internet/mantis/core/html_api.php	(working copy)
@@ -70,6 +70,7 @@
 	require_once( $t_core_dir . 'user_api.php' );
 	require_once( $t_core_dir . 'rss_api.php' );
 	require_once( $t_core_dir . 'wiki_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	$g_rss_feed_url = null;
 
@@ -1265,7 +1266,7 @@
 		echo '<td class="center">';
 		html_button_bug_change_status( $p_bug_id );
 		echo '</td>';
-
+		
 		# MONITOR/UNMONITOR button
 		echo '<td class="center">';
 		if ( !current_user_is_anonymous() ) {
Index: D:/Internet/mantis/core/bug_api.php
===================================================================
--- D:/Internet/mantis/core/bug_api.php	(revision 5156)
+++ D:/Internet/mantis/core/bug_api.php	(working copy)
@@ -31,6 +31,7 @@
 	require_once( $t_core_dir . 'sponsorship_api.php' );
 	require_once( $t_core_dir . 'twitter_api.php' );
 	require_once( $t_core_dir . 'tag_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	# MASC RELATIONSHIP
 	require_once( $t_core_dir.'relationship_api.php' );
@@ -67,6 +68,9 @@
 		var $summary = '';
 		var $sponsorship_total = 0;
 		var $sticky = 0;
+		var $votes_positive = 0;
+		var $votes_negative = 0;
+		var $votes_num_voters = 0;
 
 		# omitted:
 		# var $bug_text_id
@@ -752,6 +756,9 @@
 
 		# Delete all sponsorships
 		sponsorship_delete( sponsorship_get_all_ids( $p_bug_id ) );
+		
+		# Delete all votes on this bug
+		vote_delete_issue_votes( $p_bug_id );
 
 		# MASC RELATIONSHIP
 		# we delete relationships even if the feature is currently off.
Index: D:/Internet/mantis/core/filter_api.php
===================================================================
--- D:/Internet/mantis/core/filter_api.php	(revision 5156)
+++ D:/Internet/mantis/core/filter_api.php	(working copy)
@@ -47,6 +47,7 @@
 	define( 'FILTER_PROPERTY_PRODUCT_BUILD', 'show_build' );
 	define( 'FILTER_PROPERTY_PRODUCT_VERSION', 'show_version' );
 	define( 'FILTER_PROPERTY_MONITOR_USER_ID', 'user_monitor' );
+	define( 'FILTER_PROPERTY_VOTES_USER_ID', 'user_votes');
 	define( 'FILTER_PROPERTY_HIDE_STATUS_ID', 'hide_status' );
 	define( 'FILTER_PROPERTY_SORT_FIELD_NAME', 'sort' );
 	define( 'FILTER_PROPERTY_SORT_DIRECTION', 'dir' );
@@ -98,6 +99,7 @@
 	define( 'FILTER_SEARCH_OS', 'os' );
 	define( 'FILTER_SEARCH_OS_BUILD', 'os_build' );
 	define( 'FILTER_SEARCH_MONITOR_USER_ID', 'monitor_user_id' );
+	define( 'FILTER_SEARCH_VOTES_USER_ID' , 'votes_user_id');
 	define( 'FILTER_SEARCH_PRODUCT_BUILD', 'product_build' );
 	define( 'FILTER_SEARCH_PRODUCT_VERSION', 'product_version' );
 	define( 'FILTER_SEARCH_VIEW_STATE_ID', 'view_state_id' );
@@ -203,6 +205,10 @@
 		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
 		}
+		
+		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] ) ) {
+			$t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VOTES_USER_ID, $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] );
+		}
 
 		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
@@ -373,6 +379,7 @@
 			'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 			'hide_status'		=> Array ( '0' => $t_hide_status_default ),
 			'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+			'user_votes'			=> Array ( '0' => META_FILTER_ANY ),
 			'sort'              => 'last_updated',
 			'dir'               => 'DESC',
 			'per_page'			=> config_get( 'default_limit_view' )
@@ -418,6 +425,7 @@
 		$t_bugnote_text_table	= db_get_table( 'mantis_bugnote_text_table' );
 		$t_project_table		= db_get_table( 'mantis_project_table' );
 		$t_bug_monitor_table	= db_get_table( 'mantis_bug_monitor_table' );
+		$t_bug_votes_table 		=	db_get_table( 'mantis_bug_votes_table' );
 		$t_limit_reporters		= config_get( 'limit_reporters' );
 		$t_bug_relationship_table	= db_get_table( 'mantis_bug_relationship_table' );
 		$t_report_bug_threshold		= config_get( 'report_bug_threshold' );
@@ -1136,6 +1144,48 @@
 				array_push( $t_where_clauses, "( $t_table_name.user_id=" . db_param($t_where_param_count++). " )" );
 			}
 		}
+
+		# users voting on an issue
+		$t_select_clauses[] = '(votes_positive - votes_negative) as votes_total'; # @REVIEW is this the correct mantis way to be doing this? votes_total is a derived column
+		$t_any_found = false;
+		if ( count( $t_filter['user_votes'] ) == 0 ) {
+			$t_any_found = true;
+		}
+		else
+		{
+			foreach( $t_filter['user_votes'] as $t_filter_member ) {
+				if ( ( META_FILTER_ANY == $t_filter_member ) || ( 0 === $t_filter_member ) ) {
+					$t_any_found = true;
+				}
+			}
+		}
+		if ( !$t_any_found ) {
+			$t_clauses = array();
+			$t_table_name = 'user_votes';
+			array_push( $t_from_clauses, $t_bug_votes_table );
+			array_push( $t_join_clauses, "LEFT JOIN $t_bug_votes_table $t_table_name ON $t_table_name.issue_id = $t_bug_table.id" );
+
+			foreach( $t_filter['user_votes'] as $t_filter_member ) {
+				$c_user_monitor = db_prepare_int( $t_filter_member );
+				if ( META_FILTER_MYSELF == $c_user_monitor ) {
+					array_push( $t_clauses, $c_user_id );
+				} else {
+					array_push( $t_clauses, $c_user_monitor );
+				}
+			}
+			if ( 1 < count( $t_clauses ) ) {
+				foreach( $t_clauses as $t_clause ) {
+					$t_where_tmp[] = db_param($t_where_param_count++);
+					$t_where_params[] = $t_clause;
+				}
+				array_push( $t_where_clauses, "( $t_table_name.user_id in (". implode( ', ', $t_where_tmp ) .") )" );
+			} else {
+				$t_where_params[] = $t_clauses[0];
+				array_push( $t_where_clauses, "( $t_table_name.user_id=" . db_param($t_where_param_count++). " )" );
+			}
+		}
+		
+		
 		# bug relationship
 		$t_any_found = false;
 		$c_rel_type = $t_filter['relationship_type'];
@@ -1375,7 +1425,7 @@
 						$t_id_join
 						$t_id_where";
 			$t_query_params = array();
-			
+
 			if ( ( $i == 0 ) || ( !is_blank( $t_filter['search'] ) ) ) {
 				if( $i == 0) {
 					$q1 = "SELECT DISTINCT $t_bug_table.id AS id" . $query;
@@ -2484,9 +2534,12 @@
 			<td class="small-caption" valign="top">
 				<a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a>
 			</td>
-			<td class="small-caption" valign="top" colspan="5">
+			<td class="small-caption" valign="top" colspan="4">
 				<a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a>
 			</td>
+			<td class="small-caption" valign="top">
+				<a href="<?php PRINT $t_filters_url . 'user_votes[]'; ?>" id="user_votes_filter"><?php PRINT lang_get( 'voted_by' ) ?>:</a>
+			</td>
 			<?php if ( $t_filter_cols > 8 ) {
 				echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '">&nbsp;</td>';
 			} ?>
@@ -2507,7 +2560,7 @@
 					print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] );
 				?>
 			</td>
-			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5">
+			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="4">
 				<?php 
 					$t_tag_string = $t_filter['tag_string'];
 					if ( $t_filter['tag_select'] != 0 ) {
@@ -2518,6 +2571,45 @@
 				?>
 				<input type="hidden" name="tag_string" value="<?php echo $t_tag_string ?>"/>
 			</td>
+			<td class="small-caption" valign="top" id="user_votes_filter_target">
+							<?php
+								$t_output = '';
+								$t_any_found = false;
+								if ( count( $t_filter['user_votes'] ) == 0 ) {
+									PRINT lang_get( 'any' );
+								} else {
+									$t_first_flag = true;
+									foreach( $t_filter['user_votes'] as $t_current ) {
+										?>
+										<input type="hidden" name="user_votes[]" value="<?php echo $t_current;?>" />
+										<?php
+										$t_this_name = '';
+										if ( ( $t_current === 0 ) || ( is_blank( $t_current ) ) || ( META_FILTER_ANY == $t_current ) ) {
+											$t_any_found = true;
+										} else if ( META_FILTER_MYSELF == $t_current ) {
+											if ( access_has_project_level( config_get( 'monitor_bug_threshold' ) ) ) {
+												$t_this_name = '[' . lang_get( 'myself' ) . ']';
+											} else {
+												$t_any_found = true;
+											}
+										} else {
+											$t_this_name = user_get_name( $t_current );
+										}
+										if ( $t_first_flag != true ) {
+											$t_output = $t_output . '<br />';
+										} else {
+											$t_first_flag = false;
+										}
+										$t_output = $t_output . $t_this_name;
+									}
+									if ( true == $t_any_found ) {
+										PRINT lang_get( 'any' );
+									} else {
+										PRINT $t_output;
+									}
+								}
+							?>
+			</td>
 		</tr>
 		<?php
 
@@ -3308,6 +3400,7 @@
 									  'fixed_in_version' => 'string',
 									  'target_version' => 'string',
 									  'user_monitor' => 'int',
+										'user_votes' 	=> 'int',
 									  'show_profile' => 'int'
 									 );
 		foreach( $t_multi_select_list as $t_multi_field_name => $t_multi_field_type ) {
@@ -3440,6 +3533,24 @@
 		</select>
 		<?php
 	}
+	
+	function print_filter_user_votes(){
+		global $t_select_modifier, $t_filter;
+		?>
+		<!-- Voted by -->
+		<select <?php PRINT $t_select_modifier;?> name="user_votes[]">
+			<option value="<?php echo META_FILTER_ANY ?>" <?php check_selected( $t_filter['user_votes'], META_FILTER_ANY ); ?>>[<?php echo lang_get( 'any' ) ?>]</option>
+			<?php
+				if ( access_has_project_level( config_get( 'monitor_bug_threshold' ) ) ) {
+					PRINT '<option value="' . META_FILTER_MYSELF . '" ';
+					check_selected( $t_filter['user_votes'], META_FILTER_MYSELF );
+					PRINT '>[' . lang_get( 'myself' ) . ']</option>';
+				}
+			?>
+			<?php print_reporter_option_list( $t_filter['user_votes'] ) ?>
+		</select>
+		<?php
+	}
 
 	function print_filter_handler_id(){
 		global $t_select_modifier, $t_filter, $f_view_type;
Index: D:/Internet/mantis/core/csv_api.php
===================================================================
--- D:/Internet/mantis/core/csv_api.php	(revision 5156)
+++ D:/Internet/mantis/core/csv_api.php	(working copy)
@@ -249,4 +249,16 @@
 	function csv_format_selection( $p_duplicate_id ) {
 		return csv_escape_string( '' );
 	}
+	
+	function csv_format_votes_positive( $p_votes_positive ) {
+		return csv_escape_string( $p_votes_positive );
+	}
+	
+	function csv_format_votes_negative( $p_votes_negative ) {
+		return csv_escape_string( $p_votes_negative );
+	}
+	
+	function csv_format_votes_num_voters( $p_votes_num_voters ) {
+		return csv_escape_string( $p_votes_num_voters );
+	}
 ?>
Index: D:/Internet/mantis/core/excel_api.php
===================================================================
--- D:/Internet/mantis/core/excel_api.php	(revision 5156)
+++ D:/Internet/mantis/core/excel_api.php	(working copy)
@@ -419,4 +419,14 @@
 		// field is not linked to project
 		return excel_prepare_string( '' );
 	}
+	
+	function excel_format_votes_positive( $p_votes_positive ) {
+		return excel_prepare_string( $p_votes_positive );
+	}
+	function excel_format_votes_negative( $p_votes_negative ) {
+		return excel_prepare_string( $p_votes_negative );
+	}
+	function excel_format_votes_num_voters( $p_votes_num_voters ) {
+		return excel_prepare_string( $p_votes_num_voters );
+	}
 ?>
\ No newline at end of file
Index: D:/Internet/mantis/core/user_api.php
===================================================================
--- D:/Internet/mantis/core/user_api.php	(revision 5156)
+++ D:/Internet/mantis/core/user_api.php	(working copy)
@@ -25,6 +25,7 @@
 
 	require_once( $t_core_dir . 'email_api.php' );
 	require_once( $t_core_dir . 'ldap_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	### User API ###
 
@@ -551,6 +552,9 @@
 		$t_user_table = db_get_table('mantis_user_table');
 
 		user_ensure_unprotected( $p_user_id );
+		
+		# Remove any votes the user has made on issues
+		vote_delete_user_votes( $p_user_id );
 
 		# Remove associated profiles
 		user_delete_profiles( $p_user_id );
@@ -1031,7 +1035,6 @@
 		}
 
 		$t_filter = unserialize( $t_cookie_detail[1] );
-
 		$t_filter = filter_ensure_valid_filter( $t_filter );
 
 		return $t_filter;
Index: D:/Internet/mantis/core/columns_api.php
===================================================================
--- D:/Internet/mantis/core/columns_api.php	(revision 5156)
+++ D:/Internet/mantis/core/columns_api.php	(working copy)
@@ -51,6 +51,9 @@
 			'selection',
 			'severity',
 			'sponsorship_total',
+			'votes_positive',
+			'votes_negative',
+			'votes_num_voters',
 			'status',
 			'steps_to_reproduce',        // new
 			'summary',
@@ -782,4 +785,44 @@
 
 		echo '</td>';
 	}
+	
+	
+	function print_column_title_votes_total( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_balance' ), 'votes_total', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_total' );
+		echo '</td>';
+	}
+	
+	function print_column_title_votes_num_voters( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_num_voters' ), 'votes_num_voters', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_num_voters' );
+		echo '</td>';
+	}
+	
+	function print_column_votes_total( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		$t_votes_balance = ($p_row['votes_positive']-$p_row['votes_negative']);
+		if ($t_votes_balance > 0)
+		{
+			$t_balance_class = 'positiveBalance';
+		}
+		else if($t_votes_balance < 0)
+		{
+			$t_balance_class = 'negativeBalance';
+		}
+		else
+		{
+			$t_balance_class = '';
+		}
+		echo '<td><span class="voteRow">[ ';
+		echo '<span class="votesPositive">+'.$p_row['votes_positive'] . '</span> / <span class="votesNegative">-'. $p_row['votes_negative'] . '</span> ] = <span class="votesBalance '.$t_balance_class.'">' . (($t_votes_balance>0)?'+':'')  . $t_votes_balance . "</span>";
+		echo '</span></td>';
+	}
+	
+	function print_column_votes_num_voters( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		echo $p_row['votes_num_voters'];
+		echo '</td>';
+	}
 ?>
Index: D:/Internet/mantis/core/helper_api.php
===================================================================
--- D:/Internet/mantis/core/helper_api.php	(revision 5156)
+++ D:/Internet/mantis/core/helper_api.php	(working copy)
@@ -369,6 +369,13 @@
 		if ( OFF == $t_enable_sponsorship ) {
 			$t_keys_to_remove[] = 'sponsorship_total';
 		}
+		
+		$t_enable_voting = config_get( 'voting_enabled' );
+		if ($t_enable_voting == OFF)
+		{
+			$t_keys_to_remove[] = 'votes_total';
+			$t_keys_to_remove[] = 'votes_num_voters';
+		}
 
 		if ( $p_columns_target == COLUMNS_TARGET_CSV_PAGE || $p_columns_target == COLUMNS_TARGET_EXCEL_PAGE ||
 			 OFF == config_get( 'show_attachment_indicator' ) ) {
Index: D:/Internet/mantis/core/history_api.php
===================================================================
--- D:/Internet/mantis/core/history_api.php	(revision 5156)
+++ D:/Internet/mantis/core/history_api.php	(working copy)
@@ -427,6 +427,12 @@
 					$t_note = lang_get( 'tag_history_renamed' );
 					$t_change = $p_old_value . ' => ' . $p_new_value;
 					break;
+				case BUGVOTE_ADDED:
+					$t_note = lang_get( 'bugvote_added' ) . ": " . $p_old_value;
+					break;
+				case BUGVOTE_DELETED:
+					$t_note = lang_get( 'bugvote_deleted' ) . ": " . $p_old_value;
+					break;
 			}
 		}
 
Index: D:/Internet/mantis/core/vote_api.php
===================================================================
--- D:/Internet/mantis/core/vote_api.php	(revision 0)
+++ D:/Internet/mantis/core/vote_api.php	(revision 0)
@@ -0,0 +1,320 @@
+<?php
+$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
+require_once( $t_core_dir . 'current_user_api.php' );
+require_once( $t_core_dir . 'history_api.php' );
+require_once( $t_core_dir . 'bug_api.php' );
+require_once( $t_core_dir . 'user_api.php' );
+
+
+/**
+ * vote_add
+ * 
+ * @param int $p_issue_id issue primary key
+ * @param int $p_weight impact of vote
+ * @param int $p_user_id user primary key
+ * @return bool
+ */
+function vote_add( $p_issue_id, $p_weight, $p_user_id = null )
+{
+	$t_issue_project = bug_get_field($p_issue_id, 'project_id');
+	$t_vote_max_weight = vote_max_weight( $p_user_id, $t_issue_project );
+	
+	if ( $p_weight > $t_vote_max_weight || $p_weight == 0 )
+	{
+		return false; # not allowed to vote more than your limit, or have a vote with weight 0
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+
+	$query = "INSERT INTO $t_mantis_bug_votes_table
+		          		( issue_id, user_id, weight )
+		          	 VALUES
+		          		( " . db_param(0) . ", " . db_param(1) . ", " . db_param(2) . " )";
+	db_query_bound( $query, Array( (int)$p_issue_id, (int)$p_user_id, (int)$p_weight ) );
+	
+	# get bugvote id
+	$t_bugvote_id = db_insert_id( $t_mantis_bug_votes_table );
+	
+	
+	if ($p_weight >= 1) {
+		$t_existing_positive_votes = bug_get_field($p_issue_id,'votes_positive');
+		bug_set_field($p_issue_id, 'votes_positive', $t_existing_positive_votes + $p_weight );
+	}
+	else if ($p_weight <= -1) {
+		$t_existing_negative_votes = bug_get_field($p_issue_id,'votes_negative');
+		bug_set_field($p_issue_id, 'votes_negative', $t_existing_negative_votes - $p_weight);
+	}
+	$t_existing_num_voters = bug_get_field($p_issue_id, 'votes_num_voters');
+	bug_set_field($p_issue_id, 'votes_num_voters', $t_existing_num_voters + 1);
+	
+	# log vote history
+	$t_weight_log = ($p_weight>=1)?('+'.$p_weight):$p_weight;
+	history_log_event_special( $p_issue_id, BUGVOTE_ADDED, $t_weight_log );
+	bug_update_date($p_issue_id);
+	
+	return true;
+}
+
+/**
+ * vote_delete
+ * should only delete your vote if the bug has not been closed or resolved
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return null
+ */
+function vote_delete( $p_issue_id, $p_user_id )
+{
+	if ($p_issue_id < 1 || $p_user_id < 1){return;}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	# decrement vote counts from bugs table, get weight used for vote so we can remove the correct weighting from the summary
+	$query = "SELECT weight FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0) . " AND user_id = " . db_param(1);
+	$result	= db_query_bound( $query, Array( $p_issue_id, $p_user_id ) );
+	$t_weight = $result->fields['weight'];
+
+	if ($t_weight >= 1) {
+		$t_existing_positive_votes = bug_get_field( $p_issue_id , 'votes_positive' );
+		bug_set_field( $p_issue_id , 'votes_positive' , $t_existing_positive_votes - $t_weight );
+	}
+	else if ($t_weight <= -1) {
+		$t_existing_negative_votes = bug_get_field( $p_issue_id , 'votes_negative' );
+		bug_set_field($p_issue_id, 'votes_negative', $t_existing_negative_votes + $t_weight );
+	}
+	$t_existing_num_voters = bug_get_field($p_issue_id, 'votes_num_voters');
+	bug_set_field($p_issue_id, 'votes_num_voters', $t_existing_num_voters - 1);
+	
+	# now remove all votes from voting table
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0) . " AND user_id = " . db_param(1);
+	db_query_bound($query, Array( (int)$p_issue_id, (int)$p_user_id ));
+	
+	# log vote history
+	$t_weight_log = ($t_weight>=1)?('+'.$t_weight):$t_weight;
+	history_log_event_special( $p_issue_id, BUGVOTE_DELETED, $t_weight_log );
+	bug_update_date($p_issue_id);
+}
+
+/**
+ * vote_delete_issue_votes
+ * Deleting an issue should delete all associated votes.
+ *
+ * @param int $p_issue_id issue primary key
+ * @return null
+ */
+function vote_delete_issue_votes( $p_issue_id )
+{
+	if ($p_issue_id < 1){return;}
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	db_query_bound($query, Array( (int)$p_issue_id ));
+}
+/**
+ * vote_delete_user_votes
+ * Deleting a user should delete all associated votes.
+ *
+ * @param int $p_user_id user primary key
+ * @return null
+ */
+function vote_delete_user_votes( $p_user_id )
+{
+	if ($p_user_id < 1){return;}
+	$votes = vote_get_user_votes( $p_user_id );
+	foreach($votes as $vote)
+	{
+		vote_delete($vote['issue_id'], $p_user_id);
+	}
+}
+/**
+ * vote_get_user_votes
+ *
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return array issues and thier weight array('issue_id'=>$p_user_id, 'weight'=>$p_weight); 
+ */
+function vote_get_user_votes ( $p_user_id, $p_project_id = null)
+{
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	$query = "SELECT issue_id, weight FROM $t_mantis_bug_votes_table WHERE user_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_user_id));
+
+	$users = array();
+	while ( $row = db_fetch_array( $result ) ) {
+		if ( $p_project_id === null )
+		{
+			$users[] = $row;
+		}
+		else
+		{
+			$t_issue_project = bug_get_field($row['issue_id'], 'project_id');
+			if ( $t_issue_project == $p_project_id )
+			{
+				$users[] = $row;
+			}
+		}
+	}
+	return $users;
+}
+
+/**
+ * vote_get_issue_votes
+ * returns an array of user ids, weight
+ * 
+ * @param int $p_issue_id issue primary key
+ * @return array users and thier vote weight array('user_id'=>$p_user_id, 'weight'=>$p_weight); 
+ */
+function vote_get_issue_votes( $p_issue_id )
+{
+	if ($p_issue_id < 1){return;}
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "SELECT user_id, weight FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_issue_id));
+	$t_issue_votes = array();
+	while ( $row = db_fetch_array( $result ) ) {
+		$t_issue_votes[] = $row;
+	}
+	return $t_issue_votes;
+}
+/**
+ * vote_is_enabled
+ *
+ * @param int $p_project_id
+ * @return bool
+ */
+function vote_is_enabled( $p_project_id = ALL_PROJECTS )
+{
+	$t_enabled = ( config_get( 'voting_enabled', null, null, $p_project_id ) == ON );
+	return $t_enabled;
+}
+/**
+ * vote_can_vote
+ * whether or not the user is allowed to vote on an issue
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_can_vote( $p_issue_id, $p_user_id = null )
+{
+	$t_can_vote = ( !bug_is_readonly( $p_issue_id ) &&  access_has_bug_level( config_get( 'voting_place_vote_threshold' ),$p_issue_id , $p_user_id ) ); 
+	return $t_can_vote;
+}
+/**
+ * vote_can_view_vote_details
+ * whether or not the user is allowed to view vote details
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_can_view_vote_details( $p_issue_id, $p_user_id = null )
+{
+	$t_has_level = ( access_has_bug_level( config_get( 'voting_view_user_votes_threshold' ), $p_issue_id , $p_user_id ) );
+	return $t_has_level;
+}
+/**
+ * vote_exists
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_exists ( $p_issue_id, $p_user_id)
+{
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query 	= "SELECT COUNT(*)
+		          	FROM $t_mantis_bug_votes_table
+		          	WHERE issue_id=" . db_param(0) . " AND user_id = " . db_param(1);
+		$result	= db_query_bound( $query, Array( $p_issue_id, $p_user_id ) );
+
+		if ( 0 == db_result( $result ) ) {
+			return false;
+		} else {
+			return true;
+		}
+}
+
+/**
+ * vote_max_votes
+ * the maximum number of votes the given user can cast
+ * @param int $p_user_id
+ * @return int
+ */
+function vote_max_votes( $p_user_id )
+{
+	$t_default_num_votes = config_get('voting_default_num_votes',10);
+	
+	if (is_array($t_default_num_votes))
+	{
+		ksort($t_default_num_votes); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		foreach($t_default_num_votes as $t_vote_level => $t_votes)
+		{
+			if ($t_user_level >= $t_vote_level)
+			{
+				$t_default_num_votes = $t_votes;
+			}
+		}
+	}
+	
+	return $t_default_num_votes;
+}
+function vote_available_votes( $p_user_id, $p_project_id )
+{
+	return (vote_max_votes( $p_user_id )) - (vote_used_votes( $p_user_id, $p_project_id ));
+}
+/**
+ * vote_used_votes
+ * the number of votes already cast on a given project
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return int
+ */
+function vote_used_votes( $p_user_id, $p_project_id = null )
+{
+	$t_per_project = config_get('voting_per_project', ON);
+	if ($t_per_project == ON)
+	{
+		$t_votes = vote_get_user_votes( $p_user_id, $p_project_id );	
+	}
+	else
+	{
+		$t_votes = vote_get_user_votes( $p_user_id );
+	}
+	
+	$t_weight_used = 0;
+	foreach($t_votes as $t_vote)
+	{
+		if ($t_vote['weight']>0)
+		{
+			$t_weight_used += $t_vote['weight'];
+		}
+		else
+		{
+			$t_weight_used -= $t_vote['weight'];
+		}
+	}
+	return $t_weight_used;
+}
+function vote_max_weight( $p_user_id, $p_project_id )
+{
+	$t_available_votes = vote_available_votes( $p_user_id, $p_project_id );
+	$t_voting_max_vote_weight = config_get('voting_max_vote_weight', 5);
+	if (is_array($t_voting_max_vote_weight))
+	{
+		ksort($t_voting_max_vote_weight); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		foreach($t_voting_max_vote_weight as $t_max)
+		{
+			if ($t_user_level >= $t_max)
+			{
+				$t_voting_max_vote_weight = $t_max;
+			}
+		}
+	}
+	# return whichever is the lesser, your available votes or you max vote weight
+	$t_voting_max_vote_weight = ($t_available_votes > $t_voting_max_vote_weight)?$t_voting_max_vote_weight:$t_available_votes;
+	return $t_voting_max_vote_weight;
+}
+?>
\ No newline at end of file
Index: D:/Internet/mantis/core/my_view_inc.php
===================================================================
--- D:/Internet/mantis/core/my_view_inc.php	(revision 5156)
+++ D:/Internet/mantis/core/my_view_inc.php	(working copy)
@@ -59,7 +59,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_bug_resolved_status_threshold ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['assigned'] = 'handler_id=' . $t_current_user_id . '&amp;hide_status=' . $t_bug_resolved_status_threshold;
 
@@ -74,7 +75,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => META_FILTER_NONE ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['recent_mod'] = 'hide_status=none';
 
@@ -90,7 +92,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['reported'] = 'reporter_id=' . $t_current_user_id . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -105,7 +108,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['resolved'] = 'show_status=' . $t_bug_resolved_status_threshold . '&amp;hide_status=' . $t_bug_resolved_status_threshold;
 
@@ -120,7 +124,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['unassigned'] = 'handler_id=[none]' . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -135,7 +140,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => $t_current_user_id )
+		'user_monitor'		=> Array ( '0' => $t_current_user_id ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['monitored'] = 'user_monitor=' . $t_current_user_id . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -151,7 +157,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['feedback'] = 'reporter_id=' . $t_current_user_id . '&amp;show_status=' . FEEDBACK . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -166,7 +173,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['verify'] = 'reporter_id=' . $t_current_user_id . '&amp;show_status=' . $t_bug_resolved_status_threshold;
 
Index: D:/Internet/mantis/core/constant_inc.php
===================================================================
--- D:/Internet/mantis/core/constant_inc.php	(revision 5156)
+++ D:/Internet/mantis/core/constant_inc.php	(working copy)
@@ -170,6 +170,8 @@
 	define( 'TAG_ATTACHED', 				25 );
 	define( 'TAG_DETACHED', 				26 );
 	define( 'TAG_RENAMED', 					27 );
+	define( 'BUGVOTE_ADDED', 				28 );
+	define( 'BUGVOTE_DELETED', 			29 );
 
 	# bug relationship constants
 	define( 'BUG_DUPLICATE',	0 );
Index: D:/Internet/mantis/bug_vote_add.php
===================================================================
--- D:/Internet/mantis/bug_vote_add.php	(revision 0)
+++ D:/Internet/mantis/bug_vote_add.php	(revision 0)
@@ -0,0 +1,23 @@
+<?php
+
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if (config_get( 'voting_place_vote_threshold' ) != true){die('voting disabled');}
+
+$f_bug_id		= gpc_get_int( 'bug_id' );
+$f_weight		= gpc_get_int( 'vote_weight' );
+$t_user_id  = auth_get_current_user_id();
+
+
+access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+if (!vote_exists($f_bug_id, $t_user_id)){
+	vote_add($f_bug_id, $f_weight, $t_user_id);
+	
+}
+print_successful_redirect_to_bug($f_bug_id);
+
+?>
\ No newline at end of file
voting for r5156.txt (49,873 bytes)
cornchips

cornchips

2008-04-06 05:40

reporter   ~0017557

I've just added my patch for svn r5156. However i still need to cleanup the code to match the mantis coding guidelines. All basic features are complete, and there is also a credit system based on user access level.

I would also still like to add a 'my votes' view similar to the my sponsors view, as well as a brainstorming view like the facebook/uservoice/ubuntu examples mentioned above.

So, consider this an 'alpha' version if you like. I should have further time this week to complete the features mentioned above.

c_schmitz

c_schmitz

2008-04-06 06:14

reporter   ~0017558

That sounds really great.
I assume you are not a Mantis dev team developer. If this makes it into release version I will make sure you get a compensation too.

vboctor

vboctor

2008-04-17 22:13

manager   ~0017620

@cornships, how is the work going?

I've attempted to apply the patch but I got an error for each file. Here is an example:
error: Internet/mantis/bug_view_advanced_page.php: No such file or directory

That patch also seems to use Windows new lines, not sure if this will cause issues applying the patch on Linux.

While you are at it, please update the patch on the latest SVN code on trunk.

2008-04-20 06:02

 

voting for r5176.patch (55,841 bytes)
Index: account_voting_page.php
===================================================================
--- account_voting_page.php	(revision 0)
+++ account_voting_page.php	(revision 0)
@@ -0,0 +1,143 @@
+<?php
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+require_once( $t_core_path.'project_api.php' );
+
+$t_project = helper_get_current_project();
+
+
+if ( !vote_is_enabled($t_project) ) {
+	trigger_error( ERROR_VOTING_NOT_ENABLED, ERROR );
+}
+
+if ( current_user_is_anonymous() ) {
+	access_denied();
+}
+
+$t_current_user_id = auth_get_current_user_id();
+$t_resolved = config_get( 'bug_resolved_status_threshold' );
+$t_show_all = gpc_get_bool( 'show_all', false );
+
+# start the page
+html_page_top1( lang_get( 'my_votes' ) );
+html_page_top2();
+
+
+
+
+/*
+get all of users votes
+
+show only those that are active
+clearly indicate which they can remove/change and which are locked because they are assigned
+
+show credits balance
+*/
+# @TODO be able to filter on specific projects, for now just show all votes
+$t_votes = vote_get_user_votes($t_current_user_id, null, true); # $t_project
+
+# get all information for issues ready for display to user
+$t_votes_info = array();
+#var_dump($t_votes);
+foreach($t_votes as $t_vote)
+{
+	$t_issue = bug_get($t_vote['issue_id']);
+	if ( ($t_issue->status < $t_resolved) || $t_show_all )
+	{
+		$t_project_name = project_get_name($t_issue->project_id);
+		$t_votes_info[] = array('vote'=>$t_vote, 'issue'=>$t_issue, 'project_name'=>$t_project_name);
+	}
+}
+
+?>
+
+<br />
+<table class="width100" cellspacing="1">
+<tr>
+	<td class="form-title">
+		<?php echo lang_get( 'my_votes' ) ?>
+	</td>
+	<td class="right">
+		<?php print_account_menu( 'account_voting_page.php' ) ?>
+	</td>
+</tr>
+</table>
+
+
+
+<table class="bugList">
+	<caption>
+		<?php echo lang_get( 'own_voted' ) ?>
+	</caption>
+	<thead>
+	<tr>
+		<th><?php echo lang_get( 'email_bug' ) ?></th>
+		<th><?php echo lang_get( 'vote_weight' ) ?></th>
+		<th><?php echo lang_get( 'vote_num_voters' ) ?></th>
+		<th><?php echo lang_get( 'vote_balance' ) ?></th>
+		<th><?php echo lang_get( 'email_project' ) ?></th>
+		<th><?php echo lang_get( 'email_status' ) ?></th>
+		<th><?php echo lang_get( 'email_summary' ) ?></th>
+	</tr>
+	</thead>
+	<?php
+	if (is_array($t_votes_info) && count($t_votes_info)>0){
+	?>
+	<?php foreach($t_votes_info as $t_vote_info){ ?>
+	<tr bgcolor="<?php echo get_status_color( $t_vote_info['issue']->status )?>">
+		<td>
+			<a href="<?php echo string_get_bug_view_url( $t_vote_info['vote']['issue_id'] );?>"><?php echo bug_format_id( $t_vote_info['vote']['issue_id'] );?></a>
+		</td>
+		<td class="right">
+			<?php echo ($t_vote_info['vote']['weight']>0)?('+'.$t_vote_info['vote']['weight']):$t_vote_info['vote']['weight'] ?>
+		</td>
+		<td class="right">
+			<?php echo $t_vote_info['issue']->votes_num_voters ?>
+		</td>
+		<td class="right">
+			<?php
+			$t_balance = $t_vote_info['issue']->votes_positive - $t_vote_info['issue']->votes_negative;
+			echo ($t_balance>0)?('+'.$t_balance):$t_balance; 
+			?>
+		</td>
+		<td class="center">
+			<?php echo $t_vote_info['project_name']; ?>
+		</td>
+		<td class="center">
+			<?php echo string_attribute( get_enum_element( 'status', $t_vote_info['issue']->status ) ); ?>
+		</td>
+		<td>
+			<?php
+			echo string_display_line( $t_vote_info['issue']->summary );
+			if ( VS_PRIVATE == $t_vote_info['issue']->view_state ) {
+				printf( ' <img src="%s" alt="(%s)" title="%s" />', $t_icon_path . 'protected.gif', lang_get( 'private' ), lang_get( 'private' ) );
+			}
+			?>
+		</td>
+	</tr>
+	<?php } }else{ ?>
+	<tr><td colspan="7" class="center"><?php echo lang_get('no_votes') ?></td></tr>
+	<?php } ?>
+	<tfoot>
+		<tr>
+			<td colspan="2">
+				<?php echo lang_get( 'votes_used' ) ?> = <?php echo vote_used_votes($t_current_user_id) ?>
+			</td>
+			<td colspan="5">
+				<?php echo vote_available_votes($t_current_user_id) ?> <?php echo lang_get( 'votes_remain' ) ?>
+			</td>
+		</tr>
+	</tfoot>
+</table>
+
+<div align="center">
+<?php
+	html_button ( 'account_voting_page.php', 
+		lang_get( ( $t_show_all ? 'voting_hide' : 'voting_show' ) ), 
+		array( 'show_all' => ( $t_show_all ? 0 : 1 ) ) );
+?>
+</div>
+
+<?php html_page_bottom1( __FILE__ ) ?>
\ No newline at end of file

Property changes on: account_voting_page.php
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Index: admin/schema.php
===================================================================
--- admin/schema.php	(revision 5176)
+++ admin/schema.php	(working copy)
@@ -404,5 +404,20 @@
 	" ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_project_version_table' ), "
 	obsolete		L		NOTNULL DEFAULT \" '0' \"" ) );
+
+# first version of voting
+$upgrade[] = Array( 'CreateTableSQL', Array( db_get_table( 'mantis_bug_votes_table' ), "
+	issue_id		I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	user_id			I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	weight			I		NOTNULL DEFAULT '0'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "
+	votes_positive		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_negative		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_num_voters	I		UNSIGNED NOTNULL DEFAULT '0'
+	" ) );
+$upgrade[] = Array('CreateIndexSQL',Array('idx_votes_num_voters',db_get_table('mantis_bug_table'),'votes_num_voters'));
+
+$upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "
     due_date        T       NOTNULL DEFAULT '1970-01-01' " ) );
+
Index: bug_view_advanced_page.php
===================================================================
--- bug_view_advanced_page.php	(revision 5176)
+++ bug_view_advanced_page.php	(working copy)
@@ -577,6 +577,9 @@
 
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+	
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
 
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
Index: bug_view_page.php
===================================================================
--- bug_view_page.php	(revision 5176)
+++ bug_view_page.php	(working copy)
@@ -436,6 +436,9 @@
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
 
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
+	
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
 
Index: bug_vote_add.php
===================================================================
--- bug_vote_add.php	(revision 0)
+++ bug_vote_add.php	(revision 0)
@@ -0,0 +1,23 @@
+<?php
+
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if (config_get( 'voting_place_vote_threshold' ) != true){die('voting disabled');}
+
+$f_bug_id		= gpc_get_int( 'bug_id' );
+$f_weight		= gpc_get_int( 'vote_weight' );
+$t_user_id  = auth_get_current_user_id();
+
+
+access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+if (!vote_exists($f_bug_id, $t_user_id)){
+	vote_add($f_bug_id, $f_weight, $t_user_id);
+	
+}
+print_successful_redirect_to_bug($f_bug_id);
+
+?>
\ No newline at end of file

Property changes on: bug_vote_add.php
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Index: bug_vote_delete.php
===================================================================
--- bug_vote_delete.php	(revision 0)
+++ bug_vote_delete.php	(revision 0)
@@ -0,0 +1,18 @@
+<?php
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if (config_get( 'voting_place_vote_threshold' ) != true){die('voting disabled');}
+
+$f_bug_id		= gpc_get_int( 'bug_id' );
+$t_user_id  = auth_get_current_user_id();
+
+access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+vote_delete($f_bug_id, $t_user_id);
+
+print_successful_redirect_to_bug($f_bug_id);
+
+?>
\ No newline at end of file

Property changes on: bug_vote_delete.php
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Index: bug_vote_list_view_inc.php
===================================================================
--- bug_vote_list_view_inc.php	(revision 0)
+++ bug_vote_list_view_inc.php	(revision 0)
@@ -0,0 +1,157 @@
+<?php
+# This include file prints out the list of users that have voted for the current
+# bug.	$f_bug_id must be set to the bug id
+
+require_once( $t_core_path . 'vote_api.php' );
+require_once( $t_core_path . 'collapse_api.php' );
+
+$t_core_path = config_get( 'core_path' );
+$t_voting_enabled = vote_is_enabled();
+$t_current_user_id = auth_get_current_user_id();
+
+#
+# Determine whether the voting section should be shown.
+#
+
+if ($t_voting_enabled) {
+	
+	$t_votes = vote_get_issue_votes( $f_bug_id );
+
+	$t_votes_exist = count( $t_votes ) > 0;
+	$t_can_view_vote_details = vote_can_view_vote_details($f_bug_id, $t_current_user_id);
+	$t_can_vote = vote_can_vote($f_bug_id, $t_current_user_id);
+
+	$t_show_votes = $t_votes_exist || $t_can_vote;
+	
+	$t_total_positive = bug_get_field( $f_bug_id, 'votes_positive' );
+	$t_total_negative = bug_get_field( $f_bug_id, 'votes_negative' );
+	$t_total_votes = $t_total_positive - $t_total_negative;
+	
+	$t_total_voters = bug_get_field( $f_bug_id, 'votes_num_voters' );
+	
+	$t_button_text = lang_get('vote_cast_button');
+	$t_bug_id = string_attribute( $f_bug_id );
+	
+	$t_voting_weight_options = config_get( 'voting_weight_options' );
+	asort($t_voting_weight_options);
+	$t_voting_weight_default = config_get( 'voting_weight_default' );
+	
+	$t_max_votes = vote_max_votes( $t_current_user_id );
+	$t_used_votes = vote_used_votes( $t_current_user_id );
+	$t_issue_project = bug_get_field( $f_bug_id, 'project_id');
+	$t_available_votes = vote_available_votes( $t_current_user_id, $t_issue_project );
+	$t_voting_max_vote_weight = vote_max_weight( $t_current_user_id, $t_issue_project );
+	
+} else {
+	$t_show_votes = false;
+}
+?>
+<?php if ( $t_show_votes ) { # Voting Box	?>
+
+<a name="votings" id="votings"></a>
+<br />
+
+<?php collapse_open( 'voting' );?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title" colspan="2">
+		<?php collapse_icon( 'voting' ); ?>
+		<?php echo lang_get('voting_this_issue') ?> 
+	</td>
+	</tr>
+
+	<tr class="row-1">
+		<td class="category" width="15%">Vote on issue</td>
+		<td>
+		<?php
+		if ( $t_can_vote ) {
+			if (vote_exists($f_bug_id, $t_current_user_id) ) { #show 'remove my vote' button
+									
+				if (bug_is_resolved($f_bug_id)  )
+				{		
+					echo lang_get('voted_and_resolved');
+				}
+				else if(bug_get_field($f_bug_id,'status') == ASSIGNED)
+				{
+					echo lang_get('voted_and_assigned');
+				}
+				else
+				{
+					html_button( 'bug_vote_delete.php',
+									 lang_get( 'vote_delete_button' ),
+									 array( 'bug_id' => $f_bug_id, 'action' => 'DELETE' ) );
+				}
+			
+			}
+			else {  # show 'add vote' button
+			?>
+				
+			<form method="post" action="bug_vote_add.php">
+			<?php if ($t_available_votes>0){ ?>
+			<input type="submit" class="button" value="<?php echo $t_button_text ;?>" />
+			<select name="vote_weight">
+			<?php 
+			foreach($t_voting_weight_options as $t_option_key => $t_option_value){
+				$t_vote_cost = ($t_option_value>0)?$t_option_value:-$t_option_value; #normalise the weight
+				if ($t_voting_max_vote_weight >= $t_vote_cost && $t_vote_cost != 0){
+			?>
+				<option value="<?php echo $t_option_value?>"<?php echo($t_voting_weight_default==$t_option_value)?' selected':'' ?>><?php echo $t_option_key?></option>
+			<?php }} ?>
+			</select>
+			
+			<? } # available_votes>0 ?>
+			
+			(<?php echo $t_used_votes . '/' . $t_max_votes ?> <?php echo lang_get('votes_used')?>, <?php echo $t_available_votes ?> <?php echo lang_get('votes_remain')?>)
+			<input type="hidden" name="bug_id" value="<?php echo $t_bug_id; ?>" />
+			</form>		
+		<? 
+			} #end vote_exists
+		} #end can_vote 
+		?>
+		</td>
+	</tr>
+	<?php if ( $t_votes_exist ) {	?>
+	<tr>
+	<td class="category" width="15%">Summary</td>
+	<td>
+		<?php echo lang_get('votes_positive') ?> = <?php echo $t_total_positive;?><br>
+		<?php echo lang_get('votes_negative') ?> = <?php echo $t_total_negative;?><br>
+		<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes; ?><br>
+		<?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters; ?>
+		
+	</td>
+	</tr>
+	
+	<?php if ($t_can_view_vote_details){ ?>
+	<tr class="row-2">
+		<td class="category" width="15%">Voters List</td>
+		<td>
+		<?php	foreach($t_votes as $userVote){ ?>
+			<div class="userVote">
+				<?php echo user_get_name($userVote['user_id']) ?> <?php echo ($userVote['weight']>=1)?'+'.$userVote['weight']:$userVote['weight'] ?>
+			</div>
+		<?php } ?>
+		</td>
+	</tr>
+	<?php
+		} #end view_vote_details 
+	} #end votes_exist
+	?>
+</table>
+
+<?php collapse_closed( 'voting' ); ?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title">
+		<?php collapse_icon( 'voting' );	?>
+		<?php echo lang_get('voting_this_issue') ?> <span style="font-weight: normal;">(<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes ?>, <?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters ?>)</span>
+		</td>
+	</tr>
+</table>
+
+<?php	
+	collapse_end( 'voting' );
+} # If voting enabled
+?>

Property changes on: bug_vote_list_view_inc.php
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Index: config_defaults_inc.php
===================================================================
--- config_defaults_inc.php	(revision 5176)
+++ config_defaults_inc.php	(working copy)
@@ -529,22 +529,22 @@
 	# resolution, fixed_in_version, view_state, os, os_build, build (for product build), platform, version, date_submitted, attachment,
 	# category, sponsorship_total, severity, status, last_updated, summary, bugnotes_count, description,
 	# steps_to_reproduce, additional_information
-	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 	
 	# The default columns to be included in the Print Issues Page.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 
 	# The default columns to be included in the CSV export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# The default columns to be included in the Excel export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# --- show projects when in All Projects mode ---
 	$g_show_bug_project_links	= ON;
@@ -1423,6 +1423,7 @@
 	$g_db_table['mantis_config_table']					= '%db_table_prefix%_config%db_table_suffix%';
 	$g_db_table['mantis_database_table']				= '%db_table_prefix%_database%db_table_suffix%';
 	$g_db_table['mantis_email_table']					= '%db_table_prefix%_email%db_table_suffix%';
+	$g_db_table['mantis_bug_votes_table']			= '%db_table_prefix%_bug_votes%db_table_suffix%';
 
 	###########################
 	# Mantis Enum Strings
@@ -1989,6 +1990,36 @@
 
 	# management threshold.
 	$g_manage_plugin_threshold = ADMINISTRATOR;
+	
+	#############################
+	# Voting System
+	#############################
+  
+	# enable or disable the whole voting feature.
+	$g_voting_enabled = ON; 
+	
+	# access level required for users to vote on issues.
+	$g_voting_place_vote_threshold = REPORTER; 
+	
+	# access level required for users to view the users who voted and their votes.
+	$g_voting_view_user_votes_threshold = DEVELOPER;
+	
+	# default number of votes allowed per user
+	$g_voting_default_num_votes = 10; # votes can be set for all user levels as an integer
+	$g_voting_default_num_votes = array( DEVELOPER => 25 , REPORTER => 10 ); # or you can set votes by user type, if a level is not specified then it will use the next lowest level available
+	
+	# default voting weights and thier labels, value needs to be integer, while key is a string eg: '+10 (Highly desired)'
+	$g_voting_weight_options = array('+1'=>1, '+2'=>2, '+5'=>5, '+10'=>10, '-1'=>-1, '-2'=>-2, '-5'=>-5, '-10'=>-10);
+	
+	# the maximum weight a user at a given level may use in a single vote
+	$g_voting_max_vote_weight = 5; #max vote weight can be an integer 
+	$g_voting_max_vote_weight = array( DEVELOPER => 10 , REPORTER => 5 ); # or set by user type, eg: even though a reporter may have 10 votes, they may only use up to weight 5 in a single vote
+	
+	# voting weight that should be initially selected when casting a vote, usually the minimum positive vote
+	$g_voting_weight_default = 1;
+	
+	# whether you get your votes counted per project or globally, if ON then you will get $g_voting_default_num_votes per project, if it is OFF your votes are spread across all projects  
+	$g_voting_per_project = ON;
 
  
  	#############################
Index: core/bug_api.php
===================================================================
--- core/bug_api.php	(revision 5176)
+++ core/bug_api.php	(working copy)
@@ -31,6 +31,7 @@
 	require_once( $t_core_dir . 'sponsorship_api.php' );
 	require_once( $t_core_dir . 'twitter_api.php' );
 	require_once( $t_core_dir . 'tag_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	# MASC RELATIONSHIP
 	require_once( $t_core_dir.'relationship_api.php' );
@@ -67,6 +68,9 @@
 		var $summary = '';
 		var $sponsorship_total = 0;
 		var $sticky = 0;
+		var $votes_positive = 0;
+		var $votes_negative = 0;
+		var $votes_num_voters = 0;
 
 		# omitted:
 		# var $bug_text_id
@@ -791,6 +795,9 @@
 
 		# Delete all sponsorships
 		sponsorship_delete( sponsorship_get_all_ids( $p_bug_id ) );
+		
+		# Delete all votes on this bug
+		vote_delete_issue_votes( $p_bug_id );
 
 		# MASC RELATIONSHIP
 		# we delete relationships even if the feature is currently off.
Index: core/columns_api.php
===================================================================
--- core/columns_api.php	(revision 5176)
+++ core/columns_api.php	(working copy)
@@ -51,6 +51,9 @@
 			'selection',
 			'severity',
 			'sponsorship_total',
+			'votes_positive',
+			'votes_negative',
+			'votes_num_voters',
 			'status',
 			'steps_to_reproduce',        // new
 			'summary',
@@ -822,4 +825,31 @@
 
 		echo '</td>';
 	}
+	
+	
+	function print_column_title_votes_total( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_balance' ), 'votes_total', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_total' );
+		echo '</td>';
+	}
+	
+	function print_column_title_votes_num_voters( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_num_voters' ), 'votes_num_voters', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_num_voters' );
+		echo '</td>';
+	}
+	
+	function print_column_votes_total( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td class="right">';
+		echo (($p_row['votes_total']>0)?'+':'')  . $p_row['votes_total'];
+		echo '</td>';
+	}
+	
+	function print_column_votes_num_voters( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td class="right">';
+		echo $p_row['votes_num_voters'];
+		echo '</td>';
+	}
 ?>
Index: core/constant_inc.php
===================================================================
--- core/constant_inc.php	(revision 5176)
+++ core/constant_inc.php	(working copy)
@@ -170,6 +170,8 @@
 	define( 'TAG_ATTACHED', 				25 );
 	define( 'TAG_DETACHED', 				26 );
 	define( 'TAG_RENAMED', 					27 );
+	define( 'BUGVOTE_ADDED', 				28 );
+	define( 'BUGVOTE_DELETED', 			29 );
 
 	# bug relationship constants
 	define( 'BUG_DUPLICATE',	0 );
@@ -335,6 +337,9 @@
 	# ERROR_COLUMNS_*
 	define ( 'ERROR_COLUMNS_DUPLICATE',	2600 );
 	define ( 'ERROR_COLUMNS_INVALID',	2601 );
+	
+	#ERROR_VOTING_*
+	define( 'ERROR_VOTING_NOT_ENABLED',			2700 );
 
 	# Status Legend Position
 	define( 'STATUS_LEGEND_POSITION_TOP',		1);
Index: core/csv_api.php
===================================================================
--- core/csv_api.php	(revision 5176)
+++ core/csv_api.php	(working copy)
@@ -249,4 +249,16 @@
 	function csv_format_selection( $p_duplicate_id ) {
 		return csv_escape_string( '' );
 	}
+	
+	function csv_format_votes_positive( $p_votes_positive ) {
+		return csv_escape_string( $p_votes_positive );
+	}
+	
+	function csv_format_votes_negative( $p_votes_negative ) {
+		return csv_escape_string( $p_votes_negative );
+	}
+	
+	function csv_format_votes_num_voters( $p_votes_num_voters ) {
+		return csv_escape_string( $p_votes_num_voters );
+	}
 ?>
Index: core/excel_api.php
===================================================================
--- core/excel_api.php	(revision 5176)
+++ core/excel_api.php	(working copy)
@@ -423,4 +423,14 @@
 		// field is not linked to project
 		return excel_prepare_string( '' );
 	}
+	
+	function excel_format_votes_positive( $p_votes_positive ) {
+		return excel_prepare_string( $p_votes_positive );
+	}
+	function excel_format_votes_negative( $p_votes_negative ) {
+		return excel_prepare_string( $p_votes_negative );
+	}
+	function excel_format_votes_num_voters( $p_votes_num_voters ) {
+		return excel_prepare_string( $p_votes_num_voters );
+	}
 ?>
\ No newline at end of file
Index: core/filter_api.php
===================================================================
--- core/filter_api.php	(revision 5176)
+++ core/filter_api.php	(working copy)
@@ -47,6 +47,7 @@
 	define( 'FILTER_PROPERTY_PRODUCT_BUILD', 'show_build' );
 	define( 'FILTER_PROPERTY_PRODUCT_VERSION', 'show_version' );
 	define( 'FILTER_PROPERTY_MONITOR_USER_ID', 'user_monitor' );
+	define( 'FILTER_PROPERTY_VOTES_USER_ID', 'user_votes');
 	define( 'FILTER_PROPERTY_HIDE_STATUS_ID', 'hide_status' );
 	define( 'FILTER_PROPERTY_SORT_FIELD_NAME', 'sort' );
 	define( 'FILTER_PROPERTY_SORT_DIRECTION', 'dir' );
@@ -98,6 +99,7 @@
 	define( 'FILTER_SEARCH_OS', 'os' );
 	define( 'FILTER_SEARCH_OS_BUILD', 'os_build' );
 	define( 'FILTER_SEARCH_MONITOR_USER_ID', 'monitor_user_id' );
+	define( 'FILTER_SEARCH_VOTES_USER_ID' , 'votes_user_id');
 	define( 'FILTER_SEARCH_PRODUCT_BUILD', 'product_build' );
 	define( 'FILTER_SEARCH_PRODUCT_VERSION', 'product_version' );
 	define( 'FILTER_SEARCH_VIEW_STATE_ID', 'view_state_id' );
@@ -203,6 +205,10 @@
 		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
 		}
+		
+		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] ) ) {
+			$t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VOTES_USER_ID, $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] );
+		}
 
 		if ( !filter_str_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
@@ -373,6 +379,7 @@
 			'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 			'hide_status'		=> Array ( '0' => $t_hide_status_default ),
 			'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+			'user_votes'			=> Array ( '0' => META_FILTER_ANY ),
 			'sort'              => 'last_updated',
 			'dir'               => 'DESC',
 			'per_page'			=> config_get( 'default_limit_view' )
@@ -418,6 +425,7 @@
 		$t_bugnote_text_table	= db_get_table( 'mantis_bugnote_text_table' );
 		$t_project_table		= db_get_table( 'mantis_project_table' );
 		$t_bug_monitor_table	= db_get_table( 'mantis_bug_monitor_table' );
+		$t_bug_votes_table 		=	db_get_table( 'mantis_bug_votes_table' );
 		$t_limit_reporters		= config_get( 'limit_reporters' );
 		$t_bug_relationship_table	= db_get_table( 'mantis_bug_relationship_table' );
 		$t_report_bug_threshold		= config_get( 'report_bug_threshold' );
@@ -1136,6 +1144,48 @@
 				array_push( $t_where_clauses, "( $t_table_name.user_id=" . db_param($t_where_param_count++). " )" );
 			}
 		}
+
+		# users voting on an issue
+		$t_select_clauses[] = '(votes_positive - votes_negative) as votes_total'; # @REVIEW is this the correct mantis way to be doing this? votes_total is a derived column
+		$t_any_found = false;
+		if ( count( $t_filter['user_votes'] ) == 0 ) {
+			$t_any_found = true;
+		}
+		else
+		{
+			foreach( $t_filter['user_votes'] as $t_filter_member ) {
+				if ( ( META_FILTER_ANY == $t_filter_member ) || ( 0 === $t_filter_member ) ) {
+					$t_any_found = true;
+				}
+			}
+		}
+		if ( !$t_any_found ) {
+			$t_clauses = array();
+			$t_table_name = 'user_votes';
+			array_push( $t_from_clauses, $t_bug_votes_table );
+			array_push( $t_join_clauses, "LEFT JOIN $t_bug_votes_table $t_table_name ON $t_table_name.issue_id = $t_bug_table.id" );
+
+			foreach( $t_filter['user_votes'] as $t_filter_member ) {
+				$c_user_monitor = db_prepare_int( $t_filter_member );
+				if ( META_FILTER_MYSELF == $c_user_monitor ) {
+					array_push( $t_clauses, $c_user_id );
+				} else {
+					array_push( $t_clauses, $c_user_monitor );
+				}
+			}
+			if ( 1 < count( $t_clauses ) ) {
+				foreach( $t_clauses as $t_clause ) {
+					$t_where_tmp[] = db_param($t_where_param_count++);
+					$t_where_params[] = $t_clause;
+				}
+				array_push( $t_where_clauses, "( $t_table_name.user_id in (". implode( ', ', $t_where_tmp ) .") )" );
+			} else {
+				$t_where_params[] = $t_clauses[0];
+				array_push( $t_where_clauses, "( $t_table_name.user_id=" . db_param($t_where_param_count++). " )" );
+			}
+		}
+		
+		
 		# bug relationship
 		$t_any_found = false;
 		$c_rel_type = $t_filter['relationship_type'];
@@ -1375,7 +1425,7 @@
 						$t_id_join
 						$t_id_where";
 			$t_query_params = array();
-			
+
 			if ( ( $i == 0 ) || ( !is_blank( $t_filter['search'] ) ) ) {
 				if( $i == 0) {
 					$q1 = "SELECT DISTINCT $t_bug_table.id AS id" . $query;
@@ -2485,9 +2535,12 @@
 			<td class="small-caption" valign="top">
 				<a href="<?php PRINT $t_filters_url . 'os_build'; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a>
 			</td>
-			<td class="small-caption" valign="top" colspan="5">
+			<td class="small-caption" valign="top" colspan="4">
 				<a href="<?php PRINT $t_filters_url . 'tag_string'; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a>
 			</td>
+			<td class="small-caption" valign="top">
+				<a href="<?php PRINT $t_filters_url . 'user_votes[]'; ?>" id="user_votes_filter"><?php PRINT lang_get( 'voted_by' ) ?>:</a>
+			</td>
 			<?php if ( $t_filter_cols > 8 ) {
 				echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '">&nbsp;</td>';
 			} ?>
@@ -2508,7 +2561,7 @@
 					print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[FILTER_PROPERTY_OS_BUILD] );
 				?>
 			</td>
-			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5">
+			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="4">
 				<?php 
 					$t_tag_string = $t_filter['tag_string'];
 					if ( $t_filter['tag_select'] != 0 ) {
@@ -2519,6 +2572,45 @@
 				?>
 				<input type="hidden" name="tag_string" value="<?php echo $t_tag_string ?>"/>
 			</td>
+			<td class="small-caption" valign="top" id="user_votes_filter_target">
+							<?php
+								$t_output = '';
+								$t_any_found = false;
+								if ( count( $t_filter['user_votes'] ) == 0 ) {
+									PRINT lang_get( 'any' );
+								} else {
+									$t_first_flag = true;
+									foreach( $t_filter['user_votes'] as $t_current ) {
+										?>
+										<input type="hidden" name="user_votes[]" value="<?php echo $t_current;?>" />
+										<?php
+										$t_this_name = '';
+										if ( ( $t_current === 0 ) || ( is_blank( $t_current ) ) || ( META_FILTER_ANY == $t_current ) ) {
+											$t_any_found = true;
+										} else if ( META_FILTER_MYSELF == $t_current ) {
+											if ( access_has_project_level( config_get( 'monitor_bug_threshold' ) ) ) {
+												$t_this_name = '[' . lang_get( 'myself' ) . ']';
+											} else {
+												$t_any_found = true;
+											}
+										} else {
+											$t_this_name = user_get_name( $t_current );
+										}
+										if ( $t_first_flag != true ) {
+											$t_output = $t_output . '<br />';
+										} else {
+											$t_first_flag = false;
+										}
+										$t_output = $t_output . $t_this_name;
+									}
+									if ( true == $t_any_found ) {
+										PRINT lang_get( 'any' );
+									} else {
+										PRINT $t_output;
+									}
+								}
+							?>
+			</td>
 		</tr>
 		<?php
 
@@ -3309,6 +3401,7 @@
 									  'fixed_in_version' => 'string',
 									  'target_version' => 'string',
 									  'user_monitor' => 'int',
+										'user_votes' 	=> 'int',
 									  'show_profile' => 'int'
 									 );
 		foreach( $t_multi_select_list as $t_multi_field_name => $t_multi_field_type ) {
@@ -3441,6 +3534,24 @@
 		</select>
 		<?php
 	}
+	
+	function print_filter_user_votes(){
+		global $t_select_modifier, $t_filter;
+		?>
+		<!-- Voted by -->
+		<select <?php PRINT $t_select_modifier;?> name="user_votes[]">
+			<option value="<?php echo META_FILTER_ANY ?>" <?php check_selected( $t_filter['user_votes'], META_FILTER_ANY ); ?>>[<?php echo lang_get( 'any' ) ?>]</option>
+			<?php
+				if ( access_has_project_level( config_get( 'monitor_bug_threshold' ) ) ) {
+					PRINT '<option value="' . META_FILTER_MYSELF . '" ';
+					check_selected( $t_filter['user_votes'], META_FILTER_MYSELF );
+					PRINT '>[' . lang_get( 'myself' ) . ']</option>';
+				}
+			?>
+			<?php print_reporter_option_list( $t_filter['user_votes'] ) ?>
+		</select>
+		<?php
+	}
 
 	function print_filter_handler_id(){
 		global $t_select_modifier, $t_filter, $f_view_type;
Index: core/helper_api.php
===================================================================
--- core/helper_api.php	(revision 5176)
+++ core/helper_api.php	(working copy)
@@ -369,6 +369,13 @@
 		if ( OFF == $t_enable_sponsorship ) {
 			$t_keys_to_remove[] = 'sponsorship_total';
 		}
+		
+		$t_enable_voting = config_get( 'voting_enabled' );
+		if ($t_enable_voting == OFF)
+		{
+			$t_keys_to_remove[] = 'votes_total';
+			$t_keys_to_remove[] = 'votes_num_voters';
+		}
 
 		if ( $p_columns_target == COLUMNS_TARGET_CSV_PAGE || $p_columns_target == COLUMNS_TARGET_EXCEL_PAGE ||
 			 OFF == config_get( 'show_attachment_indicator' ) ) {
Index: core/history_api.php
===================================================================
--- core/history_api.php	(revision 5176)
+++ core/history_api.php	(working copy)
@@ -427,6 +427,12 @@
 					$t_note = lang_get( 'tag_history_renamed' );
 					$t_change = $p_old_value . ' => ' . $p_new_value;
 					break;
+				case BUGVOTE_ADDED:
+					$t_note = lang_get( 'bugvote_added' ) . ": " . $p_old_value;
+					break;
+				case BUGVOTE_DELETED:
+					$t_note = lang_get( 'bugvote_deleted' ) . ": " . $p_old_value;
+					break;
 			}
 		}
 
Index: core/html_api.php
===================================================================
--- core/html_api.php	(revision 5176)
+++ core/html_api.php	(working copy)
@@ -70,6 +70,7 @@
 	require_once( $t_core_dir . 'user_api.php' );
 	require_once( $t_core_dir . 'rss_api.php' );
 	require_once( $t_core_dir . 'wiki_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	$g_rss_feed_url = null;
 
@@ -842,6 +843,7 @@
 		$t_account_prefs_page 			= 'account_prefs_page.php';
 		$t_account_profile_menu_page 	= 'account_prof_menu_page.php';
 		$t_account_sponsor_page			= 'account_sponsor_page.php';
+		$t_account_voting_page		= 'account_voting_page.php';
 		$t_account_manage_columns_page	= 'account_manage_columns_page.php';
 
 		switch ( $p_page ) {
@@ -857,6 +859,9 @@
 			case $t_account_sponsor_page:
 				$t_account_sponsor_page = '';
 				break;
+			case $t_account_voting_page:
+				$t_account_voting_page = '';
+				break;
 			case $t_account_manage_columns_page:
 				$t_account_manage_columns_page = '';
 				break;
@@ -875,6 +880,13 @@
 			 !current_user_is_anonymous() ) {
 			print_bracket_link( helper_mantis_url( $t_account_sponsor_page ), lang_get( 'my_sponsorship' ) );
 		}
+		
+		if ( ( config_get( 'voting_enabled' ) == ON ) &&
+			 ( access_has_project_level( config_get( 'voting_place_vote_threshold' ) ) ) &&
+			 !current_user_is_anonymous() ) {
+			print_bracket_link( helper_mantis_url( $t_account_voting_page ), lang_get( 'my_votes' ) );
+		}
+		
 	}
 
 	# --------------------
@@ -1277,7 +1289,7 @@
 		echo '<td class="center">';
 		html_button_bug_change_status( $p_bug_id );
 		echo '</td>';
-
+		
 		# MONITOR/UNMONITOR button
 		echo '<td class="center">';
 		if ( !current_user_is_anonymous() ) {
Index: core/my_view_inc.php
===================================================================
--- core/my_view_inc.php	(revision 5176)
+++ core/my_view_inc.php	(working copy)
@@ -59,7 +59,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_bug_resolved_status_threshold ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['assigned'] = 'handler_id=' . $t_current_user_id . '&amp;hide_status=' . $t_bug_resolved_status_threshold;
 
@@ -74,7 +75,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => META_FILTER_NONE ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['recent_mod'] = 'hide_status=none';
 
@@ -90,7 +92,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['reported'] = 'reporter_id=' . $t_current_user_id . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -105,7 +108,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['resolved'] = 'show_status=' . $t_bug_resolved_status_threshold . '&amp;hide_status=' . $t_bug_resolved_status_threshold;
 
@@ -120,7 +124,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['unassigned'] = 'handler_id=[none]' . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -135,7 +140,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => $t_current_user_id )
+		'user_monitor'		=> Array ( '0' => $t_current_user_id ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['monitored'] = 'user_monitor=' . $t_current_user_id . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -151,7 +157,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['feedback'] = 'reporter_id=' . $t_current_user_id . '&amp;show_status=' . FEEDBACK . '&amp;hide_status=' . $t_hide_status_default;
 
@@ -166,7 +173,8 @@
 		'show_build'		=> Array ( '0' => META_FILTER_ANY ),
 		'show_version'		=> Array ( '0' => META_FILTER_ANY ),
 		'hide_status'		=> Array ( '0' => $t_hide_status_default ),
-		'user_monitor'		=> Array ( '0' => META_FILTER_ANY )
+		'user_monitor'		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['verify'] = 'reporter_id=' . $t_current_user_id . '&amp;show_status=' . $t_bug_resolved_status_threshold;
 
Index: core/user_api.php
===================================================================
--- core/user_api.php	(revision 5176)
+++ core/user_api.php	(working copy)
@@ -25,6 +25,7 @@
 
 	require_once( $t_core_dir . 'email_api.php' );
 	require_once( $t_core_dir . 'ldap_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	### User API ###
 
@@ -551,6 +552,9 @@
 		$t_user_table = db_get_table('mantis_user_table');
 
 		user_ensure_unprotected( $p_user_id );
+		
+		# Remove any votes the user has made on issues
+		vote_delete_user_votes( $p_user_id );
 
 		# Remove associated profiles
 		user_delete_profiles( $p_user_id );
@@ -1077,7 +1081,6 @@
 		}
 
 		$t_filter = unserialize( $t_cookie_detail[1] );
-
 		$t_filter = filter_ensure_valid_filter( $t_filter );
 
 		return $t_filter;
Index: core/vote_api.php
===================================================================
--- core/vote_api.php	(revision 0)
+++ core/vote_api.php	(revision 0)
@@ -0,0 +1,350 @@
+<?php
+$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
+require_once( $t_core_dir . 'current_user_api.php' );
+require_once( $t_core_dir . 'history_api.php' );
+require_once( $t_core_dir . 'bug_api.php' );
+require_once( $t_core_dir . 'user_api.php' );
+
+
+/**
+ * vote_add
+ * 
+ * @param int $p_issue_id issue primary key
+ * @param int $p_weight impact of vote
+ * @param int $p_user_id user primary key
+ * @return bool
+ */
+function vote_add( $p_issue_id, $p_weight, $p_user_id = null )
+{
+	$t_issue_project = bug_get_field($p_issue_id, 'project_id');
+	$t_vote_max_weight = vote_max_weight( $p_user_id, $t_issue_project );
+	
+	if ( $p_weight > $t_vote_max_weight || $p_weight == 0 )
+	{
+		return false; # not allowed to vote more than your limit, or have a vote with weight 0
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+
+	$query = "INSERT INTO $t_mantis_bug_votes_table
+		          		( issue_id, user_id, weight )
+		          	 VALUES
+		          		( " . db_param(0) . ", " . db_param(1) . ", " . db_param(2) . " )";
+	db_query_bound( $query, Array( (int)$p_issue_id, (int)$p_user_id, (int)$p_weight ) );
+	
+	# get bugvote id
+	$t_bugvote_id = db_insert_id( $t_mantis_bug_votes_table );
+	
+	
+	if ($p_weight >= 1) {
+		$t_existing_positive_votes = bug_get_field($p_issue_id,'votes_positive');
+		bug_set_field($p_issue_id, 'votes_positive', $t_existing_positive_votes + $p_weight );
+	}
+	else if ($p_weight <= -1) {
+		$t_existing_negative_votes = bug_get_field($p_issue_id,'votes_negative');
+		bug_set_field($p_issue_id, 'votes_negative', $t_existing_negative_votes - $p_weight);
+	}
+	$t_existing_num_voters = bug_get_field($p_issue_id, 'votes_num_voters');
+	bug_set_field($p_issue_id, 'votes_num_voters', $t_existing_num_voters + 1);
+	
+	# log vote history
+	$t_weight_log = ($p_weight>=1)?('+'.$p_weight):$p_weight;
+	history_log_event_special( $p_issue_id, BUGVOTE_ADDED, $t_weight_log );
+	bug_update_date($p_issue_id);
+	
+	return true;
+}
+
+/**
+ * vote_delete
+ * should only delete your vote if the bug has not been closed or resolved
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return null
+ */
+function vote_delete( $p_issue_id, $p_user_id )
+{
+	if ($p_issue_id < 1 || $p_user_id < 1){return;}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	# decrement vote counts from bugs table, get weight used for vote so we can remove the correct weighting from the summary
+	$query = "SELECT weight FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0) . " AND user_id = " . db_param(1);
+	$result	= db_query_bound( $query, Array( $p_issue_id, $p_user_id ) );
+	$t_weight = $result->fields['weight'];
+
+	if ($t_weight >= 1) {
+		$t_existing_positive_votes = bug_get_field( $p_issue_id , 'votes_positive' );
+		bug_set_field( $p_issue_id , 'votes_positive' , $t_existing_positive_votes - $t_weight );
+	}
+	else if ($t_weight <= -1) {
+		$t_existing_negative_votes = bug_get_field( $p_issue_id , 'votes_negative' );
+		bug_set_field($p_issue_id, 'votes_negative', $t_existing_negative_votes + $t_weight );
+	}
+	$t_existing_num_voters = bug_get_field($p_issue_id, 'votes_num_voters');
+	bug_set_field($p_issue_id, 'votes_num_voters', $t_existing_num_voters - 1);
+	
+	# now remove all votes from voting table
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0) . " AND user_id = " . db_param(1);
+	db_query_bound($query, Array( (int)$p_issue_id, (int)$p_user_id ));
+	
+	# log vote history
+	$t_weight_log = ($t_weight>=1)?('+'.$t_weight):$t_weight;
+	history_log_event_special( $p_issue_id, BUGVOTE_DELETED, $t_weight_log );
+	bug_update_date($p_issue_id);
+}
+
+/**
+ * vote_delete_issue_votes
+ * Deleting an issue should delete all associated votes.
+ *
+ * @param int $p_issue_id issue primary key
+ * @return null
+ */
+function vote_delete_issue_votes( $p_issue_id )
+{
+	if ($p_issue_id < 1){return;}
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	db_query_bound($query, Array( (int)$p_issue_id ));
+}
+
+/**
+ * vote_delete_user_votes
+ * Deleting a user should delete all associated votes.
+ *
+ * @param int $p_user_id user primary key
+ * @return null
+ */
+function vote_delete_user_votes( $p_user_id )
+{
+	if ($p_user_id < 1){return;}
+	$votes = vote_get_user_votes( $p_user_id );
+	foreach($votes as $vote)
+	{
+		vote_delete($vote['issue_id'], $p_user_id);
+	}
+}
+
+/**
+ * vote_get_user_votes
+ *
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return array issues and thier weight array('issue_id'=>$p_issue_id, 'weight'=>$p_weight); 
+ */
+function vote_get_user_votes ( $p_user_id, $p_project_id = null, $p_include_resolved = true )
+{
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	$query = "SELECT issue_id, weight FROM $t_mantis_bug_votes_table WHERE user_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_user_id));
+
+	$users = array();
+	while ( $row = db_fetch_array( $result ) ) {
+
+		$t_resolved = bug_is_resolved($row['issue_id']);
+		
+		if ($p_include_resolved || !$t_resolved )
+		{
+			if ( $p_project_id === null )
+			{
+				$users[] = $row;
+			}
+			else
+			{
+				$t_issue_project = bug_get_field($row['issue_id'], 'project_id');
+				if ( $t_issue_project == $p_project_id )
+				{
+					$users[] = $row;
+				}
+			}
+		}
+	}
+	return $users;
+}
+
+/**
+ * vote_get_issue_votes
+ * returns an array of user ids, weight
+ * 
+ * @param int $p_issue_id issue primary key
+ * @return array users and thier vote weight array('user_id'=>$p_user_id, 'weight'=>$p_weight); 
+ */
+function vote_get_issue_votes( $p_issue_id )
+{
+	if ($p_issue_id < 1){return;}
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "SELECT user_id, weight FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_issue_id));
+	$t_issue_votes = array();
+	while ( $row = db_fetch_array( $result ) ) {
+		$t_issue_votes[] = $row;
+	}
+	return $t_issue_votes;
+}
+
+/**
+ * vote_is_enabled
+ *
+ * @param int $p_project_id
+ * @return bool
+ */
+function vote_is_enabled( $p_project_id = ALL_PROJECTS )
+{
+	$t_enabled = ( config_get( 'voting_enabled', null, null, $p_project_id ) == ON );
+	return $t_enabled;
+}
+
+/**
+ * vote_can_vote
+ * whether or not the user is allowed to vote on an issue
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_can_vote( $p_issue_id, $p_user_id = null )
+{
+	$t_can_vote = ( !bug_is_readonly( $p_issue_id ) &&  access_has_bug_level( config_get( 'voting_place_vote_threshold' ),$p_issue_id , $p_user_id ) ); 
+	return $t_can_vote;
+}
+
+/**
+ * vote_can_view_vote_details
+ * whether or not the user is allowed to view vote details
+ *
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_can_view_vote_details( $p_issue_id, $p_user_id = null )
+{
+	$t_has_level = ( access_has_bug_level( config_get( 'voting_view_user_votes_threshold' ), $p_issue_id , $p_user_id ) );
+	return $t_has_level;
+}
+
+/**
+ * vote_exists
+ * whether the user has placed a vote on a given issue or not
+ * @param int $p_issue_id
+ * @param int $p_user_id
+ * @return bool
+ */
+function vote_exists ( $p_issue_id, $p_user_id)
+{
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query 	= "SELECT COUNT(*)
+		          	FROM $t_mantis_bug_votes_table
+		          	WHERE issue_id=" . db_param(0) . " AND user_id = " . db_param(1);
+		$result	= db_query_bound( $query, Array( $p_issue_id, $p_user_id ) );
+
+		if ( 0 == db_result( $result ) ) {
+			return false;
+		} else {
+			return true;
+		}
+}
+
+/**
+ * vote_max_votes
+ * the maximum number of votes the given user can cast across all issues
+ * @param int $p_user_id
+ * @return int
+ */
+function vote_max_votes( $p_user_id )
+{
+	$t_default_num_votes = config_get('voting_default_num_votes',10); #defaults to 10 votes
+	
+	if (is_array($t_default_num_votes))
+	{
+		ksort($t_default_num_votes); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		foreach($t_default_num_votes as $t_vote_level => $t_votes)
+		{
+			if ($t_user_level >= $t_vote_level)
+			{
+				$t_default_num_votes = $t_votes;
+			}
+		}
+	}
+	return $t_default_num_votes;
+}
+
+/**
+ * vote_available_votes
+ * the number of available votes a user can still cast
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return int
+ */
+function vote_available_votes( $p_user_id, $p_project_id = null )
+{
+	return (vote_max_votes( $p_user_id )) - (vote_used_votes( $p_user_id, $p_project_id ));
+}
+
+/**
+ * vote_used_votes
+ * the number of votes already cast on a given project that are not resolved
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return int
+ */
+function vote_used_votes( $p_user_id, $p_project_id = null )
+{
+	$t_per_project = config_get('voting_per_project', ON);
+	if ($t_per_project == ON)
+	{
+		$t_votes = vote_get_user_votes( $p_user_id, $p_project_id, false );	
+	}
+	else
+	{
+		$t_votes = vote_get_user_votes( $p_user_id, null, false );
+	}
+	
+	$t_weight_used = 0;
+	foreach($t_votes as $t_vote)
+	{
+		if ($t_vote['weight']>0)
+		{
+			$t_weight_used += $t_vote['weight'];
+		}
+		else
+		{
+			$t_weight_used -= $t_vote['weight'];
+		}
+	}
+	return $t_weight_used;
+}
+
+/**
+ * vote_max_weight
+ * the maximum weight a user can cast on a single vote right now - note this is different from vote_available_votes
+ * takes in to consideration how many votes a user has remaining
+ * returning whichever is the lesser, your available votes or you max vote weight
+ * @param int $p_user_id
+ * @param int $p_project_id
+ * @return int
+ */
+function vote_max_weight( $p_user_id, $p_project_id )
+{
+	$t_available_votes = vote_available_votes( $p_user_id, $p_project_id );
+	$t_voting_max_vote_weight = config_get('voting_max_vote_weight', 5);
+	if (is_array($t_voting_max_vote_weight))
+	{
+		ksort($t_voting_max_vote_weight); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		foreach($t_voting_max_vote_weight as $t_max)
+		{
+			if ($t_user_level >= $t_max)
+			{
+				$t_voting_max_vote_weight = $t_max;
+			}
+		}
+	}
+	# return whichever is the lesser, your available votes or you max vote weight
+	$t_voting_max_vote_weight = ($t_available_votes > $t_voting_max_vote_weight)?$t_voting_max_vote_weight:$t_available_votes;
+	return $t_voting_max_vote_weight;
+}
+?>
\ No newline at end of file

Property changes on: core\vote_api.php
___________________________________________________________________
Name: svn:keywords
   + Author Date Id Revision
Name: svn:eol-style
   + native

Index: css/default.css
===================================================================
--- css/default.css	(revision 5176)
+++ css/default.css	(working copy)
@@ -163,3 +163,28 @@
 
 .progress400				{ position: relative; width: 400px; border: 1px solid #d7d7d7; margin-top: 1em; margin-bottom: 1em; padding: 1px; }
 .progress400 .bar			{ display: block; position: relative; background: #6bba70; text-align: center; font-weight: normal; color: #333; height: 2em; line-height: 2em; }
+
+table.bugList
+{
+	width: 100%; border: solid 1px #000;
+	margin:0;
+	margin-bottom: 16px;
+	caption-side: top;
+	
+}
+table.bugList caption
+{
+	font-weight:bold; 
+	font-style:italic;
+	text-align:left; 
+	border: 1px solid #000; 
+	border-bottom:0; 
+	padding: 4px; 
+	margin-top:16px;
+}
+table.bugList tfoot tr td
+{
+	background-color: #ccc;
+	text-align: right;
+}
+
Index: lang/strings_english.txt
===================================================================
--- lang/strings_english.txt	(revision 5176)
+++ lang/strings_english.txt	(working copy)
@@ -637,6 +637,7 @@
 
 # bug_vote_add.php
 $s_vote_added_msg = 'Vote has been added...';
+$s_vote_removed_msg = 'Vote has been removed...';
 
 # bugnote_add.php
 $s_bugnote_added_msg = 'Note added...';
@@ -1513,6 +1514,29 @@
 $s_copy_columns_from = 'Copy Columns From';
 $s_copy_columns_to = 'Copy Columns To';
 
+# Voting
+$s_vote_cast_button = 'Cast Vote:';
+$s_vote_delete_button = 'Delete My Vote';
+$s_bugvote_added = 'Vote Added';
+$s_bugvote_deleted = 'Vote Deleted';
+$s_votes_positive = 'Votes Positive';
+$s_votes_negative = 'Votes Negative';
+$s_voted_by = 'Voted By';
+$s_vote_balance = 'Vote Balance';
+$s_vote_num_voters = '# Voters';
+$s_votes_remain = 'votes remaining';
+$s_votes_used = 'votes used';
+$s_voting_this_issue = 'Users voting for this issue';
+$s_votes_num_voters = 'Number of Voters';
+$s_my_votes = 'My Votes';
+$s_own_voted = 'Issues you have voted for:';
+$s_voting_hide = 'Hide Resolved';
+$s_voting_show = 'Show All';
+$s_vote_weight = 'Vote Weight';
+$s_no_votes = 'No votes available';
+$s_voted_and_assigned = 'You voted for this issue and it is now being worked on. You will be able to reuse the voting credits you spent on this issue once it is resolved.';
+$s_voted_and_resolved = 'You voted for this issue, and it is now resolved. Your voting credits have been returned to you.';
+
 # due date
 $s_due_date = "Due Date";
 $s_overdue = "Overdue";
Index: view_all_set.php
===================================================================
--- view_all_set.php	(revision 5176)
+++ view_all_set.php	(working copy)
@@ -190,6 +190,14 @@
 		$f_user_monitor = gpc_get_string( 'user_monitor', META_FILTER_ANY );
 		$f_user_monitor = array( $f_user_monitor );
 	}
+	
+	$f_user_votes = array();
+	if ( is_array( gpc_get( 'user_votes', null ) ) ) {
+		$f_user_votes = gpc_get_string_array( 'user_votes', META_FILTER_ANY );
+	} else {
+		$f_user_votes = gpc_get_string( 'user_votes', META_FILTER_ANY );
+		$f_user_votes = array( $f_user_votes );
+	}
 
 	# these are only single values, even when doing advanced filtering
 	$f_per_page				= gpc_get_int( 'per_page', -1 );
@@ -420,6 +428,7 @@
 				$t_setting_arr['target_version'] = $f_target_version;
 				$t_setting_arr['show_priority'] = $f_show_priority;
 				$t_setting_arr['user_monitor'] = $f_user_monitor;
+				$t_setting_arr['user_votes'] = $f_user_votes;
 				$t_setting_arr['view_state'] = $f_view_state;
 				$t_setting_arr['custom_fields'] = $f_custom_fields_data;
 				$t_setting_arr['sticky_issues'] = $f_sticky_issues;
@@ -472,6 +481,7 @@
 				$t_setting_arr['fixed_in_version']	= array( META_FILTER_ANY );
 				$t_setting_arr['target_version']	= array( META_FILTER_ANY );
 				$t_setting_arr['user_monitor'] 		= array( META_FILTER_ANY );
+				$t_setting_arr['user_votes'] 		= array( META_FILTER_ANY );
 				$t_setting_arr['relationship_type'] = -1;
 				$t_setting_arr['relationship_bug'] = 0;
 
voting for r5176.patch (55,841 bytes)
cornchips

cornchips

2008-04-20 06:08

reporter   ~0017628

I've resubmitted the patch for r5176. I also converted all files i have modified/added to unix line endings, however tortoise svn seems to make the patch itself include windows line endings. Hopefully you will have better success this time.

Also this version corrects a few bugs, and adds a 'my votes' page similar to my sponsorships.

@c_schmitz - No i'm not a Mantis team developer, just someone who has used Mantis for a while and felt like contributing something back to the project. Please direct all compensation to the regular team as they have done the hard work to make a bug tracker that rocks!

c_schmitz

c_schmitz

2008-04-23 12:22

reporter   ~0017664

Thank you, cornchips. Your patch is very much appreciated. Will do!

ViperMaul

ViperMaul

2008-05-13 12:58

reporter   ~0017821

Dear vbdoctor or Mantis Developers,
With all the great work by cornchips, what is the status of this feature being included in the next dev or stable release?

c_schmitz

c_schmitz

2008-05-16 08:27

reporter   ~0017852

I second that question....?

giallu

giallu

2008-05-16 10:06

reporter   ~0017853

victor is in vacation right now; I think he will be back this weekend so hopefully he can have a look at it.

FWIW, I think if it just barely works, it would be better to merge it now in trunk rather than waiting more, so we can have more testing when 1.2a2 is released

c_schmitz

c_schmitz

2008-05-23 15:27

reporter   ~0017909

?

ViperMaul

ViperMaul

2008-05-29 00:19

reporter   ~0017933

vboctor, What are the chances this will make it into 1.2.0a2?

vboctor

vboctor

2008-05-29 02:39

manager   ~0017938

I've set the target version to 1.2.0a2. I'll review the patch and try to re-attempt to apply it.

vboctor

vboctor

2008-05-31 02:19

manager   ~0017968

cornchips, looks great. Here is a partial review. I wasn't able to apply the patch, I'll need another one based on the latest code. I'll review the functionality once I can apply and test the patch.

General:

  1. Add standard Mantis header to all new files.

account_voting_page.php:

  1. You get $t_project_id, but don't use for filtering. You have a TODO to track this. However, if you aren't using, then no need to get it.
  2. vote_get_user_votes(), with APIs that take user ids, the user id is typically the last parameter and is defaulted to null which means currently logged in user.
  3. $t_show_all should be renamed to $f_showall since all variables set by gpc* should be prefixed with $f_.

schema.php:

  1. Should the default weight for a vote be 1?

bug_vote_add.php:

  1. voting_place_votethreshold: when accessing a configuration option more than once, assigned it to $t var and re-use the var.
  2. I don't think the first check on the configuration option is correct. Use vote_is_enabled().
  3. We don't use 1 line if statements. Please use the standard multi-line "if" format.

bug_vote_delete.php

  1. See comments on bug_vote_add.php
  2. I wonder if we should have a separate config option for removing a vote. For example, if the admin wants to configure Mantis so that users can't change their votes one they are cast.

bug_vote_list_view_inc.php

  1. You are using $t_core_path without initializing it. This can be a security issue.

config_defaults_inc.php

  1. Add a 0 for unlimited support in the default number of votes configuration option.

helper_api.php

  1. Shouldn't you also remove the voters positive and voters negative. Although these are not added by default, but they can be in the configuration and we should remove them when applicable.

vote_api.php

  1. If the vote weight is 0 or greater than max weight, then generate an error rather than returning without an error. What you are doing is correct, but is not consistent with the rest of the APIs. In the 1.2.x branch we will eventually replace these trigger_error calls with throwing exceptions.
  2. The way you update the votes count can be an issue if more than one user are voting on the same issue at the same time. I would suggest that you update the counts based on the votes table, rather than incrementing / decrementing. I would add a votes_updates_counters_for_issue( $p_issue_id )
  3. You have multiple bug_set_field(), it would be faster to have a single update for the issue.
  4. In the vote_delete(), remove the row then call votes_update_counters_for_issue().
  5. vote_delete_user_votes() doesn't update the issue history. Not sure if this is done to other features. If we do update the history, then we should update the last updated timestamp for these issues.
  6. vote_get_user_votes() should include or exclude the resolved issues using the query. This will save loading this issue in most of the cases where resolved issues are excluded. You can use the resolve issue threshold configuration option or even have another configuration option for status threshold to exclude from voting which is by default is set to resolved status threshold configuration option.
  7. voting_is_enabled() should default the parameter to currently project, rather than all projects. If the current project has voting disabled, then we should treat it as so.
  8. vote_can_vote() shouldn't depend on readonly flag being not set. Voting on an issue doesn't really modify the issue. It is like monitoring the issue.
  9. vote_max_votes() shouldn't default the config option if not found. The default should be in config_defaults_inc.php
  10. vote_used_votes() shouldn't default configuration option here.

... more to come ...

cornchips

cornchips

2008-05-31 23:02

reporter   ~0017976

I'll have time to begin to review the list and start implementing changes on the 8th of June.

ViperMaul

ViperMaul

2008-06-17 00:17

reporter   ~0018112

Thanks vboctor and cornchips!!
We appreciate your efforts.
Looking forward to any status updates.

c_schmitz

c_schmitz

2008-06-17 20:35

reporter   ~0018125

If this makes it into 1.2.0a2 I will add 50$ on top.

vboctor

vboctor

2008-06-23 01:47

manager   ~0018158

@cornchips, there seems to be a lot of votes to get this votes feature in! How are you doing with the comments that came out of the review?

mgeck

mgeck

2008-06-27 07:35

reporter   ~0018199

Thanks for your effort. This would be a great feature.

c_schmitz

c_schmitz

2008-07-22 13:09

reporter   ~0018635

@cornchips: any news?

cornchips

cornchips

2008-07-23 05:45

reporter   ~0018644

Hey, i've just recently returned from a holiday, will get those changes made this weekend and update the patch for the current mantis revision this weekend.

cornchips

cornchips

2008-07-27 04:23

reporter   ~0018676

OK, I've managed to get through about 70% of the items on the code review, It took longer than expected to bring my changes up to date through the almost 250 revisions in svn since the original patch. Will try and get the last items done tomorrow.

c_schmitz

c_schmitz

2008-07-27 13:40

reporter   ~0018679

Sounds great :-). Looking forward!

cornchips

cornchips

2008-07-28 07:07

reporter   ~0018702

Last edited: 2008-07-28 07:08

@vboctor, quick question, vote_api.php -3 , using multiple bug_set_field() calls, what alternative call should I use, i didn't want to create my own sql update since that would break the bug api encapsulation.

also vote_api.php -5, vote_delete_user_votes() intentionally doesnt update the issue history since it is only supposed to be called post issue delete, as a way to clean up a related table.

I've now completed all other updates, and thank you very much for your feedback, will post the next patch once the last couple of items are done

giallu

giallu

2008-07-28 11:04

reporter   ~0018720

for -3 I guess you can use bug_update() after you modify the required fields in the BugData object.

for -5 then the function name is somewhat deceptive; maybe something like vote_restore_user_votes() could be better, but be sure to add some documentation to the function so it's harder to get fooled.

2008-08-06 03:37

 

voting for r5500.patch (67,322 bytes)
Index: account_voting_page.php
===================================================================
--- account_voting_page.php	(revision 0)
+++ account_voting_page.php	(revision 0)
@@ -0,0 +1,156 @@
+<?php
+# Mantis - a php based bugtracking system
+
+# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+require_once( $t_core_path.'project_api.php' );
+
+if ( current_user_is_anonymous() ) {
+	access_denied();
+}
+
+$t_current_user_id = auth_get_current_user_id();
+$t_resolved = config_get( 'bug_resolved_status_threshold' );
+$f_show_all = gpc_get_bool( 'show_all', false );
+
+# start the page
+html_page_top1( lang_get( 'my_votes' ) );
+html_page_top2();
+
+$t_votes = vote_get_user_votes();
+
+# get all information for issues ready for display to user
+$t_votes_info = array();
+foreach($t_votes as $t_vote)
+{
+	$t_issue = bug_get($t_vote['issue_id']);
+	if ( ($t_issue->status < $t_resolved) || $f_show_all )
+	{
+		$t_project_name = project_get_name($t_issue->project_id);
+		$t_votes_info[] = array('vote'=>$t_vote, 'issue'=>$t_issue, 'project_name'=>$t_project_name);
+	}
+}
+
+?>
+
+<br />
+<table class="width100" cellspacing="1">
+<tr>
+	<td class="form-title">
+		<?php echo lang_get( 'my_votes' ) ?>
+	</td>
+	<td class="right">
+		<?php print_account_menu( 'account_voting_page.php' ) ?>
+	</td>
+</tr>
+</table>
+
+
+
+<table class="bugList">
+	<caption>
+		<?php echo lang_get( 'own_voted' ) ?>
+	</caption>
+	<thead>
+	<tr>
+		<th><?php echo lang_get( 'email_bug' ) ?></th>
+		<th><?php echo lang_get( 'vote_weight' ) ?></th>
+		<th><?php echo lang_get( 'vote_num_voters' ) ?></th>
+		<th><?php echo lang_get( 'vote_balance' ) ?></th>
+		<th><?php echo lang_get( 'email_project' ) ?></th>
+		<th><?php echo lang_get( 'email_status' ) ?></th>
+		<th><?php echo lang_get( 'email_summary' ) ?></th>
+	</tr>
+	</thead>
+	<?php
+	if (is_array($t_votes_info) && count($t_votes_info)>0){
+	?>
+	<?php foreach($t_votes_info as $t_vote_info){ ?>
+	<tr bgcolor="<?php echo get_status_color( $t_vote_info['issue']->status )?>">
+		<td>
+			<a href="<?php echo string_get_bug_view_url( $t_vote_info['vote']['issue_id'] );?>"><?php echo bug_format_id( $t_vote_info['vote']['issue_id'] );?></a>
+		</td>
+		<td class="right">
+			<?php echo ($t_vote_info['vote']['weight']>0)?('+'.$t_vote_info['vote']['weight']):$t_vote_info['vote']['weight'] ?>
+		</td>
+		<td class="right">
+			<?php echo $t_vote_info['issue']->votes_num_voters ?>
+		</td>
+		<td class="right">
+			<?php
+			$t_balance = $t_vote_info['issue']->votes_positive - $t_vote_info['issue']->votes_negative;
+			echo ($t_balance>0)?('+'.$t_balance):$t_balance; 
+			?>
+		</td>
+		<td class="center">
+			<?php echo $t_vote_info['project_name']; ?>
+		</td>
+		<td class="center">
+			<?php echo string_attribute( get_enum_element( 'status', $t_vote_info['issue']->status ) ); ?>
+		</td>
+		<td>
+			<?php
+			echo string_display_line( $t_vote_info['issue']->summary );
+			if ( VS_PRIVATE == $t_vote_info['issue']->view_state ) {
+				printf( ' <img src="%s" alt="(%s)" title="%s" />', $t_icon_path . 'protected.gif', lang_get( 'private' ), lang_get( 'private' ) );
+			}
+			?>
+		</td>
+	</tr>
+	<?php } }else{ ?>
+	<tr><td colspan="7" class="center"><?php echo lang_get('no_votes') ?></td></tr>
+	<?php } ?>
+	<tfoot>
+		<tr>
+			<td colspan="2">
+				<?php echo lang_get( 'votes_used' ) ?> = <?php echo vote_used_votes() ?>
+			</td>
+			<td colspan="5">
+				<?php 
+				$t_votes_available = vote_available_votes();
+				if ($t_votes_available == VOTES_UNLIMITED_VOTES)
+				{
+					echo lang_get('vote_unlimited');
+				}
+				else
+				{
+					echo $t_votes_available;
+				}
+				?> 
+				<?php echo lang_get( 'votes_remain' ) ?>
+			</td>
+		</tr>
+	</tfoot>
+</table>
+
+<div align="center">
+<?php
+	html_button ( 'account_voting_page.php', 
+		lang_get( ( $f_show_all ? 'voting_hide' : 'voting_show' ) ), 
+		array( 'show_all' => ( $f_show_all ? 0 : 1 ) ) );
+?>
+</div>
+
+<?php html_page_bottom1( __FILE__ ) ?>
Index: admin/schema.php
===================================================================
--- admin/schema.php	(revision 5500)
+++ admin/schema.php	(working copy)
@@ -404,7 +404,21 @@
 	" ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_project_version_table' ), "
 	obsolete		L		NOTNULL DEFAULT \" '0' \"" ) );
+
+# first version of voting
+$upgrade[] = Array( 'CreateTableSQL', Array( db_get_table( 'mantis_bug_votes_table' ), "
+	issue_id		I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	user_id			I		UNSIGNED NOTNULL PRIMARY DEFAULT '0',
+	weight			I		NOTNULL DEFAULT '1'
+	", Array( 'mysql' => 'TYPE=MyISAM', 'pgsql' => 'WITHOUT OIDS' ) ) );
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "
+	votes_positive		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_negative		I		UNSIGNED NOTNULL DEFAULT '0',
+	votes_num_voters	I		UNSIGNED NOTNULL DEFAULT '0'
+	" ) );
+$upgrade[] = Array('CreateIndexSQL',Array('idx_votes_num_voters',db_get_table('mantis_bug_table'),'votes_num_voters'));
+
+$upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_bug_table' ), "
     due_date        T       NOTNULL DEFAULT '" . db_null_date() . "' " ) );
 
 $upgrade[] = Array( 'AddColumnSQL', Array( db_get_table( 'mantis_custom_field_table' ), "
Index: bug_view_advanced_page.php
===================================================================
--- bug_view_advanced_page.php	(revision 5500)
+++ bug_view_advanced_page.php	(working copy)
@@ -588,6 +588,9 @@
 
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
+	
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
 
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
Index: bug_view_page.php
===================================================================
--- bug_view_page.php	(revision 5500)
+++ bug_view_page.php	(working copy)
@@ -447,6 +447,9 @@
 <?php
 	$t_mantis_dir = dirname( __FILE__ ) . DIRECTORY_SEPARATOR;
 
+	# User list voting for the bug
+	include( $t_mantis_dir . 'bug_vote_list_view_inc.php' );
+	
 	# User list sponsoring the bug
 	include( $t_mantis_dir . 'bug_sponsorship_list_view_inc.php' );
 
Index: bug_vote_add.php
===================================================================
--- bug_vote_add.php	(revision 0)
+++ bug_vote_add.php	(revision 0)
@@ -0,0 +1,46 @@
+<?php
+# Mantis - a php based bugtracking system
+
+# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+# --------------------------------------------------------
+# $Id: $
+# --------------------------------------------------------
+
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if ( vote_is_enabled() )
+{
+	$f_bug_id		= gpc_get_int( 'bug_id' );
+	$f_weight		= gpc_get_int( 'vote_weight' );
+	$t_user_id  = auth_get_current_user_id();
+
+	access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+	if (!vote_exists($f_bug_id, $t_user_id)){
+		vote_add($f_bug_id, $f_weight, $t_user_id);
+	}
+	print_successful_redirect_to_bug($f_bug_id);
+}
+else
+{
+	trigger_error( ERROR_VOTING_NOT_ENABLED, ERROR );
+}
+
Index: bug_vote_delete.php
===================================================================
--- bug_vote_delete.php	(revision 0)
+++ bug_vote_delete.php	(revision 0)
@@ -0,0 +1,44 @@
+<?php
+# Mantis - a php based bugtracking system
+
+# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+# --------------------------------------------------------
+# $Id: $
+# --------------------------------------------------------
+
+require_once( 'core.php' );
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path.'current_user_api.php' );
+require_once( $t_core_path.'vote_api.php' );
+
+if ( vote_is_enabled() )
+{
+	$f_bug_id		= gpc_get_int( 'bug_id' );
+	$t_user_id  = auth_get_current_user_id();
+
+	access_ensure_bug_level( config_get( 'voting_place_vote_threshold' ), $f_bug_id, $t_user_id );
+
+	vote_delete($f_bug_id, $t_user_id);
+
+	print_successful_redirect_to_bug($f_bug_id);
+}
+else
+{
+	trigger_error( ERROR_VOTING_NOT_ENABLED, ERROR );
+}
+
Index: bug_vote_list_view_inc.php
===================================================================
--- bug_vote_list_view_inc.php	(revision 0)
+++ bug_vote_list_view_inc.php	(revision 0)
@@ -0,0 +1,200 @@
+<?php
+# Mantis - a php based bugtracking system
+
+# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+# This include file prints out the list of users that have voted for the current
+# bug.	$f_bug_id must be set to the bug id
+$t_core_path = config_get( 'core_path' );
+require_once( $t_core_path . 'vote_api.php' );
+require_once( $t_core_path . 'collapse_api.php' );
+
+
+$t_voting_enabled = vote_is_enabled();
+$t_current_user_id = auth_get_current_user_id();
+
+#
+# Determine whether the voting section should be shown.
+#
+
+if ($t_voting_enabled) {
+	
+	$t_votes = vote_get_issue_votes( $f_bug_id );
+
+	$t_votes_exist = count( $t_votes ) > 0;
+	$t_can_view_vote_details = vote_can_view_vote_details($f_bug_id, $t_current_user_id);
+	$t_can_vote = vote_can_vote($f_bug_id, $t_current_user_id);
+
+	$t_show_votes = $t_votes_exist || $t_can_vote;
+	
+	$t_total_positive = bug_get_field( $f_bug_id, 'votes_positive' );
+	$t_total_negative = bug_get_field( $f_bug_id, 'votes_negative' );
+	$t_total_votes = $t_total_positive - $t_total_negative;
+	
+	$t_total_voters = bug_get_field( $f_bug_id, 'votes_num_voters' );
+	
+	$t_button_text = lang_get('vote_cast_button');
+	$t_bug_id = string_attribute( $f_bug_id );
+	
+	$t_voting_weight_options = config_get( 'voting_weight_options' );
+	asort($t_voting_weight_options);
+	$t_voting_weight_default = config_get( 'voting_weight_default' );
+	
+	$t_issue_project = bug_get_field( $f_bug_id, 'project_id');
+	$t_max_votes = vote_max_votes( $t_current_user_id );
+	$t_used_votes = vote_used_votes( $t_issue_project );
+	$t_unlimited = (VOTES_UNLIMITED_VOTES == $t_max_votes);
+	
+	$t_available_votes = vote_available_votes( $t_issue_project, $t_current_user_id );
+	$t_voting_max_vote_weight = vote_max_weight( $t_issue_project, $t_current_user_id );
+	
+	$t_voting_per_project = config_get( 'voting_per_project' );
+	
+} else {
+	$t_show_votes = false;
+}
+?>
+<?php if ( $t_show_votes ) { # Voting Box	?>
+
+<a name="votings" id="votings"></a>
+<br />
+
+<?php collapse_open( 'voting' );?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title" colspan="2">
+		<?php collapse_icon( 'voting' ); ?>
+		<?php echo lang_get('voting_this_issue') ?> 
+	</td>
+	</tr>
+
+	<tr class="row-1">
+		<td class="category" width="15%">Vote on issue</td>
+		<td>
+		<?php
+		if ( $t_can_vote ) {
+			if (vote_exists($f_bug_id, $t_current_user_id) ) { #show 'remove my vote' button
+									
+				if (bug_is_resolved($f_bug_id)  )
+				{		
+					echo lang_get('voted_and_resolved');
+				}
+				else if(bug_get_field($f_bug_id,'status') == ASSIGNED)
+				{
+					echo lang_get('voted_and_assigned');
+				}
+				else
+				{
+					html_button( 'bug_vote_delete.php',
+									 lang_get( 'vote_delete_button' ),
+									 array( 'bug_id' => $f_bug_id, 'action' => 'DELETE' ) );
+				}
+			
+			}
+			else {  # show 'add vote' button
+			?>
+				
+			<form method="post" action="bug_vote_add.php">
+			<?php if ( $t_available_votes > 0 || $t_unlimited ){ ?>
+			<input type="submit" class="button" value="<?php echo $t_button_text ;?>" />
+			<select name="vote_weight">
+			<?php 
+			foreach($t_voting_weight_options as $t_option_key => $t_option_value){
+				$t_vote_cost = ($t_option_value>0)?$t_option_value:-$t_option_value; #normalise the weight
+				if ( ( $t_voting_max_vote_weight >= $t_vote_cost || $t_unlimited ) && $t_vote_cost != 0){
+			?>
+				<option value="<?php echo $t_option_value?>"<?php echo($t_voting_weight_default==$t_option_value)?' selected':'' ?>><?php echo $t_option_key?></option>
+			<?php }} ?>
+			</select>
+			
+			<? } # available_votes>0 ?>
+			
+			(<?php 
+			echo $t_used_votes ;
+			if ( !$t_unlimited )
+			{
+				echo '/' . $t_max_votes;
+			} 
+			?> <?php echo lang_get('votes_used')?>, 
+			<?php
+			if ($t_available_votes == VOTES_UNLIMITED_VOTES)
+			{
+				echo lang_get('vote_unlimited');
+			}
+			else
+			{
+				echo $t_available_votes;
+			}		 
+			?> 
+			<?php echo lang_get('votes_remain');?>)
+			<input type="hidden" name="bug_id" value="<?php echo $t_bug_id; ?>" />
+			</form>		
+		<? 
+			} #end vote_exists
+		} #end can_vote 
+		?>
+		</td>
+	</tr>
+	<?php if ( $t_votes_exist ) {	?>
+	<tr>
+	<td class="category" width="15%">Summary</td>
+	<td>
+		<?php echo lang_get('votes_positive') ?> = <?php echo $t_total_positive;?><br>
+		<?php echo lang_get('votes_negative') ?> = <?php echo $t_total_negative;?><br>
+		<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes; ?><br>
+		<?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters; ?>
+		
+	</td>
+	</tr>
+	
+	<?php if ($t_can_view_vote_details){ ?>
+	<tr class="row-2">
+		<td class="category" width="15%">Voters List</td>
+		<td>
+		<?php	foreach($t_votes as $userVote){ ?>
+			<div class="userVote">
+				<?php echo user_get_name($userVote['user_id']) ?> <?php echo ($userVote['weight']>=1)?'+'.$userVote['weight']:$userVote['weight'] ?>
+			</div>
+		<?php } ?>
+		</td>
+	</tr>
+	<?php
+		} #end view_vote_details 
+	} #end votes_exist
+	?>
+</table>
+
+<?php collapse_closed( 'voting' ); ?>
+
+<table class="width100" cellspacing="1">
+	<tr>
+		<td class="form-title">
+		<?php collapse_icon( 'voting' );	?>
+		<?php echo lang_get('voting_this_issue') ?> <span style="font-weight: normal;">(<?php echo lang_get('vote_balance') ?> = <?php echo $t_total_votes ?>, <?php echo lang_get('vote_num_voters')?> = <?php echo $t_total_voters ?>)</span>
+		</td>
+	</tr>
+</table>
+
+<?php	
+	collapse_end( 'voting' );
+} # If voting enabled
+?>
Index: config_defaults_inc.php
===================================================================
--- config_defaults_inc.php	(revision 5500)
+++ config_defaults_inc.php	(working copy)
@@ -550,22 +550,22 @@
 	# resolution, fixed_in_version, view_state, os, os_build, build (for product build), platform, version, date_submitted, attachment,
 	# category, sponsorship_total, severity, status, last_updated, summary, bugnotes_count, description,
 	# steps_to_reproduce, additional_information
-	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_view_issues_page_columns = array ( 'selection', 'edit', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 	
 	# The default columns to be included in the Print Issues Page.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
+	$g_print_issues_page_columns = array ( 'selection', 'priority', 'id', 'votes_total', 'votes_num_voters', 'sponsorship_total', 'bugnotes_count', 'attachment', 'category', 'severity', 'status', 'last_updated', 'summary' );
 
 	# The default columns to be included in the CSV export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_csv_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# The default columns to be included in the Excel export.
 	# This can be overriden using Manage -> Manage Configuration -> Manage Columns
 	# Also each user can configure their own columns using My Account -> Manage Columns
-	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version' );
+	$g_excel_columns = array ( 'id', 'project_id', 'reporter_id', 'handler_id', 'priority', 'severity', 'reproducibility', 'version', 'projection', 'category', 'date_submitted', 'eta', 'os', 'os_build', 'platform', 'view_state', 'last_updated', 'summary', 'status', 'resolution', 'fixed_in_version', 'votes_positive', 'votes_negative', 'votes_num_voters' );
 
 	# --- show projects when in All Projects mode ---
 	$g_show_bug_project_links	= ON;
@@ -1443,6 +1443,7 @@
 	$g_db_table['mantis_config_table']					= '%db_table_prefix%_config%db_table_suffix%';
 	$g_db_table['mantis_database_table']				= '%db_table_prefix%_database%db_table_suffix%';
 	$g_db_table['mantis_email_table']					= '%db_table_prefix%_email%db_table_suffix%';
+	$g_db_table['mantis_bug_votes_table']			= '%db_table_prefix%_bug_votes%db_table_suffix%';
 
 	###########################
 	# Mantis Enum Strings
@@ -2012,6 +2013,36 @@
 
 	# management threshold.
 	$g_manage_plugin_threshold = ADMINISTRATOR;
+	
+	#############################
+	# Voting System
+	#############################
+  
+	# enable or disable the whole voting feature.
+	$g_voting_enabled = ON; 
+	
+	# access level required for users to vote on issues.
+	$g_voting_place_vote_threshold = REPORTER; 
+	
+	# access level required for users to view the users who voted and their votes.
+	$g_voting_view_user_votes_threshold = DEVELOPER;
+	
+	# default number of votes allowed per user
+	$g_voting_default_num_votes = 10; # votes can be set for all user levels as an integer ( set to VOTES_UNLIMITED_VOTES to get unlimited votes )
+	$g_voting_default_num_votes = array( DEVELOPER => 25 , REPORTER => 10 ); # or you can set votes by user type, if a level is not specified then it will use the next lowest level available
+	
+	# default voting weights and thier labels, value needs to be integer, while key is a string eg: '+10 (Highly desired)'
+	$g_voting_weight_options = array('+1'=>1, '+2'=>2, '+5'=>5, '+10'=>10, '-1'=>-1, '-2'=>-2, '-5'=>-5, '-10'=>-10);
+	
+	# the maximum weight a user at a given level may use in a single vote
+	$g_voting_max_vote_weight = 5; #max vote weight can be an integer 
+	$g_voting_max_vote_weight = array( DEVELOPER => 10 , REPORTER => 5 ); # or set by user type, eg: even though a reporter may have 10 votes, they may only use up to weight 5 in a single vote
+	
+	# voting weight that should be initially selected when casting a vote, usually the minimum positive vote
+	$g_voting_weight_default = 1;
+	
+	# whether you get your votes counted per project or globally, if ON then you will get $g_voting_default_num_votes per project, if it is OFF your votes are spread across all projects  
+	$g_voting_per_project = ON;
 
 	#############################
 	# Due Date 
Index: config_filter_defaults_inc.php
===================================================================
--- config_filter_defaults_inc.php	(revision 5500)
+++ config_filter_defaults_inc.php	(working copy)
@@ -37,7 +37,7 @@
     define( 'FILTER_PROPERTY_RESOLUTION_ID', 'show_resolution' );
     define( 'FILTER_PROPERTY_PRODUCT_BUILD', 'show_build' );
     define( 'FILTER_PROPERTY_PRODUCT_VERSION', 'show_version' );
-
+		define( 'FILTER_PROPERTY_VOTES_USER_ID', 'user_votes' );
     define( 'FILTER_PROPERTY_MONITOR_USER_ID', 'user_monitor' );
     define( 'FILTER_PROPERTY_HIDE_STATUS_ID', 'hide_status' );
     define( 'FILTER_PROPERTY_SORT_FIELD_NAME', 'sort' );
@@ -90,6 +90,7 @@
     define( 'FILTER_SEARCH_PLATFORM', 'platform' );
     define( 'FILTER_SEARCH_OS', 'os' );
     define( 'FILTER_SEARCH_OS_BUILD', 'os_build' );
+    define( 'FILTER_SEARCH_VOTES_USER_ID', 'votes_user_id' );
     define( 'FILTER_SEARCH_MONITOR_USER_ID', 'monitor_user_id' );
     define( 'FILTER_SEARCH_PRODUCT_BUILD', 'product_build' );
     define( 'FILTER_SEARCH_PRODUCT_VERSION', 'product_version' );
Index: core/bug_api.php
===================================================================
--- core/bug_api.php	(revision 5500)
+++ core/bug_api.php	(working copy)
@@ -35,6 +35,9 @@
 	require_once( $t_core_dir . 'sponsorship_api.php' );
 	require_once( $t_core_dir . 'twitter_api.php' );
 	require_once( $t_core_dir . 'tag_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
+
+	# MASC RELATIONSHIP
 	require_once( $t_core_dir.'relationship_api.php' );
 
 	### Bug API ###
@@ -68,6 +71,9 @@
 		var $summary = '';
 		var $sponsorship_total = 0;
 		var $sticky = 0;
+		var $votes_positive = 0;
+		var $votes_negative = 0;
+		var $votes_num_voters = 0;
 
 		# omitted:
 		# var $bug_text_id
@@ -856,6 +862,9 @@
 
 		# Delete all sponsorships
 		sponsorship_delete( sponsorship_get_all_ids( $p_bug_id ) );
+		
+		# Delete all votes on this bug
+		vote_delete_issue_votes( $p_bug_id );
 
 		# MASC RELATIONSHIP
 		# we delete relationships even if the feature is currently off.
@@ -1011,12 +1020,18 @@
 					view_state=" . db_param() .",
 					summary=" . db_param() .",
 					sponsorship_total=" . db_param() .",
+					votes_num_voters=" . db_param() .",
+					votes_positive=" . db_param() .",
+					votes_negative=" . db_param() .",
 					sticky=" . db_param() .",
 					due_date=" . db_param() ." 
 				WHERE id=" . db_param();
 		$t_fields[] = $c_bug_data->view_state;
 		$t_fields[] = $c_bug_data->summary;
 		$t_fields[] = $c_bug_data->sponsorship_total;
+		$t_fields[] = $c_bug_data->votes_num_voters;
+		$t_fields[] = $c_bug_data->votes_positive;
+		$t_fields[] = $c_bug_data->votes_negative;
 		$t_fields[] = (bool)$c_bug_data->sticky;
 		$t_fields[] = $c_due_date;
 		$t_fields[] = $c_bug_id;
@@ -1049,6 +1064,11 @@
 		history_log_event_direct( $p_bug_id, 'view_state', $t_old_data->view_state, $p_bug_data->view_state );
 		history_log_event_direct( $p_bug_id, 'summary', $t_old_data->summary, $p_bug_data->summary );
 		history_log_event_direct( $p_bug_id, 'sponsorship_total', $t_old_data->sponsorship_total, $p_bug_data->sponsorship_total );
+		# @REVIEW should these voting attributes show up in the history?
+		#history_log_event_direct( $p_bug_id, 'votes_num_voters', $t_old_data->votes_num_voters, $p_bug_data->votes_num_voters );
+		#history_log_event_direct( $p_bug_id, 'votes_positive', $t_old_data->votes_positive, $p_bug_data->votes_positive );
+		#history_log_event_direct( $p_bug_id, 'votes_negative', $t_old_data->votes_negative, $p_bug_data->votes_negative );
+				
 		history_log_event_direct( $p_bug_id, 'sticky', $t_old_data->sticky, $p_bug_data->sticky );
 		
 		history_log_event_direct( $p_bug_id, 'due_date', ( $t_old_data->due_date != db_unixtimestamp( db_null_date() ) ) ? $t_old_data->due_date : null, 
@@ -1395,6 +1415,9 @@
 			case 'view_state':
 			case 'profile_id':
 			case 'sponsorship_total':
+			case 'votes_positive':
+			case 'votes_negative':
+			case 'votes_num_voters':
 				$c_value = (int)$p_value;
 				break;
 
Index: core/columns_api.php
===================================================================
--- core/columns_api.php	(revision 5500)
+++ core/columns_api.php	(working copy)
@@ -56,6 +56,9 @@
 			'selection',
 			'severity',
 			'sponsorship_total',
+			'votes_positive',
+			'votes_negative',
+			'votes_num_voters',
 			'status',
 			'steps_to_reproduce',        // new
 			'summary',
@@ -834,4 +837,31 @@
 
 		echo '</td>';
 	}
+	
+	
+	function print_column_title_votes_total( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_balance' ), 'votes_total', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_total' );
+		echo '</td>';
+	}
+	
+	function print_column_title_votes_num_voters( $p_sort, $p_dir, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td>';
+		print_view_bug_sort_link( lang_get( 'vote_num_voters' ), 'votes_num_voters', $p_sort, $p_dir, $p_columns_target );
+		print_sort_icon( $p_dir, $p_sort, 'votes_num_voters' );
+		echo '</td>';
+	}
+	
+	function print_column_votes_total( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td class="right">';
+		echo (($p_row['votes_total']>0)?'+':'')  . $p_row['votes_total'];
+		echo '</td>';
+	}
+	
+	function print_column_votes_num_voters( $p_row, $p_columns_target = COLUMNS_TARGET_VIEW_PAGE ) {
+		echo '<td class="right">';
+		echo $p_row['votes_num_voters'];
+		echo '</td>';
+	}
 ?>
Index: core/constant_inc.php
===================================================================
--- core/constant_inc.php	(revision 5500)
+++ core/constant_inc.php	(working copy)
@@ -170,6 +170,8 @@
 	define( 'TAG_ATTACHED', 				25 );
 	define( 'TAG_DETACHED', 				26 );
 	define( 'TAG_RENAMED', 					27 );
+	define( 'BUGVOTE_ADDED', 				28 );
+	define( 'BUGVOTE_DELETED', 			29 );
 
 	# bug relationship constants
 	define( 'BUG_DUPLICATE',	0 );
@@ -343,6 +345,13 @@
 
 	# ERROR_FORM_*
 	define ( 'ERROR_FORM_TOKEN_INVALID',	2800 );
+	
+	#ERROR_VOTING_*
+	define( 'ERROR_VOTING_NOT_ENABLED',			2900 );
+	define( 'ERROR_VOTING_OVER_LIMIT', 			2901 );
+	
+	# voting 
+	define('VOTES_UNLIMITED_VOTES', -1);
 
 	# Status Legend Position
 	define( 'STATUS_LEGEND_POSITION_TOP',		1);
Index: core/csv_api.php
===================================================================
--- core/csv_api.php	(revision 5500)
+++ core/csv_api.php	(working copy)
@@ -254,4 +254,16 @@
 	function csv_format_selection( $p_duplicate_id ) {
 		return csv_escape_string( '' );
 	}
+	
+	function csv_format_votes_positive( $p_votes_positive ) {
+		return csv_escape_string( $p_votes_positive );
+	}
+	
+	function csv_format_votes_negative( $p_votes_negative ) {
+		return csv_escape_string( $p_votes_negative );
+	}
+	
+	function csv_format_votes_num_voters( $p_votes_num_voters ) {
+		return csv_escape_string( $p_votes_num_voters );
+	}
 ?>
Index: core/excel_api.php
===================================================================
--- core/excel_api.php	(revision 5500)
+++ core/excel_api.php	(working copy)
@@ -428,4 +428,14 @@
 		// field is not linked to project
 		return excel_prepare_string( '' );
 	}
+	
+	function excel_format_votes_positive( $p_votes_positive ) {
+		return excel_prepare_string( $p_votes_positive );
+	}
+	function excel_format_votes_negative( $p_votes_negative ) {
+		return excel_prepare_string( $p_votes_negative );
+	}
+	function excel_format_votes_num_voters( $p_votes_num_voters ) {
+		return excel_prepare_string( $p_votes_num_voters );
+	}
 ?>
Index: core/filter_api.php
===================================================================
--- core/filter_api.php	(revision 5500)
+++ core/filter_api.php	(working copy)
@@ -74,10 +74,18 @@
 		if ( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_STATUS_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_STATUS_ID, $p_custom_filter[FILTER_PROPERTY_STATUS_ID] );
 		}
+		
+		if ( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] ) ) {
+			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_VOTES_USER_ID, $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] );
+		}
 
 		if ( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_MONITOR_USER_ID, $p_custom_filter[FILTER_PROPERTY_MONITOR_USER_ID] );
 		}
+		
+		if ( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] ) ) {
+			$t_query[] = filter_encode_field_and_value( FILTER_PROPERTY_VOTES_USER_ID, $p_custom_filter[FILTER_PROPERTY_VOTES_USER_ID] );
+		}
 
 		if ( !filter_field_is_any( $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] ) ) {
 			$t_query[] = filter_encode_field_and_value( FILTER_SEARCH_HANDLER_ID, $p_custom_filter[FILTER_PROPERTY_HANDLER_ID] );
@@ -545,6 +553,7 @@
 									  FILTER_PROPERTY_FIXED_IN_VERSION => 'string',
 									  FILTER_PROPERTY_TARGET_VERSION => 'string',
 									  FILTER_PROPERTY_MONITOR_USER_ID => 'int',
+									  FILTER_PROPERTY_VOTES_USER_ID => 'int',
 									  'show_profile' => 'int'
 									 );
 		foreach( $t_multi_select_list as $t_multi_field_name => $t_multi_field_type ) {
@@ -628,6 +637,7 @@
 			FILTER_PROPERTY_PRODUCT_VERSION	=> Array ( '0' => META_FILTER_ANY ),
 			FILTER_PROPERTY_HIDE_STATUS_ID	=> Array ( '0' => $t_hide_status_default ),
 			FILTER_PROPERTY_MONITOR_USER_ID	=> Array ( '0' => META_FILTER_ANY ),
+			FILTER_PROPERTY_VOTES_USER_ID  => Array ( '0' => META_FILTER_ANY ),
 			FILTER_PROPERTY_SORT_FIELD_NAME => 'last_updated',
 			FILTER_PROPERTY_SORT_DIRECTION  => 'DESC',
 			FILTER_PROPERTY_ISSUES_PER_PAGE	=> config_get( 'default_limit_view' )
@@ -859,6 +869,7 @@
 		$t_bugnote_text_table	= db_get_table( 'mantis_bugnote_text_table' );
 		$t_project_table		= db_get_table( 'mantis_project_table' );
 		$t_bug_monitor_table	= db_get_table( 'mantis_bug_monitor_table' );
+		$t_bug_votes_table 		=	db_get_table( 'mantis_bug_votes_table' );
 		$t_limit_reporters		= config_get( 'limit_reporters' );
 		$t_bug_relationship_table	= db_get_table( 'mantis_bug_relationship_table' );
 		$t_report_bug_threshold		= config_get( 'report_bug_threshold' );
@@ -1518,6 +1529,35 @@
 			}
 		}
 
+		# users voting on an issue
+		$t_select_clauses[] = '(votes_positive - votes_negative) as votes_total'; # @REVIEW is this the correct mantis way to be doing this? votes_total is a derived column
+		if ( !filter_field_is_any( $t_filter[ FILTER_PROPERTY_VOTES_USER_ID ] ) ) {
+			$t_clauses = array();
+			$t_table_name = 'user_votes';
+			array_push( $t_from_clauses, $t_bug_votes_table );
+			array_push( $t_join_clauses, "LEFT JOIN $t_bug_votes_table $t_table_name ON $t_table_name.issue_id = $t_bug_table.id" );
+
+			foreach( $t_filter[FILTER_PROPERTY_VOTES_USER_ID] as $t_filter_member ) {
+				$c_user_monitor = db_prepare_int( $t_filter_member );
+				if ( META_FILTER_MYSELF == $c_user_monitor ) {
+					array_push( $t_clauses, $c_user_id );
+				} else {
+					array_push( $t_clauses, $c_user_monitor );
+				}
+			}
+			if ( 1 < count( $t_clauses ) ) {
+				foreach( $t_clauses as $t_clause ) {
+					$t_where_tmp[] = db_param($t_where_param_count++);
+					$t_where_params[] = $t_clause;
+				}
+				array_push( $t_where_clauses, "( $t_table_name.user_id in (". implode( ', ', $t_where_tmp ) .") )" );
+			} else {
+				$t_where_params[] = $t_clauses[0];
+				array_push( $t_where_clauses, "( $t_table_name.user_id=" . db_param($t_where_param_count++). " )" );
+			}
+		}
+		
+		
 		# bug relationship
 		$t_any_found = false;
 		$c_rel_type = $t_filter[ FILTER_PROPERTY_RELATIONSHIP_TYPE ];
@@ -2637,9 +2677,12 @@
 			<td class="small-caption" valign="top">
 				<a href="<?php PRINT $t_filters_url . FILTER_PROPERTY_OS_BUILD; ?>" id="os_build_filter"><?php echo lang_get( 'os_version' ) ?>:</a>
 			</td>
-			<td class="small-caption" valign="top" colspan="5">
+			<td class="small-caption" valign="top" colspan="4">
 				<a href="<?php PRINT $t_filters_url . FILTER_PROPERTY_TAG_STRING; ?>" id="tag_string_filter"><?php echo lang_get( 'tags' ) ?>:</a>
 			</td>
+			<td class="small-caption" valign="top">
+				<a href="<?php PRINT $t_filters_url . 'user_votes[]'; ?>" id="user_votes_filter"><?php PRINT lang_get( 'voted_by' ) ?>:</a>
+			</td>
 			<?php if ( $t_filter_cols > 8 ) {
 				echo '<td class="small-caption" valign="top" colspan="' . ( $t_filter_cols - 8 ) . '">&nbsp;</td>';
 			} ?>
@@ -2660,7 +2703,7 @@
 					print_multivalue_field( FILTER_PROPERTY_OS_BUILD, $t_filter[ FILTER_PROPERTY_OS_BUILD ] );
 				?>
 			</td>
-			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="5">
+			<td class="small-caption" valign="top" id="tag_string_filter_target" colspan="4">
 				<?php 
 					$t_tag_string = $t_filter[ FILTER_PROPERTY_TAG_STRING ];
 					if ( $t_filter[ FILTER_PROPERTY_TAG_SELECT ] != 0 ) {
@@ -2671,6 +2714,45 @@
 					echo '<input type="hidden" name="', FILTER_PROPERTY_TAG_STRING, '" value="', $t_tag_string, '" />';
 				?>
 			</td>
+			<td class="small-caption" valign="top" id="user_votes_filter_target">
+							<?php
+								$t_output = '';
+								$t_any_found = false;
+								if ( count( $t_filter[FILTER_PROPERTY_VOTES_USER_ID] ) == 0 ) {
+									PRINT lang_get( 'any' );
+								} else {
+									$t_first_flag = true;
+									foreach( $t_filter[FILTER_PROPERTY_VOTES_USER_ID] as $t_current ) {
+										?>
+										<input type="hidden" name="user_votes[]" value="<?php echo $t_current;?>" />
+										<?php
+										$t_this_name = '';
+										if ( ( $t_current === 0 ) || ( is_blank( $t_current ) ) || ( META_FILTER_ANY == $t_current ) ) {
+											$t_any_found = true;
+										} else if ( META_FILTER_MYSELF == $t_current ) {
+											if ( access_has_project_level( config_get( 'monitor_bug_threshold' ) ) ) {
+												$t_this_name = '[' . lang_get( 'myself' ) . ']';
+											} else {
+												$t_any_found = true;
+											}
+										} else {
+											$t_this_name = user_get_name( $t_current );
+										}
+										if ( $t_first_flag != true ) {
+											$t_output = $t_output . '<br />';
+										} else {
+											$t_first_flag = false;
+										}
+										$t_output = $t_output . $t_this_name;
+									}
+									if ( true == $t_any_found ) {
+										PRINT lang_get( 'any' );
+									} else {
+										PRINT $t_output;
+									}
+								}
+							?>
+			</td>
 		</tr>
 		<?php
 
@@ -3154,6 +3236,24 @@
 		</select>
 		<?php
 	}
+	
+	function print_filter_user_votes(){
+		global $t_select_modifier, $t_filter;
+		?>
+		<!-- Voted by -->
+		<select <?php PRINT $t_select_modifier;?> name="user_votes[]">
+			<option value="<?php echo META_FILTER_ANY ?>" <?php check_selected( $t_filter[FILTER_PROPERTY_VOTES_USER_ID], META_FILTER_ANY ); ?>>[<?php echo lang_get( 'any' ) ?>]</option>
+			<?php
+				if ( access_has_project_level( config_get( 'voting_view_user_votes_threshold' ) ) ) {
+					PRINT '<option value="' . META_FILTER_MYSELF . '" ';
+					check_selected( $t_filter[FILTER_PROPERTY_VOTES_USER_ID], META_FILTER_MYSELF );
+					PRINT '>[' . lang_get( 'myself' ) . ']</option>';
+				}
+			?>
+			<?php print_reporter_option_list( $t_filter[FILTER_PROPERTY_VOTES_USER_ID] ) ?>
+		</select>
+		<?php
+	}
 
 	/**
 	 *  print the handler field
Index: core/helper_api.php
===================================================================
--- core/helper_api.php	(revision 5500)
+++ core/helper_api.php	(working copy)
@@ -372,6 +372,15 @@
 		if ( OFF == $t_enable_sponsorship ) {
 			$t_keys_to_remove[] = 'sponsorship_total';
 		}
+		
+		$t_enable_voting = config_get( 'voting_enabled' );
+		if ($t_enable_voting == OFF)
+		{
+			$t_keys_to_remove[] = 'votes_total';
+			$t_keys_to_remove[] = 'votes_num_voters';
+			$t_keys_to_remove[] = 'votes_positive';
+			$t_keys_to_remove[] = 'votes_negative';
+		}
 
 		if ( $p_columns_target == COLUMNS_TARGET_CSV_PAGE || $p_columns_target == COLUMNS_TARGET_EXCEL_PAGE ||
 			 OFF == config_get( 'show_attachment_indicator' ) ) {
Index: core/history_api.php
===================================================================
--- core/history_api.php	(revision 5500)
+++ core/history_api.php	(working copy)
@@ -442,6 +442,12 @@
 					$t_note = lang_get( 'tag_history_renamed' );
 					$t_change = $p_old_value . ' => ' . $p_new_value;
 					break;
+				case BUGVOTE_ADDED:
+					$t_note = lang_get( 'bugvote_added' ) . ": " . $p_old_value;
+					break;
+				case BUGVOTE_DELETED:
+					$t_note = lang_get( 'bugvote_deleted' ) . ": " . $p_old_value;
+					break;
 			}
 		}
 
Index: core/html_api.php
===================================================================
--- core/html_api.php	(revision 5500)
+++ core/html_api.php	(working copy)
@@ -69,6 +69,7 @@
 	require_once( $t_core_dir . 'user_api.php' );
 	require_once( $t_core_dir . 'rss_api.php' );
 	require_once( $t_core_dir . 'wiki_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 	$g_rss_feed_url = null;
 
@@ -853,6 +854,7 @@
 		$t_account_prefs_page 			= 'account_prefs_page.php';
 		$t_account_profile_menu_page 	= 'account_prof_menu_page.php';
 		$t_account_sponsor_page			= 'account_sponsor_page.php';
+		$t_account_voting_page		= 'account_voting_page.php';
 		$t_account_manage_columns_page	= 'account_manage_columns_page.php';
 
 		switch ( $p_page ) {
@@ -868,6 +870,9 @@
 			case $t_account_sponsor_page:
 				$t_account_sponsor_page = '';
 				break;
+			case $t_account_voting_page:
+				$t_account_voting_page = '';
+				break;
 			case $t_account_manage_columns_page:
 				$t_account_manage_columns_page = '';
 				break;
@@ -886,6 +891,13 @@
 			 !current_user_is_anonymous() ) {
 			print_bracket_link( helper_mantis_url( $t_account_sponsor_page ), lang_get( 'my_sponsorship' ) );
 		}
+		
+		if ( ( config_get( 'voting_enabled' ) == ON ) &&
+			 ( access_has_project_level( config_get( 'voting_place_vote_threshold' ) ) ) &&
+			 !current_user_is_anonymous() ) {
+			print_bracket_link( helper_mantis_url( $t_account_voting_page ), lang_get( 'my_votes' ) );
+		}
+		
 	}
 
 	# --------------------
@@ -1288,7 +1300,7 @@
 		echo '<td class="center">';
 		html_button_bug_change_status( $p_bug_id );
 		echo '</td>';
-
+		
 		# MONITOR/UNMONITOR button
 		echo '<td class="center">';
 		if ( !current_user_is_anonymous() ) {
Index: core/my_view_inc.php
===================================================================
--- core/my_view_inc.php	(revision 5500)
+++ core/my_view_inc.php	(working copy)
@@ -59,7 +59,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_bug_resolved_status_threshold ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['assigned'] = FILTER_PROPERTY_HANDLER_ID . '=' . $t_current_user_id . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_bug_resolved_status_threshold;
 
@@ -74,7 +75,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => META_FILTER_NONE ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['recent_mod'] = FILTER_PROPERTY_HIDE_STATUS_ID . '=none';
 
@@ -90,7 +92,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['reported'] = FILTER_PROPERTY_REPORTER_ID . '=' . $t_current_user_id . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_hide_status_default;
 
@@ -105,7 +108,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['resolved'] = FILTER_PROPERTY_STATUS_ID . '=' . $t_bug_resolved_status_threshold . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_bug_resolved_status_threshold;
 
@@ -120,7 +124,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['unassigned'] = FILTER_PROPERTY_HANDLER_ID . '=[none]' . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_hide_status_default; #TODO: check. handler value looks wrong
 
@@ -135,7 +140,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => $t_current_user_id )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => $t_current_user_id ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['monitored'] = FILTER_PROPERTY_MONITOR_USER_ID . '=' . $t_current_user_id . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_hide_status_default;
 
@@ -151,7 +157,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['feedback'] = FILTER_PROPERTY_REPORTER_ID . '=' . $t_current_user_id . '&amp;' . FILTER_PROPERTY_STATUS_ID . '=' . FEEDBACK . '&amp;' . FILTER_PROPERTY_HIDE_STATUS_ID . '=' . $t_hide_status_default;
 
@@ -166,7 +173,8 @@
 		FILTER_PROPERTY_PRODUCT_BUILD		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_PRODUCT_VERSION		=> Array ( '0' => META_FILTER_ANY ),
 		FILTER_PROPERTY_HIDE_STATUS_ID		=> Array ( '0' => $t_hide_status_default ),
-		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY )
+		FILTER_PROPERTY_MONITOR_USER_ID		=> Array ( '0' => META_FILTER_ANY ),
+		'user_votes'			=> Array ( '0' => META_FILTER_ANY )
 	);
 	$url_link_parameters['verify'] = FILTER_PROPERTY_REPORTER_ID . '=' . $t_current_user_id . '&amp;' . FILTER_PROPERTY_STATUS_ID . '=' . $t_bug_resolved_status_threshold;
 
Index: core/print_api.php
===================================================================
--- core/print_api.php	(revision 5500)
+++ core/print_api.php	(working copy)
@@ -104,7 +104,6 @@
 	#  call print_successful_redirect() with that URL
 	function print_successful_redirect_to_bug( $p_bug_id ) {
 		$t_url = string_get_bug_view_url( $p_bug_id, auth_get_current_user_id() );
-
 		print_successful_redirect( $t_url );
 	}
 
Index: core/user_api.php
===================================================================
--- core/user_api.php	(revision 5500)
+++ core/user_api.php	(working copy)
@@ -30,6 +30,7 @@
 
 	require_once( $t_core_dir . 'email_api.php' );
 	require_once( $t_core_dir . 'ldap_api.php' );
+	require_once( $t_core_dir . 'vote_api.php' );
 
 
 	#===================================
@@ -560,6 +561,9 @@
 		$t_user_table = db_get_table('mantis_user_table');
 
 		user_ensure_unprotected( $p_user_id );
+		
+		# Remove any votes the user has made on issues
+		vote_delete_user_votes( $p_user_id );
 
 		# Remove associated profiles
 		user_delete_profiles( $p_user_id );
@@ -1086,7 +1090,6 @@
 		}
 
 		$t_filter = unserialize( $t_cookie_detail[1] );
-
 		$t_filter = filter_ensure_valid_filter( $t_filter );
 
 		return $t_filter;
Index: core/vote_api.php
===================================================================
--- core/vote_api.php	(revision 0)
+++ core/vote_api.php	(revision 0)
@@ -0,0 +1,471 @@
+<?php
+# Mantis - a php based bugtracking system
+
+# Copyright (C) 2000 - 2002  Kenzaburo Ito - kenito@300baud.org
+# Copyright (C) 2002 - 2007  Mantis Team   - mantisbt-dev@lists.sourceforge.net
+
+# Mantis is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 of the License, or
+# (at your option) any later version.
+#
+# Mantis is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Mantis.  If not, see <http://www.gnu.org/licenses/>.
+
+	# --------------------------------------------------------
+	# $Id: $
+	# --------------------------------------------------------
+
+$t_core_dir = dirname( __FILE__ ).DIRECTORY_SEPARATOR;
+require_once( $t_core_dir . 'current_user_api.php' );
+require_once( $t_core_dir . 'history_api.php' );
+require_once( $t_core_dir . 'bug_api.php' );
+require_once( $t_core_dir . 'user_api.php' );
+
+
+/**
+ * vote_add
+ * 
+ * @param integer $p_issue_id issue primary key
+ * @param integer $p_weight impact of vote
+ * @param integer $p_user_id user primary key
+ */
+function vote_add( $p_issue_id, $p_weight, $p_user_id = null )
+{
+	if ( $p_issue_id < 1 )
+	{
+		error_parameters( $p_issue_id );
+		trigger_error( ERROR_BUG_NOT_FOUND , ERROR );
+	}
+	
+	$t_issue_project = bug_get_field($p_issue_id, 'project_id');
+	$t_vote_max_weight = vote_max_weight( $t_issue_project, $p_user_id );
+	$t_unlimited = (VOTES_UNLIMITED_VOTES == $t_vote_max_weight);
+
+	if ( ( $p_weight > $t_vote_max_weight && !$t_unlimited ) || $p_weight == 0 )
+	{
+		trigger_error( ERROR_VOTING_OVER_LIMIT, ERROR );
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+
+	$query = "INSERT INTO $t_mantis_bug_votes_table
+		          		( issue_id, user_id, weight )
+		          	 VALUES
+		          		( " . db_param(0) . ", " . db_param(1) . ", " . db_param(2) . " )";
+	db_query_bound( $query, Array( (int)$p_issue_id, (int)$p_user_id, (int)$p_weight ) );
+
+	#update issue counters to keep in sync
+	vote_updates_counters_for_issue( $p_issue_id );
+	
+	# log vote history
+	history_log_event_special( $p_issue_id, BUGVOTE_ADDED );
+	bug_update_date($p_issue_id);
+}
+
+/**
+ * vote_delete
+ *
+ * @param integer $p_issue_id
+ * @param integer $p_user_id
+ */
+function vote_delete( $p_issue_id, $p_user_id )
+{
+	if ( $p_issue_id < 1 )
+	{
+		error_parameters( $p_issue_id );
+		trigger_error( ERROR_BUG_NOT_FOUND , ERROR );
+	}
+	if ( $p_user_id < 1 )
+	{
+		error_parameters( $p_user_id );
+		trigger_error( ERROR_USER_NOT_FOUND , ERROR );
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+
+	# now remove vote from voting table
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0) . " AND user_id = " . db_param(1);
+	db_query_bound($query, Array( (int)$p_issue_id, (int)$p_user_id ));
+	
+	#update issue counters to keep bug table in sync
+	vote_updates_counters_for_issue( $p_issue_id );
+	
+	# log vote history
+	history_log_event_special( $p_issue_id, BUGVOTE_DELETED );
+	bug_update_date($p_issue_id);
+}
+
+/**
+ * vote_delete_issue_votes
+ * Deleting an issue should delete all associated votes.
+ * This should only be called post issue delete
+ * 
+ * @param integer $p_issue_id issue primary key
+ */
+function vote_delete_issue_votes( $p_issue_id )
+{
+	if ( $p_issue_id < 1 )
+	{
+		error_parameters( $p_issue_id );
+		trigger_error( ERROR_BUG_NOT_FOUND , ERROR );
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "DELETE FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	db_query_bound($query, Array( (int)$p_issue_id ));
+}
+
+/**
+ * vote_delete_user_votes
+ * Deleting a user should delete all associated votes.
+ *
+ * @param integer $p_user_id user primary key
+ */
+function vote_delete_user_votes( $p_user_id )
+{
+	if ( $p_user_id < 1 )
+	{
+		error_parameters( $p_user_id );
+		trigger_error( ERROR_USER_NOT_FOUND , ERROR );
+	}
+	
+	$votes = vote_get_user_votes( null, true, $p_user_id );
+	foreach($votes as $vote)
+	{
+		vote_delete($vote['issue_id'], $p_user_id);
+	}
+}
+
+/**
+ * vote_get_user_votes
+ *
+ * @param integer $p_project_id
+ * @param boolean $p_include_resolved
+ * @param integer $p_user_id
+ * @return array issues and thier weight array('issue_id'=>$p_issue_id, 'weight'=>$p_weight); 
+ */
+function vote_get_user_votes ($p_project_id = null, $p_include_resolved = true, $p_user_id = null)
+{
+	if ($p_user_id === null)
+	{
+		$p_user_id = auth_get_current_user_id();
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	$query = "SELECT issue_id, weight FROM $t_mantis_bug_votes_table WHERE user_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_user_id));
+
+	$users = array();
+	while ( $row = db_fetch_array( $result ) ) {
+
+		$t_resolved = bug_is_resolved($row['issue_id']);
+		
+		if ($p_include_resolved || !$t_resolved )
+		{
+			
+			if ( $p_project_id === null || $p_project_id == ALL_PROJECTS )
+			{
+				$users[] = $row;
+			}
+			else
+			{
+				$t_issue_project = bug_get_field($row['issue_id'], 'project_id');
+				if ( $t_issue_project == $p_project_id )
+				{
+					$users[] = $row;
+				}
+			}
+		}
+	}
+	return $users;
+}
+
+/**
+ * vote_get_issue_votes
+ * returns an array of user ids, weight
+ * 
+ * @param integer $p_issue_id issue primary key
+ * @return array users and thier vote weight array('user_id'=>$p_user_id, 'weight'=>$p_weight); 
+ */
+function vote_get_issue_votes( $p_issue_id )
+{
+	if ( $p_issue_id < 1 )
+	{
+		error_parameters( $p_issue_id );
+		trigger_error( ERROR_BUG_NOT_FOUND , ERROR );
+	}
+	
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query = "SELECT user_id, weight FROM $t_mantis_bug_votes_table WHERE issue_id = " . db_param(0);
+	$result = db_query_bound($query, Array($p_issue_id));
+	$t_issue_votes = array();
+	while ( $row = db_fetch_array( $result ) ) {
+		$t_issue_votes[] = $row;
+	}
+	return $t_issue_votes;
+}
+
+/**
+ * vote_is_enabled
+ * check whether voting is enabled on the given project
+ * 
+ * @param integer $p_project_id
+ * @return boolean
+ */
+function vote_is_enabled( $p_project_id = null )
+{
+	if ($p_project_id === null)
+	{
+		$p_project_id = helper_get_current_project();
+	}
+	$t_enabled = ( config_get( 'voting_enabled', null, null, $p_project_id ) == ON );
+	return $t_enabled;
+}
+
+/**
+ * vote_can_vote
+ * whether or not the user is allowed to vote on an issue
+ *
+ * @param integer $p_issue_id
+ * @param integer $p_user_id
+ * @return boolean
+ */
+function vote_can_vote( $p_issue_id, $p_user_id = null )
+{
+	$t_can_vote = access_has_bug_level( config_get( 'voting_place_vote_threshold' ),$p_issue_id , $p_user_id ); 
+	return $t_can_vote;
+}
+
+/**
+ * vote_can_view_vote_details
+ * whether or not the user is allowed to view vote details
+ *
+ * @param integer $p_issue_id
+ * @param integer $p_user_id
+ * @return boolean
+ */
+function vote_can_view_vote_details( $p_issue_id, $p_user_id = null )
+{
+	$t_has_level = ( access_has_bug_level( config_get( 'voting_view_user_votes_threshold' ), $p_issue_id , $p_user_id ) );
+	return $t_has_level;
+}
+
+/**
+ * vote_exists
+ * whether the user has placed a vote on a given issue or not
+ * @param integer $p_issue_id
+ * @param integer $p_user_id
+ * @return boolean
+ */
+function vote_exists ( $p_issue_id, $p_user_id )
+{
+	$t_mantis_bug_votes_table	= db_get_table( 'mantis_bug_votes_table' );
+	$query 	= "SELECT COUNT(*)
+		          	FROM $t_mantis_bug_votes_table
+		          	WHERE issue_id=" . db_param(0) . " AND user_id = " . db_param(1);
+		$result	= db_query_bound( $query, Array( $p_issue_id, $p_user_id ) );
+
+		if ( 0 == db_result( $result ) ) {
+			return false;
+		} else {
+			return true;
+		}
+}
+
+/**
+ * vote_max_votes
+ * the maximum number of votes the given user can cast across all issues
+ * 
+ * @param integer $p_user_id
+ * @return integer
+ */
+function vote_max_votes( $p_user_id )
+{
+	$t_default_num_votes = config_get('voting_default_num_votes');
+	
+	if (is_array($t_default_num_votes))
+	{
+		ksort($t_default_num_votes); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		foreach($t_default_num_votes as $t_vote_level => $t_votes)
+		{
+			if ($t_user_level >= $t_vote_level)
+			{
+				$t_num_votes = $t_votes;
+				break;
+			}
+		}
+	}
+	else
+	{
+		$t_num_votes = intval($t_default_num_votes);
+	}
+
+	return $t_num_votes;
+}
+
+/**
+ * vote_available_votes
+ * the number of available votes a user can still cast on a given project
+ * note this may also return VOTES_UNLIMITED_VOTES so you should always test for this return value
+ * 
+ * @param integer $p_project_id
+ * @param integer $p_user_id
+ * @return integer number of available votes, also VOTES_UNLIMITED_VOTES
+ */
+function vote_available_votes( $p_project_id = null, $p_user_id = null )
+{
+	if ($p_user_id === null)
+	{
+		$p_user_id = auth_get_current_user_id();
+	}
+	
+	$t_max_votes = vote_max_votes( $p_user_id );
+	
+	
+	if ($t_max_votes == 0)
+	{
+		return VOTES_UNLIMITED_VOTES;
+	}
+	else
+	{
+		$t_used_votes = vote_used_votes( $p_project_id, $p_user_id );
+		
+		$t_available_votes = $t_max_votes -  $t_used_votes;
+		return $t_available_votes;
+	}
+}
+
+/**
+ * vote_used_votes
+ * the number of votes already cast on a given project that are not resolved
+ * 
+ * @param integer $p_project_id
+ * @param integer $p_user_id
+ * @return integer
+ */
+function vote_used_votes( $p_project_id = null, $p_user_id = null )
+{
+
+	if ($p_user_id === null)
+	{
+		$p_user_id = auth_get_current_user_id();
+	}
+	
+	if ($p_project_id === null)
+	{
+		$p_project_id = helper_get_current_project();
+	}
+	
+	$t_per_project = config_get('voting_per_project');
+	
+	if ($t_per_project == ON)
+	{
+		$t_votes = vote_get_user_votes( $p_project_id, false, $p_user_id );	
+	}
+	else
+	{
+		$t_votes = vote_get_user_votes( ALL_PROJECTS , false, $p_user_id );
+	}
+	
+	$t_weight_used = 0;
+	foreach($t_votes as $t_vote)
+	{
+		if ($t_vote['weight']>0)
+		{
+			$t_weight_used += $t_vote['weight'];
+		}
+		else
+		{
+			$t_weight_used -= $t_vote['weight'];
+		}
+	}
+	return $t_weight_used;
+}
+
+/**
+ * vote_max_weight
+ * the maximum weight a user can cast on a single vote right now - note this is different from vote_available_votes
+ * takes in to consideration how many votes a user has remaining
+ * returning whichever is the lesser, your available votes or you max vote weight
+ * 
+ * @param integer $p_project_id
+ * @param integer $p_user_id
+ * @return integer
+ */
+function vote_max_weight( $p_project_id = null, $p_user_id = null )
+{
+	if ($p_project_id === null)
+	{
+		$p_project_id = helper_get_current_project();
+	}
+	
+	if ($p_user_id === null)
+	{
+		$p_user_id = auth_get_current_user_id();
+	}
+	
+	$t_available_votes = vote_available_votes( $p_project_id, $p_user_id );
+	$t_voting_max_vote_weight = config_get('voting_max_vote_weight');
+	if (is_array($t_voting_max_vote_weight))
+	{
+		ksort($t_voting_max_vote_weight); # relies on user levels being numeric
+		$t_user_level = user_get_access_level( $p_user_id );
+		
+		#find your maximum applicable voting weight 
+		foreach($t_voting_max_vote_weight as $t_level => $t_max)
+		{
+			if ($t_user_level >= $t_level)
+			{
+				$t_voting_max_vote_weight = $t_max;
+			}
+		}
+	}
+	
+	# return whichever is the lesser, your available votes or you max vote weight
+	$t_voting_max_vote_weight = ($t_available_votes > $t_voting_max_vote_weight)?$t_voting_max_vote_weight:$t_available_votes;
+	
+	return $t_voting_max_vote_weight;
+}
+
+/**
+ * vote_updates_counters_for_issue
+ * updates a given issue/bug vote weight counters
+ * should be called post any changes to votes on an issue
+ * 
+ * @param integer $p_issue_id
+ */
+function vote_updates_counters_for_issue( $p_issue_id )
+{
+	$c_issue_id   	= db_prepare_int( $p_issue_id );
+	$t_issue_table	= db_get_table( 'mantis_bug_votes_table' );
+	
+	$t_bug = bug_get( $p_issue_id );
+	
+	$query 	= "SELECT COUNT(*) as voteCount
+	          	FROM $t_issue_table
+	          	WHERE issue_id=" . db_param();
+	$t_count_result	= db_query_bound( $query, Array( $c_issue_id ) );
+	$t_bug->votes_num_voters = (int)$t_count_result->fields['voteCount'];
+	
+	$query 	= "SELECT SUM(weight) as voteWeight
+	          	FROM $t_issue_table
+	          	WHERE weight > 0 AND issue_id=" . db_param();
+	$t_positive_result	= db_query_bound( $query, Array( $c_issue_id ) );
+	$t_bug->votes_positive = (int)$t_positive_result->fields['voteWeight'];
+	          	
+	$query 	= "SELECT SUM(weight) as voteWeight
+	          	FROM $t_issue_table
+	          	WHERE weight < 0 AND issue_id=" . db_param();
+	$t_negative_result	= db_query_bound( $query, Array( $c_issue_id ) );
+	$t_bug->votes_negative = (int)$t_negative_result->fields['voteWeight'];
+	
+	bug_update( $p_issue_id, $t_bug, false, true );
+}
Index: css/default.css
===================================================================
--- css/default.css	(revision 5500)
+++ css/default.css	(working copy)
@@ -165,3 +165,28 @@
 
 .progress400				{ position: relative; width: 400px; border: 1px solid #d7d7d7; margin-top: 1em; margin-bottom: 1em; padding: 1px; }
 .progress400 .bar			{ display: block; position: relative; background: #6bba70; text-align: center; font-weight: normal; color: #333; height: 2em; line-height: 2em; }
+
+table.bugList
+{
+	width: 100%; border: solid 1px #000;
+	margin:0;
+	margin-bottom: 16px;
+	caption-side: top;
+	
+}
+table.bugList caption
+{
+	font-weight:bold; 
+	font-style:italic;
+	text-align:left; 
+	border: 1px solid #000; 
+	border-bottom:0; 
+	padding: 4px; 
+	margin-top:16px;
+}
+table.bugList tfoot tr td
+{
+	background-color: #ccc;
+	text-align: right;
+}
+
Index: lang/strings_english.txt
===================================================================
--- lang/strings_english.txt	(revision 5500)
+++ lang/strings_english.txt	(working copy)
@@ -318,6 +318,8 @@
 $MANTIS_ERROR[ERROR_SESSION_VAR_NOT_FOUND] = 'Session variable \'%s\' not found.';
 $MANTIS_ERROR[ERROR_FORM_TOKEN_INVALID] = 'Invalid form security token.  Did you submit the form twice by accident?';
 $MANTIS_ERROR[ERROR_INVALID_REQUEST_METHOD] = 'This page cannot be accessed using this method.';
+$MANTIS_ERROR[ERROR_VOTING_OVER_LIMIT] = 'not allowed to vote more than your limit, or have a vote with weight 0';
+$MANTIS_ERROR[ERROR_VOTING_NOT_ENABLED] = 'voting is not enabled';
 
 $s_login_error = 'Your account may be disabled or blocked or the username/password you entered is incorrect.';
 $s_login_cookies_disabled = 'Your browser either doesn\'t know how to handle cookies, or refuses to handle them.';
@@ -641,6 +643,7 @@
 
 # bug_vote_add.php
 $s_vote_added_msg = 'Vote has been added...';
+$s_vote_removed_msg = 'Vote has been removed...';
 
 # bugnote_add.php
 $s_bugnote_added_msg = 'Note added...';
@@ -1519,6 +1522,30 @@
 $s_copy_columns_from = 'Copy Columns From';
 $s_copy_columns_to = 'Copy Columns To';
 
+# Voting
+$s_vote_cast_button = 'Cast Vote:';
+$s_vote_delete_button = 'Delete My Vote';
+$s_bugvote_added = 'Vote Added';
+$s_bugvote_deleted = 'Vote Deleted';
+$s_votes_positive = 'Votes Positive';
+$s_votes_negative = 'Votes Negative';
+$s_voted_by = 'Voted By';
+$s_vote_balance = 'Vote Balance';
+$s_vote_num_voters = '# Voters';
+$s_votes_remain = 'votes remaining';
+$s_votes_used = 'votes used';
+$s_voting_this_issue = 'Users voting for this issue';
+$s_votes_num_voters = 'Number of Voters';
+$s_my_votes = 'My Votes';
+$s_own_voted = 'Issues you have voted for:';
+$s_voting_hide = 'Hide Resolved';
+$s_voting_show = 'Show All';
+$s_vote_weight = 'Vote Weight';
+$s_no_votes = 'No votes available';
+$s_voted_and_assigned = 'You voted for this issue and it is now being worked on. You will be able to reuse the voting credits you spent on this issue once it is resolved.';
+$s_voted_and_resolved = 'You voted for this issue, and it is now resolved. Your voting credits have been returned to you.';
+$s_vote_unlimited = 'unlimited';
+
 # due date
 $s_due_date = "Due Date";
 $s_overdue = "Overdue";
Index: view_all_set.php
===================================================================
--- view_all_set.php	(revision 5500)
+++ view_all_set.php	(working copy)
@@ -190,6 +190,14 @@
 		$f_user_monitor = gpc_get_string( FILTER_PROPERTY_MONITOR_USER_ID, META_FILTER_ANY );
 		$f_user_monitor = array( $f_user_monitor );
 	}
+	
+	$f_user_votes = array();
+	if ( is_array( gpc_get( 'user_votes', null ) ) ) {
+		$f_user_votes = gpc_get_string_array( 'user_votes', META_FILTER_ANY );
+	} else {
+		$f_user_votes = gpc_get_string( 'user_votes', META_FILTER_ANY );
+		$f_user_votes = array( $f_user_votes );
+	}
 
 	$f_note_user_id = array();
     if ( is_array( gpc_get( FILTER_PROPERTY_NOTE_USER_ID, null ) ) ) {
@@ -428,6 +436,7 @@
 				$t_setting_arr[ FILTER_PROPERTY_TARGET_VERSION ] 		= $f_target_version;
 				$t_setting_arr[ FILTER_PROPERTY_PRIORITY_ID ] 			= $f_show_priority;
 				$t_setting_arr[ FILTER_PROPERTY_MONITOR_USER_ID ] 		= $f_user_monitor;
+				$t_setting_arr['user_votes'] 											= $f_user_votes;
 				$t_setting_arr[ FILTER_PROPERTY_VIEW_STATE_ID ] 		= $f_view_state;
 				$t_setting_arr['custom_fields'] 						= $f_custom_fields_data;
 				$t_setting_arr[ FILTER_PROPERTY_SHOW_STICKY_ISSUES ] 	= $f_sticky_issues;
@@ -482,6 +491,7 @@
 				$t_setting_arr[ FILTER_PROPERTY_TARGET_VERSION ]	= array( META_FILTER_ANY );
 				$t_setting_arr[ FILTER_PROPERTY_MONITOR_USER_ID ] 	= array( META_FILTER_ANY );
 				$t_setting_arr[ FILTER_PROPERTY_NOTE_USER_ID ]  	= array( META_FILTER_ANY );
+				$t_setting_arr['user_votes'] 	= array( META_FILTER_ANY );
 				$t_setting_arr[ FILTER_PROPERTY_RELATIONSHIP_TYPE ] = -1;
 				$t_setting_arr[ FILTER_PROPERTY_RELATIONSHIP_BUG ] 	= 0;
 
voting for r5500.patch (67,322 bytes)
cornchips

cornchips

2008-08-06 03:41

reporter   ~0019057

Just added a new patch for r5500, incorporating the feedback received. Will continue to make some more small tweaks, but would appreciate a new review.

giallu

giallu

2008-08-06 03:48

reporter   ~0019059

My 2 cents.

Since we are still in alpha state for 1.2, if you are committed to continuing tweaking/fixing the code, I'd rather commit the code as is so we can actually start using it and see how we like it.

BTW, on new files we agreed the first copyright line:

Copyright (C) 2000 - 2002 Kenzaburo Ito - kenito@300baud.org

should not be added

Victor, are you okay with this plan?

vboctor

vboctor

2008-08-22 19:55

manager   ~0019211

I did a quick review, here are some more comments:

Functional:

  • The patch implements a more complex voting model than what is spec'd in the requirements. I recommend we cut the features below to simplify the voting feature and how it can be used. I really think voting should be as simple as thumbs up/down and a number. This is the trend that is following in almost all other voting applications that I have seen. The features are:
  • Limits on number of votes per user.
  • Weights of voting.
  • Maximum weights per access level.
  • Weight default.

Some more minor code review comments:

config_default_inc.php

  • Voting per project. Note that all the settings should be settable per project. I think it makes sense to get rid of this configuration option and have the admin setup a global default config that applies per project, or different config per project.

core/my_view_inc.php

  • 'user_votes' should be changed to FILTER_PROPERTY_VOTES_USER_ID

core/html_api

  • Use voting_can_vote() / voting_is_enabled() rather than accessing voting configuration directly.

core/votes_api.php

  • You check for user/bugs exist by checking that their id is greater than 0. You should use bug_exists() / user_exists().
  • In several APIs you have the API name as the first line of comments. Shouldn't these be removed?
  • vote_can_vote() should pass the $p_user_id to the config_get() call. Same for other vote APIs like vote_can_view_vote_details() / vote_max_votes() / check the rest.
  • vote_can_vote() do we want to have this check that the voting is ON? This way other places check only one API? This is assuming we cache the result of vote_is_enabled().
vboctor

vboctor

2008-08-22 19:58

manager   ~0019212

I'm not sure if emails are being sent for this issue, since it seems that bugnote_add fails silently when it is preparing the emails.

I recommend that the filtering code be review by daryn. Can be found on the IRC channel, or myself / giallu will let him know when we meet him there.

ViperMaul

ViperMaul

2008-09-15 01:11

reporter   ~0019396

It seems we are so close and yet so far. What is the status?

I like what giallu had to say, but I do not know who is in charge.
Some of us are probably willing to tweak whatever we get as long as we get something to start with. We do not know how long we get the services of much appreciative cornchips. I also appreciate what vboctor has to say about simplification. However, since we have been waiting this long I was hoping for a release now and then simplify later. Either way who cares what I think. My main goal is keep the ball rolling here.

May I ask the current status?

grangeway

grangeway

2008-09-19 18:59

reporter   ~0019415

i'm bored atm so trying to see how easy it would be to make this a plugin within the 1.2 plugin architecture.

However, one of the things I think we're lacking atm and would need adding is plugin events on DELETE.

Paul

ViperMaul

ViperMaul

2008-12-30 15:55

reporter   ~0020501

How is the status on this looking for the new year?

ViperMaul

ViperMaul

2009-03-05 15:43

reporter   ~0021007

Last edited: 2009-03-05 15:43

Checking the status on this.
Have we forgotten about this one?

giallu

giallu

2009-03-05 15:58

reporter   ~0021008

I did not...
I have a local git branch where I applied the patch and I am trying to work out a dependable solution.

Other things prevented me to really finish the code, but given the inertia we are seeing for a 1.2 release, chances are I'll make it in time...

dhaun

dhaun

2009-05-06 17:08

reporter   ~0021785

I see this has been pushed back even further (post-1.2).

Could someone kindly explain what's holding this up? There is a patch and people have apparently been working on integrating it. So what's the problem?

c_schmitz

c_schmitz

2009-05-06 17:43

reporter   ~0021786

dhaun, thanks for putting more money on this. It's only been 2 years since I put my sponsoring of 100$. Note the sarcasm.

cornchips

cornchips

2009-05-22 03:25

reporter   ~0021874

OK, I have some more time to spend on this, should I just pickup where I left off or has further work been done to the patch?

Re: advanced functionality, all extra options like weights can be defaulted to off, no-one need know they exist unless they look for them. It's very simple to set vote weight options to just +1 and -1

giallu

giallu

2009-05-22 03:46

reporter   ~0021875

@ cornchips
I'm working on your patch right now, if you want to help I can push somewhere (github?) the branch I'm hacking on.

cornchips

cornchips

2009-05-22 05:09

reporter   ~0021879

Last edited: 2009-05-22 05:44

View 2 revisions

Yep github will be fine (user name is lukemoynihan), do you have a list of things you are working on? and what needs to be done to get this committed into mantis?

giallu

giallu

2009-05-22 06:03

reporter   ~0021881

well, what I'm doing is to move your original vote_api to a Mantis_Vote class using the new coding style we discussed in the -dev ML.

The first step is to adapt the rest of the code to make the correct calls, that's mostly search and replace.

Then we need some testing (I hope those subscribed to this bug will help there) before pushing this first implementation.

Afterwards, we'd need to make up some smarter use of the Vote class (right now it's just a collection of static methods).

Have you got any items left on your previous to-do list to add up?

red_spirit

red_spirit

2009-05-27 09:24

reporter   ~0021937

Hello people. Plz explain how to install this option(vote)?
i cant find info about
thanks

giallu

giallu

2009-05-27 17:46

reporter   ~0021945

So, who wants to check the current state of affairs can download the code as a zip/tar or by cloning the repo at:

http://github.com/giallu/mantisbt/tree/votes_refactor

expect more changes to land there

vboctor

vboctor

2009-05-28 00:16

manager   ~0021946

Giallu, I downloaded the tarball from the above git repository and got the following error. Also cloning the repo produces an empty folder with just .git.

SYSTEM WARNING: include_once(Mantis/Vote.php) [function.include-once]: failed to open stream: No such file or directory

SYSTEM WARNING: include_once() [function.include]: Failed opening 'Mantis/Vote.php' for inclusion (include_path='/var/www/html/votes/v/core:/var/www/html/votes/v/library:/var/www/html/votes/v/application:.:/usr/share/pear:/usr/share/php')

giallu

giallu

2009-05-28 03:22

reporter   ~0021949

Victor, sorry for the massive stupidity. Now I pushed some missing commits.

If you're cloning be sure to checkout the votes_refactor branch like:

git checkout -b votes --track origin/votes_refactor

vboctor

vboctor

2009-05-28 03:46

manager   ~0021950

Here are some initial comments:

  1. votes_total column doesn't seem to be available.
  2. Negative votes are not accounted properly on the issue view page, or view issues page.
  3. When a vote issue on an issue, we should consider allowing delete + add, or modify directly. Modifying to 0 will also delete.
  4. The user name is the issue history is hyperlinked, where the reporter name and user names in the voting are not.

More to come later.

giallu

giallu

2009-05-28 06:20

reporter   ~0021952

2 and 4 are now fixed.

For 1, I found out we're not adding that column, but instead making that up for filters (grep for REVIEW in filter_api).

Do you see any value in displaying the positive and negative votes count? Right now I'm leaning toward removing the two fields and replace them with just the votes_total column. Same goes for the total number of voters.

cornchips

cornchips

2009-05-28 07:15

reporter   ~0021954

Please please please don't get rid of the differentiation between positive and negative votes, if combined you might see an issue with a vote score of 3 not realizing that actually it has a positive count of 30 and a negative count of 27.

For example there has been plenty of duplicates of this issue (668), some suggested the idea of modifying sponsorship, you might get lots of positive votes because people want voting, while you also get lots of negative votes because people dont like the implementation of modifying sponsorship to incorporate voting. If I can only see a total vote score I will never know how important this issue is.

giallu

giallu

2009-05-28 18:35

reporter   ~0021965

I'm not trying to dumb down the feature but what I'm examining is the value of the added colums, and even if we change them we can still pull the +/- details from the votes table.

As I see it, the most information comes from the voters+overall value pair: a bug on +3 with 5 votes means 4 of 5 users voted +1. The same +3 in your example (30-27) imply a more controversial item.

In any case, I'm pretty sure I'll not base implementation choices on user's votes :P

vboctor

vboctor

2009-05-29 13:49

manager   ~0021967

Are we going to be adding a view like the following? I think it really adds a lot of value for this feature.
http://brainstorm.ubuntu.com/

The above tool also has the concept of "Don't Care", I wonder what people think of this. I'm not sure how I would use it.

I'll look further into the code changes / requirements / etc to provide my feedback on the columns.

vboctor

vboctor

2009-05-30 02:27

manager   ~0021972

Here are some more comments:

  1. voting_enabled doesn't hide the voting table from the user page.
  2. voting_enabled doesn't hide the voting history entries (I think that is OK).
  3. voting_enabled doesn't hide the "Voted By" filter UI.
  4. voting_enabled doesn't remove the Voted By query from the applied filter.
  5. When I use $g_voting_default_num_votes while logged in as ADMIN, the votes for REPORTER are used rather than ADMINISTRATOR or DEVELOPER.
  6. $g_default_notify_flags should support defining the email notifications criteria for voters (same way like reporters, bugnote authors, etc).
  7. Email notifications should make email notification on voting configurable (optional). By default it would be OFF anyway.
  8. Do we want to allow different budget for positive vs. negative votes? For example, I could see admins providing users with more negative votes budget than positive budget. One way to configure this would be through a multiplier. Negative/Positive = 2.
vboctor

vboctor

2009-05-30 02:58

manager   ~0021973

Last edited: 2009-05-30 03:43

View 2 revisions

  1. In the API, lets discuss offline the directory structure, I don't understand the use of the Mantis folder level under application.
  2. For the APIs that take in a project and a user, I think we typically put the user first then the project. Although I would expect the caller will either provide both or skip both.
  3. When calling config_get(), make sure to pass in the user / project, specially when they are passed as parameters.
  4. Classes should through exceptions rather than trigger errors.
  5. I think the votes area in the View Issue page takes too much space and the background for the vote counts value cell is not correct.
  6. In pages like bug_vote_delete.php, ideally we should move the access check to the API. We either have the check in the API and throw an exception, or at least move the retrieval of the appropriate access level to the API.
  7. Update the Docbook manual.
  8. Update Soap API to return the extra fields as part of the issue data.
  9. Unit tests? would be nice - at least to ensure that the class is testable.
grangeway

grangeway

2009-05-31 12:16

reporter   ~0022003

Shouldn't this be as a plugin? (see my note 0019415 for what I thought was missing from event api to stop this atm).

That way people can implement additional criteria/versions for voting. For example the MIS system vendor we use at work often asks for people to vote on change requests. They give you 100 votes to use, you can link them against any CR's that have been raised. At the end of the process, they go through the CR's with the most votes and implement them (or report back as to why they won't implement them). This works quite well as you can't vote for everything, so need to think about what you do want to vote for.

Given the r5500 patch, if we avoided storing the vote count in the bug table ( with the correct indexes, I'd expect a db calculating the vote count to be fast - there's going to be less entries in the vote table then the bug_history table), then there's nothing that stops this from being implemented completely as a plugin. (apart from the missing DELETE event)

giallu

giallu

2009-06-01 05:30

reporter   ~0022021

No, thanks, I can live without plugins...

giallu

giallu

2009-06-06 18:10

reporter   ~0022055

regarding 0000668:0021967, yes I think that's easily doable once the backend is in place

Hauptmann

Hauptmann

2010-02-09 04:30

reporter   ~0024352

What is the state of this issue?
We would find it very useful to have polls on the solution of an issue.

At our company using mantis the following practice is used: if a solution has to be discussed all users a message is send and they have to add comments to the respective issue indicating their preferences. This is difficult procedure since it tends to lengthy debating rather than finding a quick solution.

We are currently thinking about using media wiki for polls. This is of course not the preferred solution since we then have yet another system.

djcarr

djcarr

2010-06-07 01:30

reporter   ~0025716

My company has requested an ideas discussion site and Mantis ticks every box except for a like/dislike voting system. For an issue with say 8 votes you could then see a list of the 10 users that liked it and the 2 users that disliked it, and this information would stimulate further discussion about the value of an issue.

YoshikoFazuku

YoshikoFazuku

2010-10-06 12:25

reporter   ~0026981

Last edited: 2010-10-06 12:28

View 2 revisions

it took 10 years to get to this point? WTF!

citizen

citizen

2011-03-01 15:30

reporter   ~0028334

Suggestion: Allow vote (+,0,-) on both Issues and on Notes.

Also, allow unlimited number of votes (by default but optional?), because this allows expressing opinion on all proposed issues.

reesd

reesd

2011-09-02 09:43

reporter   ~0029625

+1. I would save a comment and just vote, but...

cas

cas

2011-09-30 07:44

reporter   ~0029891

Think this will only happen if someone picks up the challenge to create a plugin( despite 0000668:0022021 )

vincent_sels

vincent_sels

2012-02-08 14:49

reporter   ~0031199

We need something like this at our company, so I think I'll have a go at a plugin that does something this... :)

rombert

rombert

2012-02-09 04:05

developer   ~0031205

(In reply to comment 0000668:0031199)

We need something like this at our company, so I think I'll have a go at a
plugin that does something this... :)

IIRC there is a plugin 'out there' which does that. I've seen it on a public Mantis instance, but I don't recall exactly on which one.

Footkerchief

Footkerchief

2012-03-23 16:03

reporter   ~0031517

Does anyone have a link to the plugin? I haven't been able to find it. Thanks!

rombert

rombert

2012-03-26 04:15

developer   ~0031530

Plugin source code: http://git.mantisforge.org/w/GaugeSupport.git
A live instance using the plugin: http://www.arcengames.com/mantisbt/plugin.php?page=GaugeSupport/issue_ranking

Looks quite nice IMO

atrol

atrol

2013-08-16 12:41

developer   ~0037882

Removed assignment. giallu will not contribute to this issue in near future.

Wimpie

Wimpie

2017-09-06 04:46

reporter   ~0057620

Is there any update on this one? I would want this for reporters to be able to vote on feature requests. Could also be used to indicate a certain bug is happening to them as well (nothing is more annoying than having to link duplicates and close the duplicate, much like this issue).

Has there been anything similar developed in the meantime? (I see the alst update is from Jan 2014) For the record, we're still using mantisbt 1.2.19. Updating to the latest version is still something we have to look into, but are definitely interested in.

cas

cas

2017-09-10 05:20

reporter   ~0057650

I have updates this plugin vor version 2, see http://www.nuy.info/mantis2/view.php?id=8

dregad

dregad

2017-09-11 06:39

developer   ~0057674

@cas it may be a good idea to release this plugin (and surely, the others you authored too) as Github repositories in the mantisbt-plugins organization. Feel free to contact me to discuss if needed.

cas

cas

2017-09-11 14:38

reporter   ~0057684

@dregad you are right about that, I really should. Will try to dive into that shortly ( my poor excuse is lack of time)

nanzkie

nanzkie

2017-09-12 20:27

reporter   ~0057708

sorry i can't edit my my post.

@cas

i tried to used the plugin but encounter error when submitting vote.

i fixed it by editing the code in gauge_form.php in line 77

from
<form name="voteadding" method="post" action="plugins/GaugeSupport/pages/submit_support.php">

to
<form name="voteadding" method="post" action="<?php echo plugin_page('submit_support') ?>">

also in submit_support.php comment out line 2.
no need to require core.php since i used plugin_page in gauge_form.php

Issue History

Date Modified Username Field Change
2004-07-20 07:23 lantic Note Added: 0006172
2004-07-20 07:27 lantic Note Added: 0006173
2004-07-20 07:58 bblan Note Added: 0006174
2004-07-20 08:34 lantic Note Added: 0006175
2004-07-23 17:02 grangeway Relationship added has duplicate 0003717
2004-08-02 14:35 lantic Note Added: 0006561
2004-08-02 18:09 vboctor Relationship added related to 0004143
2004-08-03 16:10 lantic Note Added: 0006606
2004-08-03 16:12 lantic Note Edited: 0006606
2004-08-04 01:09 sgrund Note Added: 0006616
2004-08-05 01:33 sly Sponsorship Added sly: US$ 5
2004-08-05 01:33 sly Sponsorship Total 0 => 5
2004-09-23 19:02 razortt Note Added: 0007726
2004-09-24 04:34 lantic Note Added: 0007731
2004-09-24 07:09 minibbjd Note Added: 0007732
2004-09-24 07:10 minibbjd Note Edited: 0007732
2004-09-24 07:27 minibbjd Note Edited: 0007732
2004-10-06 13:50 stevemagruder Note Added: 0007937
2005-08-29 22:20 cornchips Note Added: 0011311
2005-12-29 13:47 xstaindx Note Added: 0011851
2005-12-29 13:49 xstaindx Note Edited: 0011851
2006-01-02 13:02 xstaindx Sponsorship Added xstaindx: US$ 10
2006-01-02 13:02 xstaindx Sponsorship Total 5 => 15
2006-04-17 11:17 grangeway Relationship added has duplicate 0005867
2006-08-11 23:18 c_schmitz Sponsorship Added c_schmitz: US$ 15
2006-08-11 23:18 c_schmitz Sponsorship Total 15 => 30
2006-09-25 09:18 johnwebbcole Note Added: 0013502
2007-03-10 17:14 c_schmitz Sponsorship Updated c_schmitz: US$ 85
2007-03-10 17:14 c_schmitz Sponsorship Total 30 => 100
2007-03-10 17:15 c_schmitz Sponsorship Updated c_schmitz: US$ 100
2007-03-10 17:15 c_schmitz Sponsorship Total 100 => 115
2007-03-10 17:16 c_schmitz Note Added: 0014166
2007-03-24 19:46 vboctor Assigned To prescience => vboctor
2007-03-24 19:50 vboctor Note Added: 0014239
2007-03-24 19:52 vboctor Relationship added has duplicate 0007707
2007-08-28 13:13 c_schmitz Note Added: 0015549
2008-02-19 17:54 ingorenner Note Added: 0017114
2008-02-19 19:34 CADbloke Note Added: 0017115
2008-02-20 05:14 ingorenner Note Added: 0017118
2008-02-20 05:41 c_schmitz Note Added: 0017119
2008-02-20 05:42 c_schmitz Note Edited: 0017119
2008-02-20 05:43 c_schmitz Note Edited: 0017119
2008-02-20 07:38 giallu Note Added: 0017123
2008-02-20 11:46 xstaindx Note Added: 0017129
2008-02-20 12:27 giallu Note Added: 0017130
2008-03-01 02:30 cornchips Note Added: 0017228
2008-03-01 02:41 cornchips Note Edited: 0017228
2008-03-01 02:53 giallu Note Added: 0017229
2008-03-01 19:49 cornchips Note Added: 0017236
2008-03-02 00:34 vboctor Note Added: 0017237
2008-03-09 03:25 vboctor Note Added: 0017299
2008-03-09 03:26 vboctor Note Edited: 0017299
2008-03-09 21:08 cornchips Note Added: 0017302
2008-03-16 03:19 vboctor Relationship added has duplicate 0008907
2008-03-26 17:28 vboctor Note Added: 0017463
2008-03-26 17:40 vboctor Note Added: 0017464
2008-03-26 18:39 cornchips Note Added: 0017467
2008-04-06 05:34 cornchips File Added: voting for r5156.txt
2008-04-06 05:40 cornchips Note Added: 0017557
2008-04-06 06:14 c_schmitz Note Added: 0017558
2008-04-17 22:13 vboctor Note Added: 0017620
2008-04-18 12:46 vboctor Tag Attached: patch
2008-04-20 06:02 cornchips File Added: voting for r5176.patch
2008-04-20 06:08 cornchips Note Added: 0017628
2008-04-23 12:22 c_schmitz Note Added: 0017664
2008-05-13 12:58 ViperMaul Note Added: 0017821
2008-05-16 08:27 c_schmitz Note Added: 0017852
2008-05-16 10:06 giallu Note Added: 0017853
2008-05-23 15:27 c_schmitz Note Added: 0017909
2008-05-29 00:19 ViperMaul Note Added: 0017933
2008-05-29 02:39 vboctor Note Added: 0017938
2008-05-29 02:39 vboctor Target Version => 1.2.0a2
2008-05-31 02:19 vboctor Note Added: 0017968
2008-05-31 23:02 cornchips Note Added: 0017976
2008-06-17 00:17 ViperMaul Note Added: 0018112
2008-06-17 20:35 c_schmitz Note Added: 0018125
2008-06-23 01:47 vboctor Note Added: 0018158
2008-06-27 07:35 mgeck Note Added: 0018199
2008-07-12 02:01 vboctor Target Version 1.2.0a2 => 1.2.0
2008-07-12 18:19 giallu Target Version => 1.2.0
2008-07-13 02:45 vboctor Relationship added has duplicate 0003624
2008-07-22 13:09 c_schmitz Note Added: 0018635
2008-07-23 05:45 cornchips Note Added: 0018644
2008-07-27 04:23 cornchips Note Added: 0018676
2008-07-27 13:40 c_schmitz Note Added: 0018679
2008-07-28 07:07 cornchips Note Added: 0018702
2008-07-28 07:08 cornchips Note Edited: 0018702
2008-07-28 11:04 giallu Note Added: 0018720
2008-07-30 19:48 grangeway Relationship added has duplicate 0006326
2008-08-06 03:37 cornchips File Added: voting for r5500.patch
2008-08-06 03:41 cornchips Note Added: 0019057
2008-08-06 03:48 giallu Note Added: 0019059
2008-08-07 04:49 giallu Relationship added has duplicate 0005551
2008-08-22 19:55 vboctor Note Added: 0019211
2008-08-22 19:58 vboctor Note Added: 0019212
2008-09-15 01:11 ViperMaul Note Added: 0019396
2008-09-19 18:59 grangeway Note Added: 0019415
2008-12-09 10:23 grangeway Note View State: 18720: private
2008-12-30 15:55 ViperMaul Note Added: 0020501
2009-03-05 15:43 ViperMaul Note Added: 0021007
2009-03-05 15:43 ViperMaul Note Edited: 0021007
2009-03-05 15:58 giallu Note Added: 0021008
2009-05-04 14:27 siebrand Target Version 1.2.2 => 1.x.x
2009-05-06 17:05 dhaun Sponsorship Added dhaun: US$ 25
2009-05-06 17:05 dhaun Sponsorship Total 115 => 140
2009-05-06 17:08 dhaun Note Added: 0021785
2009-05-06 17:43 c_schmitz Note Added: 0021786
2009-05-22 03:25 cornchips Note Added: 0021874
2009-05-22 03:46 giallu Note Added: 0021875
2009-05-22 03:49 vboctor Assigned To vboctor => giallu
2009-05-22 05:09 cornchips Note Added: 0021879
2009-05-22 05:44 cornchips Note Edited: 0021879 View Revisions
2009-05-22 06:03 giallu Note Added: 0021881
2009-05-27 09:24 red_spirit Note Added: 0021937
2009-05-27 17:46 giallu Note Added: 0021945
2009-05-28 00:16 vboctor Note Added: 0021946
2009-05-28 03:22 giallu Note Added: 0021949
2009-05-28 03:46 vboctor Note Added: 0021950
2009-05-28 06:20 giallu Note Added: 0021952
2009-05-28 07:15 cornchips Note Added: 0021954
2009-05-28 18:35 giallu Note Added: 0021965
2009-05-29 13:49 vboctor Note Added: 0021967
2009-05-30 02:27 vboctor Note Added: 0021972
2009-05-30 02:58 vboctor Note Added: 0021973
2009-05-30 03:43 vboctor Note Edited: 0021973 View Revisions
2009-05-31 12:16 grangeway Note Added: 0022003
2009-06-01 05:30 giallu Note Added: 0022021
2009-06-06 18:10 giallu Note Added: 0022055
2009-07-03 22:39 vboctor Note View State: 18720: public
2010-02-09 04:30 Hauptmann Note Added: 0024352
2010-06-07 01:30 djcarr Note Added: 0025716
2010-10-06 12:25 YoshikoFazuku Note Added: 0026981
2010-10-06 12:28 YoshikoFazuku Note Edited: 0026981 View Revisions
2011-03-01 15:30 citizen Note Added: 0028334
2011-05-22 16:24 rombert Sponsorship Total 140 => 0
2011-09-02 09:43 reesd Note Added: 0029625
2011-09-30 07:44 cas Note Added: 0029891
2012-02-08 14:49 vincent_sels Note Added: 0031199
2012-02-09 04:05 rombert Note Added: 0031205
2012-03-23 16:03 Footkerchief Note Added: 0031517
2012-03-26 04:15 rombert Note Added: 0031530
2013-08-16 12:41 atrol Note Added: 0037882
2013-08-16 12:41 atrol Assigned To giallu =>
2013-08-16 12:41 atrol Status assigned => new
2014-01-21 15:57 atrol Target Version 1.3.0-beta.1 =>
2017-09-06 04:46 Wimpie Note Added: 0057620
2017-09-10 05:20 cas Note Added: 0057650
2017-09-11 06:39 dregad Note Added: 0057674
2017-09-11 14:38 cas Note Added: 0057684
2017-09-12 20:27 nanzkie Note Added: 0057708