poll.module 14.2 KB
Newer Older
1
<?php
2
// $Id$
3

Dries's avatar
 
Dries committed
4 5 6 7 8 9
/**
 * @file
 * Enables your site to capture votes on different topics in the form of multiple
 * choice questions.
 */

10 11 12 13
/**
 * Implementation of hook_help().
 */
function poll_help($section) {
Steven Wittens's avatar
Steven Wittens committed
14 15
  switch ($section) {
    case 'admin/help#poll':
16
      return t("
Steven Wittens's avatar
Steven Wittens committed
17 18 19 20 21 22 23
      <p>Users with the correct <a href=\"%permissions\">permissions</a> can create and/or vote on polls.</p>
      <ul>
      <li>To create a poll a user needs the \"create polls\" permission.</li>
      <li>To vote on a poll question a user must have the \"vote on polls\" permission.</li>
      <li>To view the results one needs the \"access content\" permission.</li>
      <li>To administer polls you need the \"administer nodes\" permission.</li>
      </ul>
24
      <p>Creating a poll is much like creating any other node. Click \"create poll\" in your user box. The title of the poll should be the question, then enter the answers and the \"base\" vote counts. You can also choose the time period over which the vote will run.</p><p>The <a href=\"%poll\">Poll</a> item in the navigation links will take you to a page where you can see all the current polls, vote on them (if you haven't already) and view the results.</p>", array("%permissions" => url("admin/user/configure/permission"), "%poll" => url("poll")));
Dries's avatar
 
Dries committed
25
    case 'admin/modules#description':
26
      return t("Enables your site to capture votes on different topics in the form of multiple choice questions.");
Steven Wittens's avatar
Steven Wittens committed
27
    case 'node/add#poll':
28
      return t("A poll is a multiple-choice question which visitors can vote on.");
Steven Wittens's avatar
Steven Wittens committed
29
  }
30 31
}

32 33 34
/**
 * Implementation of hook_access().
 */
35
function poll_access($op, $node) {
Steven Wittens's avatar
Steven Wittens committed
36 37
  if ($op == 'create') {
    return user_access('create polls');
Dries's avatar
 
Dries committed
38
  }
Steven Wittens's avatar
Steven Wittens committed
39 40
}

41 42 43 44 45
/**
 * Implementation of hook_block().
 *
 * Generates a block containing the latest poll.
 */
Steven Wittens's avatar
Steven Wittens committed
46 47 48 49
function poll_block($op = 'list', $delta = 0) {
  if (user_access('access content')) {
    if ($op == 'list') {
      $blocks[0]['info'] = t('Most recent poll');
Dries's avatar
 
Dries committed
50 51
      return $blocks;
    }
52
    else if ($op == 'view') {
53
      // Retrieve the latest poll.
Dries's avatar
 
Dries committed
54
      $timestamp = db_result(db_query('SELECT MAX(n.created) FROM {node} n '. node_access_join_sql() ." WHERE n.type = 'poll' AND n.status = 1 AND ". node_access_where_sql() .' AND n.moderate = 0'));
Dries's avatar
 
Dries committed
55
      if ($timestamp) {
Steven Wittens's avatar
Steven Wittens committed
56 57
        $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'moderate' => 0, 'status' => 1));

Dries's avatar
 
Dries committed
58
        if ($poll->nid) {
59
          // poll_view() dumps the output into $poll->body.
60
          poll_view($poll, 1, 0, 1);
Dries's avatar
 
Dries committed
61
        }
Dries's avatar
 
Dries committed
62
      }
Steven Wittens's avatar
Steven Wittens committed
63 64
      $block['subject'] = t('Poll');
      $block['content'] = $poll->body;
Dries's avatar
 
Dries committed
65
      return $block;
66 67 68 69
    }
  }
}

70 71 72 73 74
/**
 * Implementation of hook_cron().
 *
 * Closes polls that have exceeded their allowed runtime.
 */
75
function poll_cron() {
Dries's avatar
 
Dries committed
76
  $result = db_query("SELECT p.nid FROM {poll} p INNER JOIN {node} n ON p.nid=n.nid WHERE (n.created + p.runtime) < '". time() ."' AND p.active = '1' AND p.runtime != '0'");
Dries's avatar
 
Dries committed
77
  while ($poll = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
78
    db_query("UPDATE {poll} SET active='0' WHERE nid = %d", $poll->nid);
Dries's avatar
 
Dries committed
79
  }
80 81
}

82 83 84
/**
 * Implementation of hook_delete().
 */
85
function poll_delete($node) {
Dries's avatar
 
Dries committed
86 87
  db_query("DELETE FROM {poll} WHERE nid=%d", $node->nid);
  db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
88 89
}

90 91 92
/**
 * Implementation of hook_validate().
 */
93
function poll_validate(&$node) {
94 95
  if (isset($node->title)) {
    // Check for at least two options and validate amount of votes:
Steven Wittens's avatar
Steven Wittens committed
96 97 98 99
    $realchoices = 0;
    foreach ($node->choice as $i => $choice) {
      if ($choice['chtext'] != '') {
        $realchoices++;
100
      }
Dries's avatar
 
Dries committed
101

Steven Wittens's avatar
Steven Wittens committed
102
      if ($choice['chvotes'] < 0) {
Dries's avatar
 
Dries committed
103
        form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
104
      }
105
    }
Dries's avatar
 
Dries committed
106

Steven Wittens's avatar
Steven Wittens committed
107
    if ($realchoices < 2) {
Dries's avatar
 
Dries committed
108
      form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
109 110
    }
  }
111 112 113 114

  $node->teaser = poll_teaser($node);
}

115 116 117
/**
 * Implementation of hook_form().
 */
Dries's avatar
 
Dries committed
118
function poll_form(&$node) {
Steven Wittens's avatar
Steven Wittens committed
119
  $admin = user_access('administer nodes');
120

Steven Wittens's avatar
Steven Wittens committed
121 122 123
  if (function_exists('taxonomy_node_form')) {
    $output = implode('', taxonomy_node_form('poll', $node));
  }
124

Steven Wittens's avatar
Steven Wittens committed
125 126 127
  if (!isset($node->choices)) {
    $node->choices = max(2, count($node->choice) ? count($node->choice) : 5);
  }
Dries's avatar
 
Dries committed
128

129
  // User ticked 'need more choices'.
Steven Wittens's avatar
Steven Wittens committed
130 131
  if ($node->morechoices) {
    $node->choices *= 2;
132 133
  }

Steven Wittens's avatar
Steven Wittens committed
134
  $output .= '<div class="poll-form">';
Dries's avatar
 
Dries committed
135

Steven Wittens's avatar
Steven Wittens committed
136
  // Poll choices
Dries's avatar
 
Dries committed
137
  $opts = drupal_map_assoc(range(2, $node->choices * 2 + 5));
138
  for ($a = 0; $a < $node->choices; $a++) {
Dries's avatar
 
Dries committed
139
    $group1 .= form_textfield(t('Choice %n', array('%n' => ($a + 1))), "choice][$a][chtext", $node->choice[$a]['chtext'], 50, 127);
140
    if ($admin) {
Dries's avatar
 
Dries committed
141
      $group1 .= form_textfield(t('Votes for choice %n', array('%n' => ($a + 1))), "choice][$a][chvotes", (int)$node->choice[$a]['chvotes'], 7, 7);
142 143
    }
  }
Steven Wittens's avatar
Steven Wittens committed
144 145 146
  $group1 .= form_hidden('choices', $node->choices);
  $group1 .= form_checkbox(t('Need more choices'), 'morechoices', 1, 0, t("If the amount of boxes above isn't enough, check this box and click the Preview button below to add some more."));
  $output .= form_group(t('Choices'), $group1);
Dries's avatar
 
Dries committed
147

Dries's avatar
 
Dries committed
148

Steven Wittens's avatar
Steven Wittens committed
149
  // Poll attributes
150
  $_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
Steven Wittens's avatar
Steven Wittens committed
151
  $_active = array(0 => t('Closed'), 1 => t('Active'));
Dries's avatar
 
Dries committed
152

Steven Wittens's avatar
Steven Wittens committed
153 154
  if ($admin) {
    $group2 .= form_radios(t('Poll status'), 'active', isset($node->active) ? $node->active : 1, $_active, t('When a poll is closed, visitors can no longer vote for it.'));
Dries's avatar
 
Dries committed
155
  }
Steven Wittens's avatar
Steven Wittens committed
156
  $group2 .= form_select(t('Poll duration'), 'runtime', $node->runtime ? $node->runtime : 0, $_duration, t('After this period, the poll will be closed automatically.'));
Dries's avatar
 
Dries committed
157 158

  $output .= form_group(t('Settings'), $group2);
Steven Wittens's avatar
Steven Wittens committed
159
  $output .= '</div>';
Dries's avatar
 
Dries committed
160 161

  return $output;
162
}
163

164
function poll_insert($node) {
Steven Wittens's avatar
Steven Wittens committed
165
  if (!user_access('administer nodes')) {
166
    // Make sure all votes are 0 initially
Steven Wittens's avatar
Steven Wittens committed
167 168 169
    foreach ($node->choice as $i => $choice) {
      $node->choice[$i]['chvotes'] = 0;
    }
170 171
    $node->active = 1;
  }
172

Dries's avatar
 
Dries committed
173
  db_query("INSERT INTO {poll} (nid, runtime, voters, active) VALUES (%d, %d, '', %d)", $node->nid, $node->runtime, $node->active);
Dries's avatar
 
Dries committed
174

Steven Wittens's avatar
Steven Wittens committed
175 176 177
  foreach ($node->choice as $choice) {
    if ($choice['chtext'] != '') {
      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
178 179
    }
  }
180 181
}

Dries's avatar
 
Dries committed
182 183 184
/**
 * Implementation of hook_menu().
 */
Dries's avatar
 
Dries committed
185
function poll_menu($may_cache) {
Dries's avatar
 
Dries committed
186
  $items = array();
Dries's avatar
 
Dries committed
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213

  if ($may_cache) {
    $items[] = array('path' => 'node/add/poll', 'title' => t('poll'),
      'access' => user_access('create polls'));
    $items[] = array('path' => 'poll', 'title' => t('polls'),
      'callback' => 'poll_page',
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM);

    $items[] = array('path' => 'poll/vote',
      'title' => t('vote'),
      'callback' => 'poll_vote',
      'access' => user_access('vote on polls'),
      'type' => MENU_CALLBACK);
  }
  else {
    if (arg(0) == 'node' && is_numeric(arg(1))) {
      $node = node_load(array('nid' => arg(1)));

      if ($node->type == 'poll' && $node->allowvotes) {
        $items[] = array('path' => 'node/'. arg(1) .'/results',
          'title' => t('results'),
          'callback' => 'poll_results',
          'access' => user_access('access content'),
          'weight' => 3,
          'type' => MENU_LOCAL_TASK);
      }
Steven Wittens's avatar
Steven Wittens committed
214 215
    }
  }
Dries's avatar
 
Dries committed
216

Dries's avatar
 
Dries committed
217 218 219
  return $items;
}

Steven Wittens's avatar
Steven Wittens committed
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235
/**
 * Determine an adjusted user id, to allow for basic tracking of anonymous
 * users (IP-based).
 */
function poll_uid() {
  global $user;
  if ($user->uid) {
     // Pad the UID with underscores to allow a simple strstr() search
    $id = '_'. $user->uid .'_';
  }
  else {
    $id = $_SERVER['REMOTE_ADDR'];
  }
  return $id;
}

236 237 238
/**
 * Implementation of hook_load().
 */
239 240
function poll_load($node) {
  // Load the appropriate choices into the $node object
Dries's avatar
 
Dries committed
241
  $poll = db_fetch_object(db_query("SELECT runtime, voters, active FROM {poll} WHERE nid = %d", $node->nid));
Dries's avatar
 
Dries committed
242

Dries's avatar
 
Dries committed
243
  $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid=%d ORDER BY chorder", $node->nid);
Steven Wittens's avatar
Steven Wittens committed
244 245
  while ($choice = db_fetch_array($result)) {
    $poll->choice[$choice['chorder']] = $choice;
246
  }
Steven Wittens's avatar
Steven Wittens committed
247

Steven Wittens's avatar
Steven Wittens committed
248 249 250 251 252 253 254
  // Determine whether or not this user is allowed to vote
  $poll->allowvotes = false;
  if (user_access('vote on polls')) {
    if (!strstr($poll->voters, poll_uid())) {
      $poll->allowvotes = $poll->active;
    }
  }
255 256
  return $poll;
}
Dries's avatar
 
Dries committed
257

258 259 260
/**
 * Implementation of hook_node_name().
 */
Dries's avatar
 
Dries committed
261 262
function poll_node_name($node) {
  return t("poll");
263
}
264

Steven Wittens's avatar
Steven Wittens committed
265
function poll_page() {
Steven Wittens's avatar
Steven Wittens committed
266
  // List all polls
Dries's avatar
 
Dries committed
267
  $result = pager_query("SELECT DISTINCT(n.nid), n.title, p.active, n.created, SUM(c.chvotes) AS votes FROM {node} n ". node_access_join_sql() ." INNER JOIN {poll} p ON n.nid=p.nid INNER JOIN {poll_choices} c ON n.nid=c.nid WHERE type = 'poll' AND status = 1 AND ". node_access_where_sql() ." AND moderate = 0 GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC", 15);
Steven Wittens's avatar
Steven Wittens committed
268
  $output = '<ul>';
Steven Wittens's avatar
Steven Wittens committed
269
  while ($node = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
270
    $output .= '<li>'. l($node->title, "node/$node->nid") .' - '. format_plural($node->votes, '1 vote', '%count votes') .' - '. ($node->active ? t('open') : t('closed')) .'</li>';
Steven Wittens's avatar
Steven Wittens committed
271
  }
Steven Wittens's avatar
Steven Wittens committed
272 273 274
  $output .= '</ul>';
  $output .= theme("pager", NULL, 15);
  print theme('page', $output);
Steven Wittens's avatar
Steven Wittens committed
275 276
}

277 278 279
/**
 * Implementation of hook_perm().
 */
280
function poll_perm() {
Steven Wittens's avatar
Steven Wittens committed
281
  return array('create polls', 'vote on polls');
282
}
Dries's avatar
 
Dries committed
283

284 285 286
/**
 * Creates a simple teaser that lists all the choices.
 */
287
function poll_teaser($node) {
Dries's avatar
Dries committed
288
  if (is_array($node->choice)) {
Steven Wittens's avatar
Steven Wittens committed
289 290
    foreach ($node->choice as $k => $choice) {
      $teaser .= '* '. $choice['chtext'] .'\n';
291 292 293 294
    }
  }
  return $teaser;
}
295

Steven Wittens's avatar
Steven Wittens committed
296
/**
297
 * Generates the voting form for a poll.
Steven Wittens's avatar
Steven Wittens committed
298 299
 */
function poll_view_voting(&$node, $main, $page, $block) {
Dries's avatar
 
Dries committed
300
  $output = '<div class="poll">';
Dries's avatar
 
Dries committed
301

Dries's avatar
 
Dries committed
302
  $form = '<div class="vote-form">';
Steven Wittens's avatar
Steven Wittens committed
303
  $form .= '<div class="choices">';
Dries's avatar
 
Dries committed
304
  if ($node->choice) {
Steven Wittens's avatar
Steven Wittens committed
305 306 307
    $list = array();
    foreach ($node->choice as $i => $choice) {
      $list[$i] = drupal_specialchars($choice['chtext']);
308
    }
Steven Wittens's avatar
Steven Wittens committed
309
    $form .= form_radios($page ? '' : $node->title, 'choice', -1, $list);
Dries's avatar
 
Dries committed
310
  }
Steven Wittens's avatar
Steven Wittens committed
311 312 313 314
  $form .= '</div>';
  $form .= form_hidden('nid', $node->nid);
  $form .= form_submit(t('Vote'), 'vote') .'</div>';

Steven Wittens's avatar
Steven Wittens committed
315
  $output .= form($form, 'post', url('poll/vote/'. $node->nid));
Steven Wittens's avatar
Steven Wittens committed
316
  $output .= '</div>';
Steven Wittens's avatar
Steven Wittens committed
317 318 319 320

  return $output;
}

321 322 323
/**
 * Generates a graphical representation of the results of a poll.
 */
Steven Wittens's avatar
Steven Wittens committed
324
function poll_view_results(&$node, $main, $page, $block) {
Steven Wittens's avatar
Steven Wittens committed
325
  // Display the results
Dries's avatar
 
Dries committed
326

Steven Wittens's avatar
Steven Wittens committed
327
  // Count the votes and find the maximum
Steven Wittens's avatar
Steven Wittens committed
328 329 330
  foreach ($node->choice as $choice) {
    $votestotal += $choice['chvotes'];
    $votesmax = max($votesmax, $choice['chvotes']);
Steven Wittens's avatar
Steven Wittens committed
331 332
  }

Dries's avatar
 
Dries committed
333
  // Output the divs for the text, bars and percentages
Steven Wittens's avatar
Steven Wittens committed
334 335 336 337 338 339 340 341 342 343 344 345
  $output .= '<div class="poll">';
  if ($block) {
    $output .= '<div class="title">'. $node->title .'</div>';
  }
  foreach ($node->choice as $i => $choice) {
    if ($choice['chtext'] != '') {
      $percentage = round($choice['chvotes'] * 100 / max($votestotal, 1));
      $output .= '<div class="text">'. drupal_specialchars($choice['chtext']) .'</div>';
      $output .= '<div class="bar">';
      $output .= '<div style="width: '. $percentage .'%;" class="foreground"></div>';
      $output .= '</div>';
      $output .= '<div class="percent">'. $percentage .'%'. (!$block ? ' ('. format_plural($choice['chvotes'], '1 vote', '%count votes') .')' : '') .'</div>';
346
    }
347
  }
Steven Wittens's avatar
Steven Wittens committed
348
  $output .= '<div class="total">'. t('Total votes') .": $votestotal</div>";
Dries's avatar
 
Dries committed
349

Steven Wittens's avatar
Steven Wittens committed
350
  $output .= '</div>';
Steven Wittens's avatar
Steven Wittens committed
351 352 353 354

  return $output;
}

Steven Wittens's avatar
Steven Wittens committed
355 356 357 358 359
/**
 * Callback for the 'results' tab for polls you can vote on
 */
function poll_results() {
  if ($node = node_load(array('nid' => arg(1)))) {
360 361
    drupal_set_title($node->title);
    print theme('page', node_show($node, 0));
Steven Wittens's avatar
Steven Wittens committed
362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
  }
  else {
    drupal_not_found();
  }
}

/**
 * Callback for processing a vote
 */
function poll_vote(&$node) {
  $nid = arg(2);
  if ($node = node_load(array('nid' => $nid))) {
    $edit = $_POST['edit'];
    $choice = $edit['choice'];
    $vote = $_POST['vote'];
Steven Wittens's avatar
Steven Wittens committed
377 378 379 380 381 382 383 384 385

    if (isset($choice) && isset($node->choice[$choice])) {
      if ($node->allowvotes) {
        $id = poll_uid();
        $node->voters = $node->voters ? ($node->voters .' '. $id) : $id;
        db_query("UPDATE {poll} SET voters='%s' WHERE nid = %d", $node->voters, $node->nid);
        db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
        $node->allowvotes = false;
        $node->choice[$choice]['chvotes']++;
Dries's avatar
 
Dries committed
386
        drupal_set_message(t('Your vote was recorded.'));
Steven Wittens's avatar
Steven Wittens committed
387 388
      }
      else {
Dries's avatar
 
Dries committed
389
        drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
Steven Wittens's avatar
Steven Wittens committed
390 391 392
      }
    }
    else {
Dries's avatar
 
Dries committed
393
      drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
Steven Wittens's avatar
Steven Wittens committed
394
    }
Steven Wittens's avatar
Steven Wittens committed
395 396 397 398 399

    drupal_goto('node/'. $nid);
  }
  else {
    drupal_not_found();
Steven Wittens's avatar
Steven Wittens committed
400 401 402
  }
}

403 404 405 406 407 408 409
/**
 * Implementation of hook_view().
 *
 * @param $block
 *   An extra parameter that adapts the hook to display a block-ready
 *   rendering of the poll.
 */
Dries's avatar
 
Dries committed
410
function poll_view(&$node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
Dries's avatar
 
Dries committed
411
  global $user;
Dries's avatar
 
Dries committed
412
  $output = '';
Steven Wittens's avatar
Steven Wittens committed
413

Steven Wittens's avatar
Steven Wittens committed
414
  if ($node->allowvotes && ($block || arg(2) != 'results')) {
Steven Wittens's avatar
Steven Wittens committed
415 416 417 418 419
    $output .= poll_view_voting($node, $main, $page, $block);
  }
  else {
    $output .= poll_view_results($node, $main, $page, $block);
  }
Steven Wittens's avatar
Steven Wittens committed
420

Steven Wittens's avatar
Steven Wittens committed
421
  // Special display for side-block
Steven Wittens's avatar
Steven Wittens committed
422
  if ($block) {
Steven Wittens's avatar
Steven Wittens committed
423 424
    // No 'read more' link
    $node->body = $node->teaser = '';
Steven Wittens's avatar
Steven Wittens committed
425

426
    $links = module_invoke_all('link', 'node', $node, 0);
Steven Wittens's avatar
Steven Wittens committed
427
    $links[] = l(t('older polls'), 'poll', array('title' => t('View the list of polls on this site.')));
Steven Wittens's avatar
Steven Wittens committed
428 429 430
    if ($node->allowvotes && $block) {
      $links[] = l(t('results'), 'node/'. $node->nid .'/results', array('title' => t('View the current poll results.')));
    }
Steven Wittens's avatar
Steven Wittens committed
431

Steven Wittens's avatar
Steven Wittens committed
432
    $output .= '<div class="links">'. theme("links", $links) .'</div>';
Steven Wittens's avatar
Steven Wittens committed
433 434 435
  }

  $node->body = $node->teaser = $output;
436 437
}

438 439 440
/**
 * Implementation of hook_update().
 */
441
function poll_update($node) {
Steven Wittens's avatar
Steven Wittens committed
442
  db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
443

Steven Wittens's avatar
Steven Wittens committed
444 445 446 447
  db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
  foreach ($node->choice as $choice) {
    $chvotes = (int)$choice['chvotes'];
    $chtext = $choice['chtext'];
448

Steven Wittens's avatar
Steven Wittens committed
449 450
    if ($chtext != '') {
      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $i++);
451
    }
452 453
  }
}
454

455
?>