? database/dif
Index: modules/poll.module
===================================================================
RCS file: /cvs/drupal/drupal/modules/poll.module,v
retrieving revision 1.196
diff -u -F^f -r1.196 poll.module
--- modules/poll.module	7 May 2006 00:08:36 -0000	1.196
+++ modules/poll.module	26 May 2006 22:45:45 -0000
@@ -161,6 +161,12 @@ function poll_form(&$node) {
   }
   $form['settings']['runtime'] = array('#type' => 'select', '#title' => t('Poll duration'), '#default_value' => $node->runtime ? $node->runtime : 0, '#options' => $_duration, '#description' => t('After this period, the poll will be closed automatically.'));
 
+  $form['settings']['votes_can_change'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Allow votes to be canceled'),
+    '#default_value' => $node->votes_can_change,
+    '#description' => t('If this box is checked, users will be able to cancel their own votes, and therefore re-vote. This allows users to change their mind.'),
+  );
   return $form;
 }
 
@@ -173,7 +179,7 @@ function poll_insert($node) {
     $node->active = 1;
   }
 
-  db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
+  db_query("INSERT INTO {poll} (nid, runtime, active, votes_can_change) VALUES (%d, %d, %d, %d)", $node->nid, $node->runtime, $node->active, $node->votes_can_change);
 
   foreach ($node->choice as $choice) {
     if ($choice['chtext'] != '') {
@@ -201,11 +207,24 @@ function poll_menu($may_cache) {
       'callback' => 'poll_vote',
       'access' => user_access('vote on polls'),
       'type' => MENU_CALLBACK);
+
+    $items[] = array('path' => 'poll/cancel',
+      'title' => t('cancel'),
+      'callback' => 'poll_cancel',
+      'access' => user_access('vote on polls'),
+      'type' => MENU_CALLBACK);
   }
   else {
     if (arg(0) == 'node' && is_numeric(arg(1))) {
       $node = node_load(arg(1));
-
+      if ($node->type == 'poll') {
+        $items[] = array('path' => 'node/'. arg(1) .'/votes',
+          'title' => t('votes'),
+          'callback' => 'poll_votes',
+          'access' => user_access('view poll votes'),
+          'weight' => 3,
+          'type' => MENU_LOCAL_TASK);
+      }
       if ($node->type == 'poll' && $node->allowvotes) {
         $items[] = array('path' => 'node/'. arg(1) .'/results',
           'title' => t('results'),
@@ -227,7 +246,7 @@ function poll_load($node) {
   global $user;
 
   // Load the appropriate choices into the $node object
-  $poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
+  $poll = db_fetch_object(db_query("SELECT runtime, active, votes_can_change FROM {poll} WHERE nid = %d", $node->nid));
 
   $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
   while ($choice = db_fetch_array($result)) {
@@ -237,10 +256,17 @@ function poll_load($node) {
   // Determine whether or not this user is allowed to vote
   $poll->allowvotes = FALSE;
   if (user_access('vote on polls') && $poll->active) {
-    if ($user->uid && db_num_rows(db_query('SELECT uid FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid)) == 0) {
-      $poll->allowvotes = TRUE;
+    if ($user->uid) {
+      $result = db_fetch_object(db_query('SELECT chorder FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
+    }
+    else {
+      $result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, $_SERVER['REMOTE_ADDR']));
     }
-    else if ($user->uid == 0 && db_num_rows(db_query("SELECT hostname FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, $_SERVER['REMOTE_ADDR'])) == 0) {
+    if (isset($result->chorder)) {
+      $poll->vote = $result->chorder;
+    }
+    else {
+      $poll->vote = -1;
       $poll->allowvotes = TRUE;
     }
   }
@@ -272,7 +298,7 @@ function poll_page() {
  * Implementation of hook_perm().
  */
 function poll_perm() {
-  return array('create polls', 'vote on polls');
+  return array('create polls', 'vote on polls', 'view poll votes');
 }
 
 /**
@@ -342,12 +368,12 @@ function poll_view_results(&$node, $teas
     }
   }
 
-  $output .= theme('poll_results', check_plain($node->title), $poll_results, $total_votes, $node->links, $block);
+  $output .= theme('poll_results', check_plain($node->title), $poll_results, $total_votes, $node->links, $block, $node->nid, $node->votes_can_change, $node->vote, check_plain($node->choice[$node->vote]['chtext']));
 
   return $output;
 }
 
-function theme_poll_results($title, $results, $votes, $links, $block) {
+function theme_poll_results($title, $results, $votes, $links, $block, $nid, $votes_can_change, $vote, $vote_str) {
   if ($block) {
     $output .= '<div class="poll">';
     $output .= '<div class="title">'. $title .'</div>';
@@ -360,6 +386,16 @@ function theme_poll_results($title, $res
     $output .= '<div class="poll">';
     $output .= $results;
     $output .= '<div class="total">'. t('Total votes: %votes', array('%votes' => $votes)) .'</div>';
+    if (isset($vote) && $vote >= 0) {
+      $output .= '<div class="vote">'. t('Your vote') .": $vote_str ";
+      if ($votes_can_change) {
+        $form['#action'] = url("poll/cancel/$nid");
+        $form['choice'] = array('#type' => 'hidden', '#value' => $vote);
+        $form['submit'] = array('#type' => 'submit', '#value' => t('Cancel vote'));
+        $output .= drupal_get_form('poll_cancel_form', $form);
+      }
+      $output .= '</div>';
+    }
     $output .= '</div>';
   }
 
@@ -395,6 +431,38 @@ function poll_results() {
 }
 
 /**
+ * Callback for the 'votes' tab for polls you can see other votes on
+ */
+function poll_votes() {
+  if ($node = node_load(arg(1))) {
+    drupal_set_title(check_plain($node->title));
+    $output = t("This table lists all the recorded votes for this poll.  If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted (which is the method used to prevent anonymous users from voting multiple times). For a summary of the results, use the %view tab", array('%view' => l(t('view'), "node/$node->nid")));
+
+    $header[] = array('data' => t('Identifier'), 'field' => 'u.name');
+    $header[] = array('data' => t('Vote'), 'field' => 'pv.chorder');
+
+    $result = pager_query("SELECT pv.chorder, pv.uid, pv.hostname, u.name FROM {poll_votes} pv LEFT JOIN {users} u ON pv.uid = u.uid WHERE pv.nid = %d" . tablesort_sql($header), 20, 0, NULL, $node->nid);
+    $rows = array();
+    while ($vote = db_fetch_object($result)) {
+      $row = array();
+      if ($vote->name) {
+        $row[] = l(check_plain($vote->name), "user/$vote->uid");
+      } else {
+        $row[] = $vote->hostname;
+      }
+      $row[] = check_plain($node->choice[$vote->chorder]['chtext']);
+      $rows[] = $row;
+    }    
+    $output .= theme('table', $header, $rows);
+    $output .= theme('pager', NULL, 20, 0);
+    print theme('page', $output);
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+/**
  * Callback for processing a vote
  */
 function poll_vote(&$node) {
@@ -408,12 +476,12 @@ function poll_vote(&$node) {
 
     if (isset($choice) && isset($node->choice[$choice])) {
       if ($node->allowvotes) {
-        // Mark the user or host as having voted.
+        // Record the vote by this user or host.
         if ($user->uid) {
-          db_query('INSERT INTO {poll_votes} (nid, uid) VALUES (%d, %d)', $node->nid, $user->uid);
+          db_query('INSERT INTO {poll_votes} (nid, chorder, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
         }
         else {
-          db_query("INSERT INTO {poll_votes} (nid, hostname) VALUES (%d, '%s')", $node->nid, $_SERVER['REMOTE_ADDR']);
+          db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, $_SERVER['REMOTE_ADDR']);
         }
 
         // Add one to the votes.
@@ -424,13 +492,49 @@ function poll_vote(&$node) {
         drupal_set_message(t('Your vote was recorded.'));
       }
       else {
-        drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
+        drupal_set_message(t("You are not allowed to vote on this poll."), 'error');
       }
     }
     else {
-      drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
+      drupal_set_message(t("You did not specify a valid poll choice."), 'error');
     }
+    drupal_goto('node/'. $nid);
+  }
+  else {
+    drupal_not_found();
+  }
+}
+
+
+/**
+ * Callback for canceling a vote
+ */
+function poll_cancel(&$node) {
+  global $user;
+
+  $nid = arg(2);
+  if ($node = node_load(array('nid' => $nid))) {
+    $edit = $_POST['edit'];
+    $choice = $edit['choice'];
+    $cancel = $_POST['cancel'];
+
+    if (isset($choice) && isset($node->choice[$choice])) {
+      if ($user->uid) { 
+        db_query('DELETE FROM {poll_votes} WHERE nid = %d and uid = %d', $node->nid, $user->uid);
+      }
+      else {
+        db_query("DELETE FROM {poll_votes} WHERE nid = %d and hostname = '%s'", $node->nid, $_SERVER['REMOTE_ADDR']);
+      }
 
+      // Subtract from the votes.
+      db_query("UPDATE {poll_choices} SET chvotes = chvotes - 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
+      $node->allowvotes = true;
+      $node->choice[$choice]['chvotes']--;
+      drupal_set_message(t('Your vote was canceled.'));
+    }
+    else {
+      drupal_set_message(t("You are not allowed to cancel an invalid poll choice."), 'error');
+    }
     drupal_goto('node/'. $nid);
   }
   else {
@@ -477,7 +581,7 @@ function poll_view(&$node, $teaser = FAL
  * Implementation of hook_update().
  */
 function poll_update($node) {
-  db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
+  db_query('UPDATE {poll} SET runtime = %d, active = %d, votes_can_change = %d WHERE nid = %d', $node->runtime, $node->active, $node->votes_can_change, $node->nid);
 
   db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
   db_query('DELETE FROM {poll_votes} WHERE nid = %d', $node->nid);
cvs diff: Diffing database
Index: database/database.4.0.mysql
===================================================================
RCS file: /cvs/drupal/drupal/database/database.4.0.mysql,v
retrieving revision 1.4
diff -u -F^f -r1.4 database.4.0.mysql
--- database/database.4.0.mysql	26 May 2006 09:21:10 -0000	1.4
+++ database/database.4.0.mysql	26 May 2006 22:45:46 -0000
@@ -527,6 +527,7 @@
   nid int(10) unsigned NOT NULL default '0',
   runtime int(10) NOT NULL default '0',
   active int(2) unsigned NOT NULL default '0',
+  votes_can_change tinyint(1) NOT NULL default '0',
   PRIMARY KEY (nid)
 );
 
@@ -537,6 +538,7 @@
 CREATE TABLE poll_votes (
   nid int(10) unsigned NOT NULL,
   uid int(10) unsigned NOT NULL default 0,
+  chorder int(10) NOT NULL default -1,
   hostname varchar(128) NOT NULL default '',
   INDEX (nid),
   INDEX (uid),
Index: database/database.4.1.mysql
===================================================================
RCS file: /cvs/drupal/drupal/database/database.4.1.mysql,v
retrieving revision 1.4
diff -u -F^f -r1.4 database.4.1.mysql
--- database/database.4.1.mysql	26 May 2006 09:21:10 -0000	1.4
+++ database/database.4.1.mysql	26 May 2006 22:45:46 -0000
@@ -562,6 +562,7 @@
   nid int(10) unsigned NOT NULL default '0',
   runtime int(10) NOT NULL default '0',
   active int(2) unsigned NOT NULL default '0',
+  votes_can_change tinyint(1) NOT NULL default '0',
   PRIMARY KEY (nid)
 )
 DEFAULT CHARACTER SET utf8;
@@ -573,6 +574,7 @@
 CREATE TABLE poll_votes (
   nid int(10) unsigned NOT NULL,
   uid int(10) unsigned NOT NULL default 0,
+  chorder int(10) NOT NULL default -1,
   hostname varchar(128) NOT NULL default '',
   INDEX (nid),
   INDEX (uid),
Index: database/database.pgsql
===================================================================
RCS file: /cvs/drupal/drupal/database/database.pgsql,v
retrieving revision 1.177
diff -u -F^f -r1.177 database.pgsql
--- database/database.pgsql	26 May 2006 09:21:10 -0000	1.177
+++ database/database.pgsql	26 May 2006 22:45:47 -0000
@@ -530,6 +530,7 @@
   nid integer NOT NULL default '0',
   runtime integer NOT NULL default '0',
   active integer NOT NULL default '0',
+  votes_can_change smallint NOT NULL default '0',
   PRIMARY KEY (nid)
 );
 
@@ -540,6 +541,7 @@
 CREATE TABLE poll_votes (
   nid int NOT NULL,
   uid int NOT NULL default 0,
+  chorder int NOT NULL default -1,
   hostname varchar(128) NOT NULL default ''
 );
 CREATE INDEX poll_votes_nid_idx ON poll_votes (nid);
Index: database/updates.inc
===================================================================
RCS file: /cvs/drupal/drupal/database/updates.inc,v
retrieving revision 1.233
diff -u -F^f -r1.233 updates.inc
--- database/updates.inc	26 May 2006 18:48:00 -0000	1.233
+++ database/updates.inc	26 May 2006 22:45:49 -0000
@@ -2040,3 +2040,25 @@ function system_update_183() {
   }
   return $ret;
 }
+
+function system_update_184() {
+  // change DB schema for better poll support
+  $ret = array();
+
+  switch ($GLOBALS['db_type']) {
+    case 'mysqli':
+    case 'mysql':
+      // alter poll_votes table
+      $ret[] = update_sql("ALTER TABLE {poll_votes} ADD COLUMN chorder int(10) NOT NULL default -1 AFTER uid");
+      $ret[] = update_sql("ALTER TABLE {poll} ADD COLUMN votes_can_change tinyint(1) NOT NULL default 0 AFTER active");
+      break;
+
+    case 'pgsql':
+      db_add_column($ret, 'poll_votes', 'chorder', 'int', array('not null' => TRUE, 'default' => "'-1'"));
+      db_add_column($ret, 'poll', 'votes_can_change', 'smallint', array('not null' => TRUE, 'default' => "'0'"));
+      break;
+  }
+
+  return $ret;
+}
+
