The Poll module is a useful little module that comes with Drupal and allows the addition of simple polls to pages or blocks. However, there was one major issue that I wanted to correct on a certain site, but I didn't want to directly edit the core functionality of the module. The default behaviour of the module is to record one vote per IP address or user, which is fine for normal uses but in some situations it does tend to fall over. A Drupal site with one administrator that allow anonymous users to vote on your polls seems fine, but lets say that this site wants votes to come from people in the business world. The problem here is that multiple people might work for the same business, be situtated behind a firewall and therefore would have the same IP address. What this means is that if one person in that company votes on your poll it will block all other people from voting for that poll.
To override the default behaviour of the Poll module I created a new module called Poll Override and intercepted the Poll module in key areas to allow a session based vote logging approach. The second thing I wanted to achive from this new module was to change the location of the "you have voted" message so that it appeared within the poll area, rather than at the top of the screen. Doing these two things is a useful way of understanding how Drupal works.
The first task is to create a .info file called poll_override.info.
; $Id$
name = Poll Override
description = "Override some of the functionality of the poll module"
package = "#! code"
dependencies[] = poll
core = 6.x<
Next, we create a .module file (called poll_override.module) and enter a description.
<?php
/**
* @file
* Overrides some of the funcionality of the Poll module.
*
* Changes the way in which anonymous users vote and how voting
* confirmation messages are displayed.
*
*/
Put both of these files in a directory called poll_override and put it in the sites/default/modules directory of your Drupal install.
The first thing to do is to create a function called poll_override_form_poll_view_voting_alter(), which is an implementation of hook_form_alter(). The only purpose of this function is to rewrite where the submit of the vote form of the Poll module goes to. In this case we are directing it to a function called poll_override_form_submit().
/**
* Implementation of hook_form_alter().
*
*/
function poll_override_form_poll_view_voting_alter(&$form, &$form_state){
// Override the normal form submit for a submit function in this module.
$form['vote']['#submit'] = array('poll_override_form_submit');
}
Next we need to create the poll_override_form_submit() to handle the action of someone voting on a poll. When the poll module is installed it creates three tables, these are as follows:
- poll - This stores details of the polls avaialable on the system.
- poll_choices - This stores the questions for each poll.
- poll_votes - This stores the votes made against each poll.
The table we are interested in is poll_votes. The normal behaviour of the Poll module is to store the vote along with the IP address of the user who submitted the vote and use this to varify the user the next time around. Rather than override this completely we will allow users without cookies enabled to vote using the old IP address method, and include an if statement that stores the session key for everyone else. Finally, we add an item called poll_override_message to the $_SESSION array so that we can print out a message when we refresh the page after the vote has been recorded.
/**
* Submit function for Poll Override module.
*
*/
function poll_override_form_submit($form, &$form_state) {
$node = $form['#node'];
$choice = $form_state['values']['choice'];
global $user;
if ($user->uid) {
db_query('INSERT INTO {poll_votes} (nid, chorder, uid) VALUES (%d, %d, %d)', $node->nid, $choice, $user->uid);
}
else {
if ( isset($_COOKIE[session_name()]) ) {
// If a cookie has been set for this user then use the session id to record the vote.
db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, $_COOKIE[session_name()]);
} else {
// Otherwise just use the IP address (this is the normal functionality).
db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, ip_address());
}
}
// Add one to the votes.
db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
// Instead of using drupal_set_message() to set the message we will just use a session variable.
// This will be deleted after the page has loaded.
$_SESSION['poll_override_message'] = t('Your vote was recorded.');
// Return the user to whatever page they voted from.
}
Now we have sorted out how we are storing the votes we need to display the poll. This means that we either display the voting form or a set of results. What we need to do here is to intercept the poll node before it is displayed and detect whether it is still eligable for voting or not. We do this with a call to the poll_override_nodeapi() function, which is a implentation of hook_nopeapi(). This function might look complicated but we are essentially running the following steps.
- Check if we are loading the node and if the node is a poll.
- Load the poll and the associated choices for that poll.
- Set a variable of the node called allowvotes to false. This is used to display either the results (if false) or a voting form (if true).
- Detect if the user is able to vote on polls and if the poll is active. If not then we skip the rest of this function and the form is not displayed.
- If the user object has a uid property then this user is logged in so we load the vote using this information.
- If the user is not logged in we can either use the session cookie or the IP address depending on the information we have available.
- Finally, if a vote has been detected in the last 2 steps we pass this onto the node, otherwise we pass nothing and set allowvotes to true so that the user can vote.
Here is the code in full.
/**
* Implementation of hook_nodeapi().
*
*/
function poll_override_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL ) {
if ( $op == 'load' && $node->type == 'poll' ) {
global $user;
$poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
// Load the appropriate choices into the $poll object.
$result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
while ( $choice = db_fetch_array($result) ) {
$node->choice[$choice['chorder']] = $choice;
}
// Determine whether or not this user is allowed to vote.
$node->allowvotes = FALSE;
if ( user_access('vote on polls') && $poll->active ) {
if ( $user->uid ) {
$result = db_fetch_object(db_query('SELECT chorder FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
} else {
if ( isset($_COOKIE[session_name()]) ) {
// If a cookie has been set for this user then use the session id to retreive the vote.
$result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, $_COOKIE[session_name()]));
} else {
// Otherwise just use the IP address (this is the normal functionality).
$result = db_fetch_object(db_query("SELECT chorder FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, ip_address()));
}
}
if ( isset($result->chorder) ) {
$node->vote = $result->chorder;
} else {
$node->vote = -1;
$node->allowvotes = TRUE;
}
}
}
}
The Poll module includes a function called theme_preprocess_poll_results(). This is essentially a low level hook that we can override by using a more specific theme function. So by calling a fucntion poll_override_preprocess_poll_results() we can override the theme_preprocess_poll_results() function within the Poll module and change the output of the results block. This code will be run when printing out the results of a poll and does two things. It is possible to cancel the vote on a poll so the first thing we do is to include the form that allows users to do this. The second thing we do it detect if the poll_override_message has been set, and if so assign this value to the $variables array (which will be passed to the template) and unset the item in the $_SESSION array.
/**
* Implementation of theme_preprocess_poll_results()
*
*/
function poll_override_preprocess_poll_results(&$variables) {
$variables['links'] = theme('links', $variables['raw_links']);
if ( isset($variables['vote']) && $variables['vote'] > -1 && user_access('cancel own vote') ) {
$variables['cancel_form'] = drupal_get_form('poll_cancel_form', $variables['nid']);
}
$variables['title'] = check_plain($variables['raw_title']);
// If this is a block, allow a different tpl.php to be used.
if ( $variables['block'] ) {
$variables['template_files'][] = 'poll-results-block';
if ( isset($_SESSION['poll_override_message']) ) {
$variables['message'] = $_SESSION['poll_override_message'];
unset($_SESSION['poll_override_message']);
}
}
}
The very final step is to create a file called poll-results-block.tpl.php and include the following code.
<div class="poll">
<div class="title"><?php print $title ?></div>
<?php print $results ?>
<div class="total">
<?php print t('Total votes: @votes', array('@votes' => $votes)); ?>
</div>
<?php
if ( isset($message) ) {
print '<p class="voted">'.$message.'</p>';
}
?>
</div>
<div class="links"><?php print $links; ?></div>
The $message variable contains the message that might have been set in the poll_override_nodeapi() function, if it is set here then we print it out. You can include this file in the poll_module directory or within your template directory, this is up to you.
Comments
Submitted by Sean Morrison on Thu, 08/20/2009 - 17:19
PermalinkSubmitted by giHlZp8M8D on Fri, 08/21/2009 - 09:01
PermalinkSubmitted by Matthias Vande… on Tue, 10/13/2009 - 20:33
PermalinkSubmitted by Matthias Vande… on Tue, 10/13/2009 - 20:59
PermalinkSubmitted by giHlZp8M8D on Tue, 10/13/2009 - 21:00
PermalinkSubmitted by giHlZp8M8D on Tue, 10/13/2009 - 21:03
PermalinkSubmitted by Kim Emax on Wed, 11/04/2009 - 07:08
PermalinkSubmitted by giHlZp8M8D on Wed, 11/04/2009 - 08:53
PermalinkSubmitted by Prathaban on Fri, 11/27/2009 - 08:10
PermalinkSubmitted by Andrei on Fri, 01/29/2010 - 20:06
PermalinkSubmitted by Sasha on Wed, 04/07/2010 - 15:21
PermalinkSubmitted by Karunakar on Wed, 04/14/2010 - 12:01
PermalinkSubmitted by giHlZp8M8D on Wed, 04/14/2010 - 12:12
PermalinkSubmitted by Mondo Jay on Thu, 05/20/2010 - 21:54
PermalinkSubmitted by pratheesh on Wed, 05/26/2010 - 17:00
PermalinkSubmitted by borgo on Fri, 07/30/2010 - 12:32
PermalinkSubmitted by ndmaque on Fri, 01/07/2011 - 10:44
PermalinkSubmitted by giHlZp8M8D on Fri, 01/07/2011 - 10:53
PermalinkSubmitted by ndmaque on Fri, 01/07/2011 - 16:39
PermalinkSubmitted by giHlZp8M8D on Fri, 01/07/2011 - 17:09
PermalinkI want to do something similar to what you did. I'd like to simply override the default Poll module with a new poll_view function in my poll_override module. However, I setup the .module and .info files as you specified and enabled the new module. But it does not execute my overriden function. Any ideas as to what to look for? Thanks!
Submitted by Sam on Tue, 06/07/2011 - 00:55
PermalinkYour poll_view function should have the same name as your module in order for it to hook in properly. A good test is to add something like die('pollview') to the overridden function to make sure that the function is actually being called. If it is then you can move on to figure out what is going on down the line.
Submitted by giHlZp8M8D on Tue, 06/07/2011 - 08:56
PermalinkI've made a couple of modifications for my own use case, which is to allow unlimited voting by all users:
<code>function poll_override_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL ) {
if ( $op == 'load' && $node->type == 'poll' ) {
global $user;
$poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
// Load the appropriate choices into the $poll object.
$result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
while ( $choice = db_fetch_array($result) ) {
$node->choice[$choice['chorder']] = $choice;
}
// Determine whether or not this user is allowed to vote.
$node->allowvotes = FALSE;
if ( user_access('vote on polls') && $poll->active ) {
$node->vote = -1;
$node->allowvotes = TRUE;
}
}
}</code>
The problem I encountered was duplicate rows in the poll_votes table if someone tried to vote multiple times (from the same session, e.g.). To solve this, I simply replaced the hostname field with a random number as follows:
<code>function poll_override_form_submit($form, &$form_state) {
$node = $form['#node'];
$choice = $form_state['values']['choice'];
// just use a random number in place of IP address to avoid duplicate rows in the poll_votes table
db_query("INSERT INTO {poll_votes} (nid, chorder, hostname) VALUES (%d, %d, '%s')", $node->nid, $choice, mt_rand());
// Add one to the votes.
db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
drupal_set_message(t('Your vote was recorded.'));
// Return the user to whatever page they voted from.
}</code>
Submitted by acy on Tue, 06/28/2011 - 22:34
PermalinkThanks very much for this! It was very helpful.
I started updating the code for Drupal 7, there's a sandbox here: http://drupal.org/sandbox/kostajh/1454452. Contributions are welcome.
Submitted by Kosta on Fri, 02/24/2012 - 16:25
PermalinkAdd new comment