View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000668 | mantisbt | feature | public | 2001-07-05 21:27 | 2021-11-15 10:30 |
Reporter | jkorab | Assigned To | |||
Priority | normal | Severity | feature | Reproducibility | unable to reproduce |
Status | new | Resolution | open | ||
Product Version | 0.15.0 | ||||
Summary | 0000668: 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. | ||||
Tags | patch | ||||
Attached Files | 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 ) . '"> </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 . '&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 . '&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 . '&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]' . '&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 . '&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 . '&show_status=' . FEEDBACK . '&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 . '&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 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 ) . '"> </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 . '&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 . '&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 . '&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]' . '&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 . '&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 . '&show_status=' . FEEDBACK . '&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 . '&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 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 ) . '"> </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 . '&' . 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 . '&' . 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 . '&' . 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]' . '&' . 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 . '&' . 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 . '&' . FILTER_PROPERTY_STATUS_ID . '=' . FEEDBACK . '&' . 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 . '&' . 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; | ||||
has duplicate | 0000697 | closed | prescience | New idea voting booth |
has duplicate | 0003717 | closed | grangeway | Allow for polls |
has duplicate | 0005867 | closed | grangeway | Vote for issue |
has duplicate | 0007707 | closed | vboctor | Vote and Validation Feature |
has duplicate | 0008907 | closed | vboctor | Limit maximum sponsorship with $g_maximum_sponsorship_amount in lieu of voting |
has duplicate | 0003624 | closed | vboctor | Add Counter, so that there is a column in "view_all_bug_page.php" that shows the hits per bug |
has duplicate | 0006326 | closed | grangeway | Inheritance of news to subprojects |
has duplicate | 0005551 | closed | giallu | Assign point contingents for users / user groups and start programming when certain threshold has been collected. |
related to | 0004143 | closed | grangeway | Add ability to limit total sponsorship fund by access level. |
Correct, I've disabled it for the time being. It'll get re-enabled eventaully, probably with some added niceties. |
|
Look forward to it. |
|
hmm i'wonder what is it good for? bugs are to be corrected not to be voted for:_) |
|
Read the docs! :) |
|
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.:-) |
|
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: 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? |
|
May make 0003890 redundant, if sponsorship can be made to work without money. |
|
from a reporter point of view, what's the difference between Priority and Votes? |
|
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. |
|
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. |
|
Voting Implementation Thoughts... User Interaction:
Database Changes Required:
Issues:
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.
edited on: 08-03-04 16:12 |
|
just my 2cents:
per project 2)Label: Vote ranking. (Should this be global or per project?) per project
I think the easiest aproach is: reduction takes place when "votes_allocated" where automaticali reduced because of closing issues.
quote from bluetooth in 0004143: 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) |
|
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. |
|
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. |
|
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. edited on: 09-24-04 07:10 edited on: 09-24-04 07:27 |
|
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. :) |
|
I would like to second the request for voting |
|
I would like to see voting implemented. is there any update on its status? |
|
+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 |
|
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. |
|
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. |
|
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? |
|
someone working on this or has a solution already? We'd really need that... |
|
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. |
|
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 |
|
I put the total money for the sponsorship on this issue almost a year ago. (100$). First money for this was bidden in 2006. 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? |
|
It's on my radar but with a low priority so any patch is really, really welcome. |
|
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. |
|
@xstaindx 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 |
|
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? |
|
@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 for a quicker integration Thank you very much in advance |
|
OK, I've started coding, about half the api and gui done. Will contact you on IRC when I have some questions. |
|
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. |
|
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: The interesting take aways from the above Ubuntu website are:
Would be interesting if we can integrate some of these ideas in our Mantis Voting implementation. |
|
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). |
|
Here is another example of a user feedback / brainstorming feature: @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. |
|
One more: |
|
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. |
|
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. |
|
That sounds really great. |
|
@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: 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. |
|
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! |
|
Thank you, cornchips. Your patch is very much appreciated. Will do! |
|
Dear vbdoctor or Mantis Developers, |
|
I second that question....? |
|
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 |
|
? |
|
vboctor, What are the chances this will make it into 1.2.0a2? |
|
I've set the target version to 1.2.0a2. I'll review the patch and try to re-attempt to apply it. |
|
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:
account_voting_page.php:
schema.php:
bug_vote_add.php:
bug_vote_delete.php
bug_vote_list_view_inc.php
config_defaults_inc.php
helper_api.php
vote_api.php
... more to come ... |
|
I'll have time to begin to review the list and start implementing changes on the 8th of June. |
|
Thanks vboctor and cornchips!! |
|
If this makes it into 1.2.0a2 I will add 50$ on top. |
|
@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? |
|
Thanks for your effort. This would be a great feature. |
|
@cornchips: any news? |
|
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. |
|
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. |
|
Sounds great :-). Looking forward! |
|
@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 |
|
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. |
|
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. |
|
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? |
|
I did a quick review, here are some more comments: Functional:
Some more minor code review comments: config_default_inc.php
core/my_view_inc.php
core/html_api
core/votes_api.php
|
|
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. |
|
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. May I ask the current status? |
|
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 |
|
How is the status on this looking for the new year? |
|
Checking the status on this. |
|
I did not... 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... |
|
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? |
|
dhaun, thanks for putting more money on this. It's only been 2 years since I put my sponsoring of 100$. Note the sarcasm. |
|
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 |
|
@ cornchips |
|
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? |
|
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? |
|
Hello people. Plz explain how to install this option(vote)? |
|
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 |
|
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') |
|
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 |
|
Here are some initial comments:
More to come later. |
|
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. |
|
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. |
|
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 |
|
Are we going to be adding a view like the following? I think it really adds a lot of value for this feature. 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. |
|
Here are some more comments:
|
|
|
|
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) |
|
No, thanks, I can live without plugins... |
|
regarding 0000668:0021967, yes I think that's easily doable once the backend is in place |
|
What is the state of this 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. |
|
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. |
|
it took 10 years to get to this point? WTF! |
|
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. |
|
+1. I would save a comment and just vote, but... |
|
Think this will only happen if someone picks up the challenge to create a plugin( despite 0000668:0022021 ) |
|
We need something like this at our company, so I think I'll have a go at a plugin that does something this... :) |
|
(In reply to comment 0000668:0031199)
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. |
|
Does anyone have a link to the plugin? I haven't been able to find it. Thanks! |
|
Plugin source code: http://git.mantisforge.org/w/GaugeSupport.git Looks quite nice IMO |
|
Removed assignment. giallu will not contribute to this issue in near future. |
|
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. |
|
I have updates this plugin vor version 2, see http://www.nuy.info/mantis2/view.php?id=8 |
|
@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. |
|
@dregad you are right about that, I really should. Will try to dive into that shortly ( my poor excuse is lack of time) |
|
sorry i can't edit my my post. i tried to used the plugin but encounter error when submitting vote. i fixed it by editing the code in from to also in |
|
Any update on that? It would be really nice to have this feature and\or plugin. |
|
It is available, see here: |
|
@cas that link seems to be password protected. What do I need to do to get this plugin so I can try it on our own instance? |
|
Here it is! |
|
@cas thanks for the link, but it seems very out-of date (copyright in the file from 2010). MantisBT UI says "outdated dependency" and shows this plugin as dependant of "MantisBT Core 1.2" (when current plugins depend on "MantisBT Core 2.0"). By quick googling found your git repo (https://github.com/mantisbt-plugins/GaugeSupport/) with a bit more up-to-date version of the plugin, but it also seems like not functional. I was able to install the plugin from UI, and I can see new tab "Issue ranking support", but when I click on it, it redirects me back to "my view" /my_view_page.php. Also I see no any new buttons (I expect smth like +1 -1) on the issue page. Also checked your instance (https://www.nuy.info/mantis2/, one needs to register first) - it gives "application error, Database query failed" when try to click "Issue ranking support". And I also don't see any new buttons on the issue page. My MantisBT version is 2.16.0 / Schema Version 209, should be the latest one. |
|
Same issues here... I have no rating buttons, and Issue Ranking just refreshes the page. Thanks for keeping this alive though... voting buttons should be a given inclusion with issue trackers. |
|
This has to do with a SQL setting. |
|
Released version 2.03 with some fixes (but no MeToo button) |
|
@cas instead of uploading a zip file here, it would be more appropriate to update the source code repository at https://github.com/mantisbt-plugins/GaugeSupport |
|
I should have done that to start with. |
|
@cas following up on your note 0022323:0066028 (posting this here to avoid hijacking 0022323)
Not sure if you're aware, but I've released an updated GaugeSupport (2.5.0) in May last year, fixing that and a number of other issues. |
|
@dregad, thanks for this update, just downloaded that version. (in the meantime updated the other plugin to 2.04 taking care of the mismatches) |
|