Problem/Motivation
Whenever a user with a picture (e.g. $user->picture != 0) logs in, their picture is removed. Specifically, the image file remains intact, but the file ID ($user->picture and/or $user->picture->fid, depending on the context) is set to 0.
Note that this is using the built-in user picture feature and not a 3rd-party module.
Steps to reproduce using 7.x-2.x-dev and 7.x-2.x-beta5:
- Log into a site with ldap_user enabled. (on my test rig, role and field provisioning to Drupal accounts is enabled, but the picture field is not setup for provisioning)
- Ensure that user pictures are enabled via admin/config/people/accounts.
- Upload a user picture.
- Verify that /user displays the uploaded picture.
- Log out.
- (Optional) Verify that the user's picture is still displayed at /user/uid.
- Log back in as the same user.
- The user's picture is no longer set.
Proposed resolution
It appears that the call to user_save() in the ldap_user_user_login(&$edit, $account) hook is to blame. Specifically, the value of $account->picture is merely the file ID rather than the full picture object that is normally returned by user_load(). This causes user_save() to set the user's picture to 0 (see Issue #935592: User picture is deleted after calls to user_save()).
The best solution I've found so far is to call user_load() prior to the save. I looked through the ldap_user_user_login(&$edit, $account) function and did not see any changes being made to $account prior to the call to user_save(); it also seems unlikely that provisioning to LDAP would modify the Drupal user in the future, so it appears to not cause issues with potential future changes.
Thus,
function ldap_user_user_login(&$edit, $account) {
// Code for provisioning from a Drupal user/account to LDAP.
// Provision from LDAP to a Drupal user.
if (/* configured to provision to Drupal users */) {
// Code to populate $user_edit with the information from LDAP.
$account = user_save($account, $user_edit, 'ldap_user');
}
}
becomes
function ldap_user_user_login(&$edit, $account) {
// Code for provisioning from a Drupal user/account to LDAP.
// Provision from LDAP to a Drupal user.
if (/* configured to provision to Drupal users */) {
// Load the user before saving to fully populate the $account->picture field.
$account = user_load($account->uid);
// Code to populate $user_edit with the information from LDAP.
$account = user_save($account, $user_edit, 'ldap_user');
}
}
Remaining tasks
(reviews needed, tests to be written or run, documentation to be written, etc.)
I have done a brief scan through the rest of the code base for other places that may be calling user_save() on user objects without fully-loaded picture objects and didn't spot any obvious ones. There are, however, several functions that are passed a user object as a parameter that do not load the user object prior to saving it, so the safety of those methods depends on the calling function. Here's a rough example of the functions that are potentially unsafe for the user's picture:
function myFunction($account, &$user_edit) {
// Code that adds to and/or changes $user_edit.
user_save($account, $user_edit);
}
The functions I've identified as at-risk are as follows:
_ldap_authorizations_user_authorizations_setrevokeSingleAuthorizationgrantsAndRevokesprovisionDrupalAccountldapAssociateDrupalAccount_ldap_user_orphans
I may have missed some, and these functions may not actually cause any problems; I just am not (yet) familiar enough with the flow of execution within these modules and consequently cannot easily verify if they will break the user's picture.
Tests should be added to check for user objects with gettype($account->picture) == 'string' being passed to user_save().
Related Issues
Issue #935592: User picture is deleted after calls to user_save()
| Comment | File | Size | Author |
|---|---|---|---|
| #9 | User pictures disappearing on login-1973352-9.patch | 1.67 KB | madprgmr |
| #7 | user_pictures_disappearing_on_login-1973352-7.patch | 931 bytes | humansky |
| #1 | User pictures disappearing on login-1973352-1.patch | 938 bytes | madprgmr |
Comments
Comment #1
madprgmr commentedHere's a patch that implements my suggested changes.
Comment #2
johnbarclay commentedThanks. Appreciae the details. I read through this and issue #935592: User picture is deleted after calls to user_save(). Its unclear if #935592: User picture is deleted after calls to user_save() is a documentation bug or an issue with the picture field.
If its a picture issue, I prefer only reloading the picture field to avoid conflicts with other modules.
I don't see any other contrib or core modules that do a user_save in hook_user_login() so the correct path is unclear to me. Does the above change work for you?
Comment #3
hansfn commentedldap_user_user_login implements hook_user_login. According to the documentation $account should be the fully loaded (global) $user object. If this isn't the case for you, there is a bug in the core of your version of Drupal. What version of Drupal are you using? Or some other module that implements hook_user_login is messing with $user->picture.
You could try to track it down by inserting some dd statements (or whatever you use to debug Drupal):
user_module_invoke('login', $edit, $user)is called.$function($edit, $account, $category).This way should be possible to determine where $account->picture is modified.
Comment #4
johnbarclay commented#1 and #2 are both viable approaches to me. #1 I believe will produce more consistent behavior over the long run. #2 is more of a quick fix that is less invasive. My preference is #1 but testing needs to happen with either.
Comment #5
johnbarclay commentedI committed #2 until #1 is tested more.
Comment #6
notmike commentedWe tried the dev version from April 24 (7.x-2.0-beta5+5-dev); however, we are still losing the user images when users log in. Has anyone else had success with the dev version?
Ten days ago, we had moved from 1.0-beta12 to 2.0-beta5 on a production system, but only discovered the user image problem a few days ago.
Comment #7
humansky commentedMy attached patch helps those who also rely on LDAP for authorization as well.
Comment #8
johnbarclay commented#7 looks correct to me. I'll commit it at some point. Can people test it?
Comment #9
madprgmr commented#7 seems to work, but I do not know if correcting
$user->picturethrough the edit array is better than directly changing the account object *shrug*.I went ahead and rolled #2 and a variant of #7 (based off #2) into a patch. I also wrapped the fix in an
ifstatement to prevent this fix from potentially kludging other changes to the user's picture (although other sources of change should be via the$editarray). Theifstatement also prevents an unnecessary call touser_load()in the event that the user/account object passed in is already fully loaded.Comment #10
madprgmr commentedIn response to #3:
Core is 7.22.
$useralready has$user->picturereduced to the (string) file ID rather than the full object.user_module_invoke()already has the$accountwithout the full picture object,After some brief poking around, I'm betting that the CAS module (1.2) is the source of the
hook_user_loginuser variable being messed up. There's a release candidate for the next version, so I'm putting off in-depth searching until I have time to migrate our test server to it.If so, then there's a good chance that fixing that would address a portion of this issue. However, as at least the authorizations portion has the
user_saveexposed via public functions, checks for (at least) these functions should remain in place (i.e. the_ldap_authorizations_user_authorizations_setfunction, since it is called from public functions).Comment #11
notmike commentedIn response to #8:
I did not have success when testing the previous patches, but I did have success when we tested #7.
Comment #12
johnbarclay commented#9 is committed to 7.x-2.x-dev.
Comment #13
haydeniv commentedIf we are looking for an automated test for this we can probably borrow from this patch Add test to ensure that user picture is not deleted after calls to user_save()
Comment #14
johnbarclay commentedComment #16
katannshaw commented@johnbarclay, I know that it's been awhile since you asked for others to test this, but patch #9 solved this problem for me with LDAP 7.x-2.0-beta5 in test and production. Thanks humansky and madprgmr.