Index: advpoll.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/advpoll.module,v
retrieving revision 1.21.2.37
diff -u -r1.21.2.37 advpoll.module
--- advpoll.module	2 Jun 2007 17:23:12 -0000	1.21.2.37
+++ advpoll.module	6 Jun 2007 16:35:19 -0000
@@ -1,6 +1,4 @@
 <?php
-// $Id: advpoll.module,v 1.21.2.37 2007/06/02 17:23:12 fajerstarter Exp $
-
 /**
  * @file
  * Advanced Poll - a sophisticated polling module for voting, elections, and group decision-making.
@@ -231,10 +229,36 @@
     '#tree' => TRUE,
   );
 
+  $form['settings']['writeins'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Write-in voting settings'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+  );
+
+  $form['settings']['writeins']['allow'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Allow user to cast a write-in vote'),
+    '#default_value' => ($node->writeins ? $node->writeins : 0),
+    '#description' => t('Enabling this option will allow an eligible voter with the \'add write-ins\' permission to write-in up to one choice.'),
+  );
+
+  $form['settings']['writeins']['display'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Display write-in votes as choices for future voters'),
+    '#default_value' => ($node->displaywriteins ? $node->displaywriteins : 0),
+    '#description' => t('Enabling this option will allow a voter to see and choose from previous voters\' write-in votes.'),
+  );
+
   $max_choice_list = array();
   for ($i = 0; $i <= $choices; $i++) {
     $max_choice_list[$i] = ($i == 0? 'No limit' : $i);
   }
+  
+  // Allow for one more choice because write-ins may be enabled. If Javascript 
+  // is enabled, this will be undone to allow jQuery to adjust the maxChoices 
+  // based on whether the write-ins box is checked.
+  $max_choice_list[$i] = $i;
 
   $form['settings']['maxchoices'] = array(
     '#type' => 'select',
@@ -358,7 +382,7 @@
 function advpoll_load($node) {
   global $user;
   $poll = db_fetch_object(db_query("SELECT * FROM {advpoll} WHERE nid = %d", $node->nid));
-  $result = db_query("SELECT vote_offset, label FROM {advpoll_choices} WHERE nid = %d ORDER BY vote_offset", $node->nid);
+  $result = db_query("SELECT vote_offset, label, writein FROM {advpoll_choices} WHERE nid = %d ORDER BY vote_offset", $node->nid);
   while ($choice = db_fetch_array($result)) {
     $poll->choice[$choice['vote_offset']] = $choice;
   }
@@ -785,7 +809,7 @@
  * Implementation of hook_perm().
  */
 function advpoll_perm() {
-  return array('create polls', 'delete polls', 'view polls', 'vote on polls', 'cancel own vote', 'administer polls', 'inspect all votes');
+  return array('create polls', 'delete polls', 'view polls', 'vote on polls', 'cancel own vote', 'administer polls', 'inspect all votes', 'add write-ins');
 }
 
 /**
@@ -838,11 +862,12 @@
     $node->settings['active'] = _advpoll_calculate_active($node);
   }
 
-  db_query("UPDATE {advpoll} SET active=%d, runtime=%d, maxchoices=%d, algorithm='%s', uselist=%d, showvotes=%d, startdate=%s WHERE nid = %d",
+  db_query("UPDATE {advpoll} SET active=%d, runtime=%d, maxchoices=%d, algorithm='%s', uselist=%d, showvotes=%d, startdate='%s', writeins=%d, displaywriteins=%d WHERE nid = %d",
     $node->settings['active'], $node->settings['runtime'],
     $node->settings['maxchoices'], $node->settings['algorithm'],
     $node->settings['uselist'], $node->settings['showvotes'],
     $node->settings['usestart']? _advpoll_create_startdate($node): 'NULL',
+    $node->settings['writeins']['allow'], $node->settings['writeins']['display'],
     $node->nid);
 
   _advpoll_insert_choices($node);
@@ -886,7 +911,7 @@
   $i = 1;
   foreach ($_POST['choice'] as $choice) {
     if ($choice['label'] != '') {
-      db_query("INSERT INTO {advpoll_choices} (nid, label, vote_offset) VALUES (%d, '%s', %d)", $node->nid, $choice['label'], $i++);
+      db_query("INSERT INTO {advpoll_choices} (nid, label, vote_offset, writein) VALUES (%d, '%s', %d, 0)", $node->nid, $choice['label'], $i++);
     }
   }
 }
@@ -913,11 +938,12 @@
     $node->settings['active'] = _advpoll_calculate_active($node);
   }
 
-  db_query("INSERT INTO {advpoll} (nid, mode, uselist, active, runtime, maxchoices, algorithm, showvotes, startdate) VALUES (%d, '%s', %d, %d, %d, %d, '%s', %d, %s)",
+  db_query("INSERT INTO {advpoll} (nid, mode, uselist, active, runtime, maxchoices, algorithm, showvotes, startdate, writeins, displaywriteins) VALUES (%d, '%s', %d, %d, %d, %d, '%s', %d, '%s', %d, %d)",
     $node->nid, $mode, $node->settings['uselist'], $node->settings['active'],
     $node->settings['runtime'], $node->settings['maxchoices'],
     $node->settings['algorithm'], $node->settings['showvotes'],
-    $node->settings['usestart']? _advpoll_create_startdate($node): 'NULL');
+    $node->settings['usestart']? _advpoll_create_startdate($node): 'NULL',
+    $node->settings['writeins']['allow'], $node->settings['writeins']['display']);
 
   // Insert the choices
   _advpoll_insert_choices($node);
@@ -982,6 +1008,11 @@
         $realchoices++;
       }
     }
+    
+    // Add one to counter if the write-ins are enabled for this node
+	if($node->writeins) {
+	  $realchoices++;
+	}
 
     if ($realchoices < 2) {
       form_set_error("choice][$realchoices][label", t('You must fill in at least two choices.'));
@@ -992,7 +1023,8 @@
       form_set_error('settings][maxchoices]', t('Maximum choices must be a non-negative integer.'));
     }
 
-    if ($node->settings['maxchoices'] > count($node->choice)) {
+    if ((!$node->writeins && $node->settings['maxchoices'] > count($node->choice)) ||
+        (($node->writeins && $node->settings['maxchoices'] > count($node->choice) + 1))) {
       form_set_error('settings][maxchoices]', t('Maximum choices cannot be larger than the number of choices submitted.'));
     }
   }
@@ -1217,3 +1249,108 @@
 
   return $text; 
 }
+
+/**
+ * Voting form validation logic specific to writeins. This has been abstracted 
+ * away from includes in the modes directory.
+ */
+function _advpoll_writeins_voting_form_validate($node, $writein_option, $writein_text, &$errors, &$ok, $ajax) {
+  // Do write-in specific checks if write-ins are enabled and user has permission
+  if($node->writeins && user_access('add write-ins')) {
+    // If something is in the write-in textbox
+    if($writein_text) {
+      $writein_choice_lower = strtolower($writein_text);
+      foreach ($node->choice as $i => $val) {
+        // Check that user isn't writing in an existing visible choice. (If user
+        // is writing in an existing choice and either write-ins are all being 
+        // displayed or the existing choice is not a write-in,)
+        if ((strtolower($val['label']) == $writein_choice_lower) && ($node->displaywriteins || !$val['writein'])) {
+          $msg = t('A write-in vote can not be for an existing choice. Select the choice\'s option instead.');
+          if ($ajax) {
+            $errors[] = $msg;
+          }
+          else {
+            form_set_error('writein_choice', $msg);
+          }
+          $ok = false;
+        }
+      }
+    }
+
+    // If the write-in option is selected and there is nothing in the write-in textbox  
+    if($writein_option && !$writein_text) {
+      $msg = t('If the \'write-in\' option is selected, a choice must be written in.');
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('writein_choice', $msg);
+      }
+      $ok = false;      
+    }
+
+    // If the write-in option is not selected but there is something in the write-in textbox 
+    if(!$writein_option && $writein_text) {
+      $msg = t('If a choice is written in, the \'write-in\' option must be selected.');
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('writein_choice', $msg);
+      }
+      $ok = false;
+    }
+  }
+}
+
+/**
+ * Voting form submission logic specific to writeins. This has been abstracted 
+ * away from includes in the modes directory.
+ */
+function _advpoll_writeins_voting_form_submit($node, $form_values, &$vote, $vote_value) {
+  // A write-in vote is being made
+  if($form_values['choice'][$form_values['writein_key']]) {
+    // Check if someone has previously voted for this choice
+    $result = db_query("SELECT vote_offset FROM {advpoll_choices} WHERE nid = %d AND LOWER(label) = LOWER('%s')", $node->nid, $form_values['writein_choice']);
+    // If there's more than one match, redo the query, being more exact
+    if(db_num_rows($result) > 1) {
+      $result = db_query("SELECT vote_offset FROM {advpoll_choices} WHERE nid = %d AND label = '%s'", $node->nid, $form_values['writein_choice']);      
+    }
+    // If there is at least one match, add a vote for the first one returned. It
+	// should be rare to find more than one choice for any one node with a given
+	// label.
+    if(db_num_rows($result)) {
+      $obj = db_fetch_object($result);
+      $existing_vote_offset = $obj->vote_offset;
+      // Set a vote
+      unset($temp);
+      $temp->value = $vote_value;
+      $temp->tag = $existing_vote_offset;
+      $temp->value_type = 'option';
+      $vote[] = $temp;
+    }
+    // This write-in choice has not been previously voted for
+    else {
+      // Get last vote offset for this node
+      $result = db_query("SELECT MAX(vote_offset) as last_vote_offset FROM {advpoll_choices} WHERE nid = %d", $node->nid);
+      $obj = db_fetch_object($result);
+      // Set the last tag value
+      $last_vote_offset = $obj->last_vote_offset;
+      // Default value
+      if(!$last_vote_offset) {
+        // Start at one rather than 0 due to Drupal FormAPI
+        $last_vote_offset = 1;
+      }
+
+      // Insert new choice into node
+      db_query("INSERT INTO {advpoll_choices} (nid, label, vote_offset, writein) VALUES (%d, '%s', %d, 1)", $node->nid, check_plain($form_values['writein_choice']), $last_vote_offset+1);
+      
+      // Add vote
+      unset($temp);
+      $temp->value = $vote_value;
+      $temp->tag = $last_vote_offset+1;
+      $temp->value_type = 'option';
+      $vote[] = $temp;
+    }
+  }
+}
Index: advpoll.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/advpoll.install,v
retrieving revision 1.5.2.9
diff -u -r1.5.2.9 advpoll.install
--- advpoll.install	10 May 2007 23:38:02 -0000	1.5.2.9
+++ advpoll.install	6 Jun 2007 16:35:18 -0000
@@ -20,6 +20,8 @@
           `algorithm` VARCHAR(100),
           `showvotes` tinyint,
           `startdate` int unsigned,
+          `writeins' tinyint NOT NULL default '0',
+          `displaywriteins' tinyint NOT NULL default '0',
           PRIMARY KEY  (`nid`)
       ) /*!40100 DEFAULT CHARACTER SET utf8 */");
 
@@ -33,6 +35,7 @@
           `nid` int(10) NOT NULL,
           `label` text NOT NULL,
           `vote_offset` int(2) unsigned default NULL,
+          `writein` tinyint NOT NULL default '0',
           PRIMARY KEY  (`nid`, `vote_offset`),
           KEY `vote_offset` (`vote_offset`)
         ) /*!40100 DEFAULT CHARACTER SET utf8 */");
@@ -50,6 +53,8 @@
           algorithm varchar(100),
           showvotes smallint,
           startdate integer,
+          writeins smallint NOT NULL DEFAULT '0',
+          displaywriteins smallint NOT NULL DEFAULT '0',
           PRIMARY KEY (nid)
       )");
 
@@ -63,6 +68,7 @@
           nid integer NOT NULL,
           label text NOT NULL,
           vote_offset smallint DEFAULT NULL,
+          writein smallint NOT NULL DEFAULT '0',
           PRIMARY KEY (nid, vote_offset)
       )");
       db_query("CREATE INDEX {advpoll_choices}_vote_offset_idx ON {advpoll_choices} (vote_offset)");
@@ -121,3 +127,24 @@
   }
   return $ret;  
 }
+
+/**
+ * Add columns for write-in support.
+ */
+function advpoll_update_2() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysql':
+    case 'mysqli':
+      $ret[] = update_sql("ALTER TABLE {advpoll} ADD `writeins` TINYINT NOT NULL DEFAULT '0'");
+      $ret[] = update_sql("ALTER TABLE {advpoll} ADD `displaywriteins` TINYINT NOT NULL DEFAULT '0'");
+      $ret[] = update_sql("ALTER TABLE {advpoll_choices} ADD `writein` TINYINT NOT NULL DEFAULT '0'");
+      break;
+    case 'pgsql':
+      $ret[] = update_sql("ALTER TABLE {advpoll} ADD writeins SMALLINT NOT NULL DEFAULT '0'");
+      $ret[] = update_sql("ALTER TABLE {advpoll} ADD displaywriteins SMALLINT NOT NULL DEFAULT '0'");
+      $ret[] = update_sql("ALTER TABLE {advpoll_choices} ADD writein SMALLINT NOT NULL DEFAULT '0'");
+      break;    
+    }
+    return $ret;
+}
Index: advpoll-form.js
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/advpoll-form.js,v
retrieving revision 1.1.2.2
diff -u -r1.1.2.2 advpoll-form.js
--- advpoll-form.js	29 Nov 2006 06:02:48 -0000	1.1.2.2
+++ advpoll-form.js	6 Jun 2007 16:35:18 -0000
@@ -45,7 +45,11 @@
       // Give each label it's correct number
       $(this).html($(this).html().replace(/\d+(?=<)/g, i++));
     });
-    
+    // Add an extra maxChoice if write-ins are enabled
+    if ($("#edit-settings-writeins-allow").attr("checked")) {
+      i++;
+    }
+        
     Drupal.advpoll.maxChoices(i-1);
     
     return false;
@@ -67,6 +71,15 @@
   }
 }
 
+Drupal.advpoll.updateWriteinsAllow = function() {
+  if ($("#edit-settings-writeins-allow").attr("checked")) {
+    Drupal.advpoll.maxChoices($("#edit-settings-maxchoices").children().length);
+  }
+  else {
+    Drupal.advpoll.maxChoices($("#edit-settings-maxchoices").children().length - 2);
+  }
+}
+
 Drupal.advpoll.nodeFormAutoAttach = function() {
   // Hide "need more choices" checkbox
   $("#morechoices").hide();
@@ -75,6 +88,15 @@
   Drupal.advpoll.updateStartDate();
   $("#edit-settings-usestart").click(Drupal.advpoll.updateStartDate);
   
+  // Remove extra maxChoices entry from write-ins
+  if (!$("#edit-settings-writeins-allow").attr("checked")) {
+    Drupal.advpoll.maxChoices($("#edit-settings-maxchoices").children().length-2);
+  }
+  
+  // Update maxChoices when user checks/unchecks write-ins box
+  Drupal.advpoll.updateStartDate();
+  $("#edit-settings-writeins-allow").click(Drupal.advpoll.updateWriteinsAllow);
+  
   // Insert Remove links
   $('<a class="remove-choice" href="#">' + Drupal.settings.advPoll.remove + '</a>').insertAfter("input.choices");
   Drupal.advpoll.removeChoiceClick();
@@ -96,6 +118,11 @@
     
     Drupal.advpoll.removeChoiceClick();
     
+    // Add an extra maxChoice if write-ins are enabled
+    if ($("#edit-settings-writeins-allow").attr("checked")) {
+      newChoiceN++;
+    }
+    
     Drupal.advpoll.maxChoices(newChoiceN);
     
     return false;
Index: modes/ranking.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/modes/ranking.inc,v
retrieving revision 1.8.2.20
diff -u -r1.8.2.20 ranking.inc
--- modes/ranking.inc	1 Jun 2007 07:50:19 -0000	1.8.2.20
+++ modes/ranking.inc	6 Jun 2007 16:35:19 -0000
@@ -1,714 +1,787 @@
-<?php
-// $Id: ranking.inc,v 1.8.2.20 2007/06/01 07:50:19 fajerstarter Exp $
-
-function advpoll_info_ranking() {
-  return array(
-    'name' => '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) {
-  $form = array();
-
-  static $ranking_form_count = 0; 
-  $form['#id'] = 'advpoll_voting_ranking_form-'. $ranking_form_count++;
-  $form['#attributes'] = array('class' => 'advpoll-vote');
-
-  $form['ajax'] = array(
-    '#type' => 'hidden',
-    '#attributes' => array('class' => 'ajax'),
-  );
-
-
-  if ($node->choice) {
-    $list = array();
-
-    $num_choices = count($node->choice);
-
-    // Generate the list of possible rankings
-    $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;
-    }
-
-    $form['choice'] = array(
-      '#tree' => TRUE,
-      // XXX: Workaround for FormAPI bug in PHP 4, see http://drupal.org/node/86657
-      '#type' => 'checkboxes',
-    );
-
-    foreach ($node->choice as $key => $choice) {
-      // Don't show blank choices
-      if ($choice['label']) {
-        $form['choice'][$key] = array(
-          '#type' => 'select',
-          '#title' => _advpoll_choice_markup($choice['label'], $node->format),
-          '#options' => $choices,
-        );
-      }
-    }
-  }
-
-  $form['nid'] = array(
-    '#type' => 'hidden',
-    '#value' => $node->nid,
-    '#attributes' => array('class' => 'edit-nid'),
-  );
-
-  if (!$node->in_preview) {
-    static $ranking_vote_count = 0;
-    $form['vote'] = array(
-      '#type' => 'submit',
-      '#value' => t('Vote'),
-      '#attributes' => array('id' => 'edit-vote-rank-'. $ranking_vote_count++),
-    );
-  }
-
-  $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 no one has voted, $results = array() and thus is empty.
-  if (!empty($results)) {
-    // Temporary list of choices indexes for the ranking
-    $ranking_list = array();
-    // Result object
-    $ranking = array();
-    $choices = array();
-    $poll = array();
-    $rounds = array();
-    foreach ($results as $result) {
-      $tag = $result->tag;
-      if ($tag == '_advpoll') {
-        // Poll-wide cached value
-        $poll[$result->function] = $result->value;
-      }
-      else if (strstr($tag, '_rounds_')) {
-        // Reconstruct round data.
-        // Extract the round from the tag.
-        $round = str_replace('_rounds_', '', $tag);
-        if (!isset($rounds[$round])) {
-          $rounds[$round] = array();
-        }
-        // $result->function actually stores $choice.
-        $rounds[$round][$result->function] = $result->value;
-      }
-      else if (isset($node->choice[$tag])) {
-        // Choice-specific cached value
-        if ($result->function == 'ranking') {
-          $ranking_list[$result->value][] = $tag;
-        }
-        else if (!isset($node->choice[$result->function])) {
-          $choices[$tag][$result->function] = $result->value;
-        }
-      }
-    }
-
-    // Re-construct the rankings object
-    foreach ($ranking_list as $i => $choice_list) {
-      $ranking[$i]->choices = array();
-      foreach ($choice_list as $choice_i) {
-        $ranking[$i]->choices[] = $choice_i;
-        $ranking[$i]->viewscore = $choices[$choice_i]['viewscore'];
-        $ranking[$i]->rawscore = $choices[$choice_i]['rawscore'];
-        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 = '';
-
-        // Loop through all choices with this ranking
-        foreach ($ranking[$i]->choices as $choice) {
-          $this_rank .= ($first_one? '' : ', ') . _advpoll_choice_markup($node->choice[$choice]['label'], $node->format);
-          $first_one = false;
-        }
-
-        $percentage = round(100 * $ranking[$i]->percentage, 0);
-        $output .= theme('advpoll_bar', $this_rank, $percentage, $ranking[$i]->viewscore);
-
-      }
-    }
-    else {
-      $output .= '<ol>';
-
-      for ($i = 0; $i < count($ranking); $i++) {
-        $output .= '<li> ';
-        $first_one = true;
-
-        // Loop through all choices with this ranking
-        foreach ($ranking[$i]->choices as $choice) {
-          $output .= ($first_one? '' : ', ') . _advpoll_choice_markup($node->choice[$choice]['label'], $node->format);
-          $first_one = false;
-        }
-
-        // Show the ranking's score if it exists (depends on algorithm)
-        if ($ranking[$i]->viewscore) {
-          $output .= ' ('. $ranking[$i]->viewscore .'%)';
-        }
-        $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) {
-            // This is the last round.
-            $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']);
-}
-
-/**
- * Calculate the results for a ranking poll based on the algorithm.
- *
- * @param $node
- *  The node object for the current poll
- *
- * @return 
- *   Should return an object that include the following attributes
- *   -results : 2d array listing the aggregate preference, including ties
- *   -rounds : 2d array listing the per-choice vote count for each round and
- *              a status message indicating who was eliminated
- *   -totalVoters : the total number of voters who participated
- */
-function advpoll_calculate_results_ranking(&$cache, $votes, $node) {
-  if ($node->algorithm == 'borda count') {
-    $results = _advpoll_calculate_bordacount($node);
-  }
-  else {
-    $results = _advpoll_calculate_instantrunoff($node);
-  }
-
-  // Cache rankings
-  // API: $cache[$tag][$type][$function] = $value (0 is the default $type)
-  for ($i = 0; $i < count($results->ranking); $i++) {
-    foreach ($results->ranking[$i]['choices'] as $choice) {
-      $cache[$choice][0]['ranking'] = $i;
-      $cache[$choice][0]['rawscore'] = $results->ranking[$i]['rawscore'];
-      $cache[$choice][0]['viewscore'] = $results->ranking[$i]['viewscore'];
-      if (isset($results->ranking[$i]['percentage'])) {
-        $cache[$choice][0]['percentage'] = $results->ranking[$i]['percentage'];
-      }
-    }
-  }
-
-  // Cache round results
-  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 total votes
-  $cache['_advpoll'][0]['total_votes'] = $results->total_votes;
-
-  // Cache total points (if it exists)
-  if (isset($results->total_points)) {
-    $cache['_advpoll'][0]['total_points'] = $results->total_points;
-  }
-}
-
-/**
- * Calculate the results using borda count.
- * 
- * @param $node
- *  The node object for the current poll.
- *
- * @return 
- *   Should return an object that include the following attributes
- *   -results : 2d array listing the aggregate preference, including ties
- *   -rounds : 2d array listing the per-choice vote count for each round and
- *              a status message indicating who was eliminated
- *   -totalVoters : the total number of voters who participated
- */
-function _advpoll_calculate_bordacount($node) {
-   $votes = array();
-  // ORDER BY value ASC lets us ensure no gaps
-  $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) {
-    // No votes yet
-    return array();
-  }
-
-  // Aggregate votes by user (uid if logged in, IP if anonymous)
-  // in ascending order of value.
-  $user_votes = array();
-
-  foreach ($votes as $vote) {
-    if ($vote->uid == 0) {
-      // Anonymous user
-      $key = $vote->hostname;
-    }
-    else {
-      // Logged-in user
-      $key = $vote->uid;
-    }
-
-    $user_votes[$key][$vote->value] = $vote->tag;
-  }
-
-  $choice_votes = array();
-
-  $total_choices = count($node->choice);
-  $total_points = 0;
-
-  // Loop through each user's vote
-  foreach ($user_votes as $uid => $user_vote) {
-    foreach ($user_vote as $ranking => $choice) {
-      // Negative values are possible if choices were removed after vote
-      $vote_value = max($total_choices - $ranking, 0);
-      $choice_votes[$choice] +=   $vote_value;
-      $total_points += $vote_value;
-    }
-  }
-
-  // Add any remaining choices that received no votes
-  foreach ($node->choice as $i => $choice) {
-    if (!isset($choice_votes[$i])) {
-      // Didn't receive any votes
-      $choice_votes[$i] = 0;
-    }
-  }
-
-  // Sort descending (although there may be ties)
-  arsort($choice_votes);
-
-  // Figure out the final ranking
-  $ranking = array();
-  $previous_total = -1;
-  $cur_result = -1;
-
-  foreach ($choice_votes as $choice => $total) {
-    if ($total != $previous_total) {
-      // Didn't tie with the previous score
-      $cur_result++;
-    }
-    $ranking[$cur_result]['choices'][] = $choice;
-    $ranking[$cur_result]['rawscore'] = $total;
-    $ranking[$cur_result]['viewscore'] = 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;
-}
-
-
-/**
- * Calculate the results using instant-runoff voting.
- * 
- * @param $node
- *  The node object for the current poll.
- *
- * @return 
- *   Should return an object that include the following attributes.
- *   -results : 2d array listing the aggregate preference, including ties
- *   -rounds : 2d array listing the per-choice vote count for each round and
- *              a status message indicating who was eliminated
- *   -totalVoters : the total number of voters who participated
- */
-function _advpoll_calculate_instantrunoff($node) {
-   $votes = array();
-  // ORDER BY value ASC lets us ensure no gaps
-  $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) {
-    // No votes yet
-    return array();
-  }
-
-  // Aggregate votes by user (uid if logged in, IP if anonymous)
-  // in ascending order of value.
-  $user_votes = array();
-
-  foreach ($votes as $vote) {
-    if ($vote->uid == 0) {
-      // Anonymous user
-      $key = $vote->hostname;
-    }
-    else {
-      // Logged-in user
-      $key = $vote->uid;
-    }
-
-    // Note: relies on ORDER BY value ASC in vote-getting SQL query.
-    // Otherwise a later vote might have a lower value.
-    $user_votes[$key][] = $vote->tag;
-  }
-
-  $total_votes = count($user_votes);
-
-  // Log of 1st-place votes per choice in each round
-  $round_log = array();
-
-  // Gradually append candidates as they are eliminated; end with the winner
-  $reverse_ranking = array();
-  
-  // If we eliminate one choice per round and have n choices, we should
-  // not be able to do more than n - 1 rounds.
-  $max_rounds = count($node->choice); 
-  for ($round = 0; $round < $max_rounds; $round++) {
-
-    // Initialize cur_round
-    $cur_round = array();
-    $total_choices = count($node->choice);
-
-    foreach ($node->choice as $chi => $temp) {
-      $cur_round[$chi] = array();
-    }
-
-    
-    // Loop through each user
-    foreach ($user_votes as $key => $user_vote) {
-      // $user_vote[0] contains the user's first remaining preference
-      $cur_round[$user_vote[0]][] = $key;
-    }
-
-    if ($round == 0) {
-      // This is the first round.
-      // Any choices with no first-place votes are considered eliminated.
-      foreach ($cur_round as $ch => $choice_votes) {
-        if (count($choice_votes) == 0) {
-          unset($cur_round[$ch]);
-          $reverse_ranking[0]['choices'][] = $ch;
-        }
-      }
-    }
-
-
-    // Add the current round to the matrix
-    $round_log[] = $cur_round;
-
-    // Calculate the min and max number of votes
-    $min_votes = -1;
-    $max_votes = 0;
-
-    // Number of choices that have already been discarded
-    $num_discarded = 0;
-
-    // Examine the number of votes each choice received this round
-    foreach ($cur_round as $ch => $choice_votes) {
-      $num_votes = count($choice_votes);
-
-      if ($num_votes > $max_votes) {
-        $max_votes = $num_votes;
-        // Store current winner in case it has a majority
-        $cur_winner = $ch;
-      }
-
-      // This choice has already been eliminated (theoretically)
-      // so don't count it as the minimum.
-      if ($num_votes == 0) {
-        $num_discarded++; // XXX: Probably don't need this variable any more
-      }
-      else if ($num_votes != 0 && ($num_votes < $min_votes || $min_votes == -1)) {
-        $min_votes = $num_votes;
-      }
-    }
-
-    // If one choice has a majority of remaining users it wins.
-    // Note: we use count($user_votes) because some users may have incomplete
-    // ballots and may have already had all of their choices eliminated.
-    if ($max_votes > count($user_votes) / 2) {
-    
-      // Prune out the winning choice if it's still in there
-      if (isset($cur_round[$cur_winner])) {
-          unset($cur_round[$cur_winner]);
-      }
-
-      // Keep computing until we figure out all final rankings
-      while (count($cur_round)  > 0) {
-        // Loop through non-winning choices
-        $current_place = array();
-        $min = -1;
-        foreach ($cur_round as $ch => $choice_votes) {
-          // Choice has already been eliminated, just unset it
-          if (count($choice_votes) == 0) {
-            unset($cur_round[$ch]);
-          }
-          else if ($min == -1
-              || count($choice_votes) < $min) {
-            // New minimum
-            $current_place = array($ch);
-            $min = count($choice_votes);
-          }
-          else if (count($choice_votes) == $min) {
-            // Tied for minimum
-            $current_place[] = $ch;
-          }
-        }
-
-        // current_place will be empty the first iteration if some
-        // choices had no first-place votes and were eliminated
-        // at the beginning.
-        if (count($current_place) > 0) {
-          $reverse_ranking[]['choices'] = $current_place;  
-          // Remove all choices that had the minimum
-          foreach ($current_place as $ch_key) {
-            unset($cur_round[$ch_key]);
-          }
-        }
-      }
-
-      // Save a reversed version of the round log to help compute winnerPercent
-      $revmat = array_reverse($round_log);
-
-      // The winner finally gets added
-      $reverse_ranking[]['choices'] = array($cur_winner);
-      $index = count($reverse_ranking) - 1;
-      $reverse_ranking[$index]['rawscore'] = round(count($revmat[0][$cur_winner]) * 100 / count($user_votes), 1);
-      $reverse_ranking[$index]['viewscore'] = $reverse_ranking[$index]['rawscore'] .'%';
-
-      $result_obj->matrix = $round_log;
-      $result_obj->total_votes = $total_votes;
-      $result_obj->ranking = array_reverse($reverse_ranking);
-      return $result_obj;
-    }
-    
-    // Since we're still here, no one has won, so eliminate one of the
-    // choices with the lowest number of votes.
-
-     // Find all choices with the minimum number of votes
-    $min_choices = array();
-    foreach ($cur_round as $ch => $choice_votes) {
-      if (count($choice_votes) == $min_votes) {
-        $min_choices[] = $ch;
-      }
-     }
-
-    // Randomly select the choice to eliminate out of the available choices.
-    // TODO: due to the randomness, this result must be cached after each vote.
-    $round_loser = array_rand($min_choices);
-
-    $reverse_ranking[]['choices'] = array($min_choices[$round_loser]);
-    
-    // Loop through the users who voted for the loser and redistribute
-    foreach ($cur_round[$min_choices[$round_loser]] as $user_key) {
-      // Remove their current first preference
-      array_shift($user_votes[$user_key]);
-
-      // Keep eliminating first preference until we run out or find an choice
-      // that hasn't been eliminated.
-      while ($cur_round[$user_votes[$user_key][0]] == array() && count($user_votes[$user_key]) > 0) {
-        array_shift($user_votes[$user_key]);
-      }
-
-      // If they have no more preferences, remove from list for simplicity.
-      if (count($user_votes[$user_key]) == 0) {
-        unset($user_votes[$user_key]);
-      }
-    }
-  }
-  // Loop detected. Signal user and record.
-  drupal_set_message("Could not find a solution within $max_rounds iterations.");
-  $result_obj->matrix = $round_log;
-  $result_obj->total_votes = $total_votes;
-  return $result_obj;
-}
-
-/**
- * Implementation of the vote hook for the runoff module.
- *
- * This takes care of registering the vote in runoff nodes.
- */
-function advpoll_voting_ranking_form_submit($form_id, $form_values) {
-  $vote = array();
-  foreach ($form_values['choice'] as $choice => $rank) {
-    unset($temp);
-    $temp->value = $rank;
-    // A zero value indicates they didn't rank that choice
-    if ($temp->value != 0) {
-      $temp->value_type = 'option';
-      $temp->tag = $choice;
-      $vote[] = $temp;
-    }
-  }
-
-  votingapi_set_vote('advpoll', $form_values['nid'], $vote);
-  $node = node_load($form_values['nid']);
-  _advpoll_vote_response($node, $form_values);
-}
-
-/**
- * Implementation of the vote validation hook for the runoff module.
- *
- * This checks if the submitted values are within range, if they are
- * not empty, and if they are not repeated.
- *
- * @returns boolean false on invalid forms, true otherwise.
- */
-function advpoll_voting_ranking_form_validate($form_id, $form_values) {
-  $node = node_load($form_values['nid']);
-  $ajax = $form_values['ajax'];
-  $ok = TRUE;
-
-  // Check if user has already voted
-  list($voted, $cancel_vote) = _advpoll_user_voted($node);
-  if ($voted) {
-    $msg = t('You have already voted in this poll.');
-    if ($ajax) {
-      $errors[] = $msg;
-    }
-    else {
-      form_set_error('choice[', $msg);
-    }
-    $ok = FALSE;
-  }
-
-
-  // Array used to check which values are set
-  $setvalues = array();
-  
-  $numchoices = 0;
-  foreach ($node->choice as $key => $choice) {
-    
-    // Count the number of choices that are ranked
-    if ($form_values['choice'][$key]) {
-      $numchoices++;
-    }
-    $intvalue = intval($form_values['choice'][$key]);
-    // Mark this value as seen
-    $setvalues[$intvalue]++;
-    // Check range
-    if ($intvalue > count($node->choice) || $intvalue < 0) {
-      // TODO: clean up this error message
-      $msg = "Illegal rank for choice $key: $intvalue (min: 1, max: "
-        . count($node->choice) .')';
-      if ($ajax) {
-        $errors[] = $msg;
-      }
-      else {
-        form_set_error('choice]['. $key, $msg);
-      }
-      $ok = FALSE;
-    }
-  }
-  
-  // Too many choices ranked
-  if ($node->maxchoices != 0 && $numchoices > $node->maxchoices) {
-    $msg = t('%num choices were selected but only %max are allowed.',
-        array('%num' => $numchoices, '%max' => $node->maxchoices));
-    if ($ajax) {
-      $errors[] = $msg;
-    }
-    else {
-      form_set_error('choice', $msg);
-    }
-    $ok = false;
-  }
-
-  // Not enough choices ranked
-  $minchoices = 1;
-  if ($numchoices < $minchoices) {
-    $msg = t('At least one choice must be selected.');
-    if ($ajax) {
-      $errors[] = $msg;
-    }
-    else {
-      form_set_error('choice', $msg);
-    }
-    $ok = false;
-  }
-
-  // Check that multiple choices are not set to the same value
-  foreach ($setvalues as $val => $count) {
-    if ($val != 0 && $count > 1) {
-      $msg = t('Multiple choices given the rank of %val.', array('%val' => $val));
-      if ($ajax) {
-        $errors[] = $msg;
-      }
-      else {
-        form_set_error('choice', $msg);
-      }
-    
-      $ok = false;
-    }
-  }
-  // If the form was posted with AJAX and has errors, print the error message.
-  if ($ajax && !$ok) {
-    drupal_set_header('Content-Type: text/plain; charset=utf-8');
-    print drupal_to_js(array('errors' => '<div class="messages error">'. implode('<br />', $errors) .'</div>'));
-    exit;
-  }
-  // Do as usual
-  else {
-    return $ok;
-  }
-}
-
-/**
- * Render the voting form.
- */
-function theme_advpoll_voting_ranking_form($form) {
-  $output = "<div class=\"poll\">\n";
-  $output .= drupal_render($form);
-  $output .= "</div>\n";
-  return $output;
-}
+<?php
+// $Id: ranking.inc,v 1.8.2.20 2007/06/01 07:50:19 fajerstarter Exp $
+
+function advpoll_info_ranking() {
+  return array(
+    'name' => '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) {
+  $form = array();
+
+  static $ranking_form_count = 0; 
+  $form['#id'] = 'advpoll_voting_ranking_form-'. $ranking_form_count++;
+  $form['#attributes'] = array('class' => 'advpoll-vote');
+
+  $form['ajax'] = array(
+    '#type' => 'hidden',
+    '#attributes' => array('class' => 'ajax'),
+  );
+
+
+  if ($node->choice) {
+    $list = array();
+
+    $num_choices = count($node->choice);
+
+    // Add one to number of choices if write-ins enabled and user has permission
+    if($node->writeins && user_access('add write-ins')) {
+      $num_choices++;
+    }
+
+    // Generate the list of possible rankings
+    $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;
+    }
+
+    $form['choice'] = array(
+      '#tree' => TRUE,
+      // XXX: Workaround for FormAPI bug in PHP 4, see http://drupal.org/node/86657
+      '#type' => 'checkboxes',
+    );
+
+    foreach ($node->choice as $key => $choice) {
+      // Don't show blank choices or write-in votes if the setting is disabled
+      if ($choice['label'] && ($node->displaywriteins || !$choice['writein'])) {
+        $form['choice'][$key] = array(
+          '#type' => 'select',
+          '#title' => _advpoll_choice_markup($choice['label'], $node->format),
+          '#options' => $choices,
+        );
+      }
+    }
+
+    // Add write-in select box if write-ins are enabled and user has permission
+    if($node->writeins && user_access('add write-ins')) {
+      $form['choice'][$key+1] = array (
+        '#type' => 'select',
+        '#title' => t('(write-in)'),
+        '#options' => $choices,
+      );
+      // Key index of the write-in option
+      $form['writein_key'] = array(
+        '#type' => 'value',
+        '#value' => $key+1,
+      );
+    }
+  }
+
+  // Add write-in text field if write-ins are enabled and user has permission
+  if($node->writeins && user_access('add write-ins')) {
+    $form['writein_choice'] = array (
+      '#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) {
+    static $ranking_vote_count = 0;
+    $form['vote'] = array(
+      '#type' => 'submit',
+      '#value' => t('Vote'),
+      '#attributes' => array('id' => 'edit-vote-rank-'. $ranking_vote_count++),
+    );
+  }
+
+  $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 no one has voted, $results = array() and thus is empty.
+  if (!empty($results)) {
+    // Temporary list of choices indexes for the ranking
+    $ranking_list = array();
+    // Result object
+    $ranking = array();
+    $choices = array();
+    $poll = array();
+    $rounds = array();
+    foreach ($results as $result) {
+      $tag = $result->tag;
+      if ($tag == '_advpoll') {
+        // Poll-wide cached value
+        $poll[$result->function] = $result->value;
+      }
+      else if (strstr($tag, '_rounds_')) {
+        // Reconstruct round data.
+        // Extract the round from the tag.
+        $round = str_replace('_rounds_', '', $tag);
+        if (!isset($rounds[$round])) {
+          $rounds[$round] = array();
+        }
+        // $result->function actually stores $choice.
+        $rounds[$round][$result->function] = $result->value;
+      }
+      else if (isset($node->choice[$tag])) {
+        // Choice-specific cached value
+        if ($result->function == 'ranking') {
+          $ranking_list[$result->value][] = $tag;
+        }
+        else if (!isset($node->choice[$result->function])) {
+          $choices[$tag][$result->function] = $result->value;
+        }
+      }
+    }
+
+    // Re-construct the rankings object
+    foreach ($ranking_list as $i => $choice_list) {
+      $ranking[$i]->choices = array();
+      foreach ($choice_list as $choice_i) {
+        $ranking[$i]->choices[] = $choice_i;
+        $ranking[$i]->viewscore = $choices[$choice_i]['viewscore'];
+        $ranking[$i]->rawscore = $choices[$choice_i]['rawscore'];
+        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 = '';
+
+        // Loop through all choices with this ranking
+        foreach ($ranking[$i]->choices as $choice) {
+          $this_rank .= ($first_one? '' : ', ') . _advpoll_choice_markup($node->choice[$choice]['label'], $node->format);
+          $first_one = false;
+        }
+
+        $percentage = round(100 * $ranking[$i]->percentage, 0);
+        $output .= theme('advpoll_bar', $this_rank, $percentage, $ranking[$i]->viewscore);
+
+      }
+    }
+    else {
+      $output .= '<ol>';
+
+      for ($i = 0; $i < count($ranking); $i++) {
+        $output .= '<li> ';
+        $first_one = true;
+
+        // Loop through all choices with this ranking
+        foreach ($ranking[$i]->choices as $choice) {
+          $output .= ($first_one? '' : ', ') . _advpoll_choice_markup($node->choice[$choice]['label'], $node->format);
+          $first_one = false;
+        }
+
+        // Show the ranking's score if it exists (depends on algorithm)
+        if ($ranking[$i]->viewscore) {
+          $output .= ' ('. $ranking[$i]->viewscore .'%)';
+        }
+        $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) {
+            // This is the last round.
+            $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']);
+}
+
+/**
+ * Calculate the results for a ranking poll based on the algorithm.
+ *
+ * @param $node
+ *  The node object for the current poll
+ *
+ * @return 
+ *   Should return an object that include the following attributes
+ *   -results : 2d array listing the aggregate preference, including ties
+ *   -rounds : 2d array listing the per-choice vote count for each round and
+ *              a status message indicating who was eliminated
+ *   -totalVoters : the total number of voters who participated
+ */
+function advpoll_calculate_results_ranking(&$cache, $votes, $node) {
+  if ($node->algorithm == 'borda count') {
+    $results = _advpoll_calculate_bordacount($node);
+  }
+  else {
+    $results = _advpoll_calculate_instantrunoff($node);
+  }
+
+  // Cache rankings
+  // API: $cache[$tag][$type][$function] = $value (0 is the default $type)
+  for ($i = 0; $i < count($results->ranking); $i++) {
+    foreach ($results->ranking[$i]['choices'] as $choice) {
+      $cache[$choice][0]['ranking'] = $i;
+      $cache[$choice][0]['rawscore'] = $results->ranking[$i]['rawscore'];
+      $cache[$choice][0]['viewscore'] = $results->ranking[$i]['viewscore'];
+      if (isset($results->ranking[$i]['percentage'])) {
+        $cache[$choice][0]['percentage'] = $results->ranking[$i]['percentage'];
+      }
+    }
+  }
+
+  // Cache round results
+  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 total votes
+  $cache['_advpoll'][0]['total_votes'] = $results->total_votes;
+
+  // Cache total points (if it exists)
+  if (isset($results->total_points)) {
+    $cache['_advpoll'][0]['total_points'] = $results->total_points;
+  }
+}
+
+/**
+ * Calculate the results using borda count.
+ * 
+ * @param $node
+ *  The node object for the current poll.
+ *
+ * @return 
+ *   Should return an object that include the following attributes
+ *   -results : 2d array listing the aggregate preference, including ties
+ *   -rounds : 2d array listing the per-choice vote count for each round and
+ *              a status message indicating who was eliminated
+ *   -totalVoters : the total number of voters who participated
+ */
+function _advpoll_calculate_bordacount($node) {
+   $votes = array();
+  // ORDER BY value ASC lets us ensure no gaps
+  $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) {
+    // No votes yet
+    return array();
+  }
+
+  // Aggregate votes by user (uid if logged in, IP if anonymous)
+  // in ascending order of value.
+  $user_votes = array();
+
+  foreach ($votes as $vote) {
+    if ($vote->uid == 0) {
+      // Anonymous user
+      $key = $vote->hostname;
+    }
+    else {
+      // Logged-in user
+      $key = $vote->uid;
+    }
+
+    $user_votes[$key][$vote->value] = $vote->tag;
+  }
+
+  $choice_votes = array();
+
+  $total_choices = count($node->choice);
+  $total_points = 0;
+
+  // Loop through each user's vote
+  foreach ($user_votes as $uid => $user_vote) {
+    foreach ($user_vote as $ranking => $choice) {
+      // Negative values are possible if choices were removed after vote
+      $vote_value = max($total_choices - $ranking, 0);
+      $choice_votes[$choice] +=   $vote_value;
+      $total_points += $vote_value;
+    }
+  }
+
+  // Add any remaining choices that received no votes
+  foreach ($node->choice as $i => $choice) {
+    if (!isset($choice_votes[$i])) {
+      // Didn't receive any votes
+      $choice_votes[$i] = 0;
+    }
+  }
+
+  // Sort descending (although there may be ties)
+  arsort($choice_votes);
+
+  // Figure out the final ranking
+  $ranking = array();
+  $previous_total = -1;
+  $cur_result = -1;
+
+  foreach ($choice_votes as $choice => $total) {
+    if ($total != $previous_total) {
+      // Didn't tie with the previous score
+      $cur_result++;
+    }
+    $ranking[$cur_result]['choices'][] = $choice;
+    $ranking[$cur_result]['rawscore'] = $total;
+    $ranking[$cur_result]['viewscore'] = 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;
+}
+
+
+/**
+ * Calculate the results using instant-runoff voting.
+ * 
+ * @param $node
+ *  The node object for the current poll.
+ *
+ * @return 
+ *   Should return an object that include the following attributes.
+ *   -results : 2d array listing the aggregate preference, including ties
+ *   -rounds : 2d array listing the per-choice vote count for each round and
+ *              a status message indicating who was eliminated
+ *   -totalVoters : the total number of voters who participated
+ */
+function _advpoll_calculate_instantrunoff($node) {
+   $votes = array();
+  // ORDER BY value ASC lets us ensure no gaps
+  $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) {
+    // No votes yet
+    return array();
+  }
+
+  // Aggregate votes by user (uid if logged in, IP if anonymous)
+  // in ascending order of value.
+  $user_votes = array();
+
+  foreach ($votes as $vote) {
+    if ($vote->uid == 0) {
+      // Anonymous user
+      $key = $vote->hostname;
+    }
+    else {
+      // Logged-in user
+      $key = $vote->uid;
+    }
+
+    // Note: relies on ORDER BY value ASC in vote-getting SQL query.
+    // Otherwise a later vote might have a lower value.
+    $user_votes[$key][] = $vote->tag;
+  }
+
+  $total_votes = count($user_votes);
+
+  // Log of 1st-place votes per choice in each round
+  $round_log = array();
+
+  // Gradually append candidates as they are eliminated; end with the winner
+  $reverse_ranking = array();
+  
+  // If we eliminate one choice per round and have n choices, we should
+  // not be able to do more than n - 1 rounds.
+  $max_rounds = count($node->choice); 
+  for ($round = 0; $round < $max_rounds; $round++) {
+
+    // Initialize cur_round
+    $cur_round = array();
+    $total_choices = count($node->choice);
+
+    foreach ($node->choice as $chi => $temp) {
+      $cur_round[$chi] = array();
+    }
+
+    
+    // Loop through each user
+    foreach ($user_votes as $key => $user_vote) {
+      // $user_vote[0] contains the user's first remaining preference
+      $cur_round[$user_vote[0]][] = $key;
+    }
+
+    if ($round == 0) {
+      // This is the first round.
+      // Any choices with no first-place votes are considered eliminated.
+      foreach ($cur_round as $ch => $choice_votes) {
+        if (count($choice_votes) == 0) {
+          unset($cur_round[$ch]);
+          $reverse_ranking[0]['choices'][] = $ch;
+        }
+      }
+    }
+
+
+    // Add the current round to the matrix
+    $round_log[] = $cur_round;
+
+    // Calculate the min and max number of votes
+    $min_votes = -1;
+    $max_votes = 0;
+
+    // Number of choices that have already been discarded
+    $num_discarded = 0;
+
+    // Examine the number of votes each choice received this round
+    foreach ($cur_round as $ch => $choice_votes) {
+      $num_votes = count($choice_votes);
+
+      if ($num_votes > $max_votes) {
+        $max_votes = $num_votes;
+        // Store current winner in case it has a majority
+        $cur_winner = $ch;
+      }
+
+      // This choice has already been eliminated (theoretically)
+      // so don't count it as the minimum.
+      if ($num_votes == 0) {
+        $num_discarded++; // XXX: Probably don't need this variable any more
+      }
+      else if ($num_votes != 0 && ($num_votes < $min_votes || $min_votes == -1)) {
+        $min_votes = $num_votes;
+      }
+    }
+
+    // If one choice has a majority of remaining users it wins.
+    // Note: we use count($user_votes) because some users may have incomplete
+    // ballots and may have already had all of their choices eliminated.
+    if ($max_votes > count($user_votes) / 2) {
+    
+      // Prune out the winning choice if it's still in there
+      if (isset($cur_round[$cur_winner])) {
+          unset($cur_round[$cur_winner]);
+      }
+
+      // Keep computing until we figure out all final rankings
+      while (count($cur_round)  > 0) {
+        // Loop through non-winning choices
+        $current_place = array();
+        $min = -1;
+        foreach ($cur_round as $ch => $choice_votes) {
+          // Choice has already been eliminated, just unset it
+          if (count($choice_votes) == 0) {
+            unset($cur_round[$ch]);
+          }
+          else if ($min == -1
+              || count($choice_votes) < $min) {
+            // New minimum
+            $current_place = array($ch);
+            $min = count($choice_votes);
+          }
+          else if (count($choice_votes) == $min) {
+            // Tied for minimum
+            $current_place[] = $ch;
+          }
+        }
+
+        // current_place will be empty the first iteration if some
+        // choices had no first-place votes and were eliminated
+        // at the beginning.
+        if (count($current_place) > 0) {
+          $reverse_ranking[]['choices'] = $current_place;  
+          // Remove all choices that had the minimum
+          foreach ($current_place as $ch_key) {
+            unset($cur_round[$ch_key]);
+          }
+        }
+      }
+
+      // Save a reversed version of the round log to help compute winnerPercent
+      $revmat = array_reverse($round_log);
+
+      // The winner finally gets added
+      $reverse_ranking[]['choices'] = array($cur_winner);
+      $index = count($reverse_ranking) - 1;
+      $reverse_ranking[$index]['rawscore'] = round(count($revmat[0][$cur_winner]) * 100 / count($user_votes), 1);
+      $reverse_ranking[$index]['viewscore'] = $reverse_ranking[$index]['rawscore'] .'%';
+
+      $result_obj->matrix = $round_log;
+      $result_obj->total_votes = $total_votes;
+      $result_obj->ranking = array_reverse($reverse_ranking);
+      return $result_obj;
+    }
+    
+    // Since we're still here, no one has won, so eliminate one of the
+    // choices with the lowest number of votes.
+
+     // Find all choices with the minimum number of votes
+    $min_choices = array();
+    foreach ($cur_round as $ch => $choice_votes) {
+      if (count($choice_votes) == $min_votes) {
+        $min_choices[] = $ch;
+      }
+     }
+
+    // Randomly select the choice to eliminate out of the available choices.
+    // TODO: due to the randomness, this result must be cached after each vote.
+    $round_loser = array_rand($min_choices);
+
+    $reverse_ranking[]['choices'] = array($min_choices[$round_loser]);
+    
+    // Loop through the users who voted for the loser and redistribute
+    foreach ($cur_round[$min_choices[$round_loser]] as $user_key) {
+      // Remove their current first preference
+      array_shift($user_votes[$user_key]);
+
+      // Keep eliminating first preference until we run out or find an choice
+      // that hasn't been eliminated.
+      while ($cur_round[$user_votes[$user_key][0]] == array() && count($user_votes[$user_key]) > 0) {
+        array_shift($user_votes[$user_key]);
+      }
+
+      // If they have no more preferences, remove from list for simplicity.
+      if (count($user_votes[$user_key]) == 0) {
+        unset($user_votes[$user_key]);
+      }
+    }
+  }
+  // Loop detected. Signal user and record.
+  drupal_set_message("Could not find a solution within $max_rounds iterations.");
+  $result_obj->matrix = $round_log;
+  $result_obj->total_votes = $total_votes;
+  return $result_obj;
+}
+
+/**
+ * Implementation of the vote hook for the runoff module.
+ *
+ * This takes care of registering the vote in runoff nodes.
+ */
+function advpoll_voting_ranking_form_submit($form_id, $form_values) {
+  $vote = array();
+  $node = node_load($form_values['nid']);
+
+  // Do submission specific to writeins
+  _advpoll_writeins_voting_form_submit($node, $form_values, $vote, $form_values['choice'][$form_values['writein_key']]);
+
+  foreach ($form_values['choice'] as $choice => $rank) {
+    // Ignore write-in choice that has already taken care of
+    if($choice != $form_values['writein_key']) {
+      unset($temp);
+      $temp->value = $rank;
+      // A zero value indicates they didn't rank that choice
+      if ($temp->value != 0) {
+        $temp->value_type = 'option';
+        $temp->tag = $choice;
+        $vote[] = $temp;
+      }
+    }
+  }
+
+  votingapi_set_vote('advpoll', $form_values['nid'], $vote);
+  _advpoll_vote_response($node, $form_values);
+}
+
+/**
+ * Implementation of the vote validation hook for the runoff module.
+ *
+ * This checks if the submitted values are within range, if they are
+ * not empty, and if they are not repeated.
+ *
+ * @returns boolean false on invalid forms, true otherwise.
+ */
+function advpoll_voting_ranking_form_validate($form_id, $form_values) {
+  $node = node_load($form_values['nid']);
+  $ajax = $form_values['ajax'];
+  $ok = TRUE;
+
+  // Whether the write-in option is selected. This is calculated differently for
+  // radio buttons and checkboxes.
+  $writein_option = false;
+  $writein_text = $form_values['writein_key'] ? $form_values['writein_choice'] : '';
+
+  // Check if user has already voted
+  list($voted, $cancel_vote) = _advpoll_user_voted($node);
+  if ($voted) {
+    $msg = t('You have already voted in this poll.');
+    if ($ajax) {
+      $errors[] = $msg;
+    }
+    else {
+      form_set_error('choice[', $msg);
+    }
+    $ok = FALSE;
+  }
+
+
+  // Array used to check which values are set
+  $setvalues = array();
+  
+  $numchoices = 0;
+
+  // If write-ins are enabled and user has permission and the write-in box is checked
+  if($node->writeins && user_access('add write-ins') && $form_values['choice'][$form_values['writein_key']]) {
+    // Increment the choices counter by one
+    $numchoices++;
+    // Set the flag to true for additional checks
+    $writein_option = true;
+  }
+
+  foreach ($node->choice as $key => $choice) {
+    
+    // Count the number of choices that are ranked
+    if ($form_values['choice'][$key]) {
+      $numchoices++;
+    }
+    $intvalue = intval($form_values['choice'][$key]);
+    // Mark this value as seen
+    $setvalues[$intvalue]++;
+    // Check range
+    if ($intvalue > ($writein_option ? count($node->choice) + 1 : count($node->choice)) || $intvalue < 0) {
+      // TODO: clean up this error message
+      $msg = "Illegal rank for choice $key: $intvalue (min: 1, max: "
+        . ($writein_option ? count($node->choice) + 1 : count($node->choice)) .')';
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice]['. $key, $msg);
+      }
+      $ok = FALSE;
+    }
+  }
+
+  // If write-ins are enabled and user has permission and the write-in box is checked
+  if($writein_option) {
+    $intvalue = intval($form_values['choice'][$form_values['writein_key']]);
+    // mark this value as seen
+    $setvalues[$intvalue]++;
+    // check range
+    if ($intvalue > ($writein_option ? count($node->choice) + 1 : count($node->choice)) || $intvalue < 0) {
+      // TODO: clean up this error message
+      $msg = "Illegal rank for the write-in choice: $intvalue (min: 1, max: "
+        . count($node->choice) . ')';
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice][', $msg);
+      }
+      $ok = FALSE;
+    }
+  }
+
+  // Too many choices ranked
+  if ($node->maxchoices != 0 && $numchoices > $node->maxchoices) {
+    $msg = t('%num choices were selected but only %max are allowed.',
+        array('%num' => $numchoices, '%max' => $node->maxchoices));
+    if ($ajax) {
+      $errors[] = $msg;
+    }
+    else {
+      form_set_error('choice', $msg);
+    }
+    $ok = false;
+  }
+
+  // Not enough choices ranked
+  $minchoices = 1;
+  if ($numchoices < $minchoices) {
+    $msg = t('At least one choice must be selected.');
+    if ($ajax) {
+      $errors[] = $msg;
+    }
+    else {
+      form_set_error('choice', $msg);
+    }
+    $ok = false;
+  }
+
+  // Check that multiple choices are not set to the same value
+  foreach ($setvalues as $val => $count) {
+    if ($val != 0 && $count > 1) {
+      $msg = t('Multiple choices given the rank of %val.', array('%val' => $val));
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice', $msg);
+      }
+    
+      $ok = false;
+    }
+  }
+
+  // Do validation specific to writeins
+  _advpoll_writeins_voting_form_validate($node, $writein_option, $writein_text, $errors, $ok, $ajax);
+
+  // If the form was posted with AJAX and has errors, print the error message.
+  if ($ajax && !$ok) {
+    drupal_set_header('Content-Type: text/plain; charset=utf-8');
+    print drupal_to_js(array('errors' => '<div class="messages error">'. implode('<br />', $errors) .'</div>'));
+    exit;
+  }
+  // Do as usual
+  else {
+    return $ok;
+  }
+}
+
+/**
+ * Render the voting form.
+ */
+function theme_advpoll_voting_ranking_form($form) {
+  $output = "<div class=\"poll\">\n";
+  $output .= drupal_render($form);
+  $output .= "</div>\n";
+  return $output;
+}
Index: modes/binary.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/modes/binary.inc,v
retrieving revision 1.7.2.16
diff -u -r1.7.2.16 binary.inc
--- modes/binary.inc	1 Jun 2007 07:50:19 -0000	1.7.2.16
+++ modes/binary.inc	6 Jun 2007 16:35:19 -0000
@@ -1,258 +1,309 @@
-<?php
-// $Id: binary.inc,v 1.7.2.16 2007/06/01 07:50:19 fajerstarter Exp $
-
-function advpoll_info_binary() {
-  return array(
-    'name' => 'binary',
-    'description' => t('Vote for or against a number of choices.'),
-  );
-}
-
-function advpoll_algorithms_binary() {
-  return array('plurality' => t('plurality'));
-}
-
-/**
- * Implementation of the view_voting hook for the poll module.
- * 
- * This creates a list of choices to allow the user to vote on choices.
- */
-function advpoll_voting_binary_form(&$node, $teaser, $page) {
-  static $binary_form_count = 0; 
-  $form['#id'] = 'advpoll_voting_binary_form-'. $binary_form_count++;
-  $form['#attributes'] = array('class' => 'advpoll-vote');
-  
-  $form['ajax'] = array(
-    '#type' => 'hidden',
-    '#attributes' => array('class' => 'ajax'),
-  );
-
-  if ($node->choice) {
-    $list = array();
-    foreach ($node->choice as $i => $choice) {
-      // Don't show blank choices
-      if ($choice['label']) {
-         $list[$i] = _advpoll_choice_markup($choice['label'], $node->format);
-      }
-    }
-    $form['choice'] = array(
-      '#options' => $list,
-    );
-
-    if ($node->in_preview) {
-      $maxchoices = $node->settings['maxchoices'];
-    }
-    else {
-      $maxchoices = $node->maxchoices; 
-    }
-    if ($maxchoices == 1) {
-      // Plurality voting
-      $form['choice']['#type'] = 'radios';
-      $form['choice']['#default_value'] = -1;
-    }
-    else {
-      // Approval voting
-      $form['choice']['#type'] = 'checkboxes';
-    }
-  }
-
-  $form['nid'] = array(
-    '#type' => 'hidden',
-    '#value' => $node->nid,
-    '#attributes' => array('class' => 'edit-nid'),
-  );
-
-  if (!$node->in_preview) {
-    static $binary_vote_count = 0;
-    $form['vote'] = array(
-      '#type' => 'submit',
-      '#value' => t('Vote'),
-      '#attributes' => array('id' => 'edit-vote-binary-'. $binary_vote_count++),
-    );
-  }
-
-  $form['#action'] = url('node/'. $node->nid);
-  return $form;
-}
-
-function advpoll_view_results_binary($node, $teaser, $page) {
-  $content_type = 'advpoll';
-  $content_id = $node->nid;
-
-  $results = votingapi_get_voting_results($content_type, $content_id);
-  $votes = array();
-  foreach ($results as $result) {
-    $voteval = $result->tag;
-    if ($voteval == '_advpoll') {
-      if ($result->function == 'total_votes') {
-        $total_votes = $result->value;
-      }
-    }
-    else if (isset($node->choice[$voteval])) {
-      if (!$votes[$voteval]) {
-        $votes[$voteval] = 0;
-      }
-      $votes[$voteval] = $result->value;
-    }
-  }
-
-  if ($node->choice && $total_votes > 0) {
-    // Add in any choices that received no votes.
-    foreach ($node->choice as $i => $ch) {
-      if (!isset($votes[$i])) {
-        $votes[$i] = 0;
-      }
-    }
-
-    // Sort results by votes, descending.
-    arsort($votes);
-
-    // Display results for each possible choice
-    foreach ($votes as $i => $count) {
-      $choice = $node->choice[$i];
-      $percentage = round(100 * $votes[$i] / $total_votes, 0);
-      $output .= theme('advpoll_bar', _advpoll_choice_markup($choice['label'], $node->format), $percentage, format_plural($count, '1 vote', '@count votes'));
-    }
-  }
-
-  return array('results' => $output, 'votes' => $total_votes);
-}
-
-function advpoll_calculate_results_binary(&$results, $votes, $node) {
-  $voters = array();
-  foreach ($votes as $vote) {
-    if ($vote->uid) {
-      $key = $vote->uid;
-    }
-    else {
-      $key = $vote->hostname;
-    }
-    $voters[$key] = TRUE;
-  }
-  $results['_advpoll'] = array(array('total_votes' => count($voters)));
-}
-
-/**
- * Registers the vote as a key for this node using votingapi_set_vote().
- */
-function advpoll_voting_binary_form_submit($form_id, $form_values) {
-  $vote = array();
-  $node = node_load($form_values['nid']);
-  if ($node->maxchoices == 1) {
-    // Plurality voting
-    $temp->value = 1;
-    $temp->tag = $form_values['choice'];
-    $temp->value_type = 'option';
-    $vote[] = $temp;
-  }
-  else {
-    // Approval voting
-    foreach ($form_values['choice'] as $choice => $selected) {
-      unset($temp);
-      $temp->value = $choice;
-      if ($selected) {
-        $temp->value_type = 'option';
-        $temp->tag = $choice;
-        $temp->value = 1;
-        $vote[] = $temp;
-      }
-    }
-  }
-
-  votingapi_set_vote('advpoll', $form_values['nid'], $vote);
-  _advpoll_vote_response($node, $form_values);
-}
-
-/**
- * Check if the submitted key exists, just to make sure the form is not bypassed.
- *
- * @returns boolean true if the form is valid
- */
-function advpoll_voting_binary_form_validate($form_id, $form_values) {
-  $node = node_load($form_values['nid']);
-  $ajax = $form_values['ajax'];
-  $ok = TRUE;
-
-  // Check if user has already voted
-  list($voted, $cancel_vote) = _advpoll_user_voted($node);
-  if ($voted) {
-    $msg = t('You have already voted in this poll.');
-    if ($ajax) {
-      $errors[] = $msg;
-    }
-    else {
-      form_set_error('choice[', $msg);
-    }
-    $ok = FALSE;
-  }
-
-  if ($node->maxchoices == 1) {
-    // Plurality voting
-    if (!($ok = array_key_exists($form_values['choice'], $node->choice))) {
-      $msg = t('At least one choice must be selected.');
-      if ($ajax) {
-        $errors[] = $msg;
-      }
-      else {
-        form_set_error('choice[', $msg);
-      }
-      $ok = FALSE;
-    }
-  }
-  else {
-    // Approval voting
-    $numchoices = 0;
-    foreach ($node->choice as $i => $val) {
-      // see if the box is checked
-      if ($form_values['choice'][$i]) {
-        $numchoices++;
-      }
-    }
-  
-    // Too many choices ranked
-    if ($node->maxchoices != 0 && $numchoices > $node->maxchoices) {
-      $msg = t('%num choices were selected but only %max are allowed.',
-        array('%num' => $numchoices, '%max' => $node->maxchoices));
-      if ($ajax) {
-        $errors[] = $msg;
-      }
-      else {
-        form_set_error('choice[', $msg);
-      }
-      $ok = false;
-    }
-
-    // Not enough choices ranked
-    $minchoices = 1;
-    if ($numchoices < $minchoices) {
-      $msg = t('At least one choice must be selected.');
-      if ($ajax) {
-        $errors[] = $msg;
-      }
-      else {
-        form_set_error('choice[', $msg);
-      }
-      $ok = false;
-    }
-  }
-  // If the form was posted with AJAX and has errors, print the error message.
-  if ($ajax && !$ok) {
-    drupal_set_header('Content-Type: text/plain; charset=utf-8');
-    print drupal_to_js(array('errors' => '<div class="messages error">'. implode('<br />', $errors) .'</div>'));
-    exit;
-  }
-  // Do as usual
-  else {
-    return $ok;
-  }
-}
-
-/**
- * Render the voting form.
- */
-function theme_advpoll_voting_binary_form($form) {
-  $output = "<div class=\"poll\">\n";
-  $output .= drupal_render($form);
-  $output .= "</div>\n";
-  return $output;
-}
+<?php
+// $Id: binary.inc,v 1.7.2.16 2007/06/01 07:50:19 fajerstarter Exp $
+
+function advpoll_info_binary() {
+  return array(
+    'name' => 'binary',
+    'description' => t('Vote for or against a number of choices.'),
+  );
+}
+
+function advpoll_algorithms_binary() {
+  return array('plurality' => t('plurality'));
+}
+
+/**
+ * Implementation of the view_voting hook for the poll module.
+ * 
+ * This creates a list of choices to allow the user to vote on choices.
+ */
+function advpoll_voting_binary_form(&$node, $teaser, $page) {
+  static $binary_form_count = 0; 
+  $form['#id'] = 'advpoll_voting_binary_form-'. $binary_form_count++;
+  $form['#attributes'] = array('class' => 'advpoll-vote');
+  
+  $form['ajax'] = array(
+    '#type' => 'hidden',
+    '#attributes' => array('class' => 'ajax'),
+  );
+
+  if ($node->choice) {
+    $list = array();
+    foreach ($node->choice as $i => $choice) {
+      // Don't show blank choices or write-in votes if the setting is disabled
+      if ($choice['label'] && ($node->displaywriteins || !$choice['writein'])) {
+         $list[$i] = _advpoll_choice_markup($choice['label'], $node->format);
+      }
+    }
+    // Add write-in checkbox/radio if write-ins are enabled and user has permission
+    if($node->writeins && user_access('add write-ins')) {
+      $list[$i+1] = t('(write-in)');
+      $form['writein_key'] = array(
+        '#type' => 'value',
+        '#value' => $i+1,
+      );
+    }
+
+    $form['choice'] = array(
+      '#options' => $list,
+    );
+
+    if ($node->in_preview) {
+      $maxchoices = $node->settings['maxchoices'];
+    }
+    else {
+      $maxchoices = $node->maxchoices; 
+    }
+    if ($maxchoices == 1) {
+      // Plurality voting
+      $form['choice']['#type'] = 'radios';
+      $form['choice']['#default_value'] = -1;
+    }
+    else {
+      // Approval voting
+      $form['choice']['#type'] = 'checkboxes';
+    }
+  }
+
+  // Add write-in text field if write-ins are enabled and user has permission
+  if($node->writeins && user_access('add write-ins')) {
+    $form['writein_choice'] = array (
+      '#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) {
+    static $binary_vote_count = 0;
+    $form['vote'] = array(
+      '#type' => 'submit',
+      '#value' => t('Vote'),
+      '#attributes' => array('id' => 'edit-vote-binary-'. $binary_vote_count++),
+    );
+  }
+
+  $form['#action'] = url('node/'. $node->nid);
+  return $form;
+}
+
+function advpoll_view_results_binary($node, $teaser, $page) {
+  $content_type = 'advpoll';
+  $content_id = $node->nid;
+
+  $results = votingapi_get_voting_results($content_type, $content_id);
+  $votes = array();
+  foreach ($results as $result) {
+    $voteval = $result->tag;
+    if ($voteval == '_advpoll') {
+      if ($result->function == 'total_votes') {
+        $total_votes = $result->value;
+      }
+    }
+    else if (isset($node->choice[$voteval])) {
+      if (!$votes[$voteval]) {
+        $votes[$voteval] = 0;
+      }
+      $votes[$voteval] = $result->value;
+    }
+  }
+
+  if ($node->choice && $total_votes > 0) {
+    // Add in any choices that received no votes.
+    foreach ($node->choice as $i => $ch) {
+      if (!isset($votes[$i])) {
+        $votes[$i] = 0;
+      }
+    }
+
+    // Sort results by votes, descending.
+    arsort($votes);
+
+    // Display results for each possible choice
+    foreach ($votes as $i => $count) {
+      $choice = $node->choice[$i];
+      $percentage = round(100 * $votes[$i] / $total_votes, 0);
+      $output .= theme('advpoll_bar', _advpoll_choice_markup($choice['label'], $node->format), $percentage, format_plural($count, '1 vote', '@count votes'));
+    }
+  }
+
+  return array('results' => $output, 'votes' => $total_votes);
+}
+
+function advpoll_calculate_results_binary(&$results, $votes, $node) {
+  $voters = array();
+  foreach ($votes as $vote) {
+    if ($vote->uid) {
+      $key = $vote->uid;
+    }
+    else {
+      $key = $vote->hostname;
+    }
+    $voters[$key] = TRUE;
+  }
+  $results['_advpoll'] = array(array('total_votes' => count($voters)));
+}
+
+/**
+ * Registers the vote as a key for this node using votingapi_set_vote().
+ */
+function advpoll_voting_binary_form_submit($form_id, $form_values) {
+  $vote = array();
+  $node = node_load($form_values['nid']);
+
+  // Do submission specific to writeins
+  _advpoll_writeins_voting_form_submit($node, $form_values, $vote, 1);
+
+  if ($node->maxchoices == 1) {
+    // Plurality voting
+    // Ignore write-in choice that has already taken care of
+    if(!$form_values['choice'][$form_values['writein_key']]) {
+      $temp->value = 1;
+      $temp->tag = $form_values['choice'];
+      $temp->value_type = 'option';
+      $vote[] = $temp;
+    }
+  }
+  else {
+    // Approval voting
+    foreach ($form_values['choice'] as $choice => $selected) {
+      // Ignore write-in choice that has already taken care of
+      if($choice != $form_values['writein_key']) {
+        unset($temp);
+        $temp->value = $choice;
+        if ($selected) {
+          $temp->value_type = 'option';
+          $temp->tag = $choice;
+          $temp->value = 1;
+          $vote[] = $temp;
+        }
+      }
+    }
+  }
+
+  votingapi_set_vote('advpoll', $form_values['nid'], $vote);
+  _advpoll_vote_response($node, $form_values);
+}
+
+/**
+ * Check if the submitted key exists, just to make sure the form is not bypassed.
+ *
+ * @returns boolean true if the form is valid
+ */
+function advpoll_voting_binary_form_validate($form_id, $form_values) {
+  $node = node_load($form_values['nid']);
+  $ajax = $form_values['ajax'];
+  $ok = TRUE;
+
+  // Whether the write-in option is selected. This is calculated differently for
+  // radio buttons and checkboxes.
+  $writein_option = false;
+  $writein_text = $form_values['writein_key'] ? $form_values['writein_choice'] : '';
+
+  // Check if user has already voted
+  list($voted, $cancel_vote) = _advpoll_user_voted($node);
+  if ($voted) {
+    $msg = t('You have already voted in this poll.');
+    if ($ajax) {
+      $errors[] = $msg;
+    }
+    else {
+      form_set_error('choice[', $msg);
+    }
+    $ok = FALSE;
+  }
+
+  if ($node->maxchoices == 1) {
+    // Plurality voting
+    if($node->writeins && user_access('add write-ins') && $form_values['choice'][$form_values['writein_key']]) {
+      // Write-ins are enabled and user has permission and it is the write-in option
+      // Set the flag to true for additional checks
+      $writein_option = true;
+    }
+    else {
+      // Nothing is selected
+      $msg = t('At least one choice must be selected.');
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice[', $msg);
+      }
+      $ok = FALSE;
+    }
+  }
+  else {
+    // Approval voting
+    $numchoices = 0;
+    foreach ($node->choice as $i => $val) {
+      // see if the box is checked
+      if ($form_values['choice'][$i]) {
+        $numchoices++;
+      }
+    }
+
+    // If write-ins are enabled and user has permission and the write-in box is checked
+    if($node->writeins && user_access('add write-ins') && $form_values['choice'][$form_values['writein_key']]) {
+      // Add one to number of choices for check on min/max boxes checked
+      $numchoices++;
+      // Set the flag to true for additional checks
+      $writein_option = true;
+    }
+  
+    // Too many choices ranked
+    if ($node->maxchoices != 0 && $numchoices > $node->maxchoices) {
+      $msg = t('%num choices were selected but only %max are allowed.',
+        array('%num' => $numchoices, '%max' => $node->maxchoices));
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice[', $msg);
+      }
+      $ok = false;
+    }
+
+    // Not enough choices ranked
+    $minchoices = 1;
+    if ($numchoices < $minchoices) {
+      $msg = t('At least one choice must be selected.');
+      if ($ajax) {
+        $errors[] = $msg;
+      }
+      else {
+        form_set_error('choice[', $msg);
+      }
+      $ok = false;
+    }
+  }
+
+  // Do validation specific to writeins
+  _advpoll_writeins_voting_form_validate($node, $writein_option, $writein_text, $errors, $ok, $ajax);
+
+  // If the form was posted with AJAX and has errors, print the error message.
+  if ($ajax && !$ok) {
+    drupal_set_header('Content-Type: text/plain; charset=utf-8');
+    print drupal_to_js(array('errors' => '<div class="messages error">'. implode('<br />', $errors) .'</div>'));
+    exit;
+  }
+  // Do as usual
+  else {
+    return $ok;
+  }
+}
+
+/**
+ * Render the voting form.
+ */
+function theme_advpoll_voting_binary_form($form) {
+  $output = "<div class=\"poll\">\n";
+  $output .= drupal_render($form);
+  $output .= "</div>\n";
+  return $output;
+}
Index: .project
===================================================================
RCS file: .project
diff -N .project
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ .project	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>advpoll5_2</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+	</buildSpec>
+	<natures>
+	</natures>
+</projectDescription>
