You are here

advpoll.module in Advanced Poll 5

Advanced Poll - a sophisticated polling module for voting, elections, and group decision-making.

File

advpoll.module
View source
<?php

/**
 * @file
 * Advanced Poll - a sophisticated polling module for voting, elections, and group decision-making.
 */
define('ADVPOLL_MAX_CHOICES', 0);
define('ADVPOLL_RUNTIME', 0);
define('ADVPOLL_ELECTORAL_LIST', 0);
define('ADVPOLL_SHOW_VOTES', 1);
define('ADVPOLL_WRITEINS', 0);
define('ADVPOLL_SHOW_WRITEINS', 0);
define('ADVPOLL_INITIAL_CHOICES', 5);
define('ADVPOLL_USE_QUESTION', 0);
define('ADVPOLL_CHOICE_MAX_LENGTH', 2048);

// Options: always, aftervote, or afterclose.
define('ADVPOLL_VIEW_RESULTS', 'aftervote');

/**
 * Implementation of hook_help().
 */
function advpoll_help($section) {
  switch ($section) {
    case 'admin/modules#description':
      return t('A sophisticated polling module for voting, elections, and group decision-making.');
      break;
  }
}

/**
 * Implementation of hook_perm().
 */
function advpoll_perm() {
  return array(
    'create polls',
    'edit polls',
    'edit own polls',
    'vote on polls',
    'cancel own vote',
    'administer polls',
    'inspect all votes',
    'access electoral list',
    'add write-ins',
  );
}

/**
 * Implementation of hook_access().
 */
function advpoll_access($op, $node) {
  global $user;
  if ($op == 'create') {
    return user_access('create polls');
  }
  if ($op == 'update') {
    if (user_access('edit polls') || $node->uid == $user->uid && user_access('edit own polls')) {
      return TRUE;
    }
  }
}

/**
 * Implementation of hook_node_info().
 */
function advpoll_node_info() {
  $modes = _advpoll_list_modes();
  $info = array();
  foreach ($modes as $mode) {
    $info['advpoll_' . $mode['name']] = array(
      'name' => t('@name poll', array(
        '@name' => $mode['name_label'],
      )),
      'module' => 'advpoll',
      'description' => $mode['description'],
      'title_label' => t('@name question', array(
        '@name' => $mode['name_label'],
      )),
      'body_label' => t('Description'),
    );
  }
  return $info;
}

/**
 * Implementation of hook_menu().
 */
function advpoll_menu($may_cache) {
  global $user;
  $modes = _advpoll_list_modes();
  $items = array();
  if ($may_cache) {
    $items[] = array(
      'path' => 'advpoll/cancel',
      'title' => t('Cancel'),
      'callback' => 'advpoll_cancel',
      'access' => user_access('cancel own vote'),
      'type' => MENU_CALLBACK,
    );
    $items[] = array(
      'path' => 'polls',
      'title' => t('Advanced Polls'),
      'callback' => 'advpoll_page',
      'access' => user_access('access content'),
      'type' => MENU_SUGGESTED_ITEM,
    );
  }
  else {

    // Use Poll modules stylesheet, no need to duplicate at this point.
    // We put this in !$may_cache so it's only added once per request.
    drupal_add_css(drupal_get_path('module', 'poll') . '/poll.css');

    // Need to be able to extract the nid.
    if (arg(0) == 'node' && is_numeric(arg(1))) {
      $nid = arg(1);
      $node = node_load($nid);

      // Make sure we're on the actual poll node's page.
      if (strstr($node->type, 'advpoll_') == 0) {

        // Show the results tab.
        if (_advpoll_is_active($node) && !$node->voted && _advpoll_can_view_results($node)) {
          $items[] = array(
            'path' => 'node/' . $nid . '/results',
            'title' => t('Results'),
            'callback' => 'advpoll_results',
            'access' => user_access('access content'),
            'weight' => 3,
            'type' => MENU_LOCAL_TASK,
          );
        }

        // Show the votes tab.
        if ($node->show_votes) {
          $items[] = array(
            'path' => 'node/' . $nid . '/votes',
            'title' => t('Votes'),
            'callback' => 'advpoll_votes_page',
            'access' => user_access('inspect all votes'),
            'weight' => 3,
            'type' => MENU_LOCAL_TASK,
          );
        }

        // Show electoral list tab if using the functionality.
        if ($node->use_list) {
          $items[] = array(
            'path' => 'node/' . $nid . '/electoral_list',
            'title' => t('Electoral list'),
            'callback' => 'advpoll_electoral_list_page',
            'access' => user_access('access electoral list'),
            'weight' => 3,
            'type' => MENU_LOCAL_TASK,
          );

          // Allow voters to be removed.
          $items[] = array(
            'path' => 'node/' . $nid . '/remove',
            'callback' => 'advpoll_remove_voter',
            'access' => user_access('administer polls'),
            'weight' => 3,
            'type' => MENU_CALLBACK,
          );
        }

        // Allow votes to be reset.
        $items[] = array(
          'path' => 'node/' . $nid . '/reset',
          'callback' => 'drupal_get_form',
          'callback arguments' => 'advpoll_reset_confirm',
          'access' => user_access('administer polls'),
          'weight' => 3,
          'type' => MENU_CALLBACK,
        );

        // Show the write-ins tab if there is at least one.
        if ($node->writeins) {
          $has_writeins = FALSE;
          foreach ($node->choice as $choice) {
            if ($choice['writein']) {
              $has_writeins = TRUE;
              break;
            }
          }
          if ($has_writeins) {
            $items[] = array(
              'path' => 'node/' . $nid . '/writeins',
              'title' => t('Write-ins'),
              'callback' => 'advpoll_writeins_page',
              'access' => user_access('administer polls'),
              'weight' => 3,
              'type' => MENU_LOCAL_TASK,
            );
          }
        }
      }
    }
  }
  return $items;
}

/**
 * Implementation of hook_init().
 */
function advpoll_init() {
  if (module_exists('views')) {
    require_once drupal_get_path('module', 'advpoll') . '/advpoll_views.inc';
  }
}

/**
 * Implementation of hook_block().
 */
function advpoll_block($op = 'list') {
  switch ($op) {
    case 'list':
      $blocks['latest_poll']['info'] = t('Latest poll');
      return $blocks;
    case 'view':
      $block['subject'] = t('Latest poll');
      $block['content'] = theme('advpoll_block_latest_poll');
      return $block;
  }
}

/**
 * Content of the block, as returned by advpoll_block('view').
 */
function theme_advpoll_block_latest_poll() {
  $node = advpoll_latest_poll();
  $output = '';
  if ($node) {
    $output .= '<h3>' . check_plain($node->title) . '</h3>';
    $output .= drupal_render($node->content);
    if ($node->voted) {
      $output .= '<p>' . l(t('Older polls'), 'polls', array(
        'class' => 'old-polls',
        'title' => t('View the list of polls on this site.'),
      )) . '</p>';
    }
  }
  return $output;
}
function advpoll_latest_poll() {
  $result = db_query('SELECT MAX(n.nid) AS nid FROM {node} n INNER JOIN {advpoll} p ON p.nid = n.nid WHERE n.status = 1 AND p.active = 1');
  $poll = db_fetch_object($result);

  // The nid will be NULL if there are no active polls.
  if ($poll->nid) {
    $node = advpoll_view(node_load($poll->nid), FALSE, FALSE);
  }
  return $node;
}

/**
 * Implementation of hook_form().
 *
 * This hook displays the form necessary to create/edit the poll.
 */
function advpoll_form($node, $form_values = NULL) {
  $mode = _advpoll_get_mode($node->type);
  $type = node_get_types('type', $node);
  $editing = isset($node->nid);
  $form = array();

  // Only add javascript once, even if _form is called multiple times.
  static $add_js;
  if (!$add_js) {

    // Pass translatable strings
    drupal_add_js(array(
      'advPoll' => array(
        'remove' => t('Remove'),
        'addChoice' => t('Add choice'),
        'noLimit' => t('No limit'),
      ),
    ), 'setting');
    drupal_add_js(drupal_get_path('module', 'advpoll') . '/advpoll-form.js', 'module');
    drupal_add_css(drupal_get_path('module', 'advpoll') . '/advpoll.css', 'module');
    $add_js = TRUE;
  }
  $form['title'] = array(
    '#type' => 'textfield',
    '#title' => check_plain($type->title_label),
    '#required' => TRUE,
    '#default_value' => $node->title,
  );
  $form['body_filter']['body'] = array(
    '#type' => 'textarea',
    '#title' => check_plain($type->body_label),
    '#required' => FALSE,
    '#default_value' => $node->body,
  );
  $form['body_filter']['format'] = filter_form($node->format);
  if (isset($form_values)) {
    $choices = $form_values['choices'];
    if ($form_values['more_choices']) {
      $choices *= 2;
    }
  }
  else {
    $choices = max(2, count($node->choice) ? count($node->choice) : ADVPOLL_INITIAL_CHOICES);
  }
  if (variable_get('advpoll_use_question_' . $type->type, ADVPOLL_USE_QUESTION) || isset($node->question) && $node->question !== '') {
    $form['question'] = array(
      '#type' => 'textfield',
      '#title' => t('Question'),
      '#default_value' => $node->question,
      '#maxlength' => 255,
    );
  }
  $form['choices'] = array(
    '#type' => 'hidden',
    '#value' => $choices,
  );

  // Advanced Poll choices
  $form['choice'] = array(
    '#type' => 'fieldset',
    '#title' => t('Poll choices'),
    '#collapsible' => TRUE,
    '#prefix' => '<div class="poll-form">',
    '#suffix' => '</div>',
    '#tree' => TRUE,
    '#weight' => 1,
  );
  if ($editing) {
    $form['choice']['choice_note'] = array(
      '#value' => '<div id="edit-settings-choice-note" class="description">' . t('Note: adding or removing choices after voting has begun is not recommended.') . '</div>',
    );
  }
  $default_choices = variable_get('advpoll_choices_' . $type->type, '');
  if (!$editing) {

    // This is a new poll so show a textarea for quick entry.
    $form['choice']['choice_area'] = array(
      '#type' => 'textarea',
      '#title' => t('Choices'),
      '#parents' => array(
        'choice_area',
      ),
      '#description' => t('Enter one choice per line.'),
      '#default_value' => $default_choices,
    );
  }
  else {
    $form['choice']['more_choices'] = array(
      '#type' => 'checkbox',
      '#title' => t('Need more choices'),
      '#value' => 0,
      '#parents' => array(
        'more_choices',
      ),
      // Don't pollute $form['choice']
      '#prefix' => '<div id="more-choices">',
      '#suffix' => '</div>',
      '#description' => t("If the number of choices above isn't enough, check this box and click the Preview button below to double the number of choices."),
      '#weight' => 1,
    );

    // First, loop through any currently existing choices.
    $current_choices = 0;
    if (isset($node->choice)) {
      foreach ($node->choice as $index => $choice) {
        $form['choice'][$index]['label'] = array(
          '#type' => 'textfield',
          '#title' => t('Choice %n', array(
            '%n' => $current_choices + 1,
          )) . ($choice['writein'] ? ' ' . t('(write-in)') : ''),
          '#default_value' => $choice['label'],
          '#attributes' => array(
            'class' => 'choices',
          ),
          '#maxlength' => variable_get('advpoll_choice_max_length', ADVPOLL_CHOICE_MAX_LENGTH),
        );
        $current_choices++;
        $next_index = $index + 1;
      }
    }
    elseif ($default_choices != '') {
      $default_choices = explode("\n", $default_choices);
      foreach ($default_choices as $index => $label) {
        $form['choice'][$index]['label'] = array(
          '#type' => 'textfield',
          '#title' => t('Choice %n', array(
            '%n' => $current_choices + 1,
          )),
          '#default_value' => $label,
          '#attributes' => array(
            'class' => 'choices',
          ),
        );
        $current_choices++;
        $next_index = $index + 1;
      }
    }
    else {
      $next_index = 1;
    }

    // Now add on extra choices if we need to.
    if ($current_choices < $choices) {
      for ($index = $next_index; $current_choices < $choices; $index++, $current_choices++) {
        $form['choice'][$index]['label'] = array(
          '#type' => 'textfield',
          '#title' => t('Choice %n', array(
            '%n' => $current_choices + 1,
          )),
          '#attributes' => array(
            'class' => 'choices',
          ),
          '#maxlength' => variable_get('advpoll_choice_max_length', ADVPOLL_CHOICE_MAX_LENGTH),
        );
      }
    }
  }
  $form['settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Poll settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#weight' => 2,
    '#tree' => TRUE,
  );
  $max_choice_list = array();
  for ($i = 0; $i <= $choices; $i++) {
    $max_choice_list[$i] = $i == 0 ? t('No limit') : $i;
  }
  $form['settings']['max_choices'] = array(
    '#type' => 'select',
    '#title' => t('Maximum choices'),
    '#default_value' => isset($node->max_choices) ? $node->max_choices : variable_get('advpoll_max_choices_' . $type->type, ADVPOLL_MAX_CHOICES),
    '#options' => $max_choice_list,
    '#DANGEROUS_SKIP_CHECK' => TRUE,
    // Allow jQuery to add new options
    '#description' => t('Limits the total number of choices voters may select.'),
  );
  $voting_algorithms = advpoll_algorithms($mode);
  if (count($voting_algorithms) > 1) {

    // Create a select field when the poll supports several algorithms.
    $form['settings']['algorithm'] = array(
      '#type' => 'select',
      '#title' => t('Algorithm'),
      '#options' => $voting_algorithms,
      '#default_value' => isset($node->algorithm) ? $node->algorithm : variable_get('advpoll_algorithm_' . $type->type, key($voting_algorithms)),
      '#description' => t('Voting algorithm to use to calculate the winner.'),
    );
  }
  else {

    // Pass the only algorithm as a value.
    $form['settings']['algorithm'] = array(
      '#type' => 'value',
      '#value' => key($voting_algorithms),
    );
  }
  $form['settings']['close'] = array(
    '#type' => 'checkbox',
    '#title' => t('Close poll'),
    '#description' => t('When a poll is closed users may no longer vote on it.'),
    '#default_value' => isset($node->active) ? !$node->active : 0,
  );
  $default_start_date = '';
  $default_end_date = '';
  $time = format_date(time(), 'custom', 'Y-m-d H:i:s O');

  // Specify default dates if default duration is set and we are creating a node.
  if (variable_get('advpoll_runtime_' . $type->type, FALSE) && !$editing) {
    $default_start_date = $time;
    $default_end_date = format_date(time() + variable_get('advpoll_runtime_' . $type->type, ''), 'custom', 'Y-m-d H:i:s O');
  }
  $form['settings']['start_date'] = array(
    '#type' => 'textfield',
    '#title' => t('Starting date'),
    '#description' => t('The date that the poll opens. Format: %time. Leave blank if you want the poll to open now.', array(
      '%time' => $time,
    )),
    '#size' => 25,
    '#maxlength' => 25,
    // Use !empty() because 0 signifies that no date should be used.
    '#default_value' => !empty($node->start_date) ? format_date($node->start_date, 'custom', 'Y-m-d H:i:s O') : $default_start_date,
  );
  $form['settings']['end_date'] = array(
    '#type' => 'textfield',
    '#title' => t('Ending date'),
    '#description' => t('The date that the poll closes. Format: %time. Leave blank if you do not want the poll to close automatically.', array(
      '%time' => $time,
    )),
    '#size' => 25,
    '#maxlength' => 25,
    // Use !empty() because 0 signifies that no date should be used.
    '#default_value' => !empty($node->end_date) ? format_date($node->end_date, 'custom', 'Y-m-d H:i:s O') : $default_end_date,
  );

  // Settings available for users with 'administer polls' permission.
  $default_use_list = isset($node->use_list) ? $node->use_list : variable_get('advpoll_electoral_list_' . $type->type, ADVPOLL_ELECTORAL_LIST);
  $default_show_votes = isset($node->show_votes) ? $node->show_votes : variable_get('advpoll_show_votes_' . $type->type, ADVPOLL_SHOW_VOTES);
  $default_writeins = isset($node->writeins) ? $node->writeins : variable_get('advpoll_writeins_' . $type->type, ADVPOLL_WRITEINS);
  $default_show_writeins = isset($node->show_writeins) ? $node->show_writeins : variable_get('advpoll_show_writeins_' . $type->type, ADVPOLL_SHOW_WRITEINS);
  if (user_access('administer polls')) {
    $form['settings']['admin_note'] = array(
      '#value' => '<div id="edit-settings-admin-note" class="description">' . t('The settings below are only available for users with the <em>administer polls</em> permission.') . '</div>',
    );
    $form['settings']['writeins'] = array(
      '#type' => 'checkbox',
      '#title' => t('Allow users to cast a write-in vote'),
      '#default_value' => $default_writeins,
      '#description' => t('Allow voters with the "add write-ins" permission to write-in up to one choice each.'),
      '#attributes' => array(
        'class' => 'settings-writeins',
      ),
    );
    $form['settings']['show_writeins'] = array(
      '#type' => 'checkbox',
      '#title' => t('Display write-in votes as choices for future voters'),
      '#default_value' => $default_show_writeins,
      '#description' => t('Allow voters to see and choose from previously submitted write-in votes.'),
      '#prefix' => '<div class="edit-settings-show-writeins">',
      '#suffix' => '</div>',
    );
    $form['settings']['use_list'] = array(
      '#type' => 'checkbox',
      '#title' => t('Restrict voting to electoral list'),
      '#description' => t('If enabled, a list of eligible voters will be created and only that group will be able to vote in the poll.'),
      '#default_value' => $default_use_list,
    );
    $form['settings']['show_votes'] = array(
      '#type' => 'checkbox',
      '#title' => t('Show individual votes'),
      '#description' => t('Users with the appropriate permissions will be able to see how each person voted.'),
      '#default_value' => $default_show_votes,
    );

    // Only show when editing a poll, not creating.
    if ($editing) {
      $form['settings']['reset'] = array(
        '#type' => 'button',
        '#value' => t('Reset votes'),
      );
    }
  }
  else {

    // Just pass the values for users without the "administer polls" permission.
    $defaults = array(
      'use_list' => $default_use_list,
      'show_votes' => $default_show_votes,
      'writeins' => $default_writeins,
      'show_writeins' => $default_show_writeins,
    );
    foreach ($defaults as $name => $value) {
      $form['settings'][$name] = array(
        '#type' => 'value',
        '#value' => $value,
      );
    }
  }
  $form['#multistep'] = TRUE;
  return $form;
}

/**
 * Implementation of hook_form_alter().
 */
function advpoll_form_alter($form_id, &$form) {
  if ($form_id == 'node_type_form' && isset($form['identity']['type'])) {
    $node_type = $form['old_type']['#value'];

    // Display poll settings if this is an advpoll content type.
    if ($form['module']['#value'] == 'advpoll') {

      // Include JS and CSS for the show_writeins setting toggle.
      drupal_add_js(drupal_get_path('module', 'advpoll') . '/advpoll-form.js', 'module');
      drupal_add_css(drupal_get_path('module', 'advpoll') . '/advpoll.css', 'module');
      $form['advpoll'] = array(
        '#type' => 'fieldset',
        '#title' => t('Poll settings'),
        '#collapsible' => TRUE,
      );
      $form['advpoll']['advpoll_choices'] = array(
        '#type' => 'textarea',
        '#title' => t('Default choices'),
        '#default_value' => variable_get('advpoll_choices_' . $node_type, ''),
        '#description' => t('Add one choice per row. This setting can be overridden on the poll edit page.'),
      );
      $form['advpoll']['advpoll_max_choices'] = array(
        '#type' => 'select',
        '#title' => t('Default maximum choices'),
        '#options' => array(
          0 => t('No limit'),
        ) + drupal_map_assoc(array(
          1,
          2,
          3,
          4,
          5,
        )),
        '#default_value' => variable_get('advpoll_max_choices_' . $node_type, ADVPOLL_MAX_CHOICES),
        '#description' => t('The default number of maximum choices for new polls. This setting can be overridden on the poll edit page.'),
      );
      $mode = _advpoll_get_mode($node_type);
      $voting_algorithms = advpoll_algorithms($mode);
      if (count($voting_algorithms) > 1) {
        $form['advpoll']['advpoll_algorithm'] = array(
          '#type' => 'select',
          '#title' => t('Default algorithm'),
          '#options' => $voting_algorithms,
          '#default_value' => variable_get('advpoll_algorithm_' . $node_type, key($voting_algorithms)),
          '#description' => t('Default voting algorithm for calculating the winner.'),
        );
      }
      $form['advpoll']['advpoll_runtime'] = array(
        '#type' => 'select',
        '#title' => t('Default duration'),
        '#default_value' => variable_get('advpoll_runtime_' . $node_type, ADVPOLL_RUNTIME),
        '#options' => array(
          0 => t('Unlimited'),
        ) + drupal_map_assoc(array(
          86400,
          172800,
          345600,
          604800,
          1209600,
          1814400,
          2419200,
          4838400,
          9676800,
          31536000,
        ), 'format_interval'),
        '#description' => t('The date the poll was created is used as start date for the default duration. This setting can be overridden on the poll edit page.'),
      );
      $form['advpoll']['advpoll_writeins'] = array(
        '#type' => 'checkbox',
        '#title' => t('Allow users to cast a write-in vote by default'),
        '#default_value' => variable_get('advpoll_writeins_' . $node_type, ADVPOLL_WRITEINS),
        '#description' => t('Allow voters with the "add write-ins" permission to write-in up to one choice each. Users with the <em>administer polls</em> permission will be able to override this setting.'),
        '#attributes' => array(
          'class' => 'settings-writeins',
        ),
      );
      $form['advpoll']['advpoll_show_writeins'] = array(
        '#type' => 'checkbox',
        '#title' => t('Display write-in votes as choices for future voters by default'),
        '#default_value' => variable_get('advpoll_show_writeins_' . $node_type, ADVPOLL_SHOW_WRITEINS),
        '#description' => t("Allow voters to see and choose from previous voters' write-in votes. Users with the <em>administer polls</em> permission will be able to override this setting."),
        '#prefix' => '<div class="edit-settings-show-writeins">',
        '#suffix' => '</div>',
      );
      $form['advpoll']['advpoll_electoral_list'] = array(
        '#type' => 'checkbox',
        '#title' => t('Use electoral list by default'),
        '#description' => t('Use an electoral list by default for new polls. Users with the <em>administer polls</em> permission will be able to override this setting.'),
        '#default_value' => variable_get('advpoll_electoral_list_' . $node_type, ADVPOLL_ELECTORAL_LIST),
      );
      $form['advpoll']['advpoll_show_votes'] = array(
        '#type' => 'checkbox',
        '#title' => t('Show individual votes by default'),
        '#description' => t('Let users with appropriate permissions see how each person voted by default for new polls. Users with the <em>administer polls</em> permission will be able to override this setting.'),
        '#default_value' => variable_get('advpoll_show_votes_' . $node_type, ADVPOLL_SHOW_VOTES),
      );
      $view_results = array(
        'always' => t('Always'),
        'aftervote' => t('After user has voted'),
        'afterclose' => t('After voting has closed'),
      );
      $form['advpoll']['advpoll_view_results'] = array(
        '#type' => 'radios',
        '#title' => t('Display results'),
        '#description' => t('Determines when users may view the results of the poll.'),
        '#default_value' => variable_get('advpoll_view_results_' . $node_type, ADVPOLL_VIEW_RESULTS),
        '#options' => $view_results,
      );
      $form['advpoll']['advpoll_use_question'] = array(
        '#type' => 'checkbox',
        '#title' => t('Use question field'),
        '#description' => t('Use a dedicated question field instead of a combined question and title field. It is recommended to rename <em>Title field label</em> above to "Title" if this is checked.'),
        '#default_value' => variable_get('advpoll_use_question_' . $node_type, ADVPOLL_USE_QUESTION),
      );
    }
  }
}

/**
 * Implementation of hook_load().
 *
 * Load the votes and poll-specific data into the node object.
 */
function advpoll_load($node) {
  global $user;
  $poll = db_fetch_object(db_query('SELECT * FROM {advpoll} WHERE nid = %d', $node->nid));
  $result = db_query('SELECT cid, weight, label, writein FROM {advpoll_choices} WHERE nid = %d ORDER BY weight', $node->nid);
  while ($choice = db_fetch_array($result)) {
    $poll->choice[$choice['cid']] = $choice;
  }
  $poll->choices = count($poll->choice);
  $result = db_query("SELECT value FROM {votingapi_cache} WHERE content_type = 'advpoll' AND content_id = %d AND tag = '_advpoll' AND function = 'total_votes'", $node->nid);
  if (db_num_rows($result) > 0) {
    $cache = db_fetch_object($result);
    $poll->votes = $cache->value;
  }
  else {
    $poll->votes = 0;
  }
  list($poll->voted, $poll->cancel_vote) = _advpoll_user_voted($node->nid);
  return $poll;
}

/**
 * Implementation of hook_validate().
 */
function advpoll_validate($node, &$form) {
  global $form_values;
  if ($form_values['op'] == t('Reset votes')) {
    drupal_goto('node/' . $node->nid . '/reset');
  }
  else {

    // Check for at least two choices.
    if (isset($node->choice_area)) {

      // Handle a textarea rather than separate fields per choice.
      $real_choices = count(explode("\n", $node->choice_area));
    }
    else {
      $node->choice = array_values($node->choice);

      // Start keys at 1 rather than 0.
      array_unshift($node->choice, '');
      unset($node->choice[0]);
      $real_choices = 0;
      foreach ($_POST['choice'] as $i => $choice) {
        if ($choice['label'] != '') {
          $real_choices++;
        }
      }
    }
    if ($real_choices < 2) {
      form_set_error("choice][{$real_choices}][label", t('You must fill in at least two choices.'));
    }

    // Validate max choices since it has #DANGEROUS_SKIP_CHECK set to true.
    if ($node->settings['max_choices'] < 0) {
      form_set_error('settings][max_choices]', t('Maximum choices must be a non-negative integer.'));
    }
    if ($node->settings['max_choices'] > $real_choices) {
      form_set_error('settings][max_choices]', t('Maximum choices cannot be larger than the number of choices submitted.'));
    }

    // Validate dates.
    if (!empty($node->settings['start_date']) && strtotime($node->settings['start_date']) <= 0) {
      form_set_error('settings][start_date', t('You have to specify a valid starting date.'));
    }
    if (!empty($node->settings['end_date']) && strtotime($node->settings['end_date']) <= 0) {
      form_set_error('settings][end_date', t('You have to specify a valid ending date.'));
    }
    if (!empty($node->settings['end_date']) && $node->settings['end_date'] < $node->settings['start_date']) {
      form_set_error('settings][end_date', t('Ending date cannot be before the starting date.'));
    }
  }
}

/**
 * Implementation of hook_insert().
 *
 * This is called upon node creation.
 */
function advpoll_insert($node) {
  $mode = _advpoll_get_mode($node->type);
  db_query("INSERT INTO {advpoll} (nid, mode, use_list, active, max_choices, algorithm, show_votes, start_date, end_date, writeins, show_writeins, question) VALUES (%d, '%s', %d, %d, %d, '%s', %d, '%s', '%s', %d, %d, '%s')", $node->nid, $mode, $node->settings['use_list'], !$node->settings['close'], $node->settings['max_choices'], $node->settings['algorithm'], $node->settings['show_votes'], $node->settings['start_date'] ? strtotime($node->settings['start_date']) : 0, $node->settings['end_date'] ? strtotime($node->settings['end_date']) : 0, $node->settings['writeins'], $node->settings['show_writeins'], $node->question);
  $raw_choices = explode("\n", $_POST['choice_area']);
  $choices = array();
  foreach ($raw_choices as $choice_line) {
    $choices[] = array(
      'label' => $choice_line,
    );
  }

  // Insert the choices.
  _advpoll_insert_choices($node->nid, $choices);
}

/**
 * Implementation of hook_update().
 *
 * This is called upon node edition.
 */
function advpoll_update($node) {
  db_query("UPDATE {advpoll} SET active = %d, max_choices = %d, algorithm = '%s', use_list = %d, show_votes = %d, start_date = '%s', end_date = '%s', writeins = %d, show_writeins = %d, question = '%s' WHERE nid = %d", !$node->settings['close'], $node->settings['max_choices'], $node->settings['algorithm'], $node->settings['use_list'], $node->settings['show_votes'], $node->settings['start_date'] ? strtotime($node->settings['start_date']) : 0, $node->settings['end_date'] ? strtotime($node->settings['end_date']) : 0, $node->settings['writeins'], $node->settings['show_writeins'], $node->question, $node->nid);
  _advpoll_insert_choices($node->nid, $_POST['choice']);
  votingapi_recalculate_results('advpoll', $node->nid);
}

/**
 * Implementation of hook_view().
 */
function advpoll_view($node, $teaser = FALSE, $page = FALSE) {
  drupal_add_css(drupal_get_path('module', 'advpoll') . '/advpoll.css', 'module');
  $status = _advpoll_is_active($node, TRUE);

  // Add question to content if defined.
  if ($node->question !== '') {
    $node->content['question'] = array(
      '#weight' => 1,
      '#value' => theme('advpoll_question', check_plain($node->question)),
    );
  }
  if ($node->in_preview) {

    // Previewing a node, so display voting form instead of results.
    $poll = drupal_get_form('advpoll_voting_' . _advpoll_get_mode($node->type) . '_form', $node, $teaser, $page, $status);
  }
  else {
    if (!$node->voted && arg(2) != 'results' && ($status == 'open' || $status == 'pending') || $_POST['op'] == 'Vote') {

      // User hasn't voted, we're not on the results tab and poll is open or
      // opening in the future. Also, we check the $_POST array if the user tried
      // to submit a vote, so we can validate and give an error if the user has
      // already voted on the poll.
      $poll = drupal_get_form('advpoll_voting_' . $node->mode . '_form', $node, $teaser, $page, $status);
      static $add_js = TRUE;
      if ($add_js) {

        // Add javascript for posting voting forms with Ajax.
        drupal_add_js(drupal_get_path('module', 'advpoll') . '/advpoll-vote.js', 'module');
        drupal_add_js(drupal_get_path('module', 'advpoll') . '/jquery.form.js', 'module');
        $add_js = FALSE;
      }
    }
    else {

      // Show results (the user has voted, poll is closed or poll has passed).
      $poll = advpoll_view_results($node, $teaser, $page);
    }
  }
  $node->content['poll'] = array(
    '#weight' => 2,
    '#value' => $poll,
  );
  return node_prepare($node, $teaser);
}

/**
 * Implementation of hook_delete().
 */
function advpoll_delete($node) {
  db_query('DELETE FROM {advpoll} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {advpoll_choices} WHERE nid = %d', $node->nid);
  db_query('DELETE FROM {advpoll_electoral_list} WHERE nid = %d', $node->nid);

  // TODO: These should be converted to a votingapi method eventually.
  db_query("DELETE FROM {votingapi_vote} WHERE content_id = %d AND content_type = 'advpoll'", $node->nid);
  db_query("DELETE FROM {votingapi_cache} WHERE content_id = %d AND content_type = 'advpoll'", $node->nid);
}
function advpoll_page() {

  // List all polls
  $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, c.value ORDER BY n.created DESC";
  $sql = db_rewrite_sql($sql);
  $result = pager_query($sql, 15);
  $output = '<ul>';
  while ($node = db_fetch_object($result)) {
    $output .= '<li>' . l($node->title, 'node/' . $node->nid) . ' - ' . format_plural($node->votes, '1 vote', '@count votes') . ' - ' . (_advpoll_is_active($node) ? t('open') : t('closed')) . '</li>';
  }
  $output .= '</ul>';
  $output .= theme('pager', NULL, 15);
  return $output;
}

/**
 * Implementation of VotingAPI's hook_calculate.
 * 
 * Recalculate results whenever a vote is added or removed.
 */
function advpoll_votingapi_calculate(&$results, $votes, $content_type, $content_id) {

  // Make sure it's an Advanced Poll content type.
  if ($content_type == 'advpoll') {

    // Don't load the node from cache in case the mode or algorithm changed.
    $node = node_load($content_id, NULL, TRUE);
    $mode = _advpoll_get_mode($node->type);
    $function = 'advpoll_calculate_results_' . $mode;
    if (function_exists($function)) {
      $function($results, $votes, $node);
    }
    cache_clear_all();
  }
}

/**
 * Check if a user has voted on a poll.
 *
 * @return Array indicating if user voted and, if so, if the vote is cancellable.
 */
function _advpoll_user_voted($nid) {
  global $user;
  $voted = FALSE;
  $cancel_vote = FALSE;
  if ($user->uid) {

    // Voter is logged in.
    $voted = count(votingapi_get_user_votes('advpoll', $nid)) > 0;
    if ($voted) {
      $cancel_vote = TRUE;
    }
  }
  else {

    // Voter is anonymous.
    // TODO: add options to check cookie rather than IP for anonymous votes.
    // Check if there is already an anonymous vote for this IP.
    $host = $_SERVER['REMOTE_ADDR'] . ($_SERVER['HTTP_X_FORWARDED_FOR'] ? '-' . $_SERVER['HTTP_X_FORWARDED_FOR'] : '');
    $result = db_query("SELECT value FROM {votingapi_vote} WHERE content_id = %d AND content_type = 'advpoll' AND hostname = '%s' AND uid = 0", $nid, $host);
    if (db_num_rows($result) > 0) {
      $voted = TRUE;
      $cancel_vote = TRUE;
    }
  }
  return array(
    $voted,
    $cancel_vote,
  );
}

/**
 * Display the electoral list page.
 */
function advpoll_electoral_list_page() {
  if ($node = node_load(arg(1))) {

    // Bail out if electoral list isn't available for this node.
    if (!$node->use_list) {
      drupal_not_found();
      return;
    }
    drupal_set_title(check_plain($node->title));
    if (user_access('administer polls')) {
      $output .= drupal_get_form('advpoll_electoral_list_form', $node->nid);
    }
    $output .= '<p>' . t('This table lists all the eligible voters for this poll.') . '</p>';
    $header[] = array(
      'data' => t('Voter'),
      'field' => 'u.name',
    );
    $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);
    $table_rows = array();
    while ($voter = db_fetch_object($result)) {
      $row = array(
        theme('username', $voter),
      );
      if (user_access('administer polls')) {
        $row[] = l(t('remove'), 'node/' . $node->nid . '/remove/' . $voter->uid);
      }
      $table_rows[] = $row;
    }
    $output .= theme('table', $header, $table_rows);
    $output .= theme('pager', NULL, 20, 0);
    print theme('page', $output);
  }
  else {
    drupal_not_found();
  }
}
function advpoll_electoral_list_form($nid) {
  $form['electoral_list'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Administer electoral list'),
    '#collapsible' => TRUE,
    '#weight' => 2,
  );
  $form['electoral_list']['add_user'] = array(
    '#type' => 'textfield',
    '#title' => t('Add user'),
    '#size' => 40,
    '#description' => t('Add an individual user to the electoral list.'),
  );

  // Enable autocompletion if user has required permission.
  if (user_access('access user profiles')) {
    $form['electoral_list']['add_user']['#autocomplete_path'] = 'user/autocomplete';
  }

  // List all roles with "vote on polls" permission, but don't include anonymous users.
  $result = db_query("SELECT r.name, r.rid FROM {role} r LEFT JOIN {permission} p ON p.rid = r.rid WHERE p.perm LIKE '%vote on polls%' AND r.rid != 1 ORDER BY r.name");
  $role_options = array(
    0 => t('(Select a role)'),
  );
  while ($role = db_fetch_object($result)) {
    $role_options[$role->rid] = $role->name;
  }
  $form['electoral_list']['add_role'] = array(
    '#type' => 'select',
    '#title' => t('Add users by role'),
    '#description' => t('Only roles that have the "vote on polls" permission are listed.'),
    '#options' => $role_options,
  );
  $form['electoral_list']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Add to electoral list'),
  );
  $form['electoral_list']['reset'] = array(
    '#type' => 'button',
    '#value' => t('Clear electoral list'),
  );
  $form['nid'] = array(
    '#type' => 'hidden',
    '#value' => $nid,
  );
  return $form;
}

/**
 * Remove an individual voter from the electoral list.
 */
function advpoll_remove_voter() {
  $nid = arg(1);
  $uid = arg(3);
  if ($uid && ($node = node_load($nid))) {
    $result = db_query('SELECT name FROM {users} WHERE uid = %d', $uid);
    if (db_num_rows($result) > 0) {
      $user = db_fetch_object($result);
      db_query('DELETE FROM {advpoll_electoral_list} WHERE nid = %d AND uid = %d', $nid, $uid);
      drupal_set_message(t('%user removed from the electoral list.', array(
        '%user' => $user->name,
      )));
    }
    else {
      drupal_set_message(t('No user found with a uid of %uid.', array(
        '%uid' => $uid,
      )));
    }
  }
  drupal_goto('node/' . $node->nid . '/electoral_list');
}

/**
 * Validate changes to the electoral list.
 */
function advpoll_electoral_list_form_validate($form_id, $form_values) {
  if ($form_values['op'] == t('Clear electoral list')) {
    if (user_access('administer polls')) {
      db_query('DELETE FROM {advpoll_electoral_list} WHERE nid = %d', $form_values['nid']);
      drupal_set_message(t('Electoral list cleared.'));
      return;
    }
  }
  $add_user = $form_values['electoral_list']['add_user'];
  if ($add_user) {

    // Check that the user exists
    $result = db_query("SELECT uid FROM {users} WHERE name = '%s'", $add_user);
    if (db_num_rows($result) == 0) {
      form_set_error('electoral_list][add_user', t('User %user does not exist.', array(
        '%user' => $add_user,
      )));
      return FALSE;
    }
  }
}

/**
 * Submit changes to the electoral list.
 */
function advpoll_electoral_list_form_submit($form_id, $form_values) {
  $add_user = $form_values['electoral_list']['add_user'];
  if ($add_user) {
    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);
    drupal_set_message(t('%user added to electoral list.', array(
      '%user' => $add_user,
    )));
  }
  $add_role = $form_values['electoral_list']['add_role'];
  if ($add_role) {

    // Get the current electoral list.
    $result = db_query('SELECT uid FROM {advpoll_electoral_list} WHERE nid = %d', $form_values['nid']);
    $current_list = array(
      0,
    );
    while ($user = db_fetch_object($result)) {
      $current_list[] = $user->uid;
    }
    $user_in_string = implode(',', $current_list);

    // Check if all authenticated users should be added.
    $is_authenticated = db_result(db_query("SELECT COUNT(*) FROM {role} r WHERE r.name = 'authenticated user' AND r.rid = %d", $add_role));
    if ($is_authenticated) {

      // Special case: any authenticated user can vote.
      // Add all current users to electoral list.
      $result = db_query("INSERT INTO {advpoll_electoral_list} (nid, uid) SELECT '%d', u.uid FROM {users} u WHERE u.uid NOT IN('%s')", $form_values['nid'], $user_in_string);
    }
    else {

      // Insert new users into the electoral_list based on the role chosen.
      $result = db_query("INSERT INTO {advpoll_electoral_list} (nid, uid) SELECT '%d', u.uid FROM {users} u LEFT JOIN {users_roles} ur ON u.uid = ur.uid WHERE ur.rid = %d AND u.uid NOT IN('%s')", $form_values['nid'], $add_role, $user_in_string);
    }
    drupal_set_message(format_plural(db_affected_rows($result), 'Added 1 user to the electoral list.', 'Added @count users to the electoral list.'));
  }
}

/**
 * Display the votes page.
 */
function advpoll_votes_page() {
  if ($node = node_load(arg(1))) {
    if (!$node->show_votes) {

      // Advanced Poll is set to not allow viewing of votes.
      drupal_not_found();
      return;
    }
    drupal_set_title(check_plain($node->title));
    $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.');
    $header[] = array(
      'data' => t('Visitor'),
      'field' => 'u.name',
    );
    $header[] = array(
      'data' => t('Vote'),
      '',
    );
    $result = pager_query("SELECT v.uid, v.hostname, v.timestamp, 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 GROUP BY v.uid, v.hostname, v.timestamp, u.name" . tablesort_sql($header), 20, 0, NULL, $node->nid);
    $uids = array();
    $hostnames = array();
    $timestamp = array();
    while ($vote = db_fetch_object($result)) {
      $uids[$vote->uid] = $vote->uid;
      $hostnames[$vote->hostname] = $vote->hostname;
      $timestamps[$vote->timestamp] = $vote->timestamp;
    }
    $rows = array();
    if (count($uids) > 0) {

      // Use db_query()'s placeholder syntax to prevent any potential SQL
      // injection attacks.
      $uid_placeholders = array_fill(0, count($uids), '%d');
      $host_placeholders = array_fill(0, count($hostnames), "'%s'");
      $time_placeholders = array_fill(0, count($timestamps), '%d');

      // Here we have to select votes based on their uid+hostname+timestamp
      // combination because there no unique id for the set of rankings that
      // corresponds to a uesr's full vote on a given poll.
      $query = "SELECT v.vote_id, v.tag, v.uid, v.hostname, v.timestamp, v.value, u.name FROM {votingapi_vote} v LEFT JOIN {users} u ON v.uid = u.uid WHERE v.content_id = %d AND v.uid IN(" . implode(', ', $uid_placeholders) . ") AND v.hostname IN(" . implode(', ', $host_placeholders) . ") AND v.timestamp IN(" . implode(', ', $time_placeholders) . ")" . tablesort_sql($header);
      $parameters = array_merge(array(
        $query,
        $node->nid,
      ), array_values($uids), array_values($hostnames), array_values($timestamps));
      $result = call_user_func_array('db_query', $parameters);
      while ($vote = db_fetch_object($result)) {
        $key = $vote->uid ? $vote->uid : $vote->hostname . '-' . $vote->timestamp;
        $rows[$key]['name'] = $vote->name ? theme('username', $vote) : check_plain($vote->hostname);
        if ($node->type == 'advpoll_ranking') {

          // Need two dimensional results (if equal rankings are allowed).
          $rows[$key]['votes'][$vote->value][] = _advpoll_choice_markup($node->choice[$vote->tag]['label'], $node->format, FALSE);
        }
        else {

          // Just need one dimensional results.
          $rows[$key]['votes'][] = _advpoll_choice_markup($node->choice[$vote->tag]['label'], $node->format, FALSE);
        }
      }
    }
    $separators = array(
      'advpoll_ranking' => ' > ',
      'advpoll_binary' => ', ',
    );

    // Create strings out of each vote.
    $results = array();
    foreach ($rows as $key => $container) {
      $ranking = $container['votes'];
      asort($ranking);
      $rankings = array();
      if ($node->type == 'advpoll_ranking') {

        // Include support for multiple choices having the same ranking.
        foreach ($ranking as $vote => $choices) {
          $rankings[$vote] = implode(' = ', $choices);
        }
      }
      else {

        // Just copy the previous array.
        $rankings = $ranking;
      }
      ksort($rankings);
      $results[$key]['name'] = $rows[$key]['name'];
      $results[$key]['vote'] = implode($separators[$node->type], $rankings);
    }
    $output .= theme('table', $header, $results);
    $output .= theme('pager', NULL, 20, 0);
    print theme('page', $output);
  }
  else {
    drupal_not_found();
  }
}

/**
 * Helper function to abstract view results checking.
 */
function _advpoll_can_view_results($node) {
  $view_results = variable_get('advpoll_view_results_' . $node->type, ADVPOLL_VIEW_RESULTS);
  return !_advpoll_is_active($node) || $node->voted && $view_results == 'aftervote' || $view_results == 'always';

  // All can view
}

/**
 * Helper function to display 'cancel vote' button if user has voted.
 */
function advpoll_cancel_form($nid) {
  $form['#action'] = url('advpoll/cancel/' . $nid);
  $form['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Cancel your vote'),
  );
  return $form;
}

/**
 * Helper function to check if a poll is active.
 */
function _advpoll_is_active($node, $return_status = FALSE) {
  $active = TRUE;
  $status = 'open';
  $start_date = $node->start_date;
  $end_date = $node->end_date;

  // Check if poll is closed.
  if (!$node->active) {
    $active = FALSE;
    $status = 'closed';
  }
  if ($active && $start_date > 0) {

    // Check that start date is in the past.
    if (!($active = time() >= $start_date)) {
      $status = 'pending';
    }
  }
  if ($active && $end_date > 0) {

    // Check that end date is in the future.
    if (!($active = time() < $end_date)) {
      $status = 'passed';
    }
  }
  return $return_status ? $status : $active;
}

/**
 * Insert/update the choices for a poll.
 *
 * Note: we pass choice data via _POST to allow for dynamic addition of choices.
 * Drupal 6 AHAH support will let us switch to a clean implementation.
 */
function _advpoll_insert_choices($nid, $choices) {
  $node = node_load($nid);
  $weight = 0;
  $seen_ids = array();
  foreach ($choices as $index => $choice) {
    if ($choice['label'] != '') {

      // Mark this choice id as being seen.
      $seen_ids[$index] = 1;
      if (!isset($node->choice[$index])) {
        db_query("INSERT INTO {advpoll_choices} (nid, label, weight) VALUES (%d, '%s', %d)", $nid, $choice['label'], $weight);
      }
      else {
        db_query("UPDATE {advpoll_choices} SET label = '%s', weight = %d WHERE cid = %d", $choice['label'], $weight, $index);
      }
      $weight++;
    }
  }

  // Delete any choices that were removed (either dynamically or by submitting
  // a blank label).
  if (isset($node->choice)) {
    foreach ($node->choice as $id => $choice) {
      if (!isset($seen_ids[$id])) {
        db_query('DELETE FROM {advpoll_choices} WHERE cid = %d', $id);
        drupal_set_message(t('Deleted choice %label', array(
          '%label' => $choice['label'],
        )), 'status');

        // We could potentially also delete any votes for this choice, but let's
        // leave them in the database so that one can go back and check if anyone
        // voted for a deleted choice.
      }
    }
  }
}
function _advpoll_get_mode($node_type) {
  if ($node_type) {
    $mode = explode('advpoll_', $node_type, 2);
    return $mode[1];
  }
  else {
    drupal_set_message(t('No mode specified for content type %type.', array(
      '%type' => $node_type,
    )), 'error');
    return '';
  }
}

/**
 * Callback to display a reset votes confirmation form.
 */
function advpoll_reset_confirm() {
  global $form_values;
  $edit = $form_values['edit'];
  $edit['nid'] = $edit['nid'] ? $edit['nid'] : arg(1);
  $node = node_load($edit['nid']);
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  $output = confirm_form($form, t('Are you sure you want to reset the votes for %title?', array(
    '%title' => $node->title,
  )), 'node/' . $node->nid, t('This action cannot be undone.'), t('Reset votes'), t('Cancel'));
  return $output;
}

/**
 * Reset votes once the confirmation is given.
 */
function advpoll_reset_confirm_submit($form_id, $form_values) {
  if ($form_values['confirm']) {
    $nid = arg(1);
    if ($node = node_load($nid)) {

      // Delete any votes for the poll.
      db_query("DELETE FROM {votingapi_vote} WHERE content_type = 'advpoll' AND content_id = %d", $node->nid);

      // Delete any write-in choices.
      db_query('DELETE FROM {advpoll_choices} WHERE writein = 1 AND nid = %d', $node->nid);
      votingapi_recalculate_results('advpoll', $node->nid);
      drupal_set_message(t('Votes have been reset.'));
      drupal_goto('node/' . $node->nid);
    }
  }
  return '';
}
function _advpoll_block_resultslink($node) {
  return array(
    'title' => t('Results'),
    'href' => 'node/' . $node->nid . '/results',
    'attributes' => array(
      'title' => t('View the current poll results.'),
    ),
  );
}

/**
 * Theme an optional question.
 */
function theme_advpoll_question($question) {
  return '<p class="poll-question">' . $question . '</p>';
}
function theme_advpoll_results($title, $results, $votes, $links, $nid, $voted, $cancel_vote) {
  $output = '<div class="poll">';
  if ($results) {
    $output .= $results;
    $output .= '<div class="total">' . t('Total voters: %votes', array(
      '%votes' => $votes,
    )) . '</div>';
  }
  else {
    $output .= '<p class="message">' . t('No votes have been recorded for this poll.') . '</p>';
  }
  $output .= '</div>';
  return $output;
}
function _advpoll_show_cancel_form($node) {
  $output = '';
  if ($node->voted && $node->cancel_vote && user_access('cancel own vote') && _advpoll_is_active($node)) {
    $output .= drupal_get_form('advpoll_cancel_form', $node->nid);
  }
  return $output;
}
function theme_advpoll_bar($title, $percentage, $votes, $choice = NULL) {
  $output = '<div class="text">' . $title . ($choice && $choice['writein'] ? ' ' . t('(write-in)') : '') . '</div>';
  $output .= '<div class="bar"><div style="width: ' . $percentage . '%;" class="foreground"></div></div>';
  $output .= '<div class="percent">' . $percentage . '% <span class="votes">(' . $votes . ')</span></div>';
  return $output;
}

/**
 * Callback for results tab.
 */
function advpoll_results() {
  if ($node = node_load(arg(1))) {
    drupal_set_title(check_plain($node->title));
    return node_show($node, 0);
  }
  else {

    // The url does not provide the appropriate node id.
    drupal_not_found();
  }
}
function _advpoll_vote_response($node, $form_values) {
  $msg = t('Your vote was registered.');

  // Ajax response
  if ($form_values['ajax']) {

    // Unset the array of choices so duplicates aren't shown.
    unset($node->choice);

    // Get all choices from database. This is necessary to get information about
    // newly submitted write-in choices.
    $result = db_query('SELECT cid, weight, label, writein FROM {advpoll_choices} WHERE nid = %d ORDER BY weight', $node->nid);
    while ($choice = db_fetch_array($result)) {
      $node->choice[$choice['cid']] = $choice;
    }

    // Update the number of choices.
    $node->choices = count($poll->choice);

    // Get updated total number of votes from database.
    $result = db_query("SELECT value FROM {votingapi_cache} WHERE content_type = 'advpoll' AND content_id = %d AND tag = '_advpoll' AND function = 'total_votes'", $node->nid);
    if (db_num_rows($result) > 0) {
      $cache = db_fetch_object($result);
      $node->votes = $cache->value;
    }
    else {
      $node->votes = 0;
    }
    list($node->voted, $node->cancel_vote) = _advpoll_user_voted($node->nid);
    $ajax_output .= advpoll_view_results($node, NULL, NULL);

    // Remove linebreaks as they will break jQuery's insert-HTML methods.
    $ajax_output = str_replace("\n", '', $ajax_output);
    drupal_set_header('Content-Type: text/plain; charset=utf-8');
    print drupal_to_js(array(
      'statusMsgs' => '<div class="messages status">' . $msg . '</div>',
      'response' => $ajax_output,
    ));
    exit;
  }
  else {
    drupal_set_message($msg);
  }
}

/**
 * Show results of the vote.
 *
 * This calls the appropriate vote results function, depending on the
 * mode. It will call advpoll_view_results_$mode, similarly to
 * advpoll_view_voting().
 */
function advpoll_view_results(&$node, $teaser, $page) {
  $mode = _advpoll_get_mode($node->type);
  if (_advpoll_can_view_results($node)) {
    if (function_exists('advpoll_view_results_' . $mode)) {
      $results = call_user_func('advpoll_view_results_' . $mode, $node, $teaser, $page);
      $output .= theme('advpoll_results', check_plain($node->title), $results['results'], $results['votes'], $node->links, $node->nid, $node->voted, $node->cancel_vote);
    }
  }
  $output .= _advpoll_show_cancel_form($node);
  return $output;
}

/**
 * Callback for canceling a vote.
 */
function advpoll_cancel($nid) {
  global $user;
  $nid = arg(2);
  if ($nid && ($node = node_load(array(
    'nid' => $nid,
  )))) {
    if (isset($node->type) && $node->voted && _advpoll_is_active($node)) {
      if ($user->uid && count($user_vote = votingapi_get_user_votes('advpoll', $node->nid)) > 0) {
        votingapi_unset_vote('advpoll', $node->nid, $user->uid);
      }
      else {
        $host = $_SERVER['HTTP_X_FORWARDED_FOR'] ? $_SERVER['HTTP_X_FORWARDED_FOR'] : $_SERVER['REMOTE_ADDR'];
        db_query("DELETE FROM {votingapi_vote} WHERE content_id = %d and hostname = '%s' AND uid = 0", $node->nid, $host);
        votingapi_recalculate_results('advpoll', $nid);
      }
      $mode = _advpoll_get_mode($node->type);
      $function = 'advpoll_cancel_' . $mode;
      if (function_exists($function)) {
        $function($node, $user_vote);
      }
      drupal_set_message(t('Your vote was canceled.'));
    }
    else {
      drupal_set_message(t('You are not allowed to cancel an invalid choice.'), 'error');
    }
    drupal_goto('node/' . $nid);
  }
  else {
    drupal_not_found();
  }
}

/**
 * Check if user is eligible to vote in this poll.
 */
function advpoll_eligible($node, $uid = NULL) {
  global $user;
  if (!isset($uid)) {
    $uid = $user->uid;
  }
  if ($node->use_list) {
    $eligible = db_result(db_query('SELECT COUNT(*) FROM {advpoll_electoral_list} WHERE nid = %d AND uid = %d', $node->nid, $uid));
  }
  else {
    $eligible = user_access('vote on polls');
  }
  return $eligible;
}
function advpoll_algorithms($mode) {
  return call_user_func('advpoll_algorithms_' . $mode);
}

/**
 * Load the available modes.
 *
 * This scans the modes subdirectory to find mode.inc files, where
 * mode is considered to be the mode name. Found files are loaded and
 * added to the mode list.
 */
function _advpoll_list_modes() {
  static $advpoll_modes;
  if (!$advpoll_modes) {
    $files = file_scan_directory(dirname(__FILE__) . '/modes/', '^([^\\.].*)\\.inc$', array(
      '.',
      '..',
      'CVS',
    ), 0, FALSE);
    foreach ($files as $file) {
      require_once $file->filename;
      $mode = $file->name;
      if (function_exists('advpoll_info_' . $mode)) {
        $advpoll_modes[$mode] = call_user_func('advpoll_info_' . $mode);
      }
    }
  }
  return $advpoll_modes;
}

/**
 * Helper function for rich text in choices.
 *  
 * We strip out the paragraphs which are not allowed within the label element
 * and created by check_markup() when "Line break converter"-filter is used.
 */
function _advpoll_choice_markup($text, $format = FILTER_FORMAT_DEFAULT, $check = TRUE) {
  $text = check_markup($text, $format, $check);
  $text = str_replace(array(
    '<p>',
    '</p>',
  ), '', $text);
  $text = trim($text);
  return $text;
}

/**
 * Wrapper function around form_set_error() to support validating of Ajax voting.
 */
function _advpoll_form_set_error($name = NULL, $message = '', $ajax = FALSE) {
  if ($ajax) {
    drupal_set_header('Content-Type: text/plain; charset=utf-8');
    print drupal_to_js(array(
      'errors' => '<div class="messages error">' . $message . '</div>',
    ));
    exit;
  }
  else {
    return form_set_error('choice[', $message);
  }
}

/**
 * Voting form validation logic specific to writeins. This has been abstracted 
 * away from includes in the modes directory.
 */
function _advpoll_writeins_voting_form_validate($node, $writein_option, $writein_text, $ajax) {

  // Do write-in specific checks if write-ins are enabled and user has permission.
  if ($node->writeins && user_access('add write-ins')) {

    // Something is in the write-in textbox.
    if ($writein_text) {
      $writein_choice_lower = strtolower($writein_text);
      foreach ($node->choice as $i => $value) {

        // Check that user isn't writing in an existing visible choice. (User is
        // writing in an existing choice and either write-ins are all being
        // displayed or the existing choice is not a write-in).
        if (strtolower($val['label']) == $writein_choice_lower && ($node->show_writeins || !$value['writein'])) {
          _advpoll_form_set_error('writein_choice', t("A write-in vote can not be for an existing choice. Select the choice's option instead."), $ajax);
        }
      }
    }

    // The write-in option is selected and there is nothing in the write-in textbox.
    if ($writein_option && !$writein_text) {
      _advpoll_form_set_error('writein_choice', t('If the "write-in" option is selected, a choice must be written in.'), $ajax);
    }

    // The write-in option is not selected, but there is something in the write-in textbox.
    if (!$writein_option && $writein_text) {
      _advpoll_form_set_error('writein_choice', t('If a choice is written in, the "write-in" option must be selected.'), $ajax);
    }
  }
}

/**
 * Voting form submission logic specific to writeins. This has been abstracted 
 * away from includes in the modes directory.
 */
function _advpoll_writeins_voting_form_submit($node, $form_values, &$votes, $vote_value) {

  // A write-in vote is being made.
  if ($form_values['writein_choice']) {

    // Check if someone has previously voted for this choice.
    $result = db_query("SELECT cid FROM {advpoll_choices} WHERE nid = %d AND LOWER(label) = LOWER('%s')", $node->nid, $form_values['writein_choice']);

    // If there's more than one match, redo the query, being more exact.
    if (db_num_rows($result) > 1) {
      $result = db_query("SELECT cid FROM {advpoll_choices} WHERE nid = %d AND label = '%s'", $node->nid, $form_values['writein_choice']);
    }

    // If there is at least one match, add a vote for the first one returned. It
    // should be rare to find more than one choice for any one node with a given
    // label.
    if (db_num_rows($result)) {
      $obj = db_fetch_object($result);
      $existing_choice = $obj->cid;

      // Set a vote
      $vote = new stdClass();
      $vote->value = $vote_value;
      $vote->tag = $existing_choice;
      $vote->value_type = 'option';
      $votes[] = $vote;
    }
    else {

      // Get highest weight for this node.
      $highest_weight = db_result(db_query("SELECT MAX(weight) FROM {advpoll_choices} WHERE nid = %d", $node->nid));
      $next_weight = $highest_weight ? $highest_weight + 1 : 1;

      // Insert new choice. We do a check_plain() on the label to ensure that
      // there is no possibility of insecure data getting into the database;
      // if HTML is needed the admin can edit the writein choice manually.
      db_query("INSERT INTO {advpoll_choices} (nid, label, weight, writein) VALUES (%d, '%s', %d, 1)", $node->nid, check_plain($form_values['writein_choice']), $next_weight);

      // Get newest choice id.
      $next_cid = db_result(db_query("SELECT cid FROM {advpoll_choices} WHERE nid = %d AND label = '%s'", $node->nid, $form_values['writein_choice']));

      // Add vote
      $vote = new stdClass();
      $vote->value = $vote_value;
      $vote->tag = $next_cid;
      $vote->value_type = 'option';
      $votes[] = $vote;
    }
  }
}
function advpoll_writeins_page() {
  $node = node_load(arg(1));
  drupal_set_title(check_plain($node->title));
  $output .= drupal_get_form('advpoll_writein_promote_form', $node);
  $output .= drupal_get_form('advpoll_writein_merge_form', $node);
  echo theme('page', $output);
}
function advpoll_writein_merge_form($node) {
  $form = array();
  $form['fieldset'] = array(
    '#type' => 'fieldset',
    '#collapsible' => FALSE,
    '#title' => t('Merge write-ins'),
  );
  $form['fieldset']['note'] = array(
    '#value' => '<div class="description">' . t('This will delete the write-in and change any votes for it into votes for the selected chocie.') . '</div>',
  );
  $form['fieldset']['merge'] = array(
    '#prefix' => '<div class="container-inline">' . t('Merge') . ' ',
    '#suffix' => '</div>',
  );
  $writein_list = array();
  $choice_list = array();
  foreach ($node->choice as $index => $choice) {
    $choice_list[$index] = $choice['label'];
    if ($choice['writein']) {
      $writein_list[$index] = $choice['label'];
    }
  }
  $form['fieldset']['merge']['source'] = array(
    '#type' => 'select',
    '#options' => $writein_list,
  );
  $form['fieldset']['merge']['into'] = array(
    '#value' => t(' into '),
  );
  $form['fieldset']['merge']['destination'] = array(
    '#type' => 'select',
    '#options' => $choice_list,
  );
  $form['fieldset']['merge']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Merge'),
  );
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  return $form;
}
function advpoll_writein_merge_form_validate($form_id, $form_values) {
  if ($form_values['source'] == $form_values['destination']) {
    form_set_error('destination', t('The write-in cannot be merged into itself.'));
  }
}
function advpoll_writein_merge_form_submit($form_id, $form_values) {

  // Get a list of votes in this node.
  $raw_votes = db_query('SELECT * FROM {votingapi_vote} WHERE content_id = %d', $form_values['nid']);
  $voters = array();
  $affected_voters = array();
  while ($vote = db_fetch_object($raw_votes)) {
    $key = $vote->uid . '-' . $vote->hostname;
    if (!isset($voters[$key])) {
      $voters[$key] = array();
    }
    array_push($voters[$key], $vote);
    if ($vote->tag == $form_values['source']) {

      // This voter is affected by the merge; save the index of the source vote.
      $affected_voters[$key] = count($voters[$key]) - 1;
    }
  }

  // Now fix the affected voters.
  foreach ($affected_voters as $key => $source_index) {

    // Find out if they voted for the destination or not.
    $voted_for_destination = FALSE;
    foreach ($voters[$key] as $index => $vote) {
      if ($vote->tag == $form_values['destination']) {
        $voted_for_destination = TRUE;
        break;
      }
    }
    if ($voted_for_destination) {

      // Since they already voted for the destination choice,  delete the vote
      // for the source.
      db_query('DELETE FROM {votingapi_vote} WHERE vote_id = %d AND tag = %d', $voters[$key][$index]->vote_id, $form_values['source']);
    }
    else {

      // They didn't already vote for the destination, so transfer the vote for
      // the source to the destination.
      db_query('UPDATE {votingapi_vote} SET tag = %d WHERE vote_id = %d AND tag = %d', $form_values['destination'], $voters[$key][$index]->vote_id, $form_values['source']);
    }
  }

  // Delete the merged choice.
  db_query('DELETE FROM {advpoll_choices} WHERE cid = %d', $form_values['source']);
  votingapi_recalculate_results('advpoll', $form_values['nid']);
  drupal_set_message(t('Write-in merged.'));

  // Unset destination form element so that drupal_goto() doesn't use it
  // mistakenly.
  unset($_REQUEST['destination']);
  drupal_goto('node/' . $form_values['nid'] . '/writeins');
}
function advpoll_writein_promote_form($node) {
  $form = array();
  $form['fieldset'] = array(
    '#type' => 'fieldset',
    '#collapsible' => FALSE,
    '#title' => t('Promote write-ins'),
  );
  $form['fieldset']['note'] = array(
    '#value' => '<p class="description">' . t('Write-ins can be converted to regular choices. This is useful if users cannot see past write-ins but you want to promote specific write-ins so that they can be seen by users who vote in the future.') . '</p>',
  );
  $writein_list = array();
  foreach ($node->choice as $index => $choice) {
    if ($choice['writein']) {
      $writein_list[$index] = $choice['label'];
    }
  }
  $form['fieldset']['promote'] = array(
    '#type' => 'checkboxes',
    '#options' => $writein_list,
  );
  $form['fieldset']['submit'] = array(
    '#type' => 'submit',
    '#value' => t('Promote'),
  );
  $form['nid'] = array(
    '#type' => 'value',
    '#value' => $node->nid,
  );
  return $form;
}
function advpoll_writein_promote_form_submit($form_id, $form_values) {
  if (count($form_values['promote'])) {
    db_query('UPDATE {advpoll_choices} SET writein = 0 WHERE nid = %d AND cid IN(%s)', $form_values['nid'], implode(', ', $form_values['promote']));
    drupal_set_message(format_plural(count($form_values['promote']), 'Write-in promoted.', 'Write-ins promoted.'));
  }
  drupal_goto('node/' . $form_values['nid']);
}

Functions

Namesort descending Description
advpoll_access Implementation of hook_access().
advpoll_algorithms
advpoll_block Implementation of hook_block().
advpoll_cancel Callback for canceling a vote.
advpoll_cancel_form Helper function to display 'cancel vote' button if user has voted.
advpoll_delete Implementation of hook_delete().
advpoll_electoral_list_form
advpoll_electoral_list_form_submit Submit changes to the electoral list.
advpoll_electoral_list_form_validate Validate changes to the electoral list.
advpoll_electoral_list_page Display the electoral list page.
advpoll_eligible Check if user is eligible to vote in this poll.
advpoll_form Implementation of hook_form().
advpoll_form_alter Implementation of hook_form_alter().
advpoll_help Implementation of hook_help().
advpoll_init Implementation of hook_init().
advpoll_insert Implementation of hook_insert().
advpoll_latest_poll
advpoll_load Implementation of hook_load().
advpoll_menu Implementation of hook_menu().
advpoll_node_info Implementation of hook_node_info().
advpoll_page
advpoll_perm Implementation of hook_perm().
advpoll_remove_voter Remove an individual voter from the electoral list.
advpoll_reset_confirm Callback to display a reset votes confirmation form.
advpoll_reset_confirm_submit Reset votes once the confirmation is given.
advpoll_results Callback for results tab.
advpoll_update Implementation of hook_update().
advpoll_validate Implementation of hook_validate().
advpoll_view Implementation of hook_view().
advpoll_view_results Show results of the vote.
advpoll_votes_page Display the votes page.
advpoll_votingapi_calculate Implementation of VotingAPI's hook_calculate.
advpoll_writeins_page
advpoll_writein_merge_form
advpoll_writein_merge_form_submit
advpoll_writein_merge_form_validate
advpoll_writein_promote_form
advpoll_writein_promote_form_submit
theme_advpoll_bar
theme_advpoll_block_latest_poll Content of the block, as returned by advpoll_block('view').
theme_advpoll_question Theme an optional question.
theme_advpoll_results
_advpoll_block_resultslink
_advpoll_can_view_results Helper function to abstract view results checking.
_advpoll_choice_markup Helper function for rich text in choices.
_advpoll_form_set_error Wrapper function around form_set_error() to support validating of Ajax voting.
_advpoll_get_mode
_advpoll_insert_choices Insert/update the choices for a poll.
_advpoll_is_active Helper function to check if a poll is active.
_advpoll_list_modes Load the available modes.
_advpoll_show_cancel_form
_advpoll_user_voted Check if a user has voted on a poll.
_advpoll_vote_response
_advpoll_writeins_voting_form_submit Voting form submission logic specific to writeins. This has been abstracted away from includes in the modes directory.
_advpoll_writeins_voting_form_validate Voting form validation logic specific to writeins. This has been abstracted away from includes in the modes directory.

Constants