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
55
      $sql = node_rewrite_sql("SELECT MAX(n.created) FROM {node} n WHERE n.type = 'poll' AND n.status = 1 AND n.moderate = 0");
      $timestamp = db_result(db_query($sql));
Dries's avatar
   
Dries committed
56
      if ($timestamp) {
Steven Wittens's avatar
Steven Wittens committed
57
58
        $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'moderate' => 0, 'status' => 1));

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

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

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

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

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

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

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

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

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

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

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

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

Steven Wittens's avatar
Steven Wittens committed
137
  // Poll choices
Dries's avatar
   
Dries committed
138
  $opts = drupal_map_assoc(range(2, $node->choices * 2 + 5));
139
  for ($a = 0; $a < $node->choices; $a++) {
Dries's avatar
   
Dries committed
140
    $group1 .= form_textfield(t('Choice %n', array('%n' => ($a + 1))), "choice][$a][chtext", $node->choice[$a]['chtext'], 50, 127);
141
    if ($admin) {
Dries's avatar
   
Dries committed
142
      $group1 .= form_textfield(t('Votes for choice %n', array('%n' => ($a + 1))), "choice][$a][chvotes", (int)$node->choice[$a]['chvotes'], 7, 7);
143
144
    }
  }
Steven Wittens's avatar
Steven Wittens committed
145
146
147
  $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
148

Dries's avatar
   
Dries committed
149

Steven Wittens's avatar
Steven Wittens committed
150
  // Poll attributes
151
  $_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
152
  $_active = array(0 => t('Closed'), 1 => t('Active'));
Dries's avatar
   
Dries committed
153

Steven Wittens's avatar
Steven Wittens committed
154
155
  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
156
  }
Steven Wittens's avatar
Steven Wittens committed
157
  $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
158
159

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

  return $output;
163
}
164

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

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

Steven Wittens's avatar
Steven Wittens committed
176
177
178
  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++);
179
180
    }
  }
181
182
}

Dries's avatar
   
Dries committed
183
184
185
/**
 * Implementation of hook_menu().
 */
Dries's avatar
   
Dries committed
186
function poll_menu($may_cache) {
Dries's avatar
   
Dries committed
187
  $items = array();
Dries's avatar
   
Dries committed
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
214

  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
215
216
    }
  }
Dries's avatar
   
Dries committed
217

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

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

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

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

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

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

Steven Wittens's avatar
Steven Wittens committed
266
function poll_page() {
Steven Wittens's avatar
Steven Wittens committed
267
  // List all polls
268
  $sql = "SELECT n.nid, n.title, p.active, n.created, 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";
Dries's avatar
   
Dries committed
269
270
  $sql = node_rewrite_sql($sql);
  $result = pager_query($sql, 15);
Steven Wittens's avatar
Steven Wittens committed
271
  $output = '<ul>';
Steven Wittens's avatar
Steven Wittens committed
272
  while ($node = db_fetch_object($result)) {
Dries's avatar
   
Dries committed
273
    $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
274
  }
Steven Wittens's avatar
Steven Wittens committed
275
276
277
  $output .= '</ul>';
  $output .= theme("pager", NULL, 15);
  print theme('page', $output);
Steven Wittens's avatar
Steven Wittens committed
278
279
}

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

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

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

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

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

  return $output;
}

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

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

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

Steven Wittens's avatar
Steven Wittens committed
353
  $output .= '</div>';
Steven Wittens's avatar
Steven Wittens committed
354
355
356
357

  return $output;
}

Steven Wittens's avatar
Steven Wittens committed
358
359
360
361
362
/**
 * Callback for the 'results' tab for polls you can vote on
 */
function poll_results() {
  if ($node = node_load(array('nid' => arg(1)))) {
363
364
    drupal_set_title($node->title);
    print theme('page', node_show($node, 0));
Steven Wittens's avatar
Steven Wittens committed
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
  }
  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
380
381
382
383
384
385
386
387
388

    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
389
        drupal_set_message(t('Your vote was recorded.'));
Steven Wittens's avatar
Steven Wittens committed
390
391
      }
      else {
Dries's avatar
   
Dries committed
392
        drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
Steven Wittens's avatar
Steven Wittens committed
393
394
395
      }
    }
    else {
Dries's avatar
   
Dries committed
396
      drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
Steven Wittens's avatar
Steven Wittens committed
397
    }
Steven Wittens's avatar
Steven Wittens committed
398
399
400
401
402

    drupal_goto('node/'. $nid);
  }
  else {
    drupal_not_found();
Steven Wittens's avatar
Steven Wittens committed
403
404
405
  }
}

406
407
408
409
410
411
412
/**
 * 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
413
function poll_view(&$node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
Dries's avatar
   
Dries committed
414
  global $user;
Dries's avatar
   
Dries committed
415
  $output = '';
Steven Wittens's avatar
Steven Wittens committed
416

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

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

Dries's avatar
Dries committed
429
    $links = module_invoke_all('link', 'node', $node, 1);
Steven Wittens's avatar
Steven Wittens committed
430
    $links[] = l(t('older polls'), 'poll', array('title' => t('View the list of polls on this site.')));
Steven Wittens's avatar
Steven Wittens committed
431
432
433
    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
434

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

  $node->body = $node->teaser = $output;
439
440
}

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

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

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

458
?>