Parent Directory
|
Revision Log
|
Revision Graph
#574178 by wonder95 - add option to display options in random order
| 1 | <?php |
| 2 | |
| 3 | /** |
| 4 | * @file |
| 5 | * |
| 6 | * Modular voting mechanisms, delegatable votes, taxonomy/category |
| 7 | * influenced controls and weighted voting |
| 8 | * |
| 9 | * See http://decisions.gnuvernment.org for more information on the project. |
| 10 | * |
| 11 | * Heavily inspired by other Drupal modules, mostly from poll.module, |
| 12 | * but we adapted it to "drupal forms api". Thanks to everyone for |
| 13 | * all the that was already written. (...and debugged!) |
| 14 | */ |
| 15 | |
| 16 | // $Id: decisions.module,v 1.221 2009/08/19 22:34:36 anarcat Exp $ |
| 17 | |
| 18 | define('DECISIONS_DEFAULT_ELECTORAL_LIST', 0); |
| 19 | // always, aftervote, or afterclose |
| 20 | define('DECISIONS_DEFAULT_VIEW_RESULTS', 'aftervote'); |
| 21 | define('DECISIONS_RUNTIME_INFINITY', 0); |
| 22 | |
| 23 | |
| 24 | /** |
| 25 | * hook_init() implementation |
| 26 | * |
| 27 | * Most of the stuff here is in subfiles now. |
| 28 | * |
| 29 | * Decision modes are in seperate modules in modes/*.module |
| 30 | */ |
| 31 | function decisions_init() { |
| 32 | // extension files are included here in order to lighten Drupal bootstrap |
| 33 | drupal_add_css(drupal_get_path('module', 'poll') .'/poll.css'); |
| 34 | } |
| 35 | |
| 36 | /** |
| 37 | * Implementation of hook_access(). |
| 38 | */ |
| 39 | function decisions_access($op, $node, $account) { |
| 40 | if ($op == 'create') { |
| 41 | return user_access('create decisions', $account); |
| 42 | } |
| 43 | if ($op == 'delete') { |
| 44 | return user_access('delete decisions', $account); |
| 45 | } |
| 46 | if ($op == 'update') { |
| 47 | /* you can update it if you can create it, provided it is your own... */ |
| 48 | if (user_access('create decisions', $account) && ($account->uid == $node->uid)) { |
| 49 | return TRUE; |
| 50 | } |
| 51 | } |
| 52 | } |
| 53 | |
| 54 | /** |
| 55 | * Implementation of hook_block(). |
| 56 | */ |
| 57 | function decisions_block($op = 'list', $delta = 'mostrecent', $edit = array()) { |
| 58 | if ($op == 'list') { |
| 59 | $block = array('mostrecent' => array('info' => t('Decisions - Newest'))); |
| 60 | } |
| 61 | elseif ($op == 'view') { |
| 62 | if (user_access('view decisions')) { |
| 63 | switch ($delta) { |
| 64 | case 'mostrecent': |
| 65 | $block = array('subject' => t('Decisions - Newest'), 'content' => _decisions_block_mostrecent()); |
| 66 | break; |
| 67 | default: |
| 68 | $block = array(); |
| 69 | break; |
| 70 | } |
| 71 | } |
| 72 | } |
| 73 | return $block; |
| 74 | } |
| 75 | |
| 76 | /** |
| 77 | * Implementation of hook_cron(). |
| 78 | * |
| 79 | * Closes decisions that have exceeded their allowed runtime. |
| 80 | */ |
| 81 | function decisions_cron() { |
| 82 | $result = db_query('SELECT d.nid FROM {decisions} d INNER JOIN {node} n ON d.nid = n.nid WHERE (d.startdate + d.runtime) < '. time() .' AND d.active = 1 AND d.runtime <> 0'); |
| 83 | while ($decision = db_fetch_object($result)) { |
| 84 | db_query("UPDATE {decisions} SET active = 0 WHERE nid=%d", $decision->nid); |
| 85 | } |
| 86 | } |
| 87 | |
| 88 | /** |
| 89 | * Implementation of votingapi_hook_calculate() |
| 90 | */ |
| 91 | function decisions_votingapi_calculate(&$cache, $votes, $content_type, $content_id) { |
| 92 | if ($content_type == 'decisions') { |
| 93 | $node = node_load($content_id); |
| 94 | $mode = _decisions_get_mode($node); |
| 95 | $function = "{$mode}_decisions_votingapi_calculate"; |
| 96 | if (function_exists($function)) { |
| 97 | return call_user_func($function, $node, $cache, $votes, $content_type, $content_id); |
| 98 | } |
| 99 | } |
| 100 | } |
| 101 | |
| 102 | /** |
| 103 | * Implementation of hook_help(). |
| 104 | */ |
| 105 | function decisions_help($path, $arg) { |
| 106 | switch ($path) { |
| 107 | case 'admin/modules#description': |
| 108 | return t('Allow people to reproduce and surpass the kinds of decision-making instances that exist in face-to-face meetings.'); |
| 109 | } |
| 110 | } |
| 111 | |
| 112 | /** |
| 113 | * Implementation of hook_menu(). |
| 114 | * |
| 115 | * Just a path for creating new decisions for now, but we could |
| 116 | * eventually have a 'my decisions' and 'view decisions' kind of |
| 117 | * page. (TODO) |
| 118 | */ |
| 119 | function decisions_menu() { |
| 120 | $items['admin/settings/decisions'] = array( |
| 121 | 'title' => 'Configure decisions', |
| 122 | 'description' => 'Configure Decisions', |
| 123 | 'page callback' => 'drupal_get_form', |
| 124 | 'page arguments' => array('decisions_admin'), |
| 125 | 'access arguments' => array('administer decisions'), |
| 126 | 'type' => MENU_NORMAL_ITEM, |
| 127 | ); |
| 128 | |
| 129 | $items['node/%node/votes'] = array( |
| 130 | 'title' => 'Votes', |
| 131 | 'page callback' => 'decisions_votes_tab', |
| 132 | 'page arguments' => array(1), |
| 133 | 'access callback' => '_decisions_votes_access', |
| 134 | 'access arguments' => array(1, 'inspect all votes'), |
| 135 | 'weight' => 4, |
| 136 | 'type' => MENU_LOCAL_TASK |
| 137 | ); |
| 138 | $items['node/%node/results'] = array( |
| 139 | 'title' => 'Results', |
| 140 | 'page callback' => 'decisions_results', |
| 141 | 'page arguments' => array(1), |
| 142 | 'access callback' => '_decisions_can_view_results', |
| 143 | 'access arguments' => array(1), |
| 144 | 'weight' => 1, |
| 145 | 'type' => MENU_LOCAL_TASK); |
| 146 | $items['node/%node/electoral_list'] = array( |
| 147 | 'title' => 'Electoral list', |
| 148 | 'page callback' => 'decisions_electoral_list_tab', |
| 149 | 'page arguments' => array(1), |
| 150 | 'access callback' => '_decisions_electoral_list_access', |
| 151 | 'access arguments' => array(1, 'view electoral list'), |
| 152 | 'weight' => 2, |
| 153 | 'type' => MENU_LOCAL_TASK |
| 154 | ); |
| 155 | // Allow voters to be removed |
| 156 | $items['node/%node/remove'] = array( |
| 157 | 'page callback' => 'decisions_electoral_list_remove_voter', |
| 158 | 'page arguments' => array(1, 3), |
| 159 | 'access callback' => '_decisions_electoral_list_access', |
| 160 | 'access arguments' => array(1, 'remove voters'), |
| 161 | 'weight' => 3, |
| 162 | 'type' => MENU_CALLBACK, |
| 163 | ); |
| 164 | $items['node/%node/reset'] = array( |
| 165 | 'title' => 'Reset votes', |
| 166 | 'page callback' => 'drupal_get_form', |
| 167 | 'page arguments' => array('decisions_reset_form', 1), |
| 168 | 'access callback' => '_decisions_reset_access', |
| 169 | 'access arguments' => array(1, 'administer decisions'), |
| 170 | 'weight' => 3, |
| 171 | 'type' => MENU_LOCAL_TASK, |
| 172 | ); |
| 173 | $items['decisions/add_choices_js'] = array( |
| 174 | 'page callback' => 'decisions_add_choices_js', |
| 175 | 'type' => MENU_CALLBACK, |
| 176 | 'access arguments' => array('create decisions'), |
| 177 | ); |
| 178 | |
| 179 | return $items; |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Implementation of hook_perm(). |
| 184 | */ |
| 185 | function decisions_perm() { |
| 186 | return array('create decisions', 'delete decisions', 'view decisions', 'vote on decisions', 'cancel own vote', 'administer decisions', 'inspect all votes', 'view electoral list', 'remove voters'); |
| 187 | } |
| 188 | |
| 189 | /** |
| 190 | * Implementation of the admin_settings hook |
| 191 | */ |
| 192 | function decisions_admin() { |
| 193 | |
| 194 | $enabled = array(0 => t('Disabled'), 1 => t('Enabled')); |
| 195 | |
| 196 | $form['main']['decisions_default_electoral_list'] = array( |
| 197 | '#type' => 'radios', |
| 198 | '#title' => t('Use electoral list by default'), |
| 199 | '#description' => t('Use an electoral list by default for new decisions.'), |
| 200 | '#default_value' => variable_get('decisions_default_electoral_list', DECISIONS_DEFAULT_ELECTORAL_LIST), |
| 201 | '#options' => $enabled, |
| 202 | ); |
| 203 | |
| 204 | $view_results = array( |
| 205 | 'always' => t('Always'), |
| 206 | 'aftervote' => t('After user has voted'), |
| 207 | 'afterclose' => t('After voting has closed'), |
| 208 | ); |
| 209 | |
| 210 | $form['main']['decisions_view_results'] = array( |
| 211 | '#type' => 'radios', |
| 212 | '#title' => t('When should results be displayed'), |
| 213 | '#description' => t('Determines when users may view the results of the decision.'), |
| 214 | '#default_value' => variable_get('decisions_view_results', DECISIONS_DEFAULT_VIEW_RESULTS), |
| 215 | '#options' => $view_results, |
| 216 | ); |
| 217 | |
| 218 | return system_settings_form($form); |
| 219 | } |
| 220 | |
| 221 | function decisions_cancel_form($form_state, $nid) { |
| 222 | $form['node'] = array('#type' => 'hidden', '#value' => $nid); |
| 223 | $form['submit'] = array('#type' => 'submit', '#value' => t('Cancel your vote')); |
| 224 | return $form; |
| 225 | } |
| 226 | |
| 227 | function decisions_cancel_form_submit($form, &$form_state) { |
| 228 | decisions_cancel($form_state['values']['node']); |
| 229 | } |
| 230 | |
| 231 | /*******************/ |
| 232 | /* Theme functions */ |
| 233 | /*******************/ |
| 234 | |
| 235 | function decisions_theme($existing, $type, $theme, $path) { |
| 236 | return array( |
| 237 | 'decisions_view_header' => array('arguments' => array('node' => NULL, 'teaser' => FALSE)), |
| 238 | 'decisions_view_voting' => array('arguments' => array('form' => NULL)), |
| 239 | 'decisions_bar' => array('arguments' => array('title' => NULL, 'percentage' => NULL, 'votes' => NULL)), |
| 240 | 'decisions_status' => array('arguments' => array('message' => NULL)), |
| 241 | 'decisions_morechoices' => array('arguments' => array(), 'decisions_morechoices' => NULL), |
| 242 | 'decisions_view_own_result' => array(), |
| 243 | ); |
| 244 | } |
| 245 | |
| 246 | function theme_decisions_view_own_result() { |
| 247 | $output = '<div class="decisions-own-result">' . t("Your vote has been recorded.") . '</div>'; |
| 248 | return $output; |
| 249 | } |
| 250 | |
| 251 | /** |
| 252 | * Theme stub for rendering ecisions header (contains dates and quorum informations). |
| 253 | */ |
| 254 | function theme_decisions_view_header($node, $teaser = FALSE) { |
| 255 | |
| 256 | $output = '<div class="decisions-header">'; |
| 257 | |
| 258 | // dates |
| 259 | $output .= '<div class="decisions-dates">'; |
| 260 | $output .= theme('item_list', |
| 261 | array( |
| 262 | t('Current date: @date', array('@date' => format_date(time()))), |
| 263 | t('Opening date: @date', array('@date' => format_date($node->startdate))), |
| 264 | ($node->runtime == DECISIONS_RUNTIME_INFINITY ? |
| 265 | t('No closing date.') : |
| 266 | t('Closing date: @date', array('@date' => format_date($node->startdate + $node->runtime)))))); |
| 267 | $output .= '</div>'; |
| 268 | |
| 269 | // votes |
| 270 | $num_eligible_voters = _decisions_count_eligible($node); |
| 271 | $num_voters = _decisions_count_voters($node); |
| 272 | $output .= '<div class="decisions-votes">'; |
| 273 | $output .= t('@num-voters out of @num-voters-eligible eligible @voters cast their ballot', |
| 274 | array( |
| 275 | '@num-voters' => $num_voters, |
| 276 | '@num-voters-eligible' => $num_eligible_voters, |
| 277 | '@voters' => format_plural($num_eligible_voters, 'voter', 'voters') |
| 278 | ) |
| 279 | ); |
| 280 | $output .= '</div>'; |
| 281 | |
| 282 | // quorum |
| 283 | $quorum = _decisions_get_quorum($node); |
| 284 | if ($quorum > 0) { |
| 285 | $output .= '<div class="decisions-quorum">'; |
| 286 | $output .= t('Quorum: @d', array('@d' => $quorum)); |
| 287 | $output .= '</div>'; |
| 288 | } |
| 289 | |
| 290 | $output .= '</div>'; |
| 291 | return $output; |
| 292 | } |
| 293 | |
| 294 | /** |
| 295 | * Theme stub for redering the voting form, to allow the chance for |
| 296 | * themes to make this nicer/different |
| 297 | */ |
| 298 | function theme_decisions_view_voting($form) { |
| 299 | |
| 300 | $render = 'drupal_render'; |
| 301 | if (!function_exists($render)) { |
| 302 | $render = 'form_render'; |
| 303 | } |
| 304 | $output .= '<div class="decisions">'; |
| 305 | $output .= ' <div class="choice-form">'; |
| 306 | $output .= ' <div class="choices">'; |
| 307 | $output .= $render($form['choice']); |
| 308 | $output .= ' </div>'; |
| 309 | $output .= $render($form['nid']); |
| 310 | $output .= $render($form['vote']); |
| 311 | $output .= ' </div>'; |
| 312 | $output .= $render($form); |
| 313 | $output .= '</div>'; |
| 314 | return $output; |
| 315 | } |
| 316 | |
| 317 | /** |
| 318 | * Theme stub for a decisions bar. |
| 319 | */ |
| 320 | function theme_decisions_bar($title, $percentage, $votes) { |
| 321 | $output = '<div class="text">'. $title .'</div>'; |
| 322 | $output .= '<div class="bar"><div style="width: '. $percentage .'%;" class="foreground"></div></div>'; |
| 323 | $output .= '<div class="percent">'. $percentage .'% ('. $votes .')</div>'; |
| 324 | return $output; |
| 325 | } |
| 326 | |
| 327 | /** |
| 328 | * Outputs a status line. |
| 329 | */ |
| 330 | function theme_decisions_status($message) { |
| 331 | return '<div class="error">'. $message .'</div>'; |
| 332 | } |
| 333 | |
| 334 | |
| 335 | /****************************/ |
| 336 | /* Electoral list functions */ |
| 337 | /****************************/ |
| 338 | |
| 339 | /** |
| 340 | * Creates the form for the electoral list. |
| 341 | */ |
| 342 | function decisions_electoral_list_form($form_state, $nid) { |
| 343 | $form = array(); |
| 344 | $form['electoral_list'] = array( |
| 345 | '#type' => 'fieldset', |
| 346 | '#tree' => TRUE, |
| 347 | '#title' => t('Administer electoral list'), |
| 348 | '#collapsible' => TRUE, |
| 349 | '#weight' => 2, |
| 350 | '#collapsed' => TRUE, |
| 351 | ); |
| 352 | |
| 353 | $form['electoral_list']['add_user'] = array( |
| 354 | '#type' => 'textfield', |
| 355 | '#title' => t('Add user'), |
| 356 | '#size' => 40, |
| 357 | '#autocomplete_path' => 'user/autocomplete', |
| 358 | '#description' => t('Add an individual user to the electoral list'), |
| 359 | ); |
| 360 | |
| 361 | $form['electoral_list']['submit'] = array( |
| 362 | '#type' => 'submit', |
| 363 | '#value' => t('Modify electoral list'), |
| 364 | ); |
| 365 | |
| 366 | $form['electoral_list']['reset'] = array( |
| 367 | '#type' => 'button', |
| 368 | '#value' => t('Reset electoral list'), |
| 369 | ); |
| 370 | |
| 371 | $form['nid'] = array('#type' => 'hidden', '#value' => $nid); |
| 372 | return $form; |
| 373 | |
| 374 | } |
| 375 | |
| 376 | /** |
| 377 | * Outputs the electoral list tab. |
| 378 | */ |
| 379 | function decisions_electoral_list_tab() { |
| 380 | if ($node = menu_get_object()) { |
| 381 | $output = ""; |
| 382 | if (!$node->uselist) { |
| 383 | drupal_not_found(); |
| 384 | return; |
| 385 | } |
| 386 | drupal_set_title(check_plain($node->title)); |
| 387 | if (user_access('administer decisions')) { |
| 388 | $form['electoral_list'] = array( |
| 389 | '#type' => 'fieldset', |
| 390 | '#tree' => TRUE, |
| 391 | '#title' => t('Administer electoral list'), |
| 392 | '#collapsible' => TRUE, |
| 393 | '#weight' => 2, |
| 394 | '#collapsed' => TRUE, |
| 395 | ); |
| 396 | |
| 397 | $form['electoral_list']['add_user'] = array( |
| 398 | '#type' => 'textfield', |
| 399 | '#title' => t('Add user'), |
| 400 | '#size' => 40, |
| 401 | '#autocomplete_path' => 'user/autocomplete', |
| 402 | '#description' => t('Add an individual user to the electoral list'), |
| 403 | ); |
| 404 | |
| 405 | $form['electoral_list']['submit'] = array( |
| 406 | '#type' => 'submit', |
| 407 | '#value' => t('Modify electoral list'), |
| 408 | ); |
| 409 | |
| 410 | $form['electoral_list']['reset'] = array( |
| 411 | '#type' => 'button', |
| 412 | '#value' => t('Reset electoral list'), |
| 413 | ); |
| 414 | |
| 415 | $form['nid'] = array('#type' => 'hidden', '#value' => $node->nid); |
| 416 | $output .= drupal_get_form('decisions_electoral_list_form', $node->nid); |
| 417 | } |
| 418 | $output .= t('This table lists all the eligible voters for this Decision.'); |
| 419 | |
| 420 | $header[] = array('data' => t('Voter'), 'field' => 'u.name'); |
| 421 | |
| 422 | $result = pager_query("SELECT u.uid, u.name FROM {decisions_electoral_list} el LEFT JOIN {users} u ON el.uid = u.uid WHERE el.nid = %d" . tablesort_sql($header), 20, 0, NULL, $node->nid); |
| 423 | $eligible_voters = array(); |
| 424 | while ($voter = db_fetch_object($result)) { |
| 425 | $temp = array(theme('username', $voter)); |
| 426 | |
| 427 | if (user_access('administer decisions')) { |
| 428 | $temp[] = l(t('remove'), 'node/'. $node->nid .'/remove/'. $voter->uid); |
| 429 | } |
| 430 | |
| 431 | $eligible_voters[] = $temp; |
| 432 | } |
| 433 | $output .= theme('table', $header, $eligible_voters); |
| 434 | $output .= theme('pager', NULL, 20, 0); |
| 435 | print theme('page', $output); |
| 436 | } |
| 437 | else { |
| 438 | drupal_not_found(); |
| 439 | } |
| 440 | } |
| 441 | |
| 442 | /** |
| 443 | * Remove an individual voter from the electoral list |
| 444 | */ |
| 445 | function decisions_electoral_list_remove_voter($node, $uid) { |
| 446 | # XXX: useless SELECT call |
| 447 | $result = db_query('SELECT name FROM {users} WHERE uid=%d', $uid); |
| 448 | if ($user = db_fetch_object($result)) { |
| 449 | db_query('DELETE FROM {decisions_electoral_list} WHERE nid=%d AND uid=%d', $node->nid, $uid); |
| 450 | drupal_set_message(t('%user removed from the electoral list.', array('%user' => $user->name))); |
| 451 | } |
| 452 | else { |
| 453 | drupal_set_message(t('No user found with a uid of %uid.', array('%uid' => $uid))); |
| 454 | } |
| 455 | |
| 456 | drupal_goto('node/'. $node->nid .'/electoral_list'); |
| 457 | } |
| 458 | |
| 459 | /** |
| 460 | * Validate changes to the electoral list |
| 461 | */ |
| 462 | function decisions_electoral_list_form_validate($form, &$form_state) { |
| 463 | if ($form_state['values']['op'] == t('Reset electoral list')) { |
| 464 | if (user_access('administer decisions')) { |
| 465 | db_query('DELETE FROM {decisions_electoral_list} WHERE nid=%d', $form_state['values']['nid']); |
| 466 | drupal_set_message(t('Electoral list cleared.')); |
| 467 | $node = menu_get_object(); |
| 468 | if (_decisions_electoral_list_reset($node)) { |
| 469 | drupal_set_message(t('Electoral list reset.')); |
| 470 | } |
| 471 | return; |
| 472 | } |
| 473 | } |
| 474 | $add_user = $form_state['values']['electoral_list']['add_user']; |
| 475 | if ($add_user) { |
| 476 | // Check that the user exists |
| 477 | if (!db_fetch_object(db_query('SELECT uid FROM {users} WHERE name="%s"', $add_user))) { |
| 478 | form_set_error('electoral_list][add_user', t('User %user does not exist.', array('%user' => $add_user))); |
| 479 | return FALSE; |
| 480 | } |
| 481 | } |
| 482 | else { |
| 483 | form_set_error('electoral_list][add_user', t('Please enter a user.')); |
| 484 | return FALSE; |
| 485 | } |
| 486 | } |
| 487 | |
| 488 | /** |
| 489 | * Submit changes to the electoral list |
| 490 | */ |
| 491 | function decisions_electoral_list_form_submit($form, &$form_state) { |
| 492 | $add_user = $form_state['values']['electoral_list']['add_user']; |
| 493 | $nid = $form_state['values']['nid']; |
| 494 | if ($add_user) { |
| 495 | db_query('REPLACE INTO {decisions_electoral_list} (nid, uid) SELECT "%d", u.uid FROM {users} u WHERE u.name = "%s"', $nid, $add_user); |
| 496 | drupal_set_message(t('%user added to electoral list.', array('%user' => $add_user))); |
| 497 | drupal_goto('node/'. $nid .'/electoral_list'); |
| 498 | } |
| 499 | else { |
| 500 | drupal_not_found(); |
| 501 | } |
| 502 | } |
| 503 | |
| 504 | |
| 505 | /***********************************/ |
| 506 | /* Decision-mode related functions */ |
| 507 | /***********************************/ |
| 508 | |
| 509 | /** |
| 510 | * Show results of the vote. |
| 511 | * |
| 512 | * This calls the appropriate vote results function, depending on the |
| 513 | * mode. It will call the decisions_view_results_$mode hook. |
| 514 | */ |
| 515 | function decisions_view_results(&$node, $teaser, $page) { |
| 516 | $mode = _decisions_get_mode($node); |
| 517 | $function = "{$mode}_decisions_view_results"; |
| 518 | if (function_exists($function)) { |
| 519 | return call_user_func($function, $node, $teaser, $page); |
| 520 | } |
| 521 | else { |
| 522 | _decisions_panic_on_mode($mode, __FUNCTION__); |
| 523 | } |
| 524 | } |
| 525 | |
| 526 | /** |
| 527 | * View the voting form. |
| 528 | * |
| 529 | * This calls a function decisions_vote_$mode, where $mode is defined |
| 530 | * in the node. If the function does not exist, a watchdog error is |
| 531 | * raised and the error is reported using drupal_set_message(). |
| 532 | * |
| 533 | * This also takes care of registering new votes, if the vote button |
| 534 | * has been pressed. |
| 535 | */ |
| 536 | function decisions_voting_form($form, &$node, $teaser = FALSE, $page = FALSE) { |
| 537 | $mode = _decisions_get_mode($node); |
| 538 | if (function_exists("{$mode}_decisions_voting_form")) { |
| 539 | return call_user_func("{$mode}_decisions_voting_form", $node, $teaser, $page); |
| 540 | } |
| 541 | else { |
| 542 | _decisions_panic_on_mode($mode, __FUNCTION__); |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | function decisions_voting_form_submit($form, &$form_state) { |
| 547 | $node = node_load($form_state['values']['nid']); |
| 548 | decisions_vote($node, $form_state['values']); |
| 549 | drupal_set_message(t('Your vote was registered.')); |
| 550 | // Transferring makes the results tab display correctly |
| 551 | drupal_goto('node/'. $node->nid); |
| 552 | } |
| 553 | |
| 554 | /** |
| 555 | * Validate vote form submission |
| 556 | * |
| 557 | * This will call a hook named decisions_vote_validate_$mode and |
| 558 | * return its value. hooks should check $POST to see if the vote data |
| 559 | * submitted is valid and use form_set_error() if the form has invalid |
| 560 | * data. |
| 561 | * |
| 562 | * @returns boolean true if form has valid data or if no hook is |
| 563 | * defined in mode |
| 564 | */ |
| 565 | function decisions_voting_form_validate($form, &$form_state) { |
| 566 | $node = node_load($form_state['values']['nid']); |
| 567 | $mode = _decisions_get_mode($node); |
| 568 | if (function_exists("{$mode}_decisions_vote_validate") ) { |
| 569 | return call_user_func("{$mode}_decisions_vote_validate", $node, $form_state['values']); |
| 570 | } |
| 571 | return TRUE; |
| 572 | } |
| 573 | |
| 574 | /** |
| 575 | * Record a vote on the node. |
| 576 | * |
| 577 | * This calls the appropriate vote recording function, depending on |
| 578 | * the mode. It will call the decisions_vote_$mode hook. |
| 579 | */ |
| 580 | function decisions_vote($node, $form_values) { |
| 581 | $mode = _decisions_get_mode($node); |
| 582 | $ok = FALSE; // error by default |
| 583 | if (_decisions_eligible($node)) { |
| 584 | if (function_exists("{$mode}_decisions_vote")) { |
| 585 | call_user_func("{$mode}_decisions_vote", $node, $form_values); |
| 586 | } |
| 587 | else { |
| 588 | _decisions_panic_on_mode($mode, __FUNCTION__); |
| 589 | } |
| 590 | } |
| 591 | else { |
| 592 | drupal_set_message(t('You are not eligible to vote on this decision.')); |
| 593 | } |
| 594 | } |
| 595 | |
| 596 | /** |
| 597 | * Helper function to list algorithms for a given mode |
| 598 | */ |
| 599 | function decisions_algorithms($mode) { |
| 600 | $algs = array(); |
| 601 | if (function_exists("{$mode}_decisions_algorithms")) { |
| 602 | $algs = call_user_func("{$mode}_decisions_algorithms"); |
| 603 | $error = FALSE; |
| 604 | if (!is_array($algs)) { |
| 605 | $error = t('Element returned by the call to function @function is not an array, returning dummy value.', |
| 606 | array('@function' => "decisions_{$mode}_algorithms")); |
| 607 | } |
| 608 | else if (count($algs) == 0) { |
| 609 | $error = t('Array returned by the call to function @function is empty, returning dummy value.', |
| 610 | array('@function' => "decisions_{$mode}_algorithms")); |
| 611 | } |
| 612 | if ($error) { |
| 613 | watchdog('decisions', $error, WATCHDOG_WARNING); |
| 614 | drupal_set_message($error, 'warning'); |
| 615 | } |
| 616 | } |
| 617 | else { |
| 618 | _decisions_panic_on_mode($mode, __FUNCTION__); |
| 619 | } |
| 620 | return $algs; |
| 621 | } |
| 622 | |
| 623 | |
| 624 | /*************/ |
| 625 | /* Callbacks */ |
| 626 | /*************/ |
| 627 | |
| 628 | /** |
| 629 | * Callback for canceling a vote. |
| 630 | */ |
| 631 | function decisions_cancel($nid) { |
| 632 | if ($node = node_load($nid)) { |
| 633 | if ($node->voted && $node->active) { |
| 634 | $criteria = votingapi_current_user_identifier(); |
| 635 | $criteria['content_type'] = 'decisions'; |
| 636 | $criteria['content_id'] = $node->nid; |
| 637 | votingapi_delete_votes(votingapi_select_votes($criteria)); |
| 638 | drupal_set_message(t('Your vote was canceled.')); |
| 639 | } |
| 640 | else { |
| 641 | drupal_set_message(t("You are not allowed to cancel an invalid choice."), 'error'); |
| 642 | } |
| 643 | drupal_goto('node/'. $nid); |
| 644 | } |
| 645 | else { |
| 646 | drupal_not_found(); |
| 647 | } |
| 648 | } |
| 649 | |
| 650 | /** |
| 651 | * Callback to display the votes tab. |
| 652 | */ |
| 653 | function decisions_votes_tab() { |
| 654 | if ($node = menu_get_object()) { |
| 655 | if (!$node->showvotes) { |
| 656 | // Decision is set to not allow viewing of votes |
| 657 | drupal_not_found(); |
| 658 | return; |
| 659 | } |
| 660 | drupal_set_title(check_plain($node->title)); |
| 661 | $output = t('This table lists all the recorded votes for this Decision. If anonymous users are allowed to vote, they will be identified by the IP address of the computer they used when they voted.'); |
| 662 | |
| 663 | $header[] = array('data' => t('Visitor'), 'field' => 'u.name'); |
| 664 | $header[] = array('data' => t('Vote'), ''); |
| 665 | |
| 666 | /* this query will group all the vote of a user in one record so that the pager can deal with it |
| 667 | * |
| 668 | * the "votes" column will look something like: |
| 669 | * |
| 670 | * v1=>t1,v2=>t2 |
| 671 | * |
| 672 | * for a table like this: |
| 673 | * |
| 674 | * uid | v1 | t1 |
| 675 | * uid | v2 | t2 |
| 676 | * |
| 677 | * where vN are values and tN are tags (tags being the choice being made and values the score given to it) |
| 678 | * |
| 679 | * XXX: highly MySQL specific |
| 680 | */ |
| 681 | $query = 'SELECT u.name, v.uid, v.vote_source, GROUP_CONCAT(DISTINCT CONCAT(v.value,"=>",v.tag) ORDER BY v.value) as votes FROM {votingapi_vote} v LEFT JOIN {users} u ON v.uid = u.uid WHERE v.content_id = %d GROUP BY v.uid' . tablesort_sql($header) . ''; |
| 682 | $query_count = 'SELECT COUNT(DISTINCT v.uid) FROM {votingapi_vote} v LEFT JOIN {users} u ON v.uid = u.uid WHERE v.content_id = %d GROUP BY content_id'. tablesort_sql($header); |
| 683 | $result = pager_query($query, variable_get('decisions_votes_per_page', 20), 0, $query_count, $node->nid); |
| 684 | $votes = array(); |
| 685 | $names = array(); |
| 686 | while ($vote = db_fetch_object($result)) { |
| 687 | $key = $vote->uid? $vote->uid : $vote->vote_source; |
| 688 | $choices = explode(',', $vote->votes); |
| 689 | foreach ($choices as $choice) { |
| 690 | $choice = explode("=>", $choice); |
| 691 | $votes[$key][] = (object) array('value' => $choice[0], 'tag' => $choice[1]); |
| 692 | } |
| 693 | $names[$key] = $vote->name ? theme('username', $vote) : check_plain($vote->vote_source); |
| 694 | } |
| 695 | |
| 696 | $mode = _decisions_get_mode($node); |
| 697 | $function_format_votes = "{$mode}_decisions_format_votes"; |
| 698 | if (!function_exists($function_format_votes)) { |
| 699 | _decisions_panic_on_mode($mode, __FUNCTION__); |
| 700 | drupal_not_found(); |
| 701 | } |
| 702 | |
| 703 | $rows = array(); |
| 704 | foreach ($names as $key => $name) { |
| 705 | $rows[$key]['name'] = $name; |
| 706 | $rows[$key]['vote'] = call_user_func($function_format_votes, $node, $votes[$key]); |
| 707 | } |
| 708 | |
| 709 | $output .= theme('table', $header, $rows); |
| 710 | $output .= theme('pager', array(), variable_get('decisions_votes_per_page', 20), 0); |
| 711 | print theme('page', $output); |
| 712 | } |
| 713 | else { |
| 714 | drupal_not_found(); |
| 715 | } |
| 716 | } |
| 717 | |
| 718 | /** |
| 719 | * Callback for 'results' tab for decisions you can vote on. |
| 720 | */ |
| 721 | function decisions_results() { |
| 722 | if ($node = menu_get_object()) { |
| 723 | drupal_set_title(check_plain($node->title)); |
| 724 | return node_show($node, 0); |
| 725 | } |
| 726 | else { |
| 727 | // The url does not provide the appropriate node id |
| 728 | drupal_not_found(); |
| 729 | } |
| 730 | } |
| 731 | |
| 732 | /** |
| 733 | * Callback to display a reset votes confirmation form |
| 734 | */ |
| 735 | function decisions_reset_form($form_state, $node) { |
| 736 | $form['nid'] = array('#type' => 'hidden', '#value' => $node->nid); |
| 737 | return confirm_form($form, |
| 738 | t('Are you sure you want to reset the votes for !title?', |
| 739 | array('!title' => theme('placeholder', $node->title))), |
| 740 | 'node/'. $node->nid, |
| 741 | t('This action cannot be undone.'), |
| 742 | t('Reset votes'), |
| 743 | t('Cancel') ); |
| 744 | } |
| 745 | |
| 746 | /** |
| 747 | * Reset votes once the confirmation is given |
| 748 | */ |
| 749 | function decisions_reset_form_submit($form, &$form_state) { |
| 750 | $nid = $form_state['values']['nid']; |
| 751 | // Delete any votes for the poll |
| 752 | db_query("DELETE FROM {votingapi_vote} WHERE content_id = %d", $nid); |
| 753 | drupal_set_message('Votes have been reset.'); |
| 754 | drupal_goto('node/'. $nid); |
| 755 | } |
| 756 | |
| 757 | /** |
| 758 | * Return the mode of a decision based on its type |
| 759 | */ |
| 760 | function _decisions_get_mode($node) { |
| 761 | if ($node->type) { |
| 762 | $types = explode('_', $node->type, 2); |
| 763 | return $types[1]; |
| 764 | } |
| 765 | else { |
| 766 | drupal_set_message('No type specified for node: '. $node->nid, 'error'); |
| 767 | return ''; |
| 768 | } |
| 769 | } |
| 770 | |
| 771 | /** |
| 772 | * Callback function to see if a node is acceptable for poll menu items. |
| 773 | */ |
| 774 | function _decisions_votes_access($node, $perm) { |
| 775 | return user_access($perm) && $node->showvotes && strpos($node->type, 'decisions_') === 0; |
| 776 | } |
| 777 | |
| 778 | function _decisions_reset_access($node, $perm) { |
| 779 | return user_access($perm) && strpos($node->type, 'decisions_') === 0; |
| 780 | } |
| 781 | |
| 782 | function _decisions_electoral_list_access($node, $perm) { |
| 783 | return user_access($perm) && $node->uselist; |
| 784 | } |
| 785 | |
| 786 | /** |
| 787 | * Function that tells if the given decision is open to votes. |
| 788 | */ |
| 789 | function _decisions_is_open($node) { |
| 790 | $time = time(); |
| 791 | return ($node->active && // node must be active |
| 792 | // current time must be past start date and before end date |
| 793 | ($time >= $node->startdate) && |
| 794 | ($node->runtime == DECISIONS_RUNTIME_INFINITY || |
| 795 | $time < ($node->startdate + $node->runtime))); |
| 796 | } |
| 797 | |
| 798 | /** |
| 799 | * Function that tells if the given user can vote on this decision. |
| 800 | */ |
| 801 | function _decisions_can_vote($node, $user = NULL) { |
| 802 | return (_decisions_is_open($node) && // node must be open |
| 803 | !$node->voted && // user should not have already voted |
| 804 | _decisions_eligible($node, $user)); // user must be eligible to vote |
| 805 | } |
| 806 | |
| 807 | /** |
| 808 | * Function that tells if the given decision meets the quorum. |
| 809 | */ |
| 810 | function _decisions_meets_quorum($node) { |
| 811 | // compute number of people that have cast their vote |
| 812 | $num_voters = _decisions_count_voters($node); |
| 813 | $quorum = _decisions_get_quorum($node); |
| 814 | return ($num_voters >= $quorum); |
| 815 | } |
| 816 | |
| 817 | |
| 818 | /** |
| 819 | * Internal function factored out that just rings lots of bells when |
| 820 | * we detect an unknown mode. |
| 821 | */ |
| 822 | function _decisions_panic_on_mode($mode, $function = '') { |
| 823 | watchdog('decisions', 'Unknown decision mode : @mode in "@function".', array('@mode' => $mode, '@function' => $function, WATCHDOG_ERROR)); |
| 824 | drupal_set_message(t('Unknown decision mode : @mode in "@function".', array('@mode' => $mode, '@function' => $function), 'error')); |
| 825 | } |
| 826 | |
| 827 | /** |
| 828 | * Get all votes from the given node. |
| 829 | */ |
| 830 | function _decisions_votes($node) { |
| 831 | $votes = array(); |
| 832 | // we bypass votingapi because we need ORDER BY value ASC lets us ensure no gaps |
| 833 | $result = db_query("SELECT * FROM {votingapi_vote} v WHERE content_type='%s' AND content_id='%d' ORDER BY value ASC", 'decisions', $node->nid); |
| 834 | while ($vobj = db_fetch_array($result)) { |
| 835 | $votes[] = $vobj; |
| 836 | } |
| 837 | return $votes; |
| 838 | } |
| 839 | |
| 840 | /** |
| 841 | * Count the elligible voters for a given decision. |
| 842 | */ |
| 843 | function _decisions_count_eligible($node) { |
| 844 | if ($node->uselist) { |
| 845 | $result = db_fetch_object(db_query("SELECT COUNT(*) AS num FROM {decisions_electoral_list} WHERE nid=%d", $node->nid)); |
| 846 | } |
| 847 | else { |
| 848 | // check first if authenticated users have the right to vote, because |
| 849 | // authenticated users are not added to the users_roles permission, |
| 850 | // probably for performance reasons |
| 851 | $roles = user_roles(FALSE, 'vote on decisions'); |
| 852 | if ($roles[DRUPAL_AUTHENTICATED_RID]) { |
| 853 | // special case: any authenticated user can vote |
| 854 | // consider all current to be elligible |
| 855 | $result = db_fetch_object(db_query("SELECT COUNT(*) AS num FROM {users} u WHERE u.uid <> 0")); |
| 856 | } |
| 857 | else { |
| 858 | // only some roles are elligible, add relevant users only |
| 859 | $result = db_fetch_object(db_query("SELECT COUNT(DISTINCT ur.uid) AS num FROM {users_roles} ur JOIN {permission} p ON ur.rid = p.rid WHERE FIND_IN_SET(' vote on decisions', p.perm) AND ur.uid <> 0")); |
| 860 | } |
| 861 | } |
| 862 | return $result->num; |
| 863 | } |
| 864 | |
| 865 | /** |
| 866 | * Returns the quorum (minimum voters) of a node. |
| 867 | */ |
| 868 | function _decisions_get_quorum($node) { |
| 869 | $num_eligible_voters = _decisions_count_eligible($node); |
| 870 | $quorum = $node->quorum_abs + ceil(($node->quorum_percent / 100.0) * $num_eligible_voters); |
| 871 | return min($quorum, $num_eligible_voters); |
| 872 | } |
| 873 | |
| 874 | /** |
| 875 | * Count the number of distinct voters. |
| 876 | */ |
| 877 | function _decisions_count_voters($node) { |
| 878 | $num_voters = 0; |
| 879 | if ($result = db_fetch_object(db_query("SELECT COUNT(DISTINCT CONCAT(uid,vote_source)) AS voters FROM {votingapi_vote} WHERE content_id=%d", $node->nid))) { |
| 880 | $num_voters = $result->voters; |
| 881 | } |
| 882 | return $num_voters; |
| 883 | } |
| 884 | |
| 885 | /** |
| 886 | * Get all votes by uid in a an array, in a uid => votes fashion. |
| 887 | */ |
| 888 | function _decisions_user_votes($node) { |
| 889 | $votes = _decisions_votes($node); |
| 890 | |
| 891 | // aggregate votes by user (uid if logged in, IP if anonymous) |
| 892 | // in ascending order of value |
| 893 | $user_votes = array(); |
| 894 | |
| 895 | foreach ($votes as $vote) { |
| 896 | $key = ($vote->uid == 0 ? $vote->vote_source: $vote->uid); |
| 897 | $user_votes[$key][] = $vote; |
| 898 | } |
| 899 | |
| 900 | return $user_votes; |
| 901 | } |
| 902 | |
| 903 | /** |
| 904 | * Check if user is eligible to this decision. |
| 905 | */ |
| 906 | function _decisions_eligible($node, $uid = NULL) { |
| 907 | global $user; |
| 908 | if (is_null($uid)) { |
| 909 | $uid = $user->uid; |
| 910 | } |
| 911 | |
| 912 | if ($node->uselist) { |
| 913 | $can_vote = db_fetch_object(db_query("SELECT COUNT(*) AS eligible FROM {decisions_electoral_list} WHERE nid=%d AND uid=%d", $node->nid, $uid)); |
| 914 | $eligible = $can_vote->eligible; |
| 915 | } |
| 916 | else { |
| 917 | $eligible = user_access('vote on decisions'); |
| 918 | } |
| 919 | return $eligible; |
| 920 | } |
| 921 | |
| 922 | /** |
| 923 | * Constructs the time select boxes. |
| 924 | * |
| 925 | * @ingroup event_support |
| 926 | * @param $timestamp The time GMT timestamp of the event to use as the default |
| 927 | * value. |
| 928 | * @return An array of form elements for month, day, year, hour, and minute |
| 929 | */ |
| 930 | function _decisions_form_date($timestamp) { |
| 931 | // populate drop down values... |
| 932 | // ...months |
| 933 | $months = array(1 => t('January'), t('February'), t('March'), t('April'), t('May'), t('June'), t('July'), t('August'), t('September'), t('October'), t('November'), t('December')); |
| 934 | // ...hours |
| 935 | if (variable_get('event_ampm', '0')) { |
| 936 | $hour_format = t('g'); |
| 937 | $hours = drupal_map_assoc(range(1, 12)); |
| 938 | $am_pms = array('am' => t('am'), 'pm' => t('pm')); |
| 939 | } |
| 940 | else { |
| 941 | $hour_format = t('H'); |
| 942 | $hours = drupal_map_assoc(range(0, 23)); |
| 943 | } |
| 944 | // ...minutes (with leading 0s) |
| 945 | for ($i = 0; $i <= 59; $i++) $minutes[$i] = $i < 10 ? "0$i" : $i; |
| 946 | |
| 947 | // This is a GMT timestamp, so the _event_date() wrapper to display local times. |
| 948 | $form['day'] = array( |
| 949 | '#prefix' => '<div class="container-inline"><div class="day">', |
| 950 | '#type' => 'textfield', |
| 951 | '#default_value' => _decisions_date('d', $timestamp), |
| 952 | '#maxlength' => 2, |
| 953 | '#size' => 2, |
| 954 | '#required' => TRUE); |
| 955 | $form['month'] = array( |
| 956 | '#type' => 'select', |
| 957 | '#default_value' => _decisions_date('n', $timestamp), |
| 958 | '#options' => $months, |
| 959 | '#required' => TRUE); |
| 960 | $form['year'] = array( |
| 961 | '#type' => 'textfield', |
| 962 | '#default_value' => _decisions_date('Y', $timestamp), |
| 963 | '#maxlength' => 4, |
| 964 | '#size' => 4, |
| 965 | '#required' => TRUE); |
| 966 | $form['hour'] = array( |
| 967 | '#prefix' => '</div>—<div class="time">', |
| 968 | '#type' => 'select', |
| 969 | '#default_value' => _decisions_date($hour_format, $timestamp), |
| 970 | '#options' => $hours, |
| 971 | '#required' => TRUE); |
| 972 | $form['minute'] = array( |
| 973 | '#prefix' => ':', |
| 974 | '#type' => 'select', |
| 975 | '#default_value' => _decisions_date('i', $timestamp), |
| 976 | '#options' => $minutes, |
| 977 | '#required' => TRUE); |
| 978 | if (isset($am_pms)) { |
| 979 | $form['ampm'] = array( |
| 980 | '#type' => 'radios', |
| 981 | '#default_value' => _decisions_date('a', $timestamp), |
| 982 | '#options' => $am_pms, |
| 983 | '#required' => TRUE); |
| 984 | } |
| 985 | $form['close'] = array( |
| 986 | '#type' => 'markup', |
| 987 | '#value' => '</div></div>'); |
| 988 | |
| 989 | return $form; |
| 990 | } |
| 991 | |
| 992 | /** |
| 993 | * Takes a time element and prepares to send it to form_date() |
| 994 | * |
| 995 | * @param $time |
| 996 | * The time to be turned into an array. This can be: |
| 997 | * - a timestamp when from the database |
| 998 | * - an array (day, month, year) when previewing |
| 999 | * - null for new nodes |
| 1000 | * @returnn |
| 1001 | * an array for form_date (day, month, year) |
| 1002 | */ |
| 1003 | function _decisions_form_prepare_datetime($time = '', $offset = 0) { |
| 1004 | // if this is empty, get the current time |
| 1005 | if ($time == '') { |
| 1006 | $time = time(); |
| 1007 | $time = strtotime("+$offset days", $time); |
| 1008 | } |
| 1009 | // If we are previewing, $time will be an array so just pass it through |
| 1010 | $time_array = array(); |
| 1011 | if (is_array($time)) { |
| 1012 | $time_array = $time; |
| 1013 | } |
| 1014 | // otherwise build the array from the timestamp |
| 1015 | elseif (is_numeric($time)) { |
| 1016 | $time_array = array( |
| 1017 | 'day' => _decisions_date('j', $time), |
| 1018 | 'month' => _decisions_date('n', $time), |
| 1019 | 'year' => _decisions_date('Y', $time), |
| 1020 | 'hour' => _decisions_date('H', $time), |
| 1021 | 'min' => _decisions_date('i', $time), |
| 1022 | 'sec' => _decisions_date('s', $time), |
| 1023 | ); |
| 1024 | } |
| 1025 | // return the array |
| 1026 | return $time_array; |
| 1027 | } |
| 1028 | |
| 1029 | /** |
| 1030 | * Content of the block, as returned by decisions_block('view') |
| 1031 | */ |
| 1032 | function _decisions_block_mostrecent() { |
| 1033 | $output = ''; |
| 1034 | $result = db_query_range('SELECT nid FROM {decisions} WHERE active=1 ORDER BY nid DESC', 1); |
| 1035 | // Check that there is an active decision |
| 1036 | if ($decision = db_fetch_object($result)) { |
| 1037 | $n = decisions_view(node_load($decision->nid), FALSE, FALSE, TRUE); |
| 1038 | /* XXX: we have to do this because somehow the #printed settings lives across multiple node_load */ |
| 1039 | unset($n->content['#printed']); |
| 1040 | $output = drupal_render($n->content); |
| 1041 | } |
| 1042 | else { |
| 1043 | $output = t('No active decisions.'); |
| 1044 | } |
| 1045 | return $output; |
| 1046 | } |
| 1047 | |
| 1048 | /** |
| 1049 | * Returns true if the user can view the results of current node. |
| 1050 | */ |
| 1051 | function _decisions_can_view_results($node) { |
| 1052 | $view_results = variable_get('decisions_view_results', DECISIONS_DEFAULT_VIEW_RESULTS); |
| 1053 | return (_decisions_meets_quorum($node) && // node meets the quorum |
| 1054 | strpos($node->type, 'decisions_') === 0 && |
| 1055 | (!_decisions_is_open($node) // node is closed |
| 1056 | || ($node->voted && $view_results == 'aftervote') // user voted |
| 1057 | || ($view_results == 'always'))); // all can view |
| 1058 | } |
| 1059 | |
| 1060 | /** |
| 1061 | * Insert the right users in the electoral list |
| 1062 | */ |
| 1063 | function _decisions_electoral_list_reset($node) { |
| 1064 | // check first if authenticated users have the right to vote, because authenticated users are not added to the users_roles permission, probably for performance reasons |
| 1065 | $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 decisions', perm) AND role.name = 'authenticated user'")); |
| 1066 | if (isset($result) && $result->hit) { |
| 1067 | // special case: any authenticated user can vote |
| 1068 | // add all current users to electoral list |
| 1069 | return db_query("INSERT INTO {decisions_electoral_list} (nid, uid) SELECT '%d', u.uid FROM users u WHERE u.uid <> 0", $node->nid); |
| 1070 | } |
| 1071 | else { |
| 1072 | // all users must not be allowed to vote, add relevant users only |
| 1073 | return db_query("INSERT INTO {decisions_electoral_list} (nid, uid) SELECT '%d', u.uid FROM users_roles u, permission p WHERE FIND_IN_SET(' view decisions', p.perm) AND u.rid = p.rid AND u.uid <> 0", $node->nid); |
| 1074 | } |
| 1075 | |
| 1076 | } |
| 1077 | |
| 1078 | /** |
| 1079 | * Implementation of hook_load(). |
| 1080 | * |
| 1081 | * Load the votes and decision-specific data into the node object. |
| 1082 | */ |
| 1083 | function decisions_load($node) { |
| 1084 | $decision = db_fetch_object(db_query("SELECT * FROM {decisions} WHERE nid = %d", $node->nid)); |
| 1085 | $result = db_query("SELECT vote_offset, label FROM {decisions_choices} WHERE nid = %d ORDER BY vote_offset", $node->nid); |
| 1086 | while ($choice = db_fetch_array($result)) { |
| 1087 | $decision->choice[$choice['vote_offset']] = $choice; |
| 1088 | } |
| 1089 | $decision->choices = count($decision->choice); |
| 1090 | |
| 1091 | // See if user has voted |
| 1092 | $criteria = votingapi_current_user_identifier(); |
| 1093 | $criteria['content_type'] = 'decisions'; |
| 1094 | $criteria['content_id'] = $node->nid; |
| 1095 | $decision->voted = count(votingapi_select_votes($criteria)) > 0; |
| 1096 | |
| 1097 | return $decision; |
| 1098 | } |
| 1099 | |
| 1100 | /** |
| 1101 | * Implementation of hook_delete(). |
| 1102 | * |
| 1103 | */ |
| 1104 | function decisions_delete($node) { |
| 1105 | db_query("DELETE FROM {decisions} WHERE nid = %d", $node->nid); |
| 1106 | db_query("DELETE FROM {decisions_choices} WHERE nid = %d", $node->nid); |
| 1107 | db_query("DELETE FROM {decisions_electoral_list} WHERE nid = %d", $node->nid); |
| 1108 | |
| 1109 | // Note: this should be converted to a votingapi method eventually |
| 1110 | db_query("DELETE FROM {votingapi_vote} WHERE content_id = %d", $node->nid); |
| 1111 | } |
| 1112 | |
| 1113 | /** |
| 1114 | * Implementation of hook_insert() |
| 1115 | * |
| 1116 | * This is called upon node creation |
| 1117 | */ |
| 1118 | function decisions_insert($node) { |
| 1119 | // Compute startdate and runtime. |
| 1120 | $startdate = _decisions_translate_form_date($node->settings['date']['startdate']['date']); |
| 1121 | if ($node->settings['date']['noenddate']) { |
| 1122 | $runtime = DECISIONS_RUNTIME_INFINITY; |
| 1123 | } |
| 1124 | else { |
| 1125 | $enddate = _decisions_translate_form_date($node->settings['date']['enddate']['date']); |
| 1126 | if ($enddate < $startdate) { |
| 1127 | form_set_error('enddate', t('The specified close date is less than the opening date, setting it to the same for now.')); |
| 1128 | $enddate = $startdate; |
| 1129 | } |
| 1130 | $runtime = $enddate - $startdate; |
| 1131 | } |
| 1132 | |
| 1133 | // just create an empty entry for now |
| 1134 | $mode = _decisions_get_mode($node); |
| 1135 | |
| 1136 | db_query("INSERT INTO {decisions} (nid, mode, quorum_abs, quorum_percent, uselist, active, runtime, maxchoices, algorithm, startdate, randomize) VALUES (%d, '%s', %d, %f, %d, %d, %d, %d, '%s', %d, %d)", $node->nid, $mode, $node->settings['quorum']['quorum_abs'], $node->settings['quorum']['quorum_percent'], $node->settings['uselist'], $node->settings['active'], $node->settings['runtime'], $node->settings['maxchoices'], $node->settings['algorithm'], $startdate, $node->settings['randomize']); |
| 1137 | |
| 1138 | // create the electoral list if desired |
| 1139 | |
| 1140 | if ($node->settings['uselist']) { |
| 1141 | _decisions_electoral_list_reset($node); |
| 1142 | } |
| 1143 | |
| 1144 | // insert the choices, same sequence than update |
| 1145 | decisions_update($node); |
| 1146 | } |
| 1147 | |
| 1148 | /** |
| 1149 | * Implementation of hook_validate(). |
| 1150 | * |
| 1151 | * XXX: No validation yet. |
| 1152 | */ |
| 1153 | function decisions_validate(&$node) { |
| 1154 | // Use form_set_error for any errors |
| 1155 | $node->choice = array_values($node->choice); |
| 1156 | |
| 1157 | // Start keys at 1 rather than 0 |
| 1158 | array_unshift($node->choice, ''); |
| 1159 | unset($node->choice[0]); |
| 1160 | |
| 1161 | // Check for at least two choices |
| 1162 | $realchoices = 0; |
| 1163 | foreach ($node->choice as $i => $choice) { |
| 1164 | if ($choice['label'] != '') { |
| 1165 | $realchoices++; |
| 1166 | } |
| 1167 | } |
| 1168 | |
| 1169 | if ($realchoices < 2) { |
| 1170 | form_set_error("choice][$realchoices][label", t('You must fill in at least two choices.')); |
| 1171 | } |
| 1172 | |
| 1173 | $startdate |