hello!
It would be handy to allow saving drafts not only to registered users but for anonymous ones too. Hehe, we shouldn't be so racist about anonymous guys :)
As inspired in https://www.drupal.org/node/2836479#comment-11830949 it should be possible to implement such draft storage via session ID.
My proposal is to alter WebformSubmissionStorage::loadDraft()
so it looks up by UID if it's a registered user and otherwise look ups by session ID (if it's an anonymous user). This, in turn, implies extending the base field definitions of WebformSubmission::baseFieldDefinitions()
to include 1 more column for session ID.
Through this concept it seems pretty aesthetic and straight forward way to include this feature.
I'll be back with a patch as soon as I get it coded.
Comment | File | Size | Author |
---|---|---|---|
#45 | drafts_for_anonymous-2838423-44.patch | 110.59 KB | jrockowitz |
| |||
#42 | drafts_for_anonymous-2838423-42.patch | 110.29 KB | jrockowitz |
| |||
#40 | drafts_for_anonymous-2838423-40.patch | 104.91 KB | jrockowitz |
| |||
#37 | drafts_for_anonymous-2838423-36.patch | 96.02 KB | jrockowitz |
#34 | drafts_for_anonymous-2838423-34.patch | 96.15 KB | jrockowitz |
Comments
Comment #2
jrockowitz CreditAttribution: jrockowitz as a volunteer commentedI am not 100% sold on this feature. It seems to have stalled for Webform 4.x. #1932560: “Save draft” form button for anonymous users
My concerns...
Still, I can give you a few thoughts...
Finally, I have not officially documented this policy but you must include tests (and hopefully you can also refactor some existing tests).
Comment #3
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedHello :)
Yes, I'll offer a patch and you will have the final word on it :) Though I'll defend my position fiercely :)
Let me actually start defending it:
I am rather biased towards using standard
WebformSubmissionStorage
facilities, i.e.{webform_submission}
and{webform_submission_data}
that way I do not have to worry about actually saving (storing in a permanent storage) drafts, and instead will simply reuse an already existing solution. Additionally, in the face of coming views integration, there are additional benefits of reusing the existing storage machinery. Additionally, if the registered user drafts are saved that way, why not saving the anonymous drafts too? I think keeping both drafts aligned will save some pain in the future since the storage implementation will be equal.Thanks for the heads up, I'll check whether it's possible to leverage those tokens.
As I said, I would prefer going through the
WebformSubmissionStorage
route, but otherwise yes, probably a new handler is the best alternative option.I am not a real fan of TDD, so I'll rather try hammering out the functionality, but when the functionality is pretty stable and I have more or less your approval/support on the implemented approach, I'll see into the tests :) Actually, I also have a rule of thumb of not releasing a stable (anything above *-rc) code without reasonable test coverage in the modules I maintain :)
Comment #4
jrockowitz CreditAttribution: jrockowitz as a volunteer commented@bucefal91 I think you are on the right track.
I am not trying to have the final word but I want to make sure (for the time being) I understand and can maintain all (or most) of the code in the core Webform module.
BTW, I have avoided Views integration and server side conditional logic because this code is extremely tricky and could delay a stable release.
Comment #5
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedHello!
Let's see what I got so far. In fact the patch is simple as ~ a dozen of line changes.
The changes are the following:
WebformSubmissionForm::draftEnabled()
so now anonymous users are allowed to save a draft, i.e. show that "save draft" button on the form UI.WebformSubmission::preSave()
, if a) it's a draft b) the owner is anonymous user then pull up user private temporary storage (it's a Drupal service that seems to execute the function of temporarily storing some adhoc data on per-user basis) and save the webform submission token into it. This way we kind of mark "this user" and "this webform" as belonging together.WebformSubmissionStorage::loadDraft()
, if we are loading the draft for anonymous user, pull up the private storage and fetch all previous drafts (tokens of those drafts). Then simply add one extra condition on the query to filter by those tokens.And that's about it. Pretty simple and elegant, hehe, at least it appears this way to me.
Some things that I find worthwhile to bring up:
WebformSubmissionStorage
and inWebformSubmissionForm
, which I find to be ugly. The only "beautiful" solution that I can think of is to encapsulate this crap intoWebformSubmissionStorage
class. I would introduce a couple of methods:AddAnonymousDraft(WebformsubmissionInterface $entity)
andgetAnonymousDrafts()
. These 2 methods would "manage" actual storage of "this user - this submission" data via the very same user private temporary storage, but now it would be encapsulated into 1 single class and only into 2 methods. ThenWebformsubmission
won't suffer any change (against current -dev) and I would just add extra logic necessary upon webform submission saving intoWebformSubmissionStorage::doPreSave()
or::doSave()
.I did some rather simple testing and it worked pretty nice. Let's see what testbot says (as I told you in another issue, for some reason I can't run webform tests locally). Also, a question about tests: do you mind if I try writing the tests to cover this new feature based on
Drupal\Tests\BrowserTestBase
instead ofDrupal\simpletest\WebTestBase
? The former comes from PHPUnit whereas the latter is SimpleTest-based and Drupal 8 seems to prefer PHPUnit over Simpletest (https://www.drupal.org/node/2469723)Comment #6
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedJust a small additional iteration on top of previous patch after some testing. I explicitly set condition to be IN when loading drafts of anonymous users.
Comment #8
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedI couldn't find issues about the anonymous draft saving with the limited testing I've executed. So I "clean up" the patch by doing necessary dependency injections.
Comment #10
jrockowitz CreditAttribution: jrockowitz as a volunteer commentedI could not get the patch working using the Test: Webform: Draft (/form/test-form-draft) form. I am holding off investigating why, because there other related tasks that I can more readily help with.
Some Notes...
@bucefal91 I really appreciate you taking on this difficult task, because you are really beginning to test the Webform module's API.
Even though, I could not get the patch working as expected, your initial research is proving that this is doable task.
One the most immediate sub-tasks that I will look into is "We need to allow admins to delete unfinished anonymous forms and more." because I am hoping we can use Views bulk operations to set up draft and date filters that would allow admins to select what submissions to should be deleted. It is possible that my solution might help with Views integration.
Comment #11
jrockowitz CreditAttribution: jrockowitz as a volunteer commented#2840845: Experiment with replacing manage Webform submissions with a View
Comment #12
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedComment #13
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedI am trying to come back to this issue now and to wrap it up too.
With the #2843400: Automatic purging of webform submissions being committed, should we consider the "We need to allow admins to delete unfinished anonymous forms and more." sub-task from this ticket finished? Or you want to do something more about webform submission purging in the context of anonymous drafts?
I have a theoretical question about "We may need to migrate anonymous submissions to logged in ones." What exact behavior are we to code? Let's consider a case:
I started working on your points (+ I rerolled the patch from #10). Do not look into it unless you're really curious, it's still work-in-progress.
Comment #14
jrockowitz CreditAttribution: jrockowitz as a volunteer commentedBesides automatic purging we need to address #2840858: Create Webform and Webform Submission Action plugins in the near future, so that admins can bulk delete drafts manually. This is not a showstopper.
Yes, the use-case that you described for "We may need to migrate anonymous submissions to logged in ones." is correct.
I think there are going to be some nuances with the UX when enabling anonymous drafts. For example, we may want to warn users that they should make sure to have a SPAM protection module enabled.
Comment #15
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedThis is a worthwhile patch. It has the following features:
A note: eventually I have to discard usage of PrivateTempStorage (a fancy tool for storing some temp data related to a specific user) because it was impossible to carry drafts through login - upon login Drupal regenerates session ID and PrivateTempStorage uses session ID as an identifier identifier so after login it was impossible to recognize which drafts were created in the same session as the session was actually destroyed and replaced with a brand new one. I've included a comment on this matter in the source code. So I decided to use pure-bone
$_SESSION
, the good old one :) and no fancy wrappers.Known issues:
From now on I'll be posting interdiffs since most of this feature is now coded.
Comment #16
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedThe problem with page cache is solved for anonymous drafts. Finally I've had a good opportunity to get to know the dynamic caching of D8 in details.
The problem can be described as following: the webform submission form must property report its cache tags and contexts. Webform submission storage, in theory, is an interface (though hardly anyone will ever do another implementation of it), nonetheless, since it's an abstract interface, the
WebformSubmissionForm
may not make any specific assumptions about how drafts are actually stored and based on that data to update it's cacheability metadata.Therefore I've introduced a new method in the
WebformSubmissionStorageInterface
, it's::draftCacheabilityMetadata()
and it returns cache metadata that should report how draft storage varies.The actual implementation of webform submission storage stores the anonymous drafts in
$_SESSION
so we return a session context in the implementation of that method.Now the
WebformSubmissionForm
, if drafts are enabled, can invokeWebformSubmissionStorageInterface::draftCacheabilityMetadata()
and merge the result with its own cacheability metadata. As a result the webform submission form additionally varies by session which is the desired outcome of this whole architecture I am describing now.As a bonus... I've adjusted
WebformSubmissionStorage::postLoad()
so all anonymous draft entities contain their full cacheability metadata (all anonymous drafts cacheability metadata is enriched by session context).Tested manually in different set ups and all of it worked just fine.
Now I'd like to ask you to review the patch. I basically believe the functionality is written and I can proceed to the automated tests, but before I invest my time into it, I'd like to verify with you whether the implementation looks good (and anyway another pair of eyes won't do hart to make sure I did not miss anything).
Comment #19
jrockowitz CreditAttribution: jrockowitz as a volunteer commentedI absolutely like what I see and agree that key functionality is done. I reviewed almost everything and tweaked a few things. The most important change was to add some initial anonymous draft test coverage to WebformDraftTest.
I think the next step is to write more tests confirming that anonymous draft get converted to authenticated draft when a user logs in.
Once we have enough test coverage, I would like to refactor some of the new methods to see if we can simplify things a little bit.
Changes
Comment #22
jrockowitz CreditAttribution: jrockowitz as a volunteer commentedNow I just need my patch to apply.
Comment #25
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #28
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #29
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 Please review my changes. Next step would be to write more tests.
Comment #30
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commented.. just a quick note for myself, while I have it in mind: the same draft may be saved more than once, so we must make sure
$_SESSION
contains only unique submission tokens.jrockowitz, yes, of course I'll review and will enrich it with some extra tests :) I just need some time to get straight my things and I'll come back here.
Comment #31
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 something about storing all the tokens in the $_SESSION seems wrong but at the same time I think you are right.
Maybe we need to consider the edge case of lots of forms generating lots of tokens that could be useless if the associated saved draft was automatically deleted.
Comment #32
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedYes, I see your logic. Basically we'd like to retain as much of control of this abstract storage for tokens as possible, so we can remove the tokens of purged drafts. Don't take it as I argue against it, just let me share a few cents about the dirty and naked reality.
Originally I wanted to use the
PrivateTempStorage
. I believe with some hacks we could remove tokens of purged drafts if we used that storage. But it has one problem, the one I described above. With this storage we can't pull an anonymous draft through a log in and make it then belong to the logged in user. Basically, due to security concerns upon login Drupal regenerates session ID and then simply copies old (anonymous)$_SESSION
into the new (logged in)$_SESSION
. The private temp storage uses session ID as its bin identifier, so we effectively lose all the connection between anonymousPrivateTempStorage
and logged inPrivateTempStorage
due to this session regeneration although it's the same HTTP client. I've studied the Drupal core around log in operation and couldn't find a way to connect those 2 session IDs. If we could recognize that the session has just regenerated, we could take the content of old bin and put it into the new bin. I've studied the Drupal code around log in operation and couldn't figure out any "pretty" way to do it. Maybe you can find it?As far as I know
PrivateTempStorage
is the only suitable kind of storage that Drupal offers out of the box. Of course, we could write some kind of custom storage for this purpose, but I see it as overkill and simply unreasonable amount of work for the benefits (and the cost of its future maintenance) it implies.So I "downgraded" the solution to storing tokens in
$_SESSION
out of these reasons. As I said, I believe I understand what you mean when you say "seems wrong". After all, it's your module, so you decide and you assume all the results of the decision as a module maintainer.My thinking regarding
$_SESSION
ending up to contain too many useless tokens:I am trying to say while this is not the best possible solution, it seems to me "just good enough". I tried to encapsulate the storage for anonymous draft tokens in the code as much as possible so if tomorrow there is a better alternative, it should be fairly easy to change the storage from
$_SESSION
to the new one.Comment #33
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 When I read your code, I completely understand and agree with your solution. I just want get your code tested, organized, and documented, so that if someone does figure out a better solution, they can contribute a patch and the existing tests will verify that things are still working as expected.
Comment #34
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 I am going reroll this patch and try finish this up this weekend.
Comment #37
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #40
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #42
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 I think your solution is great and all I am doing now is refactoring.
Changes
Note
With anonymous drafts being merged with authenticated drafts it is very possible for a user to have 2 drafts. This can be addresses via #2856472: Allow multiple drafts per users.
Comment #43
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedThis very recent request #2863425: Anonymous view own submission is making think we should be tracking all draft and completed submission sid's in an anonymous user's $_SESSION. This would allow all previous submissions to be converted from an anonymous user to an authenticated user during login.
I think I might have been wrong about...
...because the session data is 'securely' stored on the server.
There is now enough test coverage where this change should be very easy to make.
Comment #45
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedThe attached patch stores the sid in the user's session and contains a few @todos for #2863425: Anonymous view own submission.
Comment #47
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedComment #48
bucefal91 CreditAttribution: bucefal91 at Websolutions Agency commentedThank you for your work! I will say that in webform_views I also have guys requesting for "allow anonymous users see own submissions" kind of permission. So I support moving in that direction.
Sorry I couldn't assist further with this ticket last few days. I am kind of overbooked with my own stuff in life. But whenever I have a spare moment be sure I'll invest it into webforms or webform_views.
Just some ideas for thinking about this dilemma of "session" vs "custom token". I think both have their pros and cons. I think the advantage of "session" over "custom token" you've already seen since you're considering to switch away from the latter to the former. But custom token can also come handy - imagine an anonymous user has submitted a webform and received an email notification. The email notification has a link to edit his webform submission (or do any other private action with the webform). And the link is tokenized with "custom token". Then if the guy tries to edit his webform from another device (say he's submitted it from a desktop and then from his cell phone he opens the link from his email inbox), if you just use session then he'd get an access denied. But if the authentication scheme was through custom tokens, he'd be allowed through.
I am not saying one is better over another. Just a use case where your original approach is deemed beneficial.
I am glad to see this big feature become available in the webform :)
Comment #49
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@bucefal91 Your work on the Webform Views module has significantly reduced my workload and is very much appreciated. Your work here was also really helpful and I enjoyed finishing this up.
I think we are going to also support an viewing a submission via an anonymous token especially because we already support editing a submission.
Now, that I have looked further into Webform 7.x-4.x it seems really clear that tracking anonymous submissions using $_SESSION was an existing feature that just needs to be ported to 8.x-5.x.
Comment #50
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commentedOkay, so I found an interesting side-effect. When converting an anonymous draft/submission to authenticated draft/submission the call to WebformSubmission::save() is triggering all the save hooks and handlers, this includes resending of emails.
I am starting to address this issue within #2854020: Provide a mechanism to log submission transactions but I think we going to have to ask form admins to opt-in to converting anonymous submissions to authenticated.
I will create a new ticket and get this done before the next release.
Comment #51
jrockowitz CreditAttribution: jrockowitz as a volunteer and at The Big Blue House commented@see #2864851: Allow form builder to opt-in to converting anonymous drafts/submissions to authenticated drafts/submissions.
Comment #52
ptsimard CreditAttribution: ptsimard commented