uid == $account->uid && user_access('edit own polls', $account))) { 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() { global $user; $modes = _advpoll_list_modes(); $menu = array(); $menu['advpoll/cancel/%node'] = array( 'title' => 'Cancel Vote', 'page callback' => 'advpoll_cancel', 'page arguments' => array(2), 'access arguments' => array('cancel own vote'), 'type' => MENU_CALLBACK, 'file' => 'advpoll.admin.inc', ); $menu['advpoll/js_vote'] = array( 'title' => 'Vote via JavaScript', 'page callback' => 'advpoll_js_vote', 'access arguments' => array('vote on polls'), // TODO: be more specific here. 'type' => MENU_CALLBACK, ); $menu['advpoll/js_more_choices'] = array( 'title' => 'More Choices via JavaScript', 'page callback' => 'advpoll_js_more_choices', 'access arguments' => array('access content'), // TODO: be more specific here. 'type' => MENU_CALLBACK, ); $menu['polls'] = array( 'title' => 'Advanced Polls', 'page callback' => 'advpoll_page', 'access arguments' => array('access content'), 'type' => MENU_SUGGESTED_ITEM, 'file' => 'advpoll.pages.inc', ); $menu['node/%node/results'] = array( 'title' => 'Results', 'page callback' => 'advpoll_results_page', 'page arguments' => array(1), 'access callback' => '_advpoll_results_access', 'access arguments' => array(1), 'weight' => 3, 'type' => MENU_LOCAL_TASK, 'file' => 'advpoll.pages.inc', ); $menu['node/%node/votes'] = array( 'title' => 'Votes', 'page callback' => 'advpoll_votes_page', 'page arguments' => array(1), 'access callback' => '_advpoll_votes_access', 'access arguments' => array(1), 'weight' => 3, 'type' => MENU_LOCAL_TASK, 'file' => 'advpoll.pages.inc', ); // Show electoral list tab if using the functionality. $menu['node/%node/electoral_list'] = array( 'title' => 'Electoral list', 'page callback' => 'advpoll_electoral_list_page', 'page arguments' => array(1), 'access callback' => '_advpoll_electoral_list_access', 'access arguments' => array(1), 'weight' => 3, 'type' => MENU_LOCAL_TASK, 'file' => 'advpoll.pages.inc', ); // Allow voters to be removed. $menu['node/%node/remove'] = array( 'page callback' => 'advpoll_remove_voter', 'page arguments' => array(1), 'access arguments' => array('administer polls'), 'weight' => 3, 'type' => MENU_CALLBACK, 'file' => 'advpoll.admin.inc', ); // Allow votes to be cleared. $menu['node/%node/votes/clear'] = array( 'page callback' => 'advpoll_clear_votes_page', 'page arguments' => array(1), 'access callback' => '_advpoll_clear_votes_access', 'access arguments' => array(1), 'weight' => 3, 'type' => MENU_LOCAL_TASK, 'file' => 'advpoll.pages.inc', ); // Show the write-ins tab if there is at least one. $menu['node/%node/writeins'] = array( 'title' => 'Write-ins', 'page callback' => 'advpoll_writeins_page', 'page arguments' => array(1), 'access callback' => '_advpoll_writeins_access', 'access arguments' => array(1), 'weight' => 3, 'type' => MENU_LOCAL_TASK, 'file' => 'advpoll.pages.inc', ); return $menu; } /** * Implementation of hook_init(). */ function advpoll_init() { // Use poll.module's stylesheet, no need to duplicate at this point. drupal_add_css(drupal_get_path('module', 'poll') .'/poll.css'); // Load the mode include files. _advpoll_list_modes(); } /** * Results access callback. */ function _advpoll_results_access($node) { return strstr($node->type, 'advpoll_') && _advpoll_is_active($node) && $node->votes > 0 && !$node->voted && _advpoll_can_view_results($node); } /** * Electorial list access callback. */ function _advpoll_electoral_list_access($node) { return user_access('access electoral list') && strstr($node->type, 'advpoll_') && $node->use_list; } /** * Votes access callback. */ function _advpoll_votes_access($node) { return strstr($node->type, 'advpoll_') && $node->votes > 0 && ((user_access('inspect all votes') && $node->show_votes) || user_access('administer polls')); } /** * Clear votes access callback. */ function _advpoll_clear_votes_access($node) { return strstr($node->type, 'advpoll_') && $node->votes > 0 && user_access('administer polls'); } /** * Writeins access callback. */ function _advpoll_writeins_access($node) { if (!strstr($node->type, 'advpoll_') || !user_access('administer polls')) { return FALSE; } foreach ($node->choice as $choice) { if ($choice['writein']) { // Has at least one write-in choice. return TRUE; } } return FALSE; } /** * 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 .= '

'. l($node->title, 'node/'. $node->nid) .'

'; $output .= drupal_render($node->content); if ($node->voted) { $output .= '

'. l(t('Older polls'), 'polls', array('class' => 'old-polls', 'title' => t('View the list of polls on this site.'))) .'

'; } } 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_state) { $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', '#maxlength' => 255, '#title' => check_plain($type->title_label), '#required' => TRUE, '#default_value' => $node->title, ); if ($type->has_body) { $form['body_field'] = node_body_field($node, $type->body_label, $type->min_word_count); } if (isset($form_state['values']['choices'])) { $choices = $form_state['values']['choices']; if ($form_state['values']['more_choices']) { $choices *= 2; } } else { $choices = max(2, isset($node->choice) && 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' => '
', '#suffix' => '
', '#tree' => TRUE, '#weight' => 1, ); if ($editing) { $form['choice']['choice_note'] = array( '#value' => '
'. t('Note: adding or removing choices after voting has begun is not recommended.') .'
', ); } $form['choice']['more_choices'] = array( '#type' => 'checkbox', '#title' => t('Need more choices'), '#value' => 0, '#parents' => array('more_choices'), // Don't pollute $form['choice'] '#prefix' => '
', '#suffix' => '
', '#description' => t("If the number of choices above isn't enough, click hereto add more choices."), '#weight' => 1, /* '#submit' => array('advpoll_more_choices_submit'), // If no JavaScript. '#ahah' => array( 'path' => 'advpoll/js_more_choices', 'wrapper' => 'poll-choices', 'method' => 'replace', 'effect' => 'fade', ), */ ); // First, loop through any currently existing choices. $current_choices = 0; $default_choices = variable_get('advpoll_choices_'. $type->type, ''); 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); } $max_points_list = array(); for ($i = 2; $i <= 20; $i++) { $max_points_list[$i] = $i; } // Hide this option if it is a multirate poll as it is not used. $form['settings']['max_choices'] = array( '#type' => ($node->type != 'advpoll_multirate') ? 'select' : 'hidden', '#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, '#description' => t('Limits the total number of choices voters may select.') ); // Hide this option if it is not a multirate poll as it is not used otherwise $form['settings']['max_points'] = array( '#type' => ($node->type == 'advpoll_multirate') ? 'select' : 'hidden', '#title' => t('Maximum points'), '#default_value' => isset($node->max_points) ? $node->max_points : variable_get('advpoll_max_points_' . $type->type, ADVPOLL_MAX_POINTS), '#options' => $max_points_list, '#description' => t('The maximum number of points a user can give each criteria.') ); if ($node->type == 'advpoll_multirate') { $form['settings']['display_avg_results'] = array( '#type' => 'checkbox', '#title' => t('Display average results'), '#description' => t("If checked, the results displayed with be the averages of all the ratings, otherwise the results will be totals of all the ratings."), '#default_value' => isset($node->display_avg_results) ? $node->display_avg_results : ADVPOLL_DISPLAY_AVG_RESULTS, ); $form['settings']['display_overall_rating'] = array( '#type' => 'checkbox', '#title' => t('Display overall rating'), '#description' => t("If checked, an extra 'Overall' rating will be shown with the poll results, which is the average of all the criteria. Users don't give a rating for this, it is automatically calculated from their ratings of the other criteria."), '#default_value' => isset($node->display_overall_rating) ? $node->display_overall_rating : ADVPOLL_DISPLAY_OVERALL_RATING, ); $form['settings']['low_rating_label'] = array( '#type' => 'textfield', '#title' => t('Low rating label'), '#description' => t('The label you want for the lowest possible rating for a given criteria. Defaults to %default.', array('%default' => ADVPOLL_LOW_RATING_LABEL)), '#maxlength' => 32, '#default_value' => isset($node->low_rating_label) ? $node->low_rating_label : ADVPOLL_LOW_RATING_LABEL, ); $form['settings']['high_rating_label'] = array( '#type' => 'textfield', '#title' => t('High rating label'), '#description' => t('The label you want for the highest possible rating for a given criteria. Defaults to %default.', array('%default' => ADVPOLL_HIGH_RATING_LABEL)), '#maxlength' => 32, '#default_value' => isset($node->high_rating_label) ? $node->high_rating_label : ADVPOLL_HIGH_RATING_LABEL, ); $form['settings']['overall_rating_label'] = array( '#type' => 'textfield', '#title' => t('Overall rating label'), '#description' => t('The label you want for the overall rating. Defaults to %default.', array('%default' => ADVPOLL_OVERALL_RATING_LABEL)), '#maxlength' => 32, '#default_value' => isset($node->overall_rating_label) ? $node->overall_rating_label : ADVPOLL_OVERALL_RATING_LABEL, ); } $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' => '
'. t('The settings below are only available for users with the administer polls permission.') .'
', ); $form['settings']['writeins'] = array( '#type' => ($node->type != 'advpoll_multirate') ? 'checkbox' : 'hidden', '#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' => ($node->type != 'advpoll_multirate') ? 'checkbox' : 'hidden', '#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' => '
', '#suffix' => '
', ); $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, ); } 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, ); } } return $form; } /** * Process advpoll_more_choices form submissions. */ function advpoll_more_choices_submit($form, &$form_state) { // Based on poll.module node_form_submit_build_node($form, $form_state); if ($form_state['values']['more_choices']) { $n = $_GET['q'] == 'advpoll/js_more_choices' ? 1 : 5; $form_state['choice_count'] = count($form_state['values']['choice']) + $n; } } /** * Implementation of hook_form_alter(). */ function advpoll_form_alter(&$form, $form_state, $form_id) { 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 administer polls 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 administer polls permission will be able to override this setting."), '#prefix' => '
', '#suffix' => '
', ); $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 administer polls 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 administer polls 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 Title field label 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) { $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); $poll->choice = array(); $poll->writein_choices = 0; while ($choice = db_fetch_array($result)) { $poll->choice[$choice['cid']] = $choice; if ($choice['writein'] == 1) { $poll->writein_choices++; } } $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 ($cache = db_fetch_object($result)) { // Found total votes in the cache. $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(). * * Validate the editing of an advpoll node. */ function advpoll_validate($node, &$form) { // Use form_set_error for any errors. $node->choice = array_values($node->choice); // TODO: verify if this hack is still needed in Drupal 6. // Start keys at 1 rather than 0. array_unshift($node->choice, ''); unset($node->choice[0]); // Check for at least two choices. $real_choices = 0; // TODO: take out _POST 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, max_points, display_overall_rating, display_avg_results, low_rating_label, high_rating_label, overall_rating_label) VALUES (%d, '%s', %d, %d, %d, '%s', %d, '%s', '%s', %d, %d, '%s', %d, %d, %d, '%s', '%s', '%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'], isset($node->question) ? $node->question : '', $node->settings['max_points'], $node->settings['display_overall_rating'], $node->settings['display_avg_results'], $node->settings['low_rating_label'], $node->settings['high_rating_label'], $node->settings['overall_rating_label']); // Insert the choices. _advpoll_insert_choices($node); } /** * 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', max_points = %d, display_overall_rating = %d, display_avg_results = %d, low_rating_label = '%s', high_rating_label = '%s', overall_rating_label = '%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'], isset($node->question) ? $node->question : '', $node->settings['max_points'], $node->settings['display_overall_rating'], $node->settings['display_avg_results'], $node->settings['low_rating_label'], $node->settings['high_rating_label'], $node->settings['overall_rating_label'], $node->nid); _advpoll_insert_choices($node); 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->build_mode == NODE_BUILD_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')) { // OLD: 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('misc/jquery.form.js', 'module'); $add_js = FALSE; } } else { // Show results (the user has voted, poll is closed or poll has passed). if (user_access('show vote results')) { $poll = advpoll_view_results($node, $teaser, $page); } elseif (user_access('cancel own vote')) { $poll = _advpoll_show_cancel_form($node); } } $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); } /** * Implementation of VotingAPI's hook_calculate. * * Recalculate results whenever a vote is added or removed. */ function advpoll_votingapi_results_alter(&$cache, $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($cache, $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_select_votes(array('uid' => $user->uid, 'content_id' => $nid))); if ($voted) { $cancel_vote = TRUE; } } else { // Voter is anonymous. $voted = count(votingapi_select_votes(array('vote_source' => ip_address(), 'content_id' => $nid, 'uid' => 0))); if ($voted) { // Found a vote in the database. $cancel_vote = TRUE; } } return array($voted, $cancel_vote); } /** * Build electorial list form. */ function advpoll_electoral_list_form(&$form_state, $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; } /** * Validate changes to the electoral list. */ function advpoll_electoral_list_form_validate($form, &$form_state) { if ($form_state['values']['op'] == t('Clear electoral list')) { if (user_access('administer polls')) { db_query('DELETE FROM {advpoll_electoral_list} WHERE nid = %d', $form_state['values']['nid']); drupal_set_message(t('Electoral list cleared.')); return; } } $add_user = $form_state['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_fetch_object($result)) { form_set_error('electoral_list][add_user', t('User %user does not exist.', array('%user' => $add_user))); return FALSE; } } } /** * Process advpoll_electorial_list form submissions. * * Submit changes to the electoral list. */ function advpoll_electoral_list_form_submit($form, &$form_state) { $add_user = $form_state['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_state['values']['nid'], $add_user); drupal_set_message(t('%user added to electoral list.', array('%user' => $add_user))); } $add_role = $form_state['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_state['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_state['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_state['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.')); } } /** * 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 is closed || ($node->voted && $view_results == 'aftervote') // User voted || ($view_results == 'always')); // All can view } /** * Helper function to display 'cancel vote' button if user has voted. */ function advpoll_cancel_form(&$form_state, $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; } /** * Process advpoll_binary_node form submissions. * * Update the choices added or removed when editing a binary poll */ function advpoll_binary_node_form_submit($form, &$form_state) { // Get the submitted (modified) choices from _POST and replace them in the form values // Note: we pass choice data via _POST to allow for dynamic addition of choices. // TODO: Implement AHAH support for advpoll node form if (isset($_POST['choice'])) { $form_state['values']['choice'] = $_POST['choice']; } } /** * Process advpoll_ranking_node form submissions. * * Update the choices added or removed when editing a ranking poll */ function advpoll_ranking_node_form_submit($form, &$form_state) { // Get the submitted (modified) choices from _POST and replace them in the form values // Note: we pass choice data via _POST to allow for dynamic addition of choices. // TODO: Implement AHAH support for advpoll node form if (isset($_POST['choice'])) { $form_state['values']['choice'] = $_POST['choice']; } } /** * 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($node) { $nid = $node->nid; $weight = 0; $seen_ids = array(); foreach ($node->choice as $index => $choice) { if ($choice['label'] != '') { // Mark this choice id as being seen. $choice_exist = db_result(db_query("SELECT COUNT(cid) FROM {advpoll_choices} WHERE nid = %d AND cid = %d", $nid, $index)); if (!$choice_exist) { db_query("INSERT INTO {advpoll_choices} (nid, label, weight) VALUES (%d, '%s', %d)", $nid, $choice['label'], $weight); $seen_id = db_last_insert_id('advpoll_choices', 'cid'); } else { db_query("UPDATE {advpoll_choices} SET label = '%s', weight = %d WHERE cid = %d", $choice['label'], $weight, $index); $seen_id = $index; } $seen_ids[$seen_id] = 1; $weight++; } } $all_ids_query = db_query("SELECT cid, label FROM {advpoll_choices} WHERE nid = %d", $nid); $all_ids = array(); while ($row = db_fetch_array($all_ids_query)) { $all_ids[$row['cid']] = array('label' => $row['label']); } // Delete any choices that were removed (either dynamically or by submitting // a blank label). if (isset($node->choice)) { foreach ($all_ids 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 ''; } } /** * Minimal clear votes form for the votes tab. */ function advpoll_clear_votes_form(&$form_state, $nid) { $form = array(); $form['reset'] = array( '#value' => t('Clear all votes'), '#type' => 'submit', ); $form['#redirect'] = 'node/'. $nid . '/votes/clear'; return $form; } /** * Display a clear votes confirmation form. */ function advpoll_clear_votes_confirm_form(&$form_state, $nid) { $node = node_load($nid); $form = array(); $form['#nid'] = $node->nid; $confirm_question = t('Are you sure you want to clear all votes for %title?', array('%title' => $node->title)); $form['question'] = array('#value' => '

' . $confirm_question . '

'); $form = confirm_form($form, $confirm_question, 'node/'. $node->nid . '/votes', t('This will delete all votes that have been cast for the poll.'), t('Clear all votes'), t('Cancel')); // Override the default theming of confirmation forms. // TODO: need to theme this form better. unset($form['#theme']); return $form; } /** * Process advpoll_clear_votes_confirm form submissions. * * Update the choices added or removed when editing a node * Clear all votes once the confirmation is given. */ function advpoll_clear_votes_confirm_form_submit($form, &$form_state) { if ($form_state['values']['confirm']) { $nid = $form['#nid']; 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 cleared.')); watchdog('content', 'Cleared all poll votes (%num_votes).', array('%num_votes' => $node->votes), WATCHDOG_NOTICE, l(t('view'), 'node/' . $node->nid)); } $form_state['redirect'] = 'node/'. $node->nid; } } 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 '

'. $question .'

'; } function theme_advpoll_results($title, $results, $votes, $links, $nid, $voted, $cancel_vote) { $output = '
'; if ($results) { $output .= $results; $output .= '
'. t('Total voters: %votes', array('%votes' => $votes)) .'
'; } else { $output .= '

'. t('No votes have been recorded for this poll.') .'

'; } $output .= '
'; 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 = '
'. $title . ($choice && $choice['writein'] ? ' '. t('(write-in)') : '') .'
'; $output .= '
'; $output .= '
'. $percentage .'% ('. $votes .')
'; return $output; } function _advpoll_vote_response($node, $form_state) { $msg = t('Your vote was registered.'); // Ajax response if ($form_state['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($node->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 ($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 = ''; if (user_access('show vote results')) { $ajax_output .= advpoll_view_results($node, NULL, NULL); } elseif (user_access('cancel own vote')) { $ajax_output .= _advpoll_show_cancel_form($node); } // 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' => '
'. $msg .'
', 'response' => $ajax_output)); exit(); } // Usual response. 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) { $output = ''; $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'], isset($node->links) ? $node->links : array(), $node->nid, $node->voted, $node->cancel_vote); } } $output .= _advpoll_show_cancel_form($node); return $output; } /** * 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('

', '

'), '', $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' => '
'. $message .'
')); 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($value['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); } } } /** * Handle JavaScript voting. */ function advpoll_js_vote() { // TODO : fix this function. $form_state = array(); $form_build_id = $_POST['form_build_id']; if (!$form = form_get_cache($form_build_id, $form_state)) { exit(); } } /** * 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_state, &$votes, $vote_value = 1) { // A write-in vote is being made. if (isset($form_state['values']['writein_choice']) && $form_state['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_state['values']['writein_choice']); $db_vote = db_fetch_object($result); // If there's more than one match, redo the query, being more exact. if ($db_vote && $second_vote = db_fetch_object($result)) { $result = db_query("SELECT cid FROM {advpoll_choices} WHERE nid = %d AND label = '%s'", $node->nid, $form_state['values']['writein_choice']); $db_vote = db_fetch_object($result); } // 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_vote) { $existing_choice = $db_vote->cid; // Set a vote $vote = array(); // TODO: confirm this works for ranking polls. $vote['value'] = $vote_value; $vote['tag'] = $existing_choice; $vote['value_type'] = 'option'; $vote['content_type'] = 'advpoll'; $vote['content_id'] = $node->nid; $votes[] = $vote; } // This write-in choice has not been previously voted for. 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_state['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_state['values']['writein_choice'])); // Add vote $vote = array(); // TODO: confirm this works for ranking polls. $vote['value'] = $vote_value; $vote['tag'] = $next_cid; $vote['value_type'] = 'option'; $vote['content_type'] = 'advpoll'; $vote['content_id'] = $node->nid; $votes[] = $vote; } } } function advpoll_writein_merge_form(&$form_state, $node) { $form = array(); $form['fieldset'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#title' => t('Merge write-ins'), ); $form['fieldset']['note'] = array( '#value' => '
'. t('This will delete the write-in and change any votes for it into votes for the selected chocie.') .'
', ); $form['fieldset']['merge'] = array( '#prefix' => '
'. t('Merge') .' ', '#suffix' => '
', ); $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, ); $form['#node'] = $node; return $form; } function advpoll_writein_merge_form_validate($form, &$form_state) { if ($form_state['values']['source'] == $form_state['values']['destination']) { form_set_error('destination', t('The write-in cannot be merged into itself.')); } } /** * Process advpoll_writein_merge form submissions. */ function advpoll_writein_merge_form_submit($form, &$form_state) { // Get a list of votes in this node. $node = $form['#node']; $raw_votes = db_query('SELECT * FROM {votingapi_vote} WHERE content_id = %d', $form_state['values']['nid']); $voters = array(); $affected_voters = array(); while ($vote = db_fetch_object($raw_votes)) { $key = $vote->uid .'-'. $vote->vote_source; if (!isset($voters[$key])) { $voters[$key] = array(); } array_push($voters[$key], $vote); if ($vote->tag == $form_state['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_state['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_state['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_state['values']['destination'], $voters[$key][$index]->vote_id, $form_state['values']['source']); } } // Delete the merged choice. db_query('DELETE FROM {advpoll_choices} WHERE cid = %d', $form_state['values']['source']); votingapi_recalculate_results('advpoll', $form_state['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']); $writeins_remaining = FALSE; foreach ($node->choice as $choice) { if ($choice['writein'] && $choice['cid'] != $form_state['values']['source']) { $writeins_remaining = TRUE; break; } } drupal_goto('node/'. $form_state['values']['nid'] . ($writeins_remaining ? '/writeins' : '')); } function advpoll_writein_promote_form(&$form_state, $node) { $form = array(); $form['fieldset'] = array( '#type' => 'fieldset', '#collapsible' => FALSE, '#title' => t('Promote write-ins'), ); $form['fieldset']['note'] = array( '#value' => '

'. 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.') .'

', ); $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; } /** * Process advpoll_writein_promote form submissions. */ function advpoll_writein_promote_form_submit($form, &$form_state) { if (count($form_state['values']['promote'])) { db_query('UPDATE {advpoll_choices} SET writein = 0 WHERE nid = %d AND cid IN(%s)', $form_state['values']['nid'], implode(', ', $form_state['values']['promote'])); drupal_set_message(format_plural(count($form_state['values']['promote']), 'Write-in promoted.', 'Write-ins promoted.')); } drupal_goto('node/'. $form_state['values']['nid']); } /** * Implementation of hook_theme(). */ function advpoll_theme() { return array( 'advpoll_page' => array( 'arguments' => array('' => '') ), 'advpoll_block_latest_poll' => array( 'arguments' => array(), ), 'advpoll_results' => array( 'arguments' => array( 'title' => '', 'results' => '', 'votes' => '', 'links' => '', 'nid' => '', 'voted' => '', 'cancel_vote' => '', ) ), 'advpoll_bar' => array( 'arguments' => array( 'title' => '', 'percentage' => '', 'votes' => '', 'choice' => NULL, ) ), 'advpoll_voting_ranking_form' => array( 'template' => 'advpoll-display-ranking-form', 'file' => 'modes/ranking.inc', 'arguments' => array( 'form' => NULL, ) ), 'advpoll_voting_binary_form' => array( 'template' => 'advpoll-display-binary-form', 'file' => 'modes/binary.inc', 'arguments' => array( 'form' => NULL, ) ), 'advpoll_voting_multirate_form' => array( 'template' => 'advpoll-display-multirate-form', 'file' => 'modes/multirate.inc', 'arguments' => array( 'form' => NULL, ) ), ); } /** * AHAH handling of the more choices button. * * Based on http://drupal.org/node/331941 */ function advpoll_js_more_choices() { // We're starting in step #3, preparing for #4. $form_state = array('storage' => NULL, 'submitted' => FALSE); $form_build_id = $_POST['form_build_id']; // Step #4. $form = form_get_cache($form_build_id, $form_state); // Preparing for #5. $args = $form['#parameters']; $form_id = array_shift($args); $form_state['post'] = $form['#post'] = $_POST; $form['#programmed'] = $form['#redirect'] = FALSE; // Step #5. drupal_process_form($form_id, $form, $form_state); // Step #6 and #7 and #8. $form = drupal_rebuild_form($form_id, $form_state, $args, $form_build_id); // Step #9. $choice_form = $form['choice_wrapper']['choice']; unset($choice_form['#prefix'], $choice_form['#suffix']); $output = theme('status_messages') . drupal_render($choice_form); // Final rendering callback. drupal_json(array('status' => TRUE, 'data' => $output)); } /** * Implementation of hook_views_api(). */ function advpoll_views_api() { return array( 'api' => 2, 'path' => drupal_get_path('module', 'advpoll') .'/views', ); } /** * Implementation of hook_votingapi_relationships(). */ function advpoll_votingapi_relationships() { $relationships[] = array( 'description' => t('Advanced Poll'), 'content_type' => 'advpoll', 'base_table' => 'node', 'content_id_column' => 'nid', ); return $relationships; }