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/access/permissions"), "%poll" => url("poll")));
Dries's avatar
   
Dries committed
25
    case 'admin/modules#description':
26
      return t("Allows 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.
54
      $sql = db_rewrite_sql("SELECT MAX(n.created) FROM {node} n INNER JOIN {poll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1 AND n.moderate = 0");
Dries's avatar
   
Dries committed
55
      $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() {
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)) {
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) {
87
  db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
Dries's avatar
   
Dries committed
88
  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
    $realchoices = 0;
98
99
    // Renumber fields
    $node->choice = array_values($node->choice);
Steven Wittens's avatar
Steven Wittens committed
100
101
102
    foreach ($node->choice as $i => $choice) {
      if ($choice['chtext'] != '') {
        $realchoices++;
103
      }
Steven Wittens's avatar
Steven Wittens committed
104
      if ($choice['chvotes'] < 0) {
Dries's avatar
   
Dries committed
105
        form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
106
      }
107
    }
Dries's avatar
   
Dries committed
108

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

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

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

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

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

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

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

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

Dries's avatar
   
Dries committed
150

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

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

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

  return $output;
164
}
165

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

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

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

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

  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))) {
206
      $node = node_load(arg(1));
Dries's avatar
   
Dries committed
207
208
209
210
211
212
213
214
215

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

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

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

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

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

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

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

Steven Wittens's avatar
Steven Wittens committed
267
function poll_page() {
Steven Wittens's avatar
Steven Wittens committed
268
  // List all polls
269
  $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 n.status = 1 AND n.moderate = 0 GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC";
270
  $sql = db_rewrite_sql($sql);
Dries's avatar
   
Dries committed
271
  $result = pager_query($sql, 15);
Steven Wittens's avatar
Steven Wittens committed
272
  $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
  $output .= '</ul>';
  $output .= theme("pager", NULL, 15);
Dries's avatar
   
Dries committed
278
  return $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
 */
Dries's avatar
Dries committed
303
function poll_view_voting(&$node, $teaser, $page, $block) {
Dries's avatar
   
Dries committed
304
  $output = '<div class="poll">';
Dries's avatar
   
Dries committed
305

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

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

  return $output;
}

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

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

Dries's avatar
   
Dries committed
337
  // Output the divs for the text, bars and percentages
Steven Wittens's avatar
Steven Wittens committed
338
339
  $output .= '<div class="poll">';
  if ($block) {
340
    $output .= '<div class="title">'. check_plain($node->title) .'</div>';
Steven Wittens's avatar
Steven Wittens committed
341
342
343
344
  }
  foreach ($node->choice as $i => $choice) {
    if ($choice['chtext'] != '') {
      $percentage = round($choice['chvotes'] * 100 / max($votestotal, 1));
345
      $output .= '<div class="text">'. check_plain($choice['chtext']) .'</div>';
Steven Wittens's avatar
Steven Wittens committed
346
347
348
349
      $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>';
350
    }
351
  }
Steven Wittens's avatar
Steven Wittens committed
352
  $output .= '<div class="total">'. t('Total votes') .": $votestotal</div>";
Dries's avatar
   
Dries committed
353

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

  return $output;
}

Steven Wittens's avatar
Steven Wittens committed
359
360
361
362
/**
 * Callback for the 'results' tab for polls you can vote on
 */
function poll_results() {
363
  if ($node = node_load(arg(1))) {
364
    drupal_set_title(check_plain($node->title));
Dries's avatar
   
Dries committed
365
    return node_show($node, 0);
Steven Wittens's avatar
Steven Wittens committed
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);
377
  if ($node = node_load($nid)) {
Steven Wittens's avatar
Steven Wittens committed
378
379
380
    $edit = $_POST['edit'];
    $choice = $edit['choice'];
    $vote = $_POST['vote'];
Steven Wittens's avatar
Steven Wittens committed
381
382
383
384

    if (isset($choice) && isset($node->choice[$choice])) {
      if ($node->allowvotes) {
        $id = poll_uid();
385
        $node->polled = $node->polled ? ($node->polled .' '. $id) : $id;
386
        db_query("UPDATE {poll} SET polled = '%s' WHERE nid = %d", $node->polled, $node->nid);
Steven Wittens's avatar
Steven Wittens committed
387
388
389
        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
390
        drupal_set_message(t('Your vote was recorded.'));
Steven Wittens's avatar
Steven Wittens committed
391
392
      }
      else {
Dries's avatar
   
Dries committed
393
        drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
Steven Wittens's avatar
Steven Wittens committed
394
395
396
      }
    }
    else {
Dries's avatar
   
Dries committed
397
      drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
Steven Wittens's avatar
Steven Wittens committed
398
    }
Steven Wittens's avatar
Steven Wittens committed
399
400
401
402
403

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

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

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

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

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

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

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

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

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

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

459