Problem/Motivation
Currently, the poll module allows only one anonymous vote for a poll per IP address. There are use cases where this restriction isn't desired. For example when a group of people, who are on the same network, are asked to vote on the poll.
Steps to reproduce
- Create a poll.
- Log out.
- Place a vote on the poll.
- Open an other browser or go to the poll with an other device.
You see only the poll results and cannot vote again.
Proposed resolution
On the poll creation form, add an option to change the restriction mode for anonymous users.
Allow the following restriction methods:
-
One vote per IP
This is the default option and follows the current behavior. If an anonymous user places a vote, he/she cannot vote again: not in an other browser, not when clearing cookies and not even on an other device in the same network. He/she can however place another vote when switching his/her device to an other network. -
One vote per session
When an anonymous user places a vote, he/she cannot vote again during the current session. He/she can vote again when using an other browser, when clearing cookies or when using an other device in the same network. He/she can't however place another vote when only switching networks. -
Unlimited votes
There are no restrictions on how many votes the same anonymous user can place. After voting, he/she can go back to the voting form to place another vote. Can be useful in a situation where a group of people need to place a vote on the same device, for example at a stand in an exhibition hall.
This option has no effect on authenticated users, they continue to only be able to vote once per poll.
Remaining tasks
- Fix the test failures for the vote restriction option "unlimited".
- Review
User interface changes
On the poll creation form, a setting called "Anonymous vote restriction" is added. This setting is only visible when enabling the option "Allow anonymous votes".
API changes
- Constants are added to PollInterface for each vote restriction.
- A method called
getVoteRestriction()is added to PollInterface. - When calling the method
saveVote()on PollVoteStorage, the ID of the saved vote gets returned. Previously, the method returned nothing.
Data model changes
- The table "poll_vote" gets a new column called "id". This also becomes the new primary key. This is needed because multiple votes can now exist per combination of poll ID, user ID and hostname (which previously formed the primary key).
- The content entity type "poll" gets a new base field called "anonymous_vote_restriction". In this field, the setting for the vote restriction is stored.
Release notes snippet
Follow-up issue
| Comment | File | Size | Author |
|---|
Issue fork poll-2909811
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
stomusicHere is my solution. I added setting for allow vote anonymous users once per session.
Comment #3
stomusicComment #5
matroskeenI've added another option to restrict votes by cookie value.
Also, I moved these settings to the Poll entity.
Patch is attached.
Comment #6
dvanbrenk commentedHi Matroskeen,
Thanks for the patch, but I'm getting the following error:
Drupal\Core\Entity\EntityStorageException: SQLSTATE[42S22]: Column not found: 1054 Unknown column 'anonymous_vote_restrictions' in 'field list': INSERT INTO {poll_field_data} (id, langcode, uid, question, runtime, anonymous_vote_allow, anonymous_vote_restrictions, cancel_vote_allow, result_vote_allow, status, created, default_langcode) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8, :db_insert_placeholder_9, :db_insert_placeholder_10, :db_insert_placeholder_11); Array ( [:db_insert_placeholder_0] => 8 [:db_insert_placeholder_1] => nl [:db_insert_placeholder_2] => 1 [:db_insert_placeholder_3] => test [:db_insert_placeholder_4] => 0 [:db_insert_placeholder_5] => 1 [:db_insert_placeholder_6] => cookie [:db_insert_placeholder_7] => 1 [:db_insert_placeholder_8] => 0 [:db_insert_placeholder_9] => 1 [:db_insert_placeholder_10] => 1514454767 [:db_insert_placeholder_11] => 1 ) in Drupal\Core\Entity\Sql\SqlContentEntityStorage->save() (regel 805 van /var/www/vhosts/.../httpdocs/core/lib/Drupal/Core/Entity/Sql/SqlContentEntityStorage.php).
Do you maybe have an solution? Thanks in advance.
Comment #7
matroskeenHello @dvanbrenk,
For existing installations, we also need a hook_update_N() that will add
anonymous_vote_restrictionsbase field.Currently, I don't have enough time to take care of this. Maybe someone else will do.
Another solution is just to uninstall and install the module.
Comment #8
dvanbrenk commentedThanks @matroskeen, works like a charm!
Comment #9
marty2081 commentedInstead of uninstalling and installing again, you can also use "drush entup".
Comment #10
terranrich commented@Matroskeen, you'll be happy to hear that I applied your patch at #5, and it worked perfectly, at least for cookie-based voting. I reinstalled the Poll module using Devel, and after recreating my poll, I was able to vote on the same poll in multiple browsers from the same IP address.
I have not tested the middle option, "One vote per session", but the third option, "One vote per browser (cookie)", works beautifully.
Comment #11
terranrich commentedI'd still prefer if somebody else were to verify, though. Maybe I have overlooked something, although I don't think so.
Comment #12
elijah lynn2nd RTBC for #5.
It is important to note that I had to do a `drush updatedb` (updb) AND `drush entity-updates` (entup) to get it working (important if you are coming from Drupal 7).
Thanks for working on this everyone, it was the first feature I noticed missing after installing the module that I knew I would need to work on.
Comment #13
elijah lynnActually... I have some more thoughts. I think this is good to get in as is. However, I would like to see the setting to allow voting by IP, Session or Cookie actually moved into config, and not on the instance. I don't think editors need to change that type of setting for every poll, and it will probably confuse the majority.
Would be nice to have in this patch, but I think it could be a follow up, however, that would mean code to migrate that and even then, you couldn't migrate it really because every instance could have a different setting.
So, I actually think we should think about that UX for a bit and see about moving it into config.
Comment #14
elijah lynnOne more thing would be do be able to choose the default setting in the config.
Comment #15
Tyetho commentedWith this is there a way to refresh it so that a user can keep voting not just see the results until the window is open and closed again?
Comment #16
akoe commentedWe have used this patch for quite some time now and can confirm it solves the initial requirement of multiple votes from one IP.
Nevertheless the cookie introduced by the line 360 saved with core function 'user_cookie_save' comes without the secure flag
This might be a information leakage issue and with the following patch a cookie including secure flag is created if the user accesses the page via https protocol.
Btw. the cookie itself seems a bit misleading (or at least i didn't understood it correctly) since it stores only the last poll id, which a user voted for (not all polls a visitor has voted for).
Comment #17
vijaycs85Looks like these changes are reverted by mistake?
Space missing before/after "." and after ","
Comment #18
vijaycs85Here is a patch addresses my comments in #17 and other improvements on setcookie part.
Comment #19
vijaycs85Minor: Looks like I missed one space between "TRUE"s.
Comment #20
akoe commentedThanks @vijaycs85 for cleaning up the patch code style. Just ironed out the space you mentioned in #18.
Comment #21
naheemsays commentedIs it useful to store/user the users mac address - so 1 vote per device?
Comment #22
tostinni commenteddrush entity-updatesshouldn't be used anymore (see Support for automatic entity updates has been removed) so I updated the patch to add the field inpoll_update_8002().I also added a
setInitialValue('ip')but it doesn't seem to work.More eyes are welcome here.
Comment #23
tostinni commentedI also had the special case to allow unlimited anonymous votes, so here is a patch including this option.
Comment #24
thpoul commentedTested on project I have been migrating from Pollim and came across this problem.
Patch #23 works like a charm on 8.7.7. Thank you for this!
Moving to RTBC.
Comment #25
rasikap commentedPatch #23 works for me. Have used this in my project with Drupal version 8.6.16.
Hence moving to RTBC.
Thanks,
Rasika P
Comment #26
berdirThese indexes shouldn't be changed, we already have an index for the id with the primary key.
Two sentences like that isn't valid coding standard and also looks strange in drush at least. Lets try to make a single sentence of this, it can be less technical: Add unique id to votes and add setting for multiple votes per IP.
same here, the index changes are not necessary.
the description says "enable", but then it's a list field with several options. Maybe we can just remove the description, the options are pretty self-explaining?
If we really need a dynamic fallback then you can write return $this->get(...)->value ?: 'ip'; but not sure we even need that with the update path that sets the default? The field is required, so isn't allowed to be empty.
remove here too then.
we just made all other fields configurable in the UI, so should do that here too. Also, the default weight should be unique.
Not quite sure yet about the method name/API. It says VoteRestrictionS(), plural, but only returns a string. Also, it has several magic strings that are used in several places, might make sense to either define constants for them. or have dedicated methods for this somehow.
I don't really understand why this needs to be unset here, I'd expect we wouldn't set it in the first place instead?
i don't really get the cookie option. The session is a cookie too? If you know how to delete that then you can delete the cookie too?
Looks like the cookie has a much higher expiration time, but it's not actually safe to rely on this anyway.
And last, this is definitely complex enough to require tests. Right now, only the default option is tested, we basically need to test the main workflow with every option. Would also be useful to expand the issue summary a bit to better explain these different options and what this does.
Comment #27
chemsabd commentedI'm not able to get this patch working for 8.x-1.3
Comment #28
nitebreedRerolled the path from #23 to apply to the latest 8.x-1.x dev
Comment #29
liam mcdermott commentedHere's an updated patch taking into account the feedback from points 1-7 of #26, it's WIP so I'll leave the status as Needs Work.
Does anyone object if I just remove the cookie option? It's functionally very similar to the session option, the difference isn't obvious to the user either.
It also doesn't seem useful to allow one person to vote on a poll an unlimited number of times. Does anyone object if I remove that option from the patch?
I'm thinking this patch is more likely to get in if its focus is tight and the amount of change is small.
Comment #30
guptahemant commentedAdding a new patch here in this refactored code so that vote id will be stored and will be checked from database to render the Poll, Keeping the issue to needs work since we still need the tests.
Please review so that it can be improved and if the direction is correct.
Thanks
Comment #31
guptahemant commentedUpdated the patch to delete votes correctly on pressing cancel button. Keeping needs work to address the review points from #26
Comment #32
megachriz@Berdir
From #26 (point 8):
Do you think it would make sense to use a plugin system for these restrictions?
@others, @Matroskeen
I don't quite understand the benefit of the cookie option either. @Matroskeen You added that option in #5, can you explain why?
Comment #33
matroskeenI would love to, but I can't explain why 🙃
It's been almost 3 years and it was probably one of my first Drupal 8 projects...
Feel free to remove cookie option, I have nothing to say in my defence 🤷♂️
Cheers!
Comment #34
megachrizI updated the issue summary and I made changes to the patch to address the remaining points of #26.
In this updated patch I've made the following changes:
getVoteRestrictions()is renamed togetVoteRestriction()as per feedback from @Berdir in point 8 of #26.isVotingAllowed()is added in order to deal with the vote restriction type "unlimited". In this scenario, a vote can be recorded but the user still needs to be able to go back the poll form. Previously, the poll results always got shown whenever there existed a vote for the current user.I've hard time though to get things correct for the vote restriction type "unlimited". During testing I often get an error like the following:
When switching between "View results" and "View poll", the error backtrace becomes longer. See attached file for examples. One of the tests also fail on this error. Leaving to "Needs work" because of this.
Comment #35
megachrizIn this patch I fixed the following issues that were introduced in the previous patch:
isVotingAllowed(): when checking for the vote restriction "unlimited", it also needs to check if the current user is anonymous.showResults().The serialize issue seems to happen specifically on PHP 7.4. I'm not yet sure how to solve that. I also got this serialization error when switching between viewing the poll results and going back to the poll. So maybe I can write a test to see if that error also happens without this patch.
Comment #36
pdxclankeith commentedI'm seeing a strange result after applying the patch in #35, the first time an anonymous user comes to a page, the unique identifier isn't applied to the poll questions, so when they submit the poll, it doesn't show the results (vote is counted). After that, if they use the "View Poll" button, the poll is displayed with the unique id, and voting works where the results are displayed after they vote. This continues until the page is reloaded, then the process starts over again with the id not being applied.
I've tried tracking this down to see if it's a cache issue (doesn't appear to be), but can't figure out what the difference is after the anonymous user votes the first time.
Running on Drupal 8.9.16, and have tried it on a new D8 installation with only the poll module and patch added with same results. I'm happy to try and help debug this, just not sure where I should be looking.
Thanks
Comment #37
sutharsan commentedThe #35 patch worked on my end (Drupal 9.2.7) for anonymous voters.
For others who upgrade from old (< #34) to new patch: To handle the field name change (anonymous_vote_restrictions to anonymous_vote_restriction), I used the code below. Depending on your use case you need to change the value of
::setInitialValue()or write an update script to migrate the values.Comment #38
megachrizReroll of the patch in #35.
Note: the changes in
PollViewForm::showResults()need to be implemented slightly differently if #3178172: Button "View poll" is missing for authenticated users gets committed (for which I just also rerolled a patch).The serialize issue noted in #35 seem to have been restricted to Drupal 8 only and no longer an issue in Drupal 9. Therefore, moving the status to "Needs review".
Comment #40
megachrizFor some reason, the patch in #38 did not apply. I've tried it with an issue branch instead now.
Comment #41
fishfree commented@MegaChriz Thank you very much for your patch. It works! I want to add a new option: One vote per session everyday. I tried adding 2 lines as below:
In file src/Form/PollViewForm.php, public function save(array $form, FormStateInterface $form_state) {
...
// The vote is recorded so the user gets the result view instead of the
// voting form when viewing the poll. Saving a value in $_SESSION has the
// convenient side effect of preventing the user from hitting the page
// cache. When anonymous voting is allowed, the page cache should only
// contain the voting form, not the results.
$_SESSION['poll_vote'][$poll_id] = $vote_id;
$_SESSION['poll_vote'][$poll_id]['poll_time'] = \Drupal::time()->getRequestTime();
...
In file src/Form/PollViewForm.php, public function getUserVote(PollInterface $poll) {
...
case PollInterface::ANONYMOUS_VOTE_RESTRICT_SESSION_EVERYDAY:
$vote_id = FALSE;
if (!empty($_SESSION['poll_vote'][$poll->id()]) && !empty($_SESSION['poll_vote'][$poll->id()]['poll_time'])) {
if (\Drupal::time()->getRequestTime() - (int)$_SESSION['poll_vote'][$poll->id()]['poll_time'] <= 86400) {
$vote_id = $_SESSION['poll_vote'][$poll->id()];
}
}
if ($vote_id) {
$query = $this->connection->query("SELECT * FROM {poll_vote} WHERE id = :vid", [
':vid' => $vote_id,
]);
}
break;
...
But it doesn't work. I also add the const ANONYMOUS_VOTE_RESTRICT_SESSION_EVERYDAY in the file src\PollInterface.php and the related options in the related file.
Comment #42
skylord commented#40 works great, thanks a lot! I think it's a musthave for any poll module!
Comment #43
fozzieblue commentedI'm not able to update the poll module to 1.5 or 1.6.My Drupal site is running the following:- poll module 8.x-1.4
- Drupal 9.5.8
- I am using patch #35 from this issue.
When I run `composer update drupal/poll` I get the following error:I removed the patch and was able to update the poll module to 8.x-1.6. But updating with out the patch in #35, the poll module no longer allows multiple votes from the same IP.Is anyone else able to update poll to 8.x-1.6 and still use patch #35?I posted above before I tested the patch in #40. I had to remove the patch from #35 and then added the patched referenced in #40. That fixed my issue.
Comment #44
naheemsays commentedCan an alternative approach be to differentiate by mac address?
I dont think it is as common for different devices from the same IP address to be shared between multiple users as much as it was say 10 years ago, so if we still want to try and differentiate voters, it may be reasonable to look at distinguishing based on something other than IP address.
Comment #45
bramdriesenUsing this for a long time on a few projects. Would love to incorporate this on the new 2.0.x release.
Re #44: Not sure, I think the main thing used is still IP's. E.g. we still have the IP ban module, CDN's like CloudFlare offer IP bans but not Mac address bans.
Comment #47
bramdriesenReady for review once more.
Also updating credits for everyone who actively (and meaningful) participated on this issue for the last 6 years 🙈.
Comment #48
bramdriesenComment #49
ivnishNeeds reroll
error: patch failed: src/Form/PollViewForm.php:247
error: src/Form/PollViewForm.php: patch does not apply
error: patch failed: src/Form/PollViewForm.php:204
error: src/Form/PollViewForm.php: patch does not apply
Comment #50
ivnishComment #51
bramdriesenThanks @Berdir! This also needs a rebase now and a change in ID for the install/update hook
Comment #53
cgoffin commentedI rebased the changes from the dev branch. Here also an up to date patch.
Comment #54
cgoffin commentedThere was a missing newline at the end of the patch file. Here the new version.
Comment #55
lobodakyrylo commented#54 patch stopped working after last updates on dev branch.
Comment #57
bramdriesenComment #58
jvandooren commentedRerolled patch against latest 2.0.x
Comment #59
jvandooren commentedThe patch in #58 does not work as it adds a 8003 updatehook while in 2.0.x updatehook 8003 already exists for something else... Simply changing the one from the patch to 8007 exposes the problem Bram mentioned above (the updatehook is not idempotent).
Comment #61
jvandooren commentedReroll against 2.0.x with the idempotent update hook. This should be safe to use if you have already used an earlier version of this patch in your project.
Comment #62
bramdriesenComment #63
drupalite1411 commentedUsed patch #61 on poll 2.0.0-alpha2, it is providing option from UI to select per IP vote,per session vote and unlimited vote.
Select any option out of these three and save the form. When I edit the form the select field "Anonymous vote restriction" does retain it's value.
Moreover, the functionality is not working.
I can't use unlimited vote or per session vote.
Comment #64
drupalite1411 commentedUpdated. In the patch #61, removed this repetitive code from poll.php and vote per session functionality is working fine now. I am still testing
I found it is actually removed from the merge request https://git.drupalcode.org/project/poll/-/merge_requests/30/diffs?commit...
Comment #65
drupalite1411 commentedWhile debugging this patch, found that https://www.drupal.org/project/poll/issues/3263876 this issue still exists in vote per IP.
Steps to replicate is mentioned in the issue https://www.drupal.org/project/poll/issues/3263876
Comment #66
rahulk commentedUpdated the patch #61 & removed the redundant code from poll.php.
Comment #67
bramdriesenPlease stop updating and uploading manual patch files. Any work should be done on the issue fork.
Comment #68
bramdriesenComment #69
ivnishComment #70
bramdriesenI think this is ready for review once more. Preferably also some manual testing as there was one issue the tests didn't catch which I'm still not sure why that test was nog failing (see last commit).
Comment #71
bramdriesenComment #72
bramdriesenComment #73
ivnishI found some issues.
1) "Anonymous vote restriction" field doesn't save (or doesn't load saved value) when save/edit poll
2) When I uncheck "Allow anonymous votes" I can't save poll because of "Anonymous vote restriction field is required."
Comment #74
bramdriesenI could not replicate. That sounds more like the database schema was not installed/updated correctly?
The required field logic is fixed by using a state.
Comment #75
ivnishI reinstalled module again and all works fine
I tested a functionality and all works as expected
Comment #77
bramdriesenThanks everyone who participated on this issue for the past 8 years! Please create a new issue if you encounter any issues with this.