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 7 Jun 2007 05:58:15 -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 $('' + Drupal.settings.advPoll.remove + '').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: 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 7 Jun 2007 05:55:52 -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.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 7 Jun 2007 05:55:56 -0000 @@ -1,6 +1,4 @@ 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: modes/binary.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/modes/binary.inc,v retrieving revision 1.7.2.17 diff -u -r1.7.2.17 binary.inc --- modes/binary.inc 7 Jun 2007 05:33:17 -0000 1.7.2.17 +++ modes/binary.inc 7 Jun 2007 05:57:09 -0000 @@ -1,5 +1,5 @@ choice) { $list = array(); foreach ($node->choice as $i => $choice) { - // Don't show blank choices - if ($choice['label']) { + // 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, ); @@ -56,6 +65,15 @@ } } + // 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, @@ -138,23 +156,33 @@ 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 - $temp->value = 1; - $temp->tag = $form_values['choice']; - $temp->value_type = 'option'; - $vote[] = $temp; + // 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) { - unset($temp); - $temp->value = $choice; - if ($selected) { - $temp->value_type = 'option'; - $temp->tag = $choice; - $temp->value = 1; - $vote[] = $temp; + // 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; + } } } } @@ -173,6 +201,11 @@ $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) { @@ -188,7 +221,13 @@ if ($node->maxchoices == 1) { // Plurality voting - if (!($ok = array_key_exists($form_values['choice'], $node->choice))) { + 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; @@ -208,6 +247,14 @@ $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) { @@ -235,6 +282,10 @@ $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'); Index: modes/ranking.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/advpoll/modes/ranking.inc,v retrieving revision 1.8.2.21 diff -u -r1.8.2.21 ranking.inc --- modes/ranking.inc 7 Jun 2007 05:33:17 -0000 1.8.2.21 +++ modes/ranking.inc 7 Jun 2007 05:57:12 -0000 @@ -1,5 +1,5 @@ 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++) { @@ -58,8 +63,8 @@ ); foreach ($node->choice as $key => $choice) { - // Don't show blank choices - if ($choice['label']) { + // 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), @@ -67,6 +72,29 @@ ); } } + + // 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( @@ -580,19 +608,26 @@ */ 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) { - 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; + // 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); - $node = node_load($form_values['nid']); _advpoll_vote_response($node, $form_values); } @@ -609,6 +644,11 @@ $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) { @@ -627,6 +667,15 @@ $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 @@ -637,10 +686,10 @@ // Mark this value as seen $setvalues[$intvalue]++; // Check range - if ($intvalue > count($node->choice) || $intvalue < 0) { + 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: " - . count($node->choice) .')'; + . ($writein_option ? count($node->choice) + 1 : count($node->choice)) .')'; if ($ajax) { $errors[] = $msg; } @@ -650,7 +699,27 @@ $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.', @@ -691,6 +760,10 @@ $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');