If you are running a reverse proxy in front of your webserver, then the module will store the proxy's IP address instead of the visitors'. This results in users not being able to vote because it looks like they already have.

CommentFileSizeAuthor
#1 rate-change_1903262_1.patch742 bytesmurat_halici

Comments

murat_halici’s picture

Category: support » bug
Status: Active » Needs review
StatusFileSize
new742 bytes

Here's a patch.

sazcurrain’s picture

Hi,

You don't need a patch for this, because the function ip_address() already solved it.
You just have to tell to Drupal that you're behind a proxy, setting the variable reverse_proxy to true. Just add this configuration to your settings.php file.

Check out the implementation of ip_address() here http://api.drupal.org/api/drupal/includes%21bootstrap.inc/function/ip_ad... for more information.

Hope it helps.

murat_halici’s picture

I tried adding $conf['reverse_proxy'] = TRUE; in the settings.php file before creating this issue, but it didn't work for me because the IP address of our ELB is not static.

sazcurrain’s picture

Ok, you're right. ip_address will give you the wrong ip in that conditions.

I have a similar problem, but inverse, where anonymous users behind a unique public ip (like in any LAN) where considered the same.

But instead of patching Rate, i choose to make a new module overriden the storage hooks provided in Voting API to include the session id in the source of the votes.

the code looks like this:

function MY_MODULE_votingapi_storage_add_vote(&$vote) {
  if(isset($vote['vote_source'])) {
    $vote['vote_source'] = isset($_COOKIE["session_api_session"])?$vote_source.':'.$_COOKIE["session_api_session"]:$vote_source;
  }
  drupal_write_record('votingapi_vote', $vote);
}

function MY_MODULE_votingapi_storage_delete_votes($votes, $vids) {
   votingapi_votingapi_storage_delete_votes($votes, $vids);
}

function MY_MODULE_votingapi_storage_select_votes($criteria, $limit) {
   if(isset($criteria['vote_source'])) {
      $criteria['vote_source'] = isset($_COOKIE["session_api_session"])?$vote_source.':'.$_COOKIE["session_api_session"]:$vote_source;
   }
   return votingapi_votingapi_storage_select_votes($criteria, $limit);
}

function MY_MODULE_votingapi_storage_standard_results($entity_id, $entity) {
   return votingapi_votingapi_storage_standard_results($entity_id, $entity);
}

For this to work, you just need to set de variable 'votingapi_storage_module' to MY_MODULE. You can do it manually in the setting.php, but i prefer to include it in MY_MODULE.install, so you can set/unset de variable when installing/uninstalling the module.

function MY_MODULE_install() {
  variable_set('votingapi_storage_module', 'MY_MODULE');
}

function MY_MODULE_uninstall() {
  variable_del('votingapi_storage_module');
}

The advantage of doing it this way is that you can update Rate and Voting API without loosing your changes.

murat_halici’s picture

That seems like an excellent solution and I will definitely give it a shot. Thank you very much for sharing!

murat_halici’s picture

Is your custom module for D7, because I'm getting a NULL value in that votingapi_vote table for $_COOKIE["session_api_session"].

This is what I get when I print kpr($_COOKIE);

.. (Array, 2 elements)
SESS92a88ee34743fb4c539744fcb181c39c (String, 43 characters ) qxi53L3Dbz8xQsaC1eerdEPad47KSNHI8ZhHVLuDxFY
has_js (String, 1 characters ) 1

I tried using session_api_get_sid() which I found in http://drupal.org/node/319656 but that didn't seem to work either.

sazcurrain’s picture

Yes, you're right again, if there's no sessions started, that code will fail.

I didn't notice this error until I clean up the cookies and cache.
Thank you very much for finding this bug in my code! luckly for me, i didn't have it in production yet.

Unfortunately, i can't find a workaround to this issue yet. I tried to call session_get_sid(TRUE) before reading the cookie, but it didn't work as I expected.
session_get_sid() should create a new session when called for the first time, but i have to refresh the page 3 times for the value in the cookie to be available.

I'll keep working in a solution to this issue.

sazcurrain’s picture

Well, I belive I got a solution, but i feels a bit "artificial" to me.

The root problem is how Session API checks if the browser accepts cookies.
It checks if the $_COOKIE variable is set, if not it assumes that's because the browser don't accept cookies. But if you just clean your browser cache, session api will jump to a wrong conclusion and prevent the creation of the session, until another module creates a least one cookie.

So, to solve this problem you just have to create a dummy cookie before the call to session_api_get_sid().

The second problem is that after you call session_api_get_sid() for the first time, the variable $_COOKIE['session_api_session'] isn't available right away. It takes another refresh of the browser to get the value of this varibale.

The way i found to workaround this issue is to read the session id from the databse intstead of the cookies, only the first time.

I moved the code to alter de source of a vote to a new function, to avoid repeating it.

function MY_MODULE_alter_vote_source($vote_source)
{
  $session_id = '';
  if (!isset($_COOKIE['session_api_session']) || !$_COOKIE['session_api_session']) {
      $cookie_domain = ini_get('session.cookie_domain');
      //Creates a dummy cookie with fast expiration, to prevent session api from assuming the cookies are disabled
      setcookie('foo', 'foo', REQUEST_TIME + 10, '/', $cookie_domain);

      //Calls session_api_get_sid for the first time, and then reads the session_id value recently stored in the database
      $sid = session_api_get_sid(TRUE);
      $session_id = db_select('session_api', 's')
         ->condition('s.sid', $sid)
         ->fields('s', array('session_id'))
         ->execute()
         ->fetchField();
  }
  else {
      $session_id = $_COOKIE["session_api_session"];
  }
  return $vote_source.':'.$session_id;
}

functionMY_MODULE_votingapi_storage_add_vote(&$vote) {
  if(isset($vote['vote_source'])) {
    $vote['vote_source'] = rate_im_alter_vote_source($vote['vote_source']);
    dpm($vote['vote_source']);
  }
  drupal_write_record('votingapi_vote', $vote);
}

function MY_MODULE_votingapi_storage_select_votes($criteria, $limit) {
   if(isset($criteria['vote_source'])) {
      $criteria['vote_source'] = rate_im_alter_vote_source($criteria['vote_source']);
   }
   return votingapi_votingapi_storage_select_votes($criteria, $limit);
}
.
.
.

Right now, this is working for me with an anonymous user in a browser with a clean cache, but I'll apreciate all the feedback that i can get (two testers are better than one :) )

Thank you!

sazcurrain’s picture

Sorry, false alarm. This is still not working.
I had a piece of session_api.module commented and forgot about it, that's why it was working. :(

I'll keep trying...

sazcurrain’s picture

Ok, the thing with the php's set_cookie function is that it don't affect de variable $_COOKIE right away. It always takes a refresh of delay for the variable to reflect the changes.

So, the only solution is to create the cookie and set the adecuate value in $_COOKIE at the same time.

So, the code for MY_MODULE_alter_vote_source ends looking like this:

function MY_MODULE_alter_vote_source($vote_source)
{
  $session_id = '';
  if (!isset($_COOKIE['session_api_session']) || !$_COOKIE['session_api_session']) {
      $cookie_domain = ini_get('session.cookie_domain');
      //Creates a dummy cookie with fast expiration, to prevent session api from assuming the cookies are disabled
      setcookie('foo', 'foo', REQUEST_TIME + 10, '/', $cookie_domain);
      $_COOKIE["foo"] = 'foo';

      //Calls session_api_get_sid for the first time, and then reads the session_id value recently stored in the database
      $sid = session_api_get_sid(TRUE);
      $session_id = db_select('session_api', 's')
         ->condition('s.sid', $sid)
         ->fields('s', array('session_id'))
         ->execute()
         ->fetchField();
       $_COOKIE["session_api_session"] = $session_id;
  }
  else {
      $session_id = $_COOKIE["session_api_session"];
  }
  return $vote_source.':'.$session_id;
}

Now this work, but it still feels very very artificial. I hope it was a better way, but i don't see how without patching session api.

dmegatool’s picture

Just wanted say that sazcurrain solution seems to be working for me too. I needed to allow people under the same IP to vote anonymously. Here at the office, we're all able to vote even being using the same public IP. Thanks man !

Was a pain to gather the code parts in 53 different messages so here it is all in one place ready to be copy/pasted. I commented the dpm($vote['vote_source']); line. It was throwing me an error as Devel ain't installed.

MY_MODULE.install

function MY_MODULE_install() {
  variable_set('votingapi_storage_module', 'MY_MODULE');
}
function MY_MODULE_uninstall() {
  variable_del('votingapi_storage_module');
}

MY_MODULE.module

function MY_MODULE_alter_vote_source($vote_source)
{
  $session_id = '';
  if (!isset($_COOKIE['session_api_session']) || !$_COOKIE['session_api_session']) {
      $cookie_domain = ini_get('session.cookie_domain');
      //Creates a dummy cookie with fast expiration, to prevent session api from assuming the cookies are disabled
      setcookie('foo', 'foo', REQUEST_TIME + 10, '/', $cookie_domain);
      $_COOKIE["foo"] = 'foo';

      //Calls session_api_get_sid for the first time, and then reads the session_id value recently stored in the database
      $sid = session_api_get_sid(TRUE);
      $session_id = db_select('session_api', 's')
         ->condition('s.sid', $sid)
         ->fields('s', array('session_id'))
         ->execute()
         ->fetchField();
       $_COOKIE["session_api_session"] = $session_id;
  }
  else {
      $session_id = $_COOKIE["session_api_session"];
  }
  return $vote_source.':'.$session_id;
}

function MY_MODULE_votingapi_storage_add_vote(&$vote) {
  if(isset($vote['vote_source'])) {
    $vote['vote_source'] = MY_MODULE_alter_vote_source($vote['vote_source']);
    // dpm($vote['vote_source']);
  }
  drupal_write_record('votingapi_vote', $vote);
}
function MY_MODULE_votingapi_storage_delete_votes($votes, $vids) {
   votingapi_votingapi_storage_delete_votes($votes, $vids);
}
function MY_MODULE_votingapi_storage_select_votes($criteria, $limit) {
   if(isset($criteria['vote_source'])) {
      $criteria['vote_source'] = MY_MODULE_alter_vote_source($criteria['vote_source']);
   }
   return votingapi_votingapi_storage_select_votes($criteria, $limit);
}

function MY_MODULE_votingapi_storage_standard_results($entity_id, $entity) {
   return votingapi_votingapi_storage_standard_results($entity_id, $entity);
}

Solthun’s picture

Issue summary: View changes

The cookie implementation here is pretty great, but for the original topic of the issue, the first comments solved the issue completely.
I my case hosting was on AWS and the following 2 lines solved the voting issues I had with anonymous users:
$conf['reverse_proxy'] = TRUE;
$conf['reverse_proxy_addresses'] = array($_SERVER['REMOTE_ADDR']);

jamieonkeys’s picture

Here’s a version of @dmegatool and @sazcurrain’s code which works with Drupal 10 (and probably 8+). I used Claude to write it but it’s working fine on an actual project. The full module code, with comments, is at GitHub.

use Drupal\Core\Form\FormStateInterface;
use Drupal\votingapi\VoteInterface;
use Drupal\Core\Entity\EntityInterface;

function cookie_voter_get_id() {
  $cookie_name = 'Drupal_CookieVoter';

  if (empty($_COOKIE[$cookie_name])) {
    $unique_id = uniqid('vote_', true);
    setcookie(
      $cookie_name,
      $unique_id,
      time() + 31536000, // 1 year expiration
      '/'
    );
    $_COOKIE[$cookie_name] = $unique_id;
  }

  return $_COOKIE[$cookie_name];
}

function cookie_voter_entity_create(EntityInterface $entity) {
  if ($entity instanceof VoteInterface && !\Drupal::currentUser()->id()) {
    $cookie_id = cookie_voter_get_id();
    $entity->set('vote_source', $cookie_id);
  }
}

function cookie_voter_rate_vote_data_alter(&$vote_data, $entity_type, $entity_bundle, $entity_id, $rate_widget, $settings, $user_id) {
  if (empty($user_id) || $user_id == 0) {
    $cookie_id = cookie_voter_get_id();
    $vote_data['vote_source'] = $cookie_id;
  }
}

function cookie_voter_entity_presave(EntityInterface $entity) {
  if ($entity instanceof VoteInterface && $entity->getOwnerId() == 0) {
    $cookie_id = cookie_voter_get_id();
    $entity->set('vote_source', $cookie_id);
  }
}

function cookie_voter_query_votingapi_vote_check_alter($query) {
  foreach ($query->conditions() as $key => $condition) {
    if (isset($condition['field']) &&
        $condition['field'] === 'vote_source' &&
        !\Drupal::currentUser()->id()) {
      $cookie_id = cookie_voter_get_id();
      $condition['value'] = $cookie_id;
      $query->condition('vote_source', $cookie_id, '=');
    }
  }
}

function cookie_voter_module_implements_alter(&$implementations, $hook) {
  if (in_array($hook, ['entity_create', 'rate_vote_data_alter'])) {
    if (isset($implementations['cookie_voter'])) {
      $group = $implementations['cookie_voter'];
      unset($implementations['cookie_voter']);
      $implementations = ['cookie_voter' => $group] + $implementations;
    }
  }
}
ivnish’s picture

Status: Needs review » Closed (outdated)

Drupal 7 is EOL. Issue will be closed, but patches are still here

Now that this issue is closed, please review the contribution record.

As a contributor, attribute any organization that helped you, or if you volunteered your own time.

Maintainers, please credit people who helped resolve this issue.