Support for Drupal 7 is ending on 5 January 2025—it’s time to migrate to Drupal 10! Learn about the many benefits of Drupal 10 and find migration tools in our resource center.
I've tested this on a clean install at simplytest.me and on 7.x-3.x-dev with the same issue
The steps to reproduce are to delete a role from the user edit screen and save. The role expiration is not removed, the expected result is that it would be.
Comment | File | Size | Author |
---|---|---|---|
#38 | 2589043-38.patch | 2.66 KB | chrisrockwell |
| |||
#37 | 2589043-37.patch | 2 KB | chrisrockwell |
#33 | 2589043-30.patch | 668 bytes | chrisrockwell |
| |||
#27 | 2589043-27.patch | 632 bytes | chrisrockwell |
#16 | 2589043-16.patch | 3.31 KB | chrisrockwell |
|
Comments
Comment #2
chrisrockwell CreditAttribution: chrisrockwell commentedPatch attached
Comment #3
TR CreditAttribution: TR commentedCan you explain what the problem is with those lines you changed in the patch? Clearly it used to work - is this just a PHP version issue or ... ?
I would also be very useful if you could add a test case for this.
Comment #4
chrisrockwell CreditAttribution: chrisrockwell commentedHi TR. The issue is that this conditional will never be true:
if (!in_array($rid, array_keys($edit['roles'])) && $rid != DRUPAL_AUTHENTICATED_RID)
as $edit['roles'] has every role available.I can't speak to whether or not it worked before but, thinking about it, it's possible that it didn't and no one noticed. I came across this with what I would consider an extreme edge case:
1. Admin was removing a role the Drupal way
2. If site user didn't have role but there was an expiration set for this role on this user, a custom module was granting the role(s) again when the site user visited their 'dashboard'
Because this custom module was checking the expiration, it led me to this implementation of hook_user_presave(). I can imagine that a site admin would think they were revoking a role the Drupal way and never check - the role would be revoked, but the expiration would not be. Therefore, it could stand to reason that role expirations were never properly done but, because the role was revoked, no one was ever the wiser. If there is an "expiring roles" admin report of sorts, I would imagine it would be incorrect.
I am yet to write test cases so I'll try to get some decent ones for you.
Comment #5
TR CreditAttribution: TR commentedOK, sounds good.
If $edit['roles'] contains all roles, then couldn't we also eliminate the first two tests here
(authenticated and administrator are always present, so the $edit['roles'] should always exist and always be an array)
and perhaps just change
to
That would seem to be simpler than your fix.
Comment #6
chrisrockwell CreditAttribution: chrisrockwell commentedYes it would be :). I went ahead and put it in a patch
Comment #7
TR CreditAttribution: TR commentedHere's the patch in the correct format - if the testbot likes it I'll commit.
Comment #8
TR CreditAttribution: TR commentedComment #10
TR CreditAttribution: TR commentedIt would have been nice to have a testable patch sooner so this issue didn't have to hang around so long before something was done about it.
Apparently, this statement:
is not true. That's why the patch is failing. Given that, I don't see what is wrong with the original code. Perhaps a contributed module that you have installed is doing something to $edit['roles'], which is populated by the core User module and which should only contain the roles that are assigned to that user.
I'll try to write a test to verify if this code works or doesn't work correctly, but currently I don't see a bug. Moving to 'Task' until the test is available.
Comment #11
chrisrockwell CreditAttribution: chrisrockwell commentedSorry about this TR, I completely forgot about this after implementing a fix. I think this is definitely a bug so I'm going to switch it back (if only to make sure you see it again), I apologize if that's out-of-order for me to do.
To restate the issue: If a user has an existing expiration (stored in uc_roles_expirations) and an admin manually removes the role by unchecking it on the user profile form, that expiration is not removed from uc_roles_expirations which would be the expected behavior as indicated in uc_roles.module.
The issue is only somewhat mitigated by the fact that the user no longer has a role, so access checks work as expected. However, in a situation where an admin removes a role that had an expiration date with 20 days left, for example, and then the user makes another purchase which grants that same role - that purchased expiration date is *added* to the existing expiration (the one that should have been deleted).
Unfortunately, I only tested this on the user profile form - in that case my statement is true: `$edit['roles'] `always contains every role except for anonymous (#options on that form element is built using `user_roles(TRUE)` ). Therefore, the role expiration will never be deleted if done from the user profile page.
However, I failed to consider when that form is submitted programmatically with a direct call to user_login_submit(). If the $form argument is passed as an empty array(), then we get the error the test produced. In this instance, your modifications to the patch actually delete the role expirations when they should not be.
I'd like to propose the attached patch which accounts for the valid use case of calling `user_login_submit(array(), $form_state)`. It just maintains the old conditional around your revision to my patch.
To reproduce this I just did a fresh install with only these modules:
Comment #13
chrisrockwell CreditAttribution: chrisrockwell commentedI'll need to spend some time going through the tests; if it's getting caught on offset 1 I think that means it's checking anonymous users as well, should it be? (I'm sorry, I'm completely unfamiliar with tests so it'll take me sometime to figure it out). It looks like a new method in uc_roles.test would be the correct place to start.
Comment #14
TR CreditAttribution: TR commentedOK, here's a patch which adds a test function to uc_roles.test.
What the new test does is to create a role, attach a role to a product, purchase that product, check that the user now has the role. So far so good, that stuff is already tested. Now comes the new stuff:
1) First verify that the "Ubercart roles" fieldset shows up on the user edit page, and verify that the user has a role expiration listed there.
2) Now delete the role from the user edit page by unchecking the role and submitting the changes. Verify that the role was removed from the user entity.
3) Lastly, see if the role expiration was removed from the "Ubercart roles" fieldset.
#3 fails, which is the subject of your bug report, which I can now confirm. (I am setting this issue to "needs review" so the testbot will run this new test, but the expected result is 1 FAIL which demonstrates the current bug.)
The next step is to create a patch which includes this new test and makes this new test and all other existing tests PASS. You can do that by concatenating your patch with my patch - make sure you don't add any blank lines in there anywhere.
The patch in #11 doesn't work - it causes the existing tests to fail.
Comment #16
chrisrockwell CreditAttribution: chrisrockwell commentedThis checks to make sure uc_roles even has control over the role in question; I thought about doing
if ($rid != DRUPAL_AUTHENTICATED_RID && $rid != DRUPAL_ANONYMOUS_RID && !edit['roles'][$rid]
but I feel the anonymous user rid should never be there anyway as uc_roles shouldn't have the ability to take that role away.Comment #17
gaurav_manerkar CreditAttribution: gaurav_manerkar commentedHi i had same issue
Comment #18
gaurav_manerkar CreditAttribution: gaurav_manerkar commentedMy patch works fine
Comment #19
gaurav_manerkar CreditAttribution: gaurav_manerkar commentedMy patch works fine
Comment #20
chrisrockwell CreditAttribution: chrisrockwell commented@gauravmanerkar, did #16 not work?
Comment #21
DamienMcKenna#19 is a duplicate of #18, and #18 is the same as #16 with the exception of a file permissions change.
Comment #22
TR CreditAttribution: TR commented@gauravmanerkar: What we need is a review of the patch in #16. I'm not sure why you posted a patch that is basically a duplicate of #16, as that doesn't help us move this forward.
@chrisrockwell: Actually, I had intended to commit your patch last month, but this issue fell off my radar. I'll get around to it this weekend when I have time to do some work.
Comment #23
TR CreditAttribution: TR commentedCommitted the fixes in #16. Thanks for the bug report and the work on the patch.
The patch and the test need to be ported to 8.x-4.x.
Comment #25
TR CreditAttribution: TR commentedHmm, not sure how/why this issue version and status was reverted from the values I set in #23 ... setting them back to what they should be.
Comment #26
TR CreditAttribution: TR commentedMarked #2770193: Notice: Undefined offset: # in uc_roles_user_presave() as a duplicate, but it does point out a potential problem with the fix for 7.x-3.x.
Comment #27
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedI can reproduce #2770193 and I see why.
1. user_admin_account_submit() calls user_multiple_role_edit(), which only passes the roles the user *should* have (see the array_diff at line 3329).
2. user_profile_form_submit() passes #edit['roles'] with all available roles, in the format 'role_id' => (bool)
To prevent the warning, we need to check the following:
1. The role id is not in $edit['roles']
2. The role id is in $edit['roles'] but is FALSE
There is an alternative to the now-3-part conditional, and that's to use hook_user_update() which always includes *only* the roles the user should have. In that case we could just use array_key_exists.
Comment #28
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedI'm not sure what to change the status to on this (needs review and back to 7.x?)
Comment #29
TR CreditAttribution: TR commentedYup.
Comment #33
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedFixed path
Comment #34
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedComment #35
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedComment #36
TR CreditAttribution: TR commentedOK. Would you be able to modify the existing test or add a new test to trigger the failure? That way we won't fall into the same trap when we refactor this code (and it really does need some work...).
Comment #37
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedThis should produce one undefined offset exception in uc_roles_user_presave on line 340.
Comment #38
chrisrockwell CreditAttribution: chrisrockwell as a volunteer and commentedThis one includes test (#37) and patch (#33).
Comment #40
TR CreditAttribution: TR commentedAwesome. Thanks! I'll commit that when I get back next week - I only have my phone right now and will be in a no reception area until next Monday.
Comment #41
TR CreditAttribution: TR commentedComment #43
TR CreditAttribution: TR commentedComment #44
GUE-Andreas CreditAttribution: GUE-Andreas commentedThis problem just appeared on our site a few days ago, seemingly out of the blue (meaning, I haven't updated or installed any modules).
EDIT (previous explanation removed):
Flushing the CSS and Javascript cache separately seems to have fixed it.
EDIT 2:
Problem returned, but more selectively. The expirations are deleted if I remove a role from my own account (user ID 1), but not from any other account.
Any ideas of what might be going on here..?
Thanks in advance.
Comment #45
cmseasy CreditAttribution: cmseasy commentedI made a simple rule with the rules module to solve this:
{ "rules_bij_annulering_gebruikersrol_weghalen" : {
"LABEL" : "Bij annulereing de gebruikersrol verwijderen",
"PLUGIN" : "reaction rule",
"OWNER" : "rules",
"TAGS" : [ "User" ],
"REQUIRES" : [ "rules" ],
"ON" : { "user_delete" : [] },
"DO" : [
{ "user_remove_role" : { "account" : [ "account" ], "roles" : { "value" : { "3" : "3" } } } }
]
}
}