View source
<?php
function advpoll_info_ranking() {
return array(
'name' => 'ranking',
'name_label' => t('Ranking'),
'description' => t('Rank a number of choices.'),
);
}
function advpoll_algorithms_ranking() {
return array(
'borda_count' => t('Borda count'),
'instant_runoff' => t('Instant runoff'),
);
}
function advpoll_voting_ranking_form(&$node, $teaser, $page, $status) {
static $ranking_form_count = 0;
$form = array(
'#id' => 'advpoll_voting_ranking_form-' . $ranking_form_count++,
'#attributes' => array(
'class' => 'advpoll-vote',
),
);
$form['ajax'] = array(
'#type' => 'hidden',
'#attributes' => array(
'class' => 'ajax',
),
);
if ($node->choice) {
$list = array();
$num_choices = count($node->choice);
$choices[0] = '--';
for ($i = 1; $i <= $num_choices; $i++) {
if ($i == 1) {
$val = t('1st');
}
elseif ($i == 2) {
$val = t('2nd');
}
elseif ($i == 3) {
$val = t('3rd');
}
else {
$val = t($i . 'th');
}
$choices[$i] = $val;
}
array(
t('4th'),
t('5th'),
t('6th'),
t('7th'),
t('8th'),
t('9th'),
t('10th'),
t('11th'),
t('12th'),
t('13th'),
t('14th'),
t('15th'),
);
$form['choice'] = array(
'#tree' => TRUE,
'#type' => 'checkboxes',
'#prefix' => '<div class="vote-choices">',
'#suffix' => '</div>',
);
$check = $node->in_preview;
foreach ($node->choice as $key => $choice) {
if ($choice['label'] && ($node->show_writeins || !$choice['writein'])) {
$form['choice'][$key] = array(
'#type' => 'select',
'#title' => _advpoll_choice_markup($choice['label'], $node->format, $check) . ($choice['writein'] ? ' ' . t('(write-in)') : ''),
'#options' => $choices,
);
}
}
if ($node->writeins && user_access('add write-ins')) {
$form['choice'][$key + 1] = array(
'#type' => 'select',
'#title' => t('(write-in)'),
'#options' => $choices,
);
$form['writein_key'] = array(
'#type' => 'value',
'#value' => $key + 1,
);
}
}
if ($node->writeins && user_access('add write-ins')) {
$form['writein_choice'] = array(
'#prefix' => '<div class="writein-choice">',
'#suffix' => '</div>',
'#type' => 'textfield',
'#title' => t('Write-in vote'),
'#size' => 25,
);
}
$form['nid'] = array(
'#type' => 'hidden',
'#value' => $node->nid,
'#attributes' => array(
'class' => 'edit-nid',
),
);
if (!$node->in_preview && advpoll_eligible($node) && $status == 'open') {
static $ranking_vote_count = 0;
$form['vote'] = array(
'#type' => 'submit',
'#value' => t('Vote'),
'#attributes' => array(
'id' => 'edit-vote-rank-' . $ranking_vote_count++,
),
);
}
elseif ($node->in_preview) {
}
elseif ($status == 'pending') {
$form['message']['#value'] = t('This poll opens @time.', array(
'@time' => format_date($node->start_date),
));
}
else {
global $user;
$login_message = t('<a href="@login">Login</a> to vote in this poll.', array(
'@login' => url('user/login', drupal_get_destination()),
));
$form['message']['#value'] = $user->uid ? t('You are not eligible to vote in this poll.') : $login_message;
}
$form['#action'] = url('node/' . $node->nid);
return $form;
}
function advpoll_view_results_ranking($node, $teaser, $page) {
$results = votingapi_get_voting_results('advpoll', $node->nid);
$round_table = '';
if (!empty($results)) {
$ranking_list = array();
$ranking = array();
$choices = array();
$poll = array();
$rounds = array();
foreach ($results as $result) {
$tag = $result->tag;
if ($tag == '_advpoll') {
$poll[$result->function] = $result->value;
}
else {
if (strstr($tag, '_rounds_')) {
$round = str_replace('_rounds_', '', $tag);
if (!isset($rounds[$round])) {
$rounds[$round] = array();
}
$rounds[$round][$result->function] = $result->value;
}
else {
if (isset($node->choice[$tag])) {
if ($result->function == 'ranking') {
$ranking_list[$result->value][] = $tag;
}
else {
if (!isset($node->choice[$result->function])) {
$choices[$tag][$result->function] = $result->value;
}
}
}
}
}
}
foreach ($ranking_list as $i => $choice_list) {
$ranking[$i]->choices = array();
foreach ($choice_list as $choice_i) {
$ranking[$i]->choices[] = $choice_i;
$ranking[$i]->view_score = $choices[$choice_i]['view_score'];
$ranking[$i]->raw_score = $choices[$choice_i]['raw_score'];
if ($choices[$choice_i]['percentage']) {
$ranking[$i]->percentage = $choices[$choice_i]['percentage'];
}
}
}
if ($node->algorithm == 'borda_count') {
for ($i = 0; $i < count($ranking); $i++) {
$first_one = TRUE;
$this_rank = '';
foreach ($ranking[$i]->choices as $choice) {
$label = isset($node->choice[$choice]) ? _advpoll_choice_markup($node->choice[$choice]['label'], $node->format, FALSE) . ($node->choice[$choice]['writein'] ? ' ' . t('(write-in)') : '') : t('(deleted)');
$this_rank .= ($first_one ? '' : ', ') . $label;
$first_one = FALSE;
}
$percentage = round(100 * $ranking[$i]->percentage, 0);
$output .= theme('advpoll_bar', $this_rank, $percentage, $ranking[$i]->view_score);
}
}
else {
$output .= '<ol>';
for ($i = 0; $i < count($ranking); $i++) {
$output .= '<li> ';
$first_one = TRUE;
$check = $node->in_preview;
foreach ($ranking[$i]->choices as $choice) {
$label = isset($node->choice[$choice]) ? _advpoll_choice_markup($node->choice[$choice]['label'], $node->format, FALSE) . ($node->choice[$choice]['writein'] ? ' ' . t('(write-in)') : '') : t('(deleted)');
$output .= ($first_one ? '' : ', ') . $label;
$first_one = FALSE;
}
if ($ranking[$i]->view_score) {
$output .= ' (' . $ranking[$i]->view_score . '%)';
}
$output .= '</li>';
}
}
$output .= '</ol>';
if (user_access('inspect all votes') && isset($rounds)) {
if (count($rounds) > 0) {
$header[0] = t('Rounds');
$total_rounds = count($rounds);
for ($i = 0; $i < count($rounds); $i++) {
$choices = $rounds[$i];
if ($i + 1 == $total_rounds) {
$header[$i + 1] = t('Final');
}
else {
$header[$i + 1] = $i + 1;
}
if ($i == 0) {
$rows = array();
}
foreach ($node->choice as $key => $data) {
$rows[$key][0] = $data['label'];
$rows[$key][$i + 1] = isset($choices[$key]) && $choices[$key] ? $choices[$key] : '';
}
}
$round_table = theme('table', $header, $rows, array(), t('Per-round breakdown of votes for each choice'));
}
}
}
$output .= $round_table;
return array(
'results' => $output,
'votes' => $poll['total_votes'],
);
}
function advpoll_calculate_results_ranking(&$cache, $votes, $node) {
if ($node->algorithm == 'borda_count') {
$results = _advpoll_calculate_bordacount($node);
}
else {
$results = _advpoll_calculate_instantrunoff($node);
}
for ($i = 0; $i < count($results->ranking); $i++) {
foreach ($results->ranking[$i]['choices'] as $choice) {
$cache[$choice][0]['ranking'] = $i;
$cache[$choice][0]['raw_score'] = $results->ranking[$i]['raw_score'];
$cache[$choice][0]['view_score'] = $results->ranking[$i]['view_score'];
if (isset($results->ranking[$i]['percentage'])) {
$cache[$choice][0]['percentage'] = $results->ranking[$i]['percentage'];
}
}
}
if (isset($results->matrix)) {
foreach ($results->matrix as $i => $round) {
$key = '_rounds_' . $i;
$cache[$key] = array();
foreach ($round as $choice => $votes) {
$cache[$key][0][$choice] = count($votes);
}
}
}
$cache['_advpoll'][0]['total_votes'] = $results->total_votes;
if (isset($results->total_points)) {
$cache['_advpoll'][0]['total_points'] = $results->total_points;
}
}
function _advpoll_calculate_bordacount($node) {
$votes = array();
$result = db_query("SELECT * FROM {votingapi_vote} v WHERE content_type = '%s' AND content_id = %d ORDER BY value ASC", 'advpoll', $node->nid);
while ($vobj = db_fetch_object($result)) {
$votes[] = $vobj;
}
if (count($votes) == 0) {
return array();
}
$user_votes = array();
foreach ($votes as $vote) {
if ($vote->uid == 0) {
$key = $vote->hostname;
}
else {
$key = $vote->uid;
}
$user_votes[$key][$vote->value] = $vote->tag;
}
$choice_votes = array();
$total_choices = count($node->choice);
$total_points = 0;
foreach ($user_votes as $uid => $user_vote) {
foreach ($user_vote as $ranking => $choice) {
$vote_value = max($total_choices - $ranking, 0);
$choice_votes[$choice] += $vote_value;
$total_points += $vote_value;
}
}
foreach ($node->choice as $i => $choice) {
if (!isset($choice_votes[$i])) {
$choice_votes[$i] = 0;
}
}
arsort($choice_votes);
$ranking = array();
$previous_total = -1;
$cur_result = -1;
foreach ($choice_votes as $choice => $total) {
if ($total != $previous_total) {
$cur_result++;
}
$ranking[$cur_result]['choices'][] = $choice;
$ranking[$cur_result]['raw_score'] = $total;
$ranking[$cur_result]['view_score'] = format_plural($total, '1 point', '@count points');
$ranking[$cur_result]['percentage'] = $total_points ? $total / $total_points : 0;
$previous_total = $total;
}
$total_votes = count($user_votes);
$result_obj->ranking = $ranking;
$result_obj->total_votes = $total_votes;
$result_obj->total_points = $total_points;
return $result_obj;
}
function _advpoll_calculate_instantrunoff($node) {
$votes = array();
$result = db_query("SELECT * FROM {votingapi_vote} v WHERE content_type = '%s' AND content_id = %d ORDER BY value ASC", 'advpoll', $node->nid);
while ($vobj = db_fetch_object($result)) {
$votes[] = $vobj;
}
if (count($votes) == 0) {
return array();
}
$user_votes = array();
foreach ($votes as $vote) {
if ($vote->uid == 0) {
$key = $vote->hostname;
}
else {
$key = $vote->uid;
}
$user_votes[$key][] = $vote->tag;
}
$total_votes = count($user_votes);
$round_log = array();
$reverse_ranking = array();
$max_rounds = count($node->choice);
for ($round = 0; $round < $max_rounds; $round++) {
$cur_round = array();
$total_choices = count($node->choice);
foreach ($node->choice as $key => $data) {
$cur_round[$key] = array();
}
foreach ($user_votes as $key => $user_vote) {
$cur_round[$user_vote[0]][] = $key;
}
if ($round == 0) {
foreach ($cur_round as $key => $choice_votes) {
if (count($choice_votes) == 0) {
unset($cur_round[$key]);
$reverse_ranking[0]['choices'][] = $key;
}
}
}
$round_log[] = $cur_round;
$min_votes = -1;
$max_votes = 0;
$num_discarded = 0;
foreach ($cur_round as $ch => $choice_votes) {
$num_votes = count($choice_votes);
if ($num_votes > $max_votes) {
$max_votes = $num_votes;
$cur_winner = $ch;
}
if ($num_votes == 0) {
$num_discarded++;
}
else {
if ($num_votes != 0 && ($num_votes < $min_votes || $min_votes == -1)) {
$min_votes = $num_votes;
}
}
}
if ($max_votes > count($user_votes) / 2) {
if (isset($cur_round[$cur_winner])) {
unset($cur_round[$cur_winner]);
}
while (count($cur_round) > 0) {
$current_place = array();
$min = -1;
foreach ($cur_round as $ch => $choice_votes) {
if (count($choice_votes) == 0) {
unset($cur_round[$ch]);
}
else {
if ($min == -1 || count($choice_votes) < $min) {
$current_place = array(
$ch,
);
$min = count($choice_votes);
}
else {
if (count($choice_votes) == $min) {
$current_place[] = $ch;
}
}
}
}
if (count($current_place) > 0) {
$reverse_ranking[]['choices'] = $current_place;
foreach ($current_place as $ch_key) {
unset($cur_round[$ch_key]);
}
}
}
$revmat = array_reverse($round_log);
$reverse_ranking[]['choices'] = array(
$cur_winner,
);
$index = count($reverse_ranking) - 1;
$reverse_ranking[$index]['raw_score'] = round(count($revmat[0][$cur_winner]) * 100 / count($user_votes), 1);
$reverse_ranking[$index]['view_score'] = $reverse_ranking[$index]['raw_score'] . '%';
$result_obj->matrix = $round_log;
$result_obj->total_votes = $total_votes;
$result_obj->ranking = array_reverse($reverse_ranking);
return $result_obj;
}
$min_choices = array();
foreach ($cur_round as $ch => $choice_votes) {
if (count($choice_votes) == $min_votes) {
$min_choices[] = $ch;
}
}
$round_loser = array_rand($min_choices);
$reverse_ranking[]['choices'] = array(
$min_choices[$round_loser],
);
foreach ($cur_round[$min_choices[$round_loser]] as $user_key) {
array_shift($user_votes[$user_key]);
while ($cur_round[$user_votes[$user_key][0]] == array() && count($user_votes[$user_key]) > 0) {
array_shift($user_votes[$user_key]);
}
if (count($user_votes[$user_key]) == 0) {
unset($user_votes[$user_key]);
}
}
}
drupal_set_message(t('Could not find a solution within @rounds iterations.', array(
'@rounds' => $max_rounds,
)));
$result_obj->matrix = $round_log;
$result_obj->total_votes = $total_votes;
return $result_obj;
}
function advpoll_voting_ranking_form_submit($form_id, $form_values) {
$votes = array();
$node = node_load($form_values['nid']);
_advpoll_writeins_voting_form_submit($node, $form_values, $votes, $form_values['choice'][$form_values['writein_key']]);
foreach ($form_values['choice'] as $choice => $rank) {
$vote = new stdClass();
if (!$node->writeins || $choice != $form_values['writein_key']) {
$vote->value = $rank;
if ($vote->value != 0) {
$vote->value_type = 'option';
$vote->tag = $choice;
$votes[] = $vote;
}
}
}
votingapi_set_vote('advpoll', $form_values['nid'], $votes);
_advpoll_vote_response($node, $form_values);
}
function advpoll_voting_ranking_form_validate($form_id, $form_values) {
$node = node_load($form_values['nid']);
$ajax = $form_values['ajax'];
if (!advpoll_eligible($node)) {
_advpoll_form_set_error('choice[', t('You are not allowed to vote in this poll.'), $ajax);
}
if (!_advpoll_is_active($node)) {
_advpoll_form_set_error('choice[', t('This poll is closed.'), $ajax);
}
$writein_option = FALSE;
$writein_text = $form_values['writein_key'] ? $form_values['writein_choice'] : '';
list($voted, $cancel_vote) = _advpoll_user_voted($node->nid);
if ($voted) {
_advpoll_form_set_error('choice[', t('You have already voted in this poll.'), $ajax);
drupal_goto('node/' . $node->nid);
}
$set_values = array();
$num_choices = 0;
if ($node->writeins && user_access('add write-ins') && $form_values['choice'][$form_values['writein_key']]) {
$num_choices++;
$writein_option = TRUE;
}
foreach ($node->choice as $key => $choice) {
if ($form_values['choice'][$key]) {
$num_choices++;
}
$int_value = intval($form_values['choice'][$key]);
$set_values[$int_value]++;
if ($int_value > count($node->choice) || $int_value < 0) {
$message = "Illegal rank for choice {$key}: {$int_value} (min: 1, max: " . count($node->choice) . ')';
_advpoll_form_set_error('choice][' . $key, $message, $ajax);
}
}
if ($writein_option) {
$int_value = intval($form_values['choice'][$form_values['writein_key']]);
$set_values[$int_value]++;
if ($int_value > count($node->choice) || $int_value < 0) {
$message = "Illegal rank for the write-in choice: {$int_value} (min: 1, max: " . count($node->choice) . ')';
_advpoll_form_set_error('choice][' . $form_values['writein_key'], $message, $ajax);
}
}
_advpoll_writeins_voting_form_validate($node, $writein_option, $writein_text, $ajax);
if ($node->max_choices != 0 && $num_choices > $node->max_choices) {
$message = t('%num choices were selected but only %max are allowed.', array(
'%num' => $num_choices,
'%max' => $node->max_choices,
));
_advpoll_form_set_error('choice', $message, $ajax);
}
$min_choices = 1;
if ($num_choices < $min_choices) {
_advpoll_form_set_error('choice', t('At least one choice must be selected.'), $ajax);
}
foreach ($set_values as $val => $count) {
if ($val != 0 && $count > 1) {
$message = t('Multiple choices given the rank of %value.', array(
'%value' => $val,
));
_advpoll_form_set_error('choice', $message, $ajax);
}
}
}
function theme_advpoll_voting_ranking_form($form) {
$message = drupal_render($form['message']);
$output = "<div class=\"poll\">\n";
$output .= drupal_render($form);
if ($message) {
$output .= "<p class=\"message\">{$message}</p>\n";
}
$output .= "</div>\n";
return $output;
}
function advpoll_cancel_ranking($node, $user_vote) {
if ($node->writeins) {
$recalculate = FALSE;
foreach ($user_vote as $vote) {
if ($node->choice[$vote->tag]['writein']) {
$count = db_result(db_query('SELECT COUNT(1) FROM {votingapi_vote} WHERE content_id = %d AND tag = %d', $node->nid, $vote->tag));
if ($count == 0) {
db_query('DELETE FROM {advpoll_choices} WHERE cid = %d', $vote->tag);
$recalculate = TRUE;
watchdog('content', t('Removed write-in choice %choice after the last vote was cancelled.', array(
'%choice' => $node->choice[$vote->tag]['label'],
)));
}
}
}
if ($recalculate) {
votingapi_recalculate_results('advpoll', $node->nid);
}
}
}