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

4 5 6 7
/**
 * Implementation of hook_help().
 */
function poll_help($section) {
Steven Wittens's avatar
Steven Wittens committed
8 9
  switch ($section) {
    case 'admin/help#poll':
10
      return t("
Steven Wittens's avatar
Steven Wittens committed
11 12 13 14 15 16 17 18
      <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>
      <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/permission"), "%poll" => url("poll")));
Dries's avatar
 
Dries committed
19
    case 'admin/modules#description':
20
      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
21
    case 'node/add#poll':
22
      return t("A poll is a multiple-choice question which visitors can vote on.");
Steven Wittens's avatar
Steven Wittens committed
23
  }
24 25
}

26 27 28
/**
 * Implementation of hook_access().
 */
29
function poll_access($op, $node) {
Steven Wittens's avatar
Steven Wittens committed
30
  if ($op == 'view') {
31
    return $node->status;
32 33
  }

Steven Wittens's avatar
Steven Wittens committed
34 35
  if ($op == 'create') {
    return user_access('create polls');
Dries's avatar
 
Dries committed
36
  }
Steven Wittens's avatar
Steven Wittens committed
37 38
}

39 40 41 42 43
/**
 * Implementation of hook_block().
 *
 * Generates a block containing the latest poll.
 */
Steven Wittens's avatar
Steven Wittens committed
44 45 46 47
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
48 49 50
      return $blocks;
    }
    else {
51
      // Retrieve the latest poll.
Dries's avatar
 
Dries committed
52
      $timestamp = db_result(db_query("SELECT MAX(created) FROM {node} WHERE type = 'poll' AND status = '1' AND moderate = '0'"));
Dries's avatar
 
Dries committed
53
      if ($timestamp) {
Steven Wittens's avatar
Steven Wittens committed
54 55
        $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'moderate' => 0, 'status' => 1));

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

68 69 70 71 72
/**
 * Implementation of hook_cron().
 *
 * Closes polls that have exceeded their allowed runtime.
 */
73
function poll_cron() {
Dries's avatar
 
Dries committed
74
  $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
75
  while ($poll = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
76
    db_query("UPDATE {poll} SET active='0' WHERE nid = %d", $poll->nid);
Dries's avatar
 
Dries committed
77
  }
78 79
}

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

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

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

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

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

  return $error;
}

115 116 117
/**
 * Implementation of hook_form().
 */
Steven Wittens's avatar
Steven Wittens committed
118 119
function poll_form(&$node, &$error) {
  $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++) {
Steven Wittens's avatar
Steven Wittens committed
139
    $group1 .= form_textfield(t('Choice %n', array('%n' => ($a + 1))), "choice][$a][chtext", $node->choice[$a]['chtext'], 50, 127, $error["choice][$a][chtext"]);
140
    if ($admin) {
Steven Wittens's avatar
Steven Wittens committed
141
      $group1 .= form_textfield(t('Votes for choice %n', array('%n' => ($a + 1))), "choice][$a][chvotes", (int)$node->choice[$a]['chvotes'], 7, 7, $error["choice][$a][chvotes"]);
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_link().
 */
Steven Wittens's avatar
Steven Wittens committed
185
function poll_link($type, $node = 0, $main) {
Dries's avatar
 
Dries committed
186 187
  $links = array();

Dries's avatar
 
Dries committed
188
  if ($type == 'page' && user_access('access content')) {
Steven Wittens's avatar
Steven Wittens committed
189
    $links[] = l(t('polls'), 'poll', array('title' => t('View the list of polls on this site.')));
Steven Wittens's avatar
Steven Wittens committed
190
  }
Steven Wittens's avatar
Steven Wittens committed
191
  else if ($type == 'node' && $node->type == 'poll') {
Steven Wittens's avatar
Steven Wittens committed
192 193 194 195 196
    /*
    ** Add links to allow the user to switch between the results and the voting
    ** form, if he/she hasn't voted yet.
    */

Steven Wittens's avatar
Steven Wittens committed
197 198
    if ($node->allowvotes) {
      if (arg(3) == 'results') {
Dries's avatar
 
Dries committed
199
        $links[] = l(t('voting form'), 'node/'. $node->nid);
Steven Wittens's avatar
Steven Wittens committed
200 201
      }
      else {
Dries's avatar
 
Dries committed
202
        $links[] = l(t('view results'), 'node/'. $node->nid .'/results');
Steven Wittens's avatar
Steven Wittens committed
203 204 205
      }
    }
  }
Dries's avatar
 
Dries committed
206 207

  return $links;
208
}
Dries's avatar
 
Dries committed
209

Dries's avatar
 
Dries committed
210 211 212 213 214 215 216 217 218 219 220 221 222 223
/**
 * Implementation of hook_menu().
 */
function poll_menu() {
  $items = array();
  $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);
  return $items;
}

Steven Wittens's avatar
Steven Wittens committed
224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239
/**
 * 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;
}

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

Dries's avatar
 
Dries committed
247
  $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
248 249
  while ($choice = db_fetch_array($result)) {
    $poll->choice[$choice['chorder']] = $choice;
250
  }
Steven Wittens's avatar
Steven Wittens committed
251

Steven Wittens's avatar
Steven Wittens committed
252 253 254 255 256 257 258
  // 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;
    }
  }
259 260
  return $poll;
}
Dries's avatar
 
Dries committed
261

262 263 264
/**
 * Implementation of hook_node_name().
 */
Dries's avatar
 
Dries committed
265 266
function poll_node_name($node) {
  return t("poll");
267
}
268

Steven Wittens's avatar
Steven Wittens committed
269
function poll_page() {
Steven Wittens's avatar
Steven Wittens committed
270 271 272
  // List all polls
  $result = pager_query("SELECT n.nid, n.title, p.active, SUM(c.chvotes) AS votes FROM {node} n 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 moderate = '0' GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC", 15);
  $output = '<ul>';
Steven Wittens's avatar
Steven Wittens committed
273
  while ($node = db_fetch_object($result)) {
Dries's avatar
 
Dries committed
274
    $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
275
  }
Steven Wittens's avatar
Steven Wittens committed
276 277 278
  $output .= '</ul>';
  $output .= theme("pager", NULL, 15);
  print theme('page', $output);
Steven Wittens's avatar
Steven Wittens committed
279 280
}

281 282 283
/**
 * Implementation of hook_perm().
 */
284
function poll_perm() {
Steven Wittens's avatar
Steven Wittens committed
285
  return array('create polls', 'vote on polls');
286
}
Dries's avatar
 
Dries committed
287

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

Steven Wittens's avatar
Steven Wittens committed
300
/**
301
 * Generates the voting form for a poll.
Steven Wittens's avatar
Steven Wittens committed
302 303
 */
function poll_view_voting(&$node, $main, $page, $block) {
Steven Wittens's avatar
Steven Wittens committed
304
  $url = request_uri();
Steven Wittens's avatar
Steven Wittens committed
305
  $output .= '<div class="poll">';
Dries's avatar
 
Dries committed
306

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

Dries's avatar
 
Dries committed
320
  $output .= form($form, 'post', url('node/'. $node->nid));
Steven Wittens's avatar
Steven Wittens committed
321
  $output .= '</div>';
Steven Wittens's avatar
Steven Wittens committed
322 323 324 325

  return $output;
}

326 327 328
/**
 * Generates a graphical representation of the results of a poll.
 */
Steven Wittens's avatar
Steven Wittens committed
329
function poll_view_results(&$node, $main, $page, $block) {
Steven Wittens's avatar
Steven Wittens committed
330
  // Display the results
Dries's avatar
 
Dries committed
331

Steven Wittens's avatar
Steven Wittens committed
332
  // Count the votes and find the maximum
Steven Wittens's avatar
Steven Wittens committed
333 334 335
  foreach ($node->choice as $choice) {
    $votestotal += $choice['chvotes'];
    $votesmax = max($votesmax, $choice['chvotes']);
Steven Wittens's avatar
Steven Wittens committed
336 337
  }

Dries's avatar
 
Dries committed
338
  // Output the divs for the text, bars and percentages
Steven Wittens's avatar
Steven Wittens committed
339 340 341 342 343 344 345 346 347 348 349 350
  $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>';
351
    }
352
  }
Steven Wittens's avatar
Steven Wittens committed
353
  $output .= '<div class="total">'. t('Total votes') .": $votestotal</div>";
Dries's avatar
 
Dries committed
354

Steven Wittens's avatar
Steven Wittens committed
355
  $output .= '</div>';
Steven Wittens's avatar
Steven Wittens committed
356 357 358 359 360

  return $output;
}

function poll_view_processvote(&$node) {
Steven Wittens's avatar
Steven Wittens committed
361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381
  $edit = $_POST['edit'];
  $choice = $edit['choice'];
  $vote = $_POST['vote'];

  if ($vote == t('Vote')) {
    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']++;
        drupal_set_message(t('Your vote was recorded.'));
      }
      else {
        drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
      }
    }
    else {
      drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
Steven Wittens's avatar
Steven Wittens committed
382
    }
Steven Wittens's avatar
Steven Wittens committed
383 384 385
  }
}

386 387 388 389 390 391 392
/**
 * Implementation of hook_view().
 *
 * @param $block
 *   An extra parameter that adapts the hook to display a block-ready
 *   rendering of the poll.
 */
393
function poll_view(&$node, $main = 0, $page = 0, $block = 0) {
Dries's avatar
 
Dries committed
394
  global $user;
Steven Wittens's avatar
Steven Wittens committed
395

Steven Wittens's avatar
Steven Wittens committed
396 397 398 399
  if (!$block) {
    // Because the voting form is embedded in the node-display, we process the data here
    poll_view_processvote($node);
  }
Steven Wittens's avatar
Steven Wittens committed
400

Steven Wittens's avatar
Steven Wittens committed
401 402 403 404 405 406
  if ($node->allowvotes && (arg(2) != $node->nid || arg(3) != 'results')) {
    $output .= poll_view_voting($node, $main, $page, $block);
  }
  else {
    $output .= poll_view_results($node, $main, $page, $block);
  }
Steven Wittens's avatar
Steven Wittens committed
407

Steven Wittens's avatar
Steven Wittens committed
408
  // Special display for side-block
Steven Wittens's avatar
Steven Wittens committed
409
  if ($block) {
Steven Wittens's avatar
Steven Wittens committed
410 411
    // No 'read more' link
    $node->body = $node->teaser = '';
Steven Wittens's avatar
Steven Wittens committed
412

Steven Wittens's avatar
Steven Wittens committed
413
    $links = link_node($node, $main);
Steven Wittens's avatar
Steven Wittens committed
414
    $links[] = l(t('older polls'), 'poll', array('title' => t('View the list of polls on this site.')));
Steven Wittens's avatar
Steven Wittens committed
415

Steven Wittens's avatar
Steven Wittens committed
416
    $output .= '<div class="links">'. theme("links", $links) .'</div>';
Steven Wittens's avatar
Steven Wittens committed
417 418 419
  }

  $node->body = $node->teaser = $output;
420

421
  // We also use poll_view() for the side-block
Steven Wittens's avatar
Steven Wittens committed
422
  if (!$block) {
Steven Wittens's avatar
Steven Wittens committed
423
    return theme('node', $node, $main, $page);
424 425 426
  }
}

427 428 429
/**
 * Implementation of hook_update().
 */
430
function poll_update($node) {
Steven Wittens's avatar
Steven Wittens committed
431
  db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
432

Steven Wittens's avatar
Steven Wittens committed
433 434 435 436
  db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
  foreach ($node->choice as $choice) {
    $chvotes = (int)$choice['chvotes'];
    $chtext = $choice['chtext'];
437

Steven Wittens's avatar
Steven Wittens committed
438 439
    if ($chtext != '') {
      db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $i++);
440
    }
441 442
  }
}
443

444
?>