initial import from onelab svn codebase
[plewww.git] / modules / poll.module
1 <?php
2 // $Id: poll.module 144 2007-03-28 07:52:20Z thierry $
3
4 /**
5  * @file
6  * Enables your site to capture votes on different topics in the form of multiple
7  * choice questions.
8  */
9
10 /**
11  * Implementation of hook_help().
12  */
13 function poll_help($section) {
14   switch ($section) {
15     case 'admin/help#poll':
16       $output = '<p>'. t('The poll module can be used to create simple polls for site users.  A poll is a simple multiple choice questionnaire which displays the cumulative results of the answers to the poll.  Having polls on the site is a good way to get instant feedback from community members.') .'</p>';
17       $output .= '<p>'. t('Users can create a poll. 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.The <a href="%poll">poll</a> item in the navigation menu 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.', array('%poll' => url('poll'))) .'</p>';
18       $output .= t('<p>You can</p>
19 <ul>
20 <li>view the <a href="%poll">polls page</a>.</li>
21 <li><a href="%admin-node-configure-types-poll">administer &gt;&gt; settings &gt;&gt; content types &gt;&gt; configure poll</a>.</li>
22 </ul>
23 ', array('%poll' => url('poll'), '%admin-node-configure-types-poll' => url('admin/settings/content-types/poll')));
24       $output .= '<p>'. t('For more information please read the configuration and customization handbook <a href="%poll">Poll page</a>.', array('%poll' => 'http://drupal.org/handbook/modules/poll/')) .'</p>';
25       return $output;
26     case 'admin/modules#description':
27       return t("Allows your site to capture votes on different topics in the form of multiple choice questions.");
28     case 'node/add#poll':
29       return t("A poll is a multiple-choice question which visitors can vote on.");
30   }
31 }
32
33 /**
34  * Implementation of hook_access().
35  */
36 function poll_access($op, $node) {
37   if ($op == 'create') {
38     return user_access('create polls');
39   }
40 }
41
42 /**
43  * Implementation of hook_block().
44  *
45  * Generates a block containing the latest poll.
46  */
47 function poll_block($op = 'list', $delta = 0) {
48   if (user_access('access content')) {
49     if ($op == 'list') {
50       $blocks[0]['info'] = t('Most recent poll');
51       return $blocks;
52     }
53     else if ($op == 'view') {
54       // Retrieve the latest poll.
55       $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");
56       $timestamp = db_result(db_query($sql));
57       if ($timestamp) {
58         $poll = node_load(array('type' => 'poll', 'created' => $timestamp, 'moderate' => 0, 'status' => 1));
59
60         if ($poll->nid) {
61           // poll_view() dumps the output into $poll->body.
62           poll_view($poll, 1, 0, 1);
63         }
64       }
65       $block['subject'] = t('Poll');
66       $block['content'] = $poll->body;
67       return $block;
68     }
69   }
70 }
71
72 /**
73  * Implementation of hook_cron().
74  *
75  * Closes polls that have exceeded their allowed runtime.
76  */
77 function poll_cron() {
78   $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');
79   while ($poll = db_fetch_object($result)) {
80     db_query("UPDATE {poll} SET active = 0 WHERE nid = %d", $poll->nid);
81   }
82 }
83
84 /**
85  * Implementation of hook_delete().
86  */
87 function poll_delete($node) {
88   db_query("DELETE FROM {poll} WHERE nid = %d", $node->nid);
89   db_query("DELETE FROM {poll_choices} WHERE nid = %d", $node->nid);
90   db_query("DELETE FROM {poll_votes} WHERE nid = %d", $node->nid);
91 }
92
93 /**
94  * Implementation of hook_submit().
95  */
96 function poll_submit(&$node) {
97   // Renumber fields
98   $node->choice = array_values($node->choice);
99   $node->teaser = poll_teaser($node);
100 }
101
102 /**
103  * Implementation of hook_validate().
104  */
105 function poll_validate($node) {
106   if (isset($node->title)) {
107     // Check for at least two options and validate amount of votes:
108     $realchoices = 0;
109     // Renumber fields
110     $node->choice = array_values($node->choice);
111     foreach ($node->choice as $i => $choice) {
112       if ($choice['chtext'] != '') {
113         $realchoices++;
114       }
115       if ($choice['chvotes'] < 0) {
116         form_set_error("choice][$i][chvotes", t('Negative values are not allowed.'));
117       }
118     }
119
120     if ($realchoices < 2) {
121       form_set_error("choice][$realchoices][chtext", t('You must fill in at least two choices.'));
122     }
123   }
124 }
125
126 /**
127  * Implementation of hook_form().
128  */
129 function poll_form(&$node) {
130   $admin = user_access('administer nodes');
131
132   $form['title'] = array('#type' => 'textfield', '#title' => t('Question'), '#required' => TRUE, '#default_value' => $node->title, '#weight' => -1);
133
134   $form['choice']['choices'] = array('#type' => 'hidden', '#default_value' => max(2, count($node->choice) ? count($node->choice) : 5));
135   $form['choice']['morechoices'] = array('#type' => 'checkbox', '#title' => t('Need more choices'), '#default_value' => 0, '#description' => t("If the amount of boxes above isn't enough, check this box and click the Preview button below to add some more."), '#weight' => 1);
136   $form['choice'] = form_builder('poll_node_form', $form['choice']);
137   if ($form['choice']['morechoices']['#value']) {
138     $form['choice']['morechoices']['#value'] = 0;
139     $form['choice']['choices']['#value'] *= 2;
140   }
141
142   // if the value was changed in a previous iteration, retain it.
143   $node->choices = $form['choice']['choices']['#value'];
144
145   // Poll choices
146   $form['choice'] += array('#type' => 'fieldset', '#title' => t('Choices'), '#prefix' => '<div class="poll-form">', '#suffix' => '</div>', '#tree' => TRUE);
147   for ($a = 0; $a < $node->choices; $a++) {
148     $form['choice'][$a]['chtext'] = array('#type' => 'textfield', '#title' => t('Choice %n', array('%n' => ($a + 1))), '#default_value' => $node->choice[$a]['chtext']);
149     if ($admin) {
150       $form['choice'][$a]['chvotes'] = array('#type' => 'textfield', '#title' => t('Votes for choice %n', array('%n' => ($a + 1))), '#default_value' => (int)$node->choice[$a]['chvotes'], '#size' => 5, '#maxlength' => 7);
151     }
152   }
153
154   // Poll attributes
155   $_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), "format_interval");
156   $_active = array(0 => t('Closed'), 1 => t('Active'));
157
158   if ($admin) {
159     $form['settings'] = array('#type' => 'fieldset', '#title' => t('Settings'));
160     $form['settings']['active'] = array('#type' => 'radios', '#title' => t('Poll status'), '#default_value' => isset($node->active) ? $node->active : 1, '#options' => $_active, '#description' => t('When a poll is closed, visitors can no longer vote for it.'));
161   }
162   $form['settings']['runtime'] = array('#type' => 'select', '#title' => t('Poll duration'), '#default_value' => $node->runtime ? $node->runtime : 0, '#options' => $_duration, '#description' => t('After this period, the poll will be closed automatically.'));
163
164   return $form;
165 }
166
167 function poll_insert($node) {
168   if (!user_access('administer nodes')) {
169     // Make sure all votes are 0 initially
170     foreach ($node->choice as $i => $choice) {
171       $node->choice[$i]['chvotes'] = 0;
172     }
173     $node->active = 1;
174   }
175
176   db_query("INSERT INTO {poll} (nid, runtime, active) VALUES (%d, %d, %d)", $node->nid, $node->runtime, $node->active);
177
178   foreach ($node->choice as $choice) {
179     if ($choice['chtext'] != '') {
180       db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $choice['chtext'], $choice['chvotes'], $i++);
181     }
182   }
183 }
184
185 /**
186  * Implementation of hook_menu().
187  */
188 function poll_menu($may_cache) {
189   $items = array();
190
191   if ($may_cache) {
192     $items[] = array('path' => 'node/add/poll', 'title' => t('poll'),
193       'access' => user_access('create polls'));
194     $items[] = array('path' => 'poll', 'title' => t('polls'),
195       'callback' => 'poll_page',
196       'access' => user_access('access content'),
197       'type' => MENU_SUGGESTED_ITEM);
198
199     $items[] = array('path' => 'poll/vote',
200       'title' => t('vote'),
201       'callback' => 'poll_vote',
202       'access' => user_access('vote on polls'),
203       'type' => MENU_CALLBACK);
204   }
205   else {
206     if (arg(0) == 'node' && is_numeric(arg(1))) {
207       $node = node_load(arg(1));
208
209       if ($node->type == 'poll' && $node->allowvotes) {
210         $items[] = array('path' => 'node/'. arg(1) .'/results',
211           'title' => t('results'),
212           'callback' => 'poll_results',
213           'access' => user_access('access content'),
214           'weight' => 3,
215           'type' => MENU_LOCAL_TASK);
216       }
217     }
218   }
219
220   return $items;
221 }
222
223 /**
224  * Implementation of hook_load().
225  */
226 function poll_load($node) {
227   global $user;
228
229   // Load the appropriate choices into the $node object
230   $poll = db_fetch_object(db_query("SELECT runtime, active FROM {poll} WHERE nid = %d", $node->nid));
231
232   $result = db_query("SELECT chtext, chvotes, chorder FROM {poll_choices} WHERE nid = %d ORDER BY chorder", $node->nid);
233   while ($choice = db_fetch_array($result)) {
234     $poll->choice[$choice['chorder']] = $choice;
235   }
236
237   // Determine whether or not this user is allowed to vote
238   $poll->allowvotes = FALSE;
239   if (user_access('vote on polls') && $poll->active) {
240     if ($user->uid && db_num_rows(db_query('SELECT uid FROM {poll_votes} WHERE nid = %d AND uid = %d', $node->nid, $user->uid)) == 0) {
241       $poll->allowvotes = TRUE;
242     }
243     else if ($user->uid == 0 && db_num_rows(db_query("SELECT hostname FROM {poll_votes} WHERE nid = %d AND hostname = '%s'", $node->nid, $_SERVER['REMOTE_ADDR'])) == 0) {
244       $poll->allowvotes = TRUE;
245     }
246   }
247   return $poll;
248 }
249
250 /**
251  * Implementation of hook_node_info().
252  */
253 function poll_node_info() {
254   return array('poll' => array('name' => t("poll"), 'base' => 'poll'));
255 }
256
257 function poll_page() {
258   // List all polls
259   $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";
260   $sql = db_rewrite_sql($sql);
261   $result = pager_query($sql, 15);
262   $output = '<ul>';
263   while ($node = db_fetch_object($result)) {
264     $output .= '<li>'. l($node->title, "node/$node->nid") .' - '. format_plural($node->votes, '1 vote', '%count votes') .' - '. ($node->active ? t('open') : t('closed')) .'</li>';
265   }
266   $output .= '</ul>';
267   $output .= theme("pager", NULL, 15);
268   return $output;
269 }
270
271 /**
272  * Implementation of hook_perm().
273  */
274 function poll_perm() {
275   return array('create polls', 'vote on polls');
276 }
277
278 /**
279  * Creates a simple teaser that lists all the choices.
280  */
281 function poll_teaser($node) {
282   $teaser = NULL;
283   if (is_array($node->choice)) {
284     foreach ($node->choice as $k => $choice) {
285       $teaser .= '* '. $choice['chtext'] .'\n';
286     }
287   }
288   return $teaser;
289 }
290
291 /**
292  * Generates the voting form for a poll.
293  */
294 function poll_view_voting(&$node, $teaser, $page, $block) {
295   if ($_POST['op'] == t('Vote')) {
296     poll_vote($node);
297   }
298
299   if ($node->choice) {
300     $list = array();
301     foreach ($node->choice as $i => $choice) {
302       $list[$i] = check_plain($choice['chtext']);
303     }
304     $form['choice'] = array('#type' => 'radios', '#title' => $page ? '' : check_plain($node->title), '#default_value' => -1, '#options' => $list);
305   }
306   $form['nid'] = array('#type' => 'hidden', '#value' => $node->nid);
307   $form['vote'] = array('#type' => 'submit', '#value' => t('Vote'));
308   $form['#action'] = url('node/'. $node->nid);
309   return drupal_get_form('poll_view_voting', $form);
310 }
311
312 /**
313  * Themes the voting form for a poll.
314  */
315 function theme_poll_view_voting($form) {
316   $output .= '<div class="poll">';
317   $output .= '  <div class="vote-form">';
318   $output .= '    <div class="choices">';
319   $output .= form_render($form['choice']);
320   $output .= '    </div>';
321   $output .= form_render($form['nid']);
322   $output .= form_render($form['vote']);
323   $output .= '  </div>';
324   $output .= form_render($form);
325   $output .= '</div>';
326   return $output;
327 }
328
329 /**
330  * Generates a graphical representation of the results of a poll.
331  */
332 function poll_view_results(&$node, $teaser, $page, $block) {
333   // Count the votes and find the maximum
334   foreach ($node->choice as $choice) {
335     $total_votes += $choice['chvotes'];
336     $max_votes = max($max_votes, $choice['chvotes']);
337   }
338
339   foreach ($node->choice as $i => $choice) {
340     if ($choice['chtext'] != '') {
341       $poll_results .= theme('poll_bar', check_plain($choice['chtext']), round($choice['chvotes'] * 100 / max($total_votes, 1)), format_plural($choice['chvotes'], '1 vote', '%count votes'), $block);
342     }
343   }
344
345   $output .= theme('poll_results', check_plain($node->title), $poll_results, $total_votes, $node->links, $block);
346
347   return $output;
348 }
349
350 function theme_poll_results($title, $results, $votes, $links, $block) {
351   if ($block) {
352     $output .= '<div class="poll">';
353     $output .= '<div class="title">'. $title .'</div>';
354     $output .= $results;
355     $output .= '<div class="total">'. t('Total votes: %votes', array('%votes' => $votes)) .'</div>';
356     $output .= '</div>';
357     $output .= '<div class="links">'. theme('links', $links) .'</div>';
358   }
359   else {
360     $output .= '<div class="poll">';
361     $output .= $results;
362     $output .= '<div class="total">'. t('Total votes: %votes', array('%votes' => $votes)) .'</div>';
363     $output .= '</div>';
364   }
365
366   return $output;
367 }
368
369 function theme_poll_bar($title, $percentage, $votes, $block) {
370   if ($block) {
371     $output  = '<div class="text">'. $title .'</div>';
372     $output .= '<div class="bar"><div style="width: '. $percentage .'%;" class="foreground"></div></div>';
373     $output .= '<div class="percent">'. $percentage .'%</div>';
374   }
375   else {
376     $output  = '<div class="text">'. $title .'</div>';
377     $output .= '<div class="bar"><div style="width: '. $percentage .'%;" class="foreground"></div></div>';
378     $output .= '<div class="percent">'. $percentage .'% ('. $votes .')</div>';
379   }
380
381   return $output;
382 }
383
384 /**
385  * Callback for the 'results' tab for polls you can vote on
386  */
387 function poll_results() {
388   if ($node = node_load(arg(1))) {
389     drupal_set_title(check_plain($node->title));
390     return node_show($node, 0);
391   }
392   else {
393     drupal_not_found();
394   }
395 }
396
397 /**
398  * Callback for processing a vote
399  */
400 function poll_vote(&$node) {
401   global $user;
402   $nid = arg(1);
403
404   if ($node = node_load($nid)) {
405     $edit = $_POST['edit'];
406     $choice = $edit['choice'];
407     $vote = $_POST['vote'];
408
409     if (isset($choice) && isset($node->choice[$choice])) {
410       if ($node->allowvotes) {
411         // Mark the user or host as having voted.
412         if ($user->uid) {
413           db_query('INSERT INTO {poll_votes} (nid, uid) VALUES (%d, %d)', $node->nid, $user->uid);
414         }
415         else {
416           db_query("INSERT INTO {poll_votes} (nid, hostname) VALUES (%d, '%s')", $node->nid, $_SERVER['REMOTE_ADDR']);
417         }
418
419         // Add one to the votes.
420         db_query("UPDATE {poll_choices} SET chvotes = chvotes + 1 WHERE nid = %d AND chorder = %d", $node->nid, $choice);
421
422         $node->allowvotes = FALSE;
423         $node->choice[$choice]['chvotes']++;
424         drupal_set_message(t('Your vote was recorded.'));
425       }
426       else {
427         drupal_set_message(t("You're not allowed to vote on this poll."), 'error');
428       }
429     }
430     else {
431       drupal_set_message(t("You didn't specify a valid poll choice."), 'error');
432     }
433
434     drupal_goto('node/'. $nid);
435   }
436   else {
437     drupal_not_found();
438   }
439 }
440
441 /**
442  * Implementation of hook_view().
443  *
444  * @param $block
445  *   An extra parameter that adapts the hook to display a block-ready
446  *   rendering of the poll.
447  */
448 function poll_view(&$node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
449   global $user;
450   $output = '';
451
452   // Special display for side-block
453   if ($block) {
454     // No 'read more' link
455     $node->body = $node->teaser = '';
456
457     $links = module_invoke_all('link', 'node', $node, 1);
458     $links[] = l(t('older polls'), 'poll', array('title' => t('View the list of polls on this site.')));
459     if ($node->allowvotes && $block) {
460       $links[] = l(t('results'), 'node/'. $node->nid .'/results', array('title' => t('View the current poll results.')));
461     }
462
463     $node->links = $links;
464   }
465
466   if ($node->allowvotes && ($block || arg(2) != 'results')) {
467     $output .= poll_view_voting($node, $teaser, $page, $block);
468   }
469   else {
470     $output .= poll_view_results($node, $teaser, $page, $block);
471   }
472
473   $node->body = $node->teaser = $output;
474 }
475
476 /**
477  * Implementation of hook_update().
478  */
479 function poll_update($node) {
480   db_query('UPDATE {poll} SET runtime = %d, active = %d WHERE nid = %d', $node->runtime, $node->active, $node->nid);
481
482   db_query('DELETE FROM {poll_choices} WHERE nid = %d', $node->nid);
483   db_query('DELETE FROM {poll_votes} WHERE nid = %d', $node->nid);
484   foreach ($node->choice as $choice) {
485     $chvotes = (int)$choice['chvotes'];
486     $chtext = $choice['chtext'];
487
488     if ($chtext != '') {
489       db_query("INSERT INTO {poll_choices} (nid, chtext, chvotes, chorder) VALUES (%d, '%s', %d, %d)", $node->nid, $chtext, $chvotes, $i++);
490     }
491   }
492 }
493
494 /**
495  * Implementation of hook_user().
496  */
497 function poll_user($op, &$edit, &$user) {
498   if ($op == 'delete') {
499     db_query('UPDATE {poll_votes} SET uid = 0 WHERE uid = %d', $user->uid);
500   }
501 }