/[drupal]/contributions/modules/advpoll/advpoll.module
ViewVC logotype

Contents of /contributions/modules/advpoll/advpoll.module

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.45 - (show annotations) (download) (as text)
Wed Nov 29 02:27:06 2006 UTC (2 years, 11 months ago) by chriskennedy
Branch: MAIN
CVS Tags: HEAD
Changes since 1.44: +48 -13 lines
File MIME type: text/x-php
#99252 Cast votes using Ajax; refactor some results handling and split javascript into two main files - patch by fajerstarter and me
1 <?php
2 // $Id: advpoll.module,v 1.44 2006/11/28 06:54:15 chriskennedy Exp $
3 /**
4 * @file
5 * Advanced Poll - a sophisticated polling module for voting, elections, and group decision-making.
6 */
7
8
9 define('ADVPOLL_DEFAULT_ELECTORAL_LIST', 0);
10 // always, aftervote, or afterclose
11 define('ADVPOLL_DEFAULT_VIEW_RESULTS', 'aftervote');
12
13 /**
14 * Implementation of hook_access().
15 */
16 function advpoll_access($op, $node) {
17 global $user;
18
19 if ($op == 'create') {
20 return user_access('create polls');
21 }
22 if ($op == 'delete') {
23 return user_access('delete polls');
24 }
25 if ($op == 'update') {
26 if (user_access('create polls') && ($user->uid == $node->uid)) {
27 return TRUE;
28 }
29 }
30 }
31
32 /**
33 * Implementation of hook_block().
34 */
35 function advpoll_block($op = 'list', $delta = 'mostrecent', $edit = array()) {
36 switch($op) {
37 case 'list':
38 $blocks['mostrecent']['info'] = t('Advanced Poll - Newest');
39 return $blocks;
40 case 'view':
41 if (user_access('view polls')) {
42 switch ($delta) {
43 case 'mostrecent': default:
44 $block['content'] = advpoll_block_mostrecent();
45 $block['subject'] = t('Advanced Poll - Newest');
46 }
47
48 if ($block['content']) {
49 return $block;
50 }
51 }
52 default:
53 }
54 }
55
56 /**
57 * Content of the block, as returned by advpoll_block('view')
58 */
59 function advpoll_block_mostrecent() {
60 $output = '';
61 $result = db_query('SELECT nid FROM {advpoll} WHERE active=1 ORDER BY nid DESC LIMIT 1');
62 // Check that there is an active poll
63 if (db_num_rows($result) > 0) {
64 $poll = db_fetch_object($result);
65 $node = advpoll_view(node_load($poll->nid), FALSE, FALSE, TRUE);
66 $output = drupal_render($node->content);
67 }
68 else {
69 $output .= t('No active Advanced Poll.');
70 }
71 return $output;
72 }
73
74 /**
75 *
76 */
77 function advpoll_page() {
78 // List all polls
79 $sql = 'SELECT n.nid, n.title, p.active, n.created, c.value AS votes FROM {node} n INNER JOIN {advpoll} p ON n.nid = p.nid INNER JOIN {votingapi_cache} c ON n.nid = c.content_id WHERE n.status = 1 AND c.tag = "_advpoll" AND c.function="total_votes" AND c.content_type="advpoll" GROUP BY n.nid, n.title, p.active, n.created ORDER BY n.created DESC';
80 $sql = db_rewrite_sql($sql);
81 $result = pager_query($sql, 15);
82 $output = '<ul>';
83 while ($node = db_fetch_object($result)) {
84 $output .= '<li>'. l($node->title, "node/$node->nid") .' - '. format_plural($node->votes, '1 vote', '@count votes') .' - '. ($node->active ? t('open') : t('closed')) .'</li>';
85 }
86 $output .= '</ul>';
87 $output .= theme('pager', NULL, 15);
88 return $output;
89 }
90
91 /**
92 * Implementation of VotingAPI's hook_calculate
93 * Recalculate results whenever a vote is added or removed
94 */
95 function advpoll_votingapi_calculate(&$results, $votes, $content_type, $content_id) {
96 // Make sure it's an Advanced Poll content type
97 if ($content_type == 'advpoll') {
98 $node = node_load($content_id);
99 $mode = _advpoll_get_mode($node);
100 $function = 'advpoll_calculate_results_' . $mode;
101 if (function_exists($function)) {
102 $function($results, $votes, $node);
103 }
104 cache_clear_all();
105 }
106 }
107
108
109 /**
110 * Implementation of hook_cron().
111 *
112 * Closes polls that have exceeded their allowed runtime.
113 */
114 function advpoll_cron() {
115 // Open polls with a startdate that is in the past but that need to be opened
116 $sql = 'SELECT p.nid FROM {advpoll} p WHERE p.startdate IS NOT NULL AND (p.runtime = 0 OR p.startdate + p.runtime >= ' . (time() - time() % (3600 * 24)) . ') AND p.startdate < ' . time() . ' AND p.active = 0';
117 $result = db_query($sql);
118 drupal_set_message($sql);
119 while ($poll = db_fetch_object($result)) {
120 db_query("UPDATE {advpoll} SET active = 1 WHERE nid=%d", $poll->nid);
121 }
122
123 // Close polls
124 $sql = 'SELECT p.nid FROM {advpoll} p INNER JOIN {node} n ON p.nid = n.nid WHERE ((p.startdate IS NULL AND (n.created + p.runtime) < '. time() .') OR (p.startdate IS NOT NULL AND (p.startdate + p.runtime) < ' . time() . ')) AND p.active = 1 AND p.runtime != 0';
125 $result = db_query($sql);
126 while ($poll = db_fetch_object($result)) {
127 db_query("UPDATE {advpoll} SET active = 0 WHERE nid=%d", $poll->nid);
128 }
129 }
130
131
132 /**
133 * Implementation of hook_delete().
134 *
135 */
136 function advpoll_delete($node) {
137 db_query("DELETE FROM {advpoll} WHERE nid = %d", $node->nid);
138 db_query("DELETE FROM {advpoll_choices} WHERE nid = %d", $node->nid);
139 db_query("DELETE FROM {advpoll_electoral_list} WHERE nid = %d", $node->nid);
140
141 // Note: these should be converted to a votingapi method eventually
142 db_query('DELETE FROM {votingapi_vote} WHERE content_id = %d AND
143 content_type="advpoll"', $node->nid);
144 db_query('DELETE FROM {votingapi_cache} WHERE content_id = %d AND
145 content_type="advpoll"', $node->nid);
146 }
147
148 /**
149 * Implementation of hook_form().
150 *
151 * This hook displays the form necessary to create/edit the poll.
152 */
153 function advpoll_form($node, $form_values = NULL) {
154 $mode = _advpoll_get_mode($node);
155
156 // Only add javascript once, even if _form is called multiple times
157 static $add_js;
158 if (!$add_js) {
159 // Pass translatable strings
160 drupal_add_js(array('advPoll' => array('remove' => t('Remove'), 'addChoice' => t('Add choice'), 'noLimit' => t('No limit'))), 'setting');
161 drupal_add_js(drupal_get_path('module', 'advpoll') .'/advpoll-form.js', 'module');
162 drupal_add_css(drupal_get_path('module', 'advpoll') .'/advpoll.css', 'module');
163 $add_js = true;
164 }
165
166 $form['title'] = array(
167 '#type' => 'textfield',
168 '#title' => ucfirst($mode) .' '. t('question'),
169 '#required' => TRUE,
170 '#default_value' => $node->title,
171 );
172
173 $form['body'] = array(
174 '#type' => 'textarea',
175 '#title' => t('Description'),
176 '#required' => FALSE,
177 '#default_value' => $node->body,
178 );
179
180 if (isset($form_values)) {
181 $choices = $form_values['choices'];
182 if ($form_values['morechoices']) {
183 $choices *= 2;
184 }
185 }
186 else {
187 $choices = max(2, count($node->choice)? count($node->choice) : 5);
188 }
189
190 $form['choices'] = array(
191 '#type' => 'hidden',
192 '#value' => $choices,
193 );
194
195 // Advanced Poll choices
196 $form['choice'] = array(
197 '#type' => 'fieldset',
198 '#title' => t('Poll choices'),
199 '#collapsible' => TRUE,
200 '#prefix' => '<div class="poll-form">',
201 '#suffix' => '</div>',
202 '#tree' => TRUE,
203 '#weight' => 1,
204 );
205
206 $form['choice']['morechoices'] = array(
207 '#type' => 'checkbox',
208 '#title' => t('Need more choices'),
209 '#value' => 0,
210 '#parents' => array('morechoices'), // don't pollute $form['choice']
211 '#prefix' => '<div id="morechoices">',
212 '#suffix' => '</div>',
213 '#description' => t("If the amount of boxes above isn't enough, check this box and click the Preview button below to add some more."),
214 '#weight' => 1
215 );
216
217 for ($a = 1; $a <= $choices; $a++) {
218 $form['choice'][$a]['label'] = array(
219 '#type' => 'textfield',
220 '#title' => t('Choice %n', array('%n' => $a)),
221 '#default_value' => $node->choice[$a]['label'],
222 '#attributes' => array('class' => 'choices'),
223 );
224 }
225
226 $form['settings'] = array(
227 '#type' => 'fieldset',
228 '#title' => t('Poll settings'),
229 '#collapsible' => TRUE,
230 '#weight' => 2,
231 '#tree' => TRUE,
232 );
233
234 $maxChoiceList = array();
235 for ($i = 0; $i <= $choices; $i++) {
236 $maxChoiceList[$i] = ($i == 0? 'No limit' : $i);
237 }
238
239 $form['settings']['maxchoices'] = array(
240 '#type' => 'select',
241 '#title' => t('Maximum choices'),
242 '#default_value' => ($node->maxchoices? $node->maxchoices : 0),
243 '#options' => $maxChoiceList,
244 '#DANGEROUS_SKIP_CHECK' => true, // allow jQuery to add new options
245 '#description' => t('Limits the total number of choices voters may select.')
246 );
247
248 $voting_algorithms = array();
249
250 foreach (advpoll_algorithms($mode) as $alg) {
251 $voting_algorithms[$alg] = ucfirst($alg);
252 }
253
254 $defaultalg = ($form_values['settings']['algorithm']?
255 $form_values['settings']['algorithm'] : $node->algorithm);
256
257 if (count($voting_algorithms) > 1) {
258 $form['settings']['algorithm'] = array(
259 '#type' => 'select',
260 '#title' => t('Algorithm'),
261 '#options' => $voting_algorithms,
262 '#default_value' => $defaultalg,
263 '#description' => 'Voting algorithm to use to calculate the winner.',
264 );
265 }
266 else {
267 $form['settings']['algorithm'] = array(
268 '#type' => 'hidden',
269 '#value' => $defaultalg,
270 );
271 }
272
273 $active = array(1 => t('Active'), 0 => t('Closed'));
274 $form['settings']['active'] = array(
275 '#type' => 'radios',
276 '#title' => t('Status'),
277 '#options' => $active,
278 '#default_value' => (isset($node->active)? $node->active : 1),
279 '#description' => t('When a poll is closed users may no longer vote on it.'),
280 );
281
282 $_duration = array(0 => t('Unlimited')) + drupal_map_assoc(array(86400, 172800, 345600, 604800, 1209600, 2419200, 4838400, 9676800, 31536000), 'format_interval');
283
284 $form['settings']['usestart'] = array(
285 '#type' => 'checkbox',
286 '#title' => t('Use start date'),
287 '#description' => t('Specify a date that the poll opens.'),
288 '#default_value' => (isset($node->startdate)? TRUE :
289 ($form_values['startdate']? $form_values['startdate'] : FALSE)),
290 );
291
292 $date = $form_values['startdate']? $form_values['startdate'] :
293 $node->startdate? $node->startdate : time();
294
295 $form['settings']['startdate'] = array(
296 '#prefix' => '<div class="edit-settings-startdate">',
297 '#suffix' => '</div>',
298 '#type' => 'date',
299 '#title' => t('Starting date'),
300 '#description' => t('Date that the poll opens'),
301 '#default_value' => array('year' => date('Y', $date), 'month' => date('m', $date), 'day' => date('d', $date)),
302 );
303
304 $form['settings']['startdatejs'] = array(
305 '#type' => 'hidden',
306 '#default_value' => $date,
307 );
308
309
310
311 $form['settings']['runtime'] = array(
312 '#type' => 'select',
313 '#title' => t('Duration'),
314 '#default_value' => ($node->runtime? $node->runtime : 0),
315 '#options' => $_duration,
316 '#description' => t('After this period, the poll will be closed automatically. This is relative to the start date if it is specified, otherwise the date the poll was created.')
317 );
318
319
320 $form['settings']['uselist'] = array(
321 '#type' => 'checkbox',
322 '#title' => t('Restrict voting to electoral list'),
323 '#description' => t('If enabled, a list of eligible voters will be created and only that group will be able to vote in the poll.'),
324 '#default_value' => isset($node->uselist)? $node->uselist : variable_get('advpoll_default_electoral_list', ADVPOLL_DEFAULT_ELECTORAL_LIST),
325 );
326
327 $form['settings']['showvotes'] = array(
328 '#type' => 'checkbox',
329 '#title' => t('Show individual votes'),
330 '#description' => t('Users with the appropriate permissions will be able to see how each person voted.'),
331 '#default_value' => isset($node->showvotes)? $node->showvotes : 1,
332 );
333
334 if (user_access('administer polls') && $node->nid) {
335 $form['settings']['reset'] = array(
336 '#type' => 'button',
337 '#value' => t('Reset votes'),
338 );
339 }
340
341 $form['format'] = filter_form($node->form);
342 $form['#multistep'] = TRUE;
343 return $form;
344 }
345
346
347 /**
348 * Implementation of hook_help().
349 */
350 function advpoll_help($section) {
351 switch ($section) {
352 case 'admin/modules#description':
353 return t('A sophisticated polling module for voting, elections, and group decision-making.');
354 default:
355 }
356 }
357
358 /**
359 * Implementation of hook_load().
360 *
361 * Load the votes and poll-specific data into the node object.
362 */
363 function advpoll_load($node) {
364 global $user;
365 $poll = db_fetch_object(db_query("SELECT * FROM {advpoll} WHERE nid = %d", $node->nid));
366 $result = db_query("SELECT vote_offset, label FROM {advpoll_choices} WHERE nid = %d ORDER BY vote_offset", $node->nid);
367 while ($choice = db_fetch_array($result)) {
368 $poll->choice[$choice['vote_offset']] = $choice;
369 }
370 $poll->choices = count($poll->choice);
371
372 $poll->voted = FALSE;
373 $poll->cancel_vote = FALSE;
374
375 // See if user has voted
376 if ($user->uid) {
377 $poll->voted = (count(votingapi_get_user_votes('advpoll', $node->nid)) > 0);
378 if ($poll->voted) {
379 $poll->cancel_vote = TRUE;
380 }
381 }
382 else {
383 $result = db_query('SELECT uid, value FROM {votingapi_vote} '
384 . 'WHERE content_id=%d AND content_type="advpoll" AND hostname="%s"',
385 $node->nid, $_SERVER['REMOTE_ADDR']);
386 if (db_num_rows($result) > 0) {
387 $poll->voted = TRUE;
388 $obj = db_fetch_object($result);
389 if ($obj->uid == 0) {
390 // Only allow cancelling if initial vote was also anonymous
391 $poll->cancel_vote = TRUE;
392 }
393 }
394 }
395
396 return $poll;
397 }
398
399 /**
400 * Implementation of hook_menu().
401 *
402 */
403 function advpoll_menu($may_cache) {
404 global $user;
405
406 // Load the modes in here instead of _init() toprofit from caching,
407 // as recommended by http://api.drupal.org/api/head/function/hook_init
408 $modes = _advpoll_list_modes();
409
410 $items = array();
411
412 if ($may_cache) {
413 foreach ($modes as $mode) {
414 $items[] = array(
415 'path' => 'node/add/advpoll-' . $mode['name'],
416 'title' => t('Advanced Poll - ' . $mode['name']),
417 'access' => user_access('create polls'),
418 );
419 }
420
421 $items[] = array(
422 'path' => 'advpoll/cancel',
423 'title' => t('Cancel'),
424 'callback' => 'advpoll_cancel',
425 'access' => user_access('cancel own vote'),
426 'type' => MENU_CALLBACK
427 );
428
429 $items[] = array(
430 'path' => 'admin/settings/advanced-poll',
431 'title' => t('Advanced Poll'),
432 'description' => t('Update settings for Advanced Poll'),
433 'callback' => 'drupal_get_form',
434 'callback arguments' => array('advpoll_admin_settings'),
435 'access' => user_access('administer polls'),
436 );
437
438 $items[] = array(
439 'path' => 'polls',
440 'title' => t('Advanced Polls'),
441 'callback' => 'advpoll_page',
442 'access' => user_access('view polls'),
443 'type' => MENU_SUGGESTED_ITEM,
444 );
445 }
446 else {
447 // need to be able to extract the nid
448 if (arg(0) == 'node' && is_numeric(arg(1))) {
449 $nid = arg(1);
450 $node = node_load($nid);
451 // Make sure we're on the actual poll node's page
452 if (strstr($node->type, 'advpoll_') == 0) {
453 // Show the results tab
454 if ($node->active && !$node->voted
455 && _advpoll_can_view_results($node)) {
456 $items[] = array(
457 'path' => 'node/' . $nid . '/results',
458 'title' => t('Results'),
459 'callback' => 'advpoll_results',
460 'access' => user_access('view polls'),
461 'weight' => 3,
462 'type' => MENU_LOCAL_TASK,
463 );
464 }
465
466 // Show the votes tab
467 if ($node->showvotes) {
468 $items[] = array(
469 'path' => 'node/' . $nid . '/votes',
470 'title' => t('Votes'),
471 'callback' => 'advpoll_tab_votes',
472 'access' => user_access('inspect all votes'),
473 'weight' => 3,
474 'type' => MENU_LOCAL_TASK,
475 );
476 }
477
478 // Show electoral list tab if using the functionality
479 if ($node->uselist) {
480 $items[] = array(
481 'path' => 'node/' . $nid . '/electoral_list',
482 'title' => t('Electoral list'),
483 'callback' => 'advpoll_tab_electoral_list',
484 'access' => user_access('view polls'),
485 'weight' => 3,
486 'type' => MENU_LOCAL_TASK,
487 );
488
489 // Allow voters to be removed
490 $items[] = array(
491 'path' => 'node/' . $nid . '/remove',
492 'callback' => 'advpoll_remove_voter',
493 'access' => user_access('administer polls'),
494 'weight' => 3,
495 'type' => MENU_CALLBACK,
496 );
497 }
498
499 // Allow votes to be reset
500 $items[] = array(
501 'path' => 'node/' . $nid . '/reset',
502 'callback' => 'drupal_get_form',
503 'callback arguments' => 'advpoll_reset_confirm',
504 'access' => user_access('administer polls'),
505 'weight' => 3,
506 'type' => MENU_CALLBACK,
507 );
508
509
510 }
511 }
512 }
513
514 return $items;
515 }
516
517 /**
518 * Display the electoral list tab
519 */
520 function advpoll_tab_electoral_list() {
521 if ($node = node_load(arg(1))) {
522 if (!$node->uselist) {
523 drupal_not_found();
524 return;
525 }
526 drupal_set_title(check_plain($node->title));
527
528 if (user_access('administer polls')) {
529 $output .= drupal_get_form('advpoll_electoral_list_form', $node->nid);
530 }
531
532 $output .= '<p>'
533 . t('This table lists all the eligible voters for this poll.')
534 . '</p>';
535
536 $header[] = array('data' => t('Voter'), 'field' => 'u.name');
537
538 $result = pager_query("SELECT u.uid, u.name FROM {advpoll_electoral_list} el LEFT JOIN {users} u ON el.uid = u.uid WHERE el.nid = %d" . tablesort_sql($header), 20, 0, NULL, $node->nid);
539 $eligible_voters = array();
540 while ($voter = db_fetch_object($result)) {
541 $temp = array(
542 theme('username', $voter),
543 );
544
545 if (user_access('administer polls')) {
546 $temp[] = l(t('remove'), 'node/'. $node->nid .'/remove/' . $voter->uid);
547 }
548
549 $eligible_voters[] = $temp;
550 }
551 $output .= theme('table', $header, $eligible_voters);
552 $output .= theme('pager', NULL, 20, 0);
553 print theme('page', $output);
554 }
555 else {
556 drupal_not_found();
557 }
558 }
559
560 function advpoll_electoral_list_form($nid) {
561 $form['electoral_list'] = array(
562 '#type' => 'fieldset',
563 '#tree' => TRUE,
564 '#title' => t('Administer electoral list'),
565 '#collapsible' => TRUE,
566 '#weight' => 2,
567 '#collapsed' => TRUE,
568 );
569
570 $form['electoral_list']['add_user'] = array(
571 '#type' => 'textfield',
572 '#title' => t('Add user'),
573 '#size' => 40,
574 '#autocomplete_path' => 'user/autocomplete',
575 '#description' => t('Add an individual user to the electoral list'),
576 );
577
578 $form['electoral_list']['submit'] = array(
579 '#type' => 'submit',
580 '#value' => t('Modify electoral list'),
581 );
582
583 $form['electoral_list']['reset'] = array(
584 '#type' => 'button',
585 '#value' => t('Reset electoral list'),
586 );
587
588 $form['nid'] = array('#type' => 'hidden', '#value' => $nid);
589 return $form;
590 }
591
592
593 /**
594 * Remove an individual voter from the electoral list
595 */
596 function advpoll_remove_voter() {
597 $nid = arg(1);
598 $uid = arg(3);
599 if ($uid && $node = node_load($nid)) {
600 $result = db_query('SELECT name FROM {users} WHERE uid=%d', $uid);
601 if (db_num_rows($result) > 0) {
602 $user = db_fetch_object($result);
603 db_query('DELETE FROM {advpoll_electoral_list} WHERE nid=%d AND uid=%d',
604 $nid, $uid);
605 drupal_set_message(t('%user removed from the electoral list.', array('%user' => $user->name)));
606 }
607 else {
608 drupal_set_message(t('No user found with a uid of %uid.', array('%uid' => $uid)));
609 }
610
611 }
612 drupal_goto('node/' . $node->nid . '/electoral_list');
613 }
614
615 /**
616 * Validate changes to the electoral list
617 */
618 function advpoll_electoral_list_form_validate($form_id, $form_values) {
619 if ($form_values['op'] == t('Reset electoral list')) {
620 if (user_access('administer polls')) {
621 db_query('DELETE FROM {advpoll_electoral_list} WHERE nid=%d', $form_values['nid']);
622 drupal_set_message(t('Electoral list cleared.'));
623 return;
624 }
625 }
626
627 $add_user = $form_values['electoral_list']['add_user'];
628 if ($add_user) {
629 // Check that the user exists
630 $result = db_query('SELECT uid FROM {users} WHERE name="%s"', $add_user);
631 if (db_num_rows($result) == 0) {
632 form_set_error('electoral_list][add_user', t('User %user does not exist.', array('%user' => $add_user)));
633 return FALSE;
634 }
635 }
636 }
637
638 /**
639 * Submit changes to the electoral list
640 */
641 function advpoll_electoral_list_form_submit($form_id, $form_values) {
642 $add_user = $form_values['electoral_list']['add_user'];
643 if ($add_user) {
644 db_query('REPLACE INTO {advpoll_electoral_list} (nid, uid) SELECT "%d", u.uid FROM users u WHERE u.name = "%s"', $form_values['nid'], $add_user);
645 drupal_set_message(t('%user added to electoral list.', array('%user' => $add_user)));
646 }
647 }
648
649 /**
650 * Display the votes tab
651 */
652 function advpoll_tab_votes() {
653 if ($node = node_load(arg(1))) {
654 if (!$node->showvotes) {
655 // Advanced Poll is set to not allow viewing of votes
656 drupal_not_found();
657 return;
658 }
659 drupal_set_title(check_plain($node->title));
660 $output = t('This table lists all the recorded votes for this poll. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.');
661
662 $header[] = array('data' => t('Visitor'), 'field' => 'u.name');
663 $header[] = array('data' => t('Vote'), '');
664
665 $result = pager_query('SELECT v.value, v.uid, v.hostname, v.tag, u.name FROM {votingapi_vote} v LEFT JOIN {users} u ON v.uid = u.uid WHERE v.content_type="advpoll" AND v.content_id = %d' . tablesort_sql($header), 20, 0, NULL, $node->nid);
666 $rows = array();
667 while ($vote = db_fetch_object($result)) {
668 $key = $vote->uid? $vote->uid : $vote->hostname;
669 $rows[$key]['name'] = $vote->name ? theme('username', $vote) : check_plain($vote->hostname);
670 if ($node->type == 'advpoll_ranking') {
671 // Need two dimensional results (if equal rankings are allowed)
672 $rows[$key]['votes'][$vote->value][] = check_plain($node->choice[$vote->tag]['label']);
673 }
674 else {
675 // Just need one dimensional results
676 $rows[$key]['votes'][] = check_plain($node->choice[$vote->tag]['label']);
677 }
678 }
679
680 $separators = array('advpoll_ranking' => ' > ', 'advpoll_binary' => ', ');
681
682 // create strings out of each vote
683 $results = array();
684
685 foreach ($rows as $key => $container) {
686 $ranking = $container['votes'];
687 asort($ranking);
688 $rankings = array();
689 if ($node->type == 'advpoll_ranking') {
690 // Include support for multiple choices having the same ranking
691 foreach ($ranking as $vote => $choices) {
692 $rankings[$vote] = implode(' = ', $choices);
693 }
694 }
695 else {
696 // Just copy the previous array
697 $rankings = $ranking;
698 }
699 ksort($rankings);
700 $results[$key]['name'] = $rows[$key]['name'];
701 $results[$key]['vote'] = implode($separators[$node->type], $rankings);
702 }
703
704 $output .= theme('table', $header, $results);
705 $output .= theme('pager', NULL, 20, 0);
706 print theme('page', $output);
707 }
708 else {
709 drupal_not_found();
710 }
711 }
712
713
714 /**
715 * Helper function to abstract view results checking
716 */
717 function _advpoll_can_view_results($node) {
718 $view_results = variable_get('advpoll_view_results'
719 , ADVPOLL_DEFAULT_VIEW_RESULTS);
720 return (!$node->active // node is closed
721 || ($node->voted && $view_results == 'aftervote') // user voted
722 || ($view_results == 'always')); // all can view
723 }
724
725 /**
726 * Implementation of hook_node_info().
727 */
728 function advpoll_node_info() {
729 $modes = _advpoll_list_modes();
730 $info = array();
731 foreach ($modes as $mode) {
732 $info['advpoll_' . $mode['name']] = array(
733 'name' => t('Advanced Poll - ' . $mode['name']),
734 'module' => 'advpoll',
735 'description' => $mode['description'],
736 );
737 }
738 return $info;
739 }
740
741 /**
742 * Implementation of hook_perm().
743 */
744 function advpoll_perm() {
745 return array('create polls', 'delete polls', 'view polls', 'vote on polls', 'cancel own vote', 'administer polls', 'inspect all votes');
746 }
747
748 /**
749 * Settings page for Drupal 5.0
750 */
751 function advpoll_admin_settings() {
752 $enabled = array(0 => t('Disabled'), 1 => t('Enabled'));
753
754 $form['main']['advpoll_default_electoral_list'] = array(
755 '#type' => 'radios',
756 '#title' => t('Use electoral list by default'),
757 '#description' => t('Use an electoral list by default for new advpoll.'),
758 '#default_value' => variable_get('advpoll_default_electoral_list', ADVPOLL_DEFAULT_ELECTORAL_LIST),
759 '#options' => $enabled,
760 );
761
762 $view_results = array(
763 'always' => t('Always'),
764 'aftervote' => t('After user has voted'),
765 'afterclose' => t('After voting has closed'),
766 );
767
768 $form['main']['advpoll_view_results'] = array(
769 '#type' => 'radios',
770 '#title' => t('When should results be displayed'),
771 '#description' => t('Determines when users may view the results of the poll.'),
772 '#default_value' => variable_get('advpoll_view_results', ADVPOLL_DEFAULT_VIEW_RESULTS),
773 '#options' => $view_results,
774 );
775 return system_settings_form($form);
776 }
777
778
779 /**
780 * Helper function to display 'cancel vote' button if user has voted
781 */
782 function advpoll_cancel_form($nid) {
783 $form['#action'] = url("advpoll/cancel/$nid");
784 $form['submit'] = array('#type' => 'submit', '#value' => t('Cancel your vote'));
785 return $form;
786 }
787
788 /**
789 * Implementation of hook_update().
790 *
791 * This is called upon node edition.
792 */
793 function advpoll_update($node) {
794 if ($node->settings['usestart']) {
795 $node->settings['active'] = _advpoll_calculate_active($node);
796 }
797
798 db_query("UPDATE {advpoll} SET active=%d, runtime=%d, maxchoices=%d, algorithm='%s', uselist=%d, showvotes=%d, startdate=%s WHERE nid = %d",
799 $node->settings['active'], $node->settings['runtime'],
800 $node->settings['maxchoices'], $node->settings['algorithm'],
801 $node->settings['uselist'], $node->settings['showvotes'],
802 $node->settings['usestart']? _advpoll_create_startdate($node): 'NULL',
803 $node->nid);
804
805 _advpoll_insert_choices($node);
806 votingapi_recalculate_results('advpoll', $node->nid);
807 }
808
809
810 /**
811 * Determine if node should be active for _insert and _update operations
812 *
813 * This function is needed when changing the startdate and/or duration
814 */
815 function _advpoll_calculate_active($node) {
816 $startdate = _advpoll_create_startdate($node);
817 if ($node->settings['runtime']) {
818 // Check that startdate is in the past and that the duration hasn't elapsed
819 $active = time() >= $startdate
820 && time() < $node->settings['runtime'] + $startdate;
821 }
822 else {
823 // Just need to ensure that startdate is in the past
824 $active = time() >= $startdate;
825 }
826 return $active? 1 : 0;
827 }
828
829 /*
830 * Create a unix timestamp for startdate based on FormAPI inputs
831 */
832 function _advpoll_create_startdate($node) {
833 list($hour, $min) = explode(':', date('h:m', time()));
834 return mktime($hour, $min, 0,
835 $node->settings['startdate']['month'],
836 $node->settings['startdate']['day'],
837 $node->settings['startdate']['year']);
838 }
839
840 function _advpoll_insert_choices($node) {
841 db_query('DELETE FROM {advpoll_choices} WHERE nid = %d', $node->nid);
842 // Start at one rather than 0 due to Drupal FormAPI
843 $i = 1;
844 foreach ($_POST['choice'] as $choice) {
845 if ($choice['label'] != '') {
846 db_query("INSERT INTO {advpoll_choices} (nid, label, vote_offset) VALUES (%d, '%s', %d)", $node->nid, check_plain($choice['label']), $i++);
847 }
848 }
849 }
850
851 function _advpoll_get_mode($node) {
852 if ($node->type) {
853 $types = explode('_', $node->type, 2);
854 return $types[1];
855 }
856 else {
857 drupal_set_message(t('No type specified for node %nid', array('%nid' => $node->nid)), 'error');
858 return '';
859 }
860 }
861
862 /**
863 * Implementation of hook_insert()
864 *
865 * This is called upon node creation
866 */
867 function advpoll_insert($node) {
868 $mode = _advpoll_get_mode($node);
869 if ($node->settings['usestart']) {
870 $node->settings['active'] = _advpoll_calculate_active($node);
871 }
872
873 db_query('INSERT INTO {advpoll} (nid, mode, uselist, active, runtime, '
874 . 'maxchoices, algorithm, showvotes, startdate)'
875 . ' VALUES (%d, "%s", %d, %d, %d, %d, "%s", %d, %s)',
876 $node->nid, $mode, $node->settings['uselist'], $node->settings['active'],
877 $node->settings['runtime'], $node->settings['maxchoices'],
878 $node->settings['algorithm'], $node->settings['showvotes'],
879 $node->settings['usestart']? _advpoll_create_startdate($node): 'NULL');
880
881 // create the electoral list if desired
882
883 if ($node->settings['uselist']) {
884 // Check if authenticated users have the right to vote, because authenticated users are not added to the users_roles permission, probably for performance reasons
885 $result = db_fetch_object(db_query("SELECT COUNT(*) AS hit FROM {permission} JOIN role ON role.rid = permission.rid WHERE FIND_IN_SET(' vote on advpoll', perm) AND role.name = 'authenticated user'"));
886 if ($result->hit) {
887 // Special case: any authenticated user can vote
888 // Add all current users to electoral list
889 db_query("INSERT INTO {advpoll_electoral_list} (nid, uid) SELECT '%d', u.uid FROM users u WHERE u.uid != 0", $node->nid);
890 }
891 else {
892 // All users must not be allowed to vote, add relevant users only
893 db_query("INSERT INTO {advpoll_electoral_list} (nid, uid) SELECT '%d', u.uid FROM users_roles u, permission p WHERE FIND_IN_SET(' view advpoll', p.perm) AND u.rid = p.rid AND u.uid != 0", $node->nid);
894 }
895 }
896
897 // Insert the choices
898 _advpoll_insert_choices($node);
899 }
900
901 /**
902 * Callback to display a reset votes confirmation form
903 */
904 function advpoll_reset_confirm() {
905 $edit = $_POST['edit'];
906 $edit['nid'] = $edit['nid']? $edit['nid'] : arg(1);
907 $node = node_load($edit['nid']);
908
909 $form['nid'] = array('#type' => 'value', '#value' => $node->nid);
910 $output = confirm_form($form,
911 t('Are you sure you want to reset the votes for %title?',
912 array('%title' => $node->title)),
913 'node/' . $node->nid . '/edit',
914 t('This action cannot be undone.'),
915 t('Reset votes'),
916 t('Cancel'));
917 return $output;
918 }
919
920 /**
921 * Reset votes once the confirmation is given
922 */
923 function advpoll_reset_confirm_submit($form_id, $form_values) {
924 if ($form_values['confirm']) {
925 if ($node = node_load(array('nid' => arg(1)))) {
926 // Delete any votes for the poll
927 db_query('DELETE FROM {votingapi_vote} WHERE content_type="advpoll" AND content_id = %d', $node->nid);
928 drupal_set_message('Votes have been reset.');
929 drupal_goto('node/' . $node->nid . '/edit');
930 }
931 }
932
933 return '';
934 }
935
936 /**
937 * Implementation of hook_validate().
938 */
939 function advpoll_validate(&$node) {
940 if ($_POST['op'] == t('Reset votes')) {
941 drupal_goto('node/' . $node->nid . '/reset');
942 }
943 else {
944 // Use form_set_error for any errors
945 $node->choice = array_values($node->choice);
946
947 // Start keys at 1 rather than 0
948 array_unshift($node->choice, '');
949 unset($node->choice[0]);
950
951 // Check for at least two choices
952 $realchoices = 0;
953 foreach ($node->choice as $i => $choice) {
954 if ($choice['label'] != '') {
955 $realchoices++;
956 }
957 }
958
959 if ($realchoices < 2) {
960 form_set_error("choice][$realchoices][label", t('You must fill in at least two choices.'));
961 }
962
963 // Validate maxchoices since it has #DANGEROUS_SKIP_CHECK set to true
964
965 if ($node->settings['maxchoices'] < 0) {
966 form_set_error('settings][maxchoices]', t('Maximum choices must be a non-negative integer.'));
967 }
968
969 if ($node->settings['maxchoices'] > count($node->choice)) {
970 form_set_error('settings][maxchoices]', t('Maximum choices cannot be larger than the number of choices submitted.'));
971 }
972 }
973 }
974
975 function advpoll_submit(&$node) {
976 $node->choice = array_values($node->choice);
977 // Start keys at 1 rather than 0
978 array_unshift($node->choice, '');
979 unset($node->choice[0]);
980 }
981
982 /**
983 * Implementation of hook_view().
984 */
985 function advpoll_view($node, $teaser = FALSE, $page = FALSE, $block = FALSE) {
986 $mode = _advpoll_get_mode($node);
987
988 if ($block) {
989 // No 'readmore' link
990 $node->readmore = FALSE;
991 //$links = module_invoke_all('links', 'node', $node, 1);
992 $links[] = array(
993 'title' => 'Older polls',
994 'href' => 'polls',
995 'attributes' => array('title' =>
996 t('View the list of polls on this site.')),
997 );
998 $node->links = $links;
999 }
1000
1001 // Previewing a node, so don't show results
1002 if ($node->in_preview) {
1003 // Show the voting form but don't let them vote
1004 $node->content['body'] = array(
1005 '#value' => drupal_get_form('advpoll_voting_' . $mode . '_form', $node, $teaser, $page, $block),
1006 );
1007 }
1008 else if (!$node->voted && arg(2) != 'results' && $node->active && advpoll_eligible($node)) {
1009 // User hasn't voted and we're not on the results tab
1010
1011 if ($block) {
1012 $node->links[] = array(
1013 'title' => t('Results'),
1014 'href' => 'node/'. $node->nid .'/results',
1015 'attributes' => array('title' => t('View the current poll results.')),
1016 );
1017 }
1018 $node->content['body'] = array(
1019 '#value' => drupal_get_form('advpoll_voting_' . $node->mode . '_form',
1020 $node, $teaser, $page, $block)
1021 //. advpoll_view_electoral_list($node, $teaser),
1022 );
1023 static $addjs = TRUE;
1024 if ($addjs) {
1025 // Add javascript for posting voting forms with ajax
1026 drupal_add_js(drupal_get_path('module', 'advpoll') .'/advpoll-vote.js', 'module');
1027 drupal_add_js(drupal_get_path('module', 'advpoll') .'/jquery.form.js', 'module');
1028 $addjs = FALSE;
1029 }
1030 }
1031 else {
1032 // Show results only if the user has voted or poll is closed
1033 $node->content['body'] = array(
1034 '#value' => advpoll_view_results($node, $teaser, $page, $block)
1035 );
1036 }
1037
1038 return $node;
1039 }
1040
1041 /**
1042 * Currently deprecated
1043 */
1044 function advpoll_view_electoral_list($node, $teaser = FALSE) {
1045 $output = '';
1046 if ($node->uselist) {
1047 $result = db_query("SELECT COUNT(*) AS voters FROM {advpoll_electoral_list} WHERE nid=%d", $node->nid);
1048 $electoral_list = db_fetch_object($result);
1049 }
1050 $result = db_query("SELECT COUNT(DISTINCT uid) AS voters FROM {votingapi_vote} WHERE content_id=%d GROUP BY uid", $node->nid);
1051 $votes = db_num_rows($result);
1052
1053 $output = '<div class="advpoll-electoral-list">';
1054
1055 $output .= t('Total votes: %d', array('%d' => $votes));
1056 if ($node->uselist) {
1057 $output .= t(' (out of %v eligible voter' .
1058 ($electoral_list->voters == 1? '' : 's') . ')',
1059 array('%v' => $electoral_list->voters));
1060 }
1061 $output .= '</div>';
1062 return $output;
1063 }
1064
1065 /**
1066 * Render the voting form.
1067 */
1068 function theme_advpoll_view_voting($form) {
1069 $output .= '<div class="advpoll">';
1070 $output .= ' <div class="choice-form">';
1071 $output .= ' <div class="choices">';
1072 $output .= drupal_render($form['choice']);
1073 $output .= ' </div>';
1074 $output .= drupal_render($form['nid']);
1075 $output .= drupal_render($form['vote']);
1076 $output .= ' </div>';
1077 $output .= drupal_render($form);
1078 $output .= '</div>';
1079 return $output;
1080 }
1081
1082 function theme_advpoll_results($title, $results, $votes, $links, $block, $nid, $voted, $cancel_vote) {
1083 if ($block) {
1084 $output .= '<div class="poll">';
1085 $output .= '<div class="title">'. $title .'</div>';
1086 $output .= $results;
1087 $output .= '<div class="total">'. t('Total votes: %votes', array('%votes' => $votes)) .'</div>';
1088 $output .= '</div>';
1089 $output .= '<div class="links">'. theme('links', $links) .'</div>';
1090 }
1091 else {
1092 $output .= '<div class="poll">';
1093 $output .= $results;
1094 $output .= '<div class="total">'. t('Total votes: %votes', array('%votes' => $votes)) .'</div>';
1095 $output .= '</div>';
1096 }
1097 return $output;
1098 }
1099
1100 function _advpoll_show_cancel_form($node, $block) {
1101 $output = '';
1102 if ($node->voted && $node->cancel_vote && user_access('cancel own vote')
1103 && !$block) {
1104 $output .= drupal_get_form('advpoll_cancel_form', $node->nid);
1105 }
1106 return $output;
1107 }
1108
1109 function theme_advpoll_bar($title, $percentage, $votes, $block) {
1110 if ($block) {
1111 $output = '<div class="text">'. $title .'</div>';
1112 $output .= '<div class="bar"><div style="width: '. $percentage .'%;" class="foreground"></div></div>';
1113 $output .= '<div class="percent">'. $percentage .'%</div>';
1114 }
1115 else {
1116 $output = '<div class="text">'. $title .'</div>';
1117 $output .= '<div class="bar"><div style="width: '. $percentage .'%;" class="foreground"></div></div>';
1118 $output .=