Maybe this is by design, but I ran into the following problem. We are developing a user management system (bootstrapped) and I thought EntityWrappers were the way to go because they expose all custom user fields in a similar way. However I can't seem to set the user picture and password.

These are two fields I'd really like to set and I was wondering why there are not part of the wrapped user's property list. Could somebody explain this to me?

Signature & timezone appear to be missing as well.

Comments

neograph734’s picture

Issue summary: View changes
dsdeiz’s picture

Wanted to try my hands on this but I'm not sure if I am doing this right. E.g. for the picture, I got this working with:

  $properties['picture'] = array(
    'label' => t("Picture"),
    'description' => t("The picture of the user."),
    'type' => 'struct',
    'schema field' => 'picture',
    'setter callback' => 'entity_property_verbatim_set',
  );

The getter seems fine although setting it would be like so $wrapper->picture->set(file_load($fid));. I wonder what might be a better approach.

neograph734’s picture

I guess that would actually be a good way. You can combine that with an upload (file_save_data() ) as well.

  ..
  $file = file_save_data($data, $destination, $replace);
  $wrapper->picture->set($file);
  ..
neograph734’s picture

Status: Active » Needs review
StatusFileSize
new2.14 KB

I've patched a simple extension against the user module including picture, password, signature & timezone.

The only thing I'm not sure of is setting the password. It injects the plaintext in the user database, so it doesn't get hashed. Should the module handle that or the implementation of it?

Status: Needs review » Needs work

The last submitted patch, 4: 2224645-4.patch, failed testing.

neograph734’s picture

StatusFileSize
new3.04 KB

Added the properties to the callbacks as well.

neograph734’s picture

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 6: 2224645-6.patch, failed testing.

dsdeiz’s picture

For passwords, I guess you can use entity_metadata_user_set_properties() and from there you can use, user_hash_password(). Hope that helps.

neograph734’s picture

Version: 7.x-1.4 » 7.x-1.x-dev
Category: Support request » Feature request
Status: Needs work » Needs review
StatusFileSize
new3.34 KB

Thanks dsdeiz, that does indeed seem to work.
I'll manually test all fields to validate they indeed work as expected, but simpletest is still throwing an exception for the picture I don't understand.

I thought the type 'file' would be better then using 'struct' and I was under the impression that file was a valid entity type like node and user. But it is not yet behaving as I expected. Requesting $user->picture->uri->value() for example fails, while $node->author->mail->value() does work. Perhaps any of you has an idea on this?

  $properties['picture'] = array(
    'label' => t("Picture"),
    'description' => t("The user's picture."),
    'getter callback' => 'entity_metadata_user_get_properties',
    'setter callback' => 'entity_property_verbatim_set',
    'type' => 'file',
    'access callback' => 'entity_metadata_user_properties_access',
    'schema field' => 'picture',
  );

Status: Needs review » Needs work

The last submitted patch, 10: 2224645-10.patch, failed testing.

drunken monkey’s picture

#2192593: User Picture missing in fields? is older than this issue, but since there's already a patch here I marked it as a duplicate.

neograph734’s picture

StatusFileSize
new7.72 KB

Oke, it took me a while to realise I had to update the test as well. So here it is.

The test kept failing when I attempted to get and set the picture property, but then I found that the node test does not include the author property as well. So I suppose the test is incompatible with the entity metadatawrapper chaining.
The picture property return test is working and it returns a created file, so I guess this is already tested elsewhere. Perhaps someone could confirm this?

I've tested most of the properties successfully. The only thing I'm worried about is when a user picture is uploaded to another folder than the default user pictures folder, it might not pass hook_file_download(). Would it be the responsibility of this module to move the file or should the user do that?

neograph734’s picture

Status: Needs work » Needs review
dsdeiz’s picture

  1. +++ b/modules/callbacks.inc
    @@ -376,6 +376,18 @@ function entity_metadata_user_get_properties($account, array $options, $name, $e
    +    case 'pass':
    +      return NULL;
    

    I think it would be better to just return the hashed password. Same as retrieving it via $user->pass.

  2. +++ b/modules/callbacks.inc
    @@ -388,6 +400,11 @@ function entity_metadata_user_set_properties($account, $name, $value) {
    +      require_once('includes/password.inc');
    +      $account->pass = user_hash_password($value);
    

    I think it's better to follow how core includes includes/password.inc. See some usage examples in the API.

    require_once DRUPAL_ROOT . '/' . variable_get('password_inc', 'includes/password.inc');
    
  3. +++ b/entity.test
    @@ -1706,7 +1706,11 @@ class EntityMetadataIntegrationTestCase extends EntityWebTestCase {
    +      if($key == 'pass') {
    +        $this->assertNull($wrapper->$key->value(), "pass property was not returned, NULL was presented instead.");
    +      } else {
    +        $this->assertValue($wrapper, $key);
    +      }
    

    Change is probably unnecessary here if $wrapper->pass->value() returns the hashed value.

Hope this helps!

neograph734’s picture

You are right. I was under the impression Drupal emptied the password after loading, but it is in the global user object as well so I'll just return the hashed pass (although I don't see a use case for it).

Do you perhaps have an idea on the picture folder as well?

dsdeiz’s picture

Do you perhaps have an idea on the picture folder as well?

Hm, I'm not sure yet. Can you perhaps explain further?

neograph734’s picture

Yeah sure.

Since the image is a file now, it's access is checked by entity_metadata_file_access(), which in turn calls hook_file_download() to check if the user can access the file. That code looks like this:

function hook_file_download($uri) {
  // Check if the file is controlled by the current module.
  if (!file_prepare_directory($uri)) {
    $uri = FALSE;
  }
  if (strpos(file_uri_target($uri), variable_get('user_picture_path', 'pictures') . '/picture-') === 0) {
    if (!user_access('access user profiles')) {
      // Access to the file is denied.
      return -1;
    }
    else {
      $info = image_get_info($uri);
      return array('Content-Type' => $info['mime_type']);
    }
  }
}

So it basically checks if the image is in the user_picture_path (the default location for the user form to upload images). And returns true if the user profile is visbile. However with the patch provided any managed file can be set as a user image which might not even be visible.
Therefor, should I attempt to move the file to the correct folder in entity_metadata_user_set_properties(), should I throw an exception or should I do nothing and leave this responsibility to the person using the wrapper.

neograph734’s picture

I suppose it would be best to implement a validation callback just like the mail property has. And check if the picture path is valid. Then we should document somewhere that the files have to be like: public://pictures/picture-<uid>-<timestamp>.<ext>
At that same validation we can check if the file is actually an image.

Moving files seems like a bad idea as we would to load the file with file_load(), then move it with file_move and save it again. Where the user might have already have used entity_load or an entity metadata wrapper to attach it to the user, so we would have to clear those caches as well to be sure. Seems like a potential lot of overhead to me.

dsdeiz’s picture

Status: Needs review » Needs work

Ah yeah, makes total sense.

I suppose it would be best to implement a validation callback just like the mail property has. And check if the picture path is valid.

I guess that'll work. Marking as "Needs work".

neograph734’s picture

Status: Needs work » Needs review
StatusFileSize
new8.73 KB

Thanks for your reply. I forgot to create a local commit last time, so I don't have an interdiff, but the callback is added and the test has been edited a bit.

On my test environment the file move in the following part failed, but that might be due a misconfigured wamp. (Simpletest doesn't work on my server due to a basedir restriction) I can't figure out what goes wrong and the code looks good to me:

    // For the user picture property we pass an image file
    if ($info['name'] == 'picture') {
      $file = $this->createFile('image');
      // Fix name, filename and uri
      $file = file_move($file, 'public://' . variable_get('user_picture_path', 'pictures') . '/picture-' . $file->uid . '-' . $file->timestamp . '.' . pathinfo($file->filename, PATHINFO_EXTENSION));
      return $file;
    }

Status: Needs review » Needs work

The last submitted patch, 21: 2224645-21.patch, failed testing.

neograph734’s picture

Status: Needs work » Needs review
StatusFileSize
new1.76 KB
new9.05 KB

Apparently I needed some extra streamwrapper functions, that I found in the user_save() function. Attached is a working patch and interdiff.

neograph734’s picture

Status: Needs review » Needs work

I recently figured there are still some issues with settings pictues and signatures. I will try to fix this in the upcoming days.

doors’s picture

I applied the patch in #23 and the author picture field comes up within my Search API view.

The problem is that the field is not being shown in the final website page view. What could be causing this?

neograph734’s picture

Hi doors,

I've initially written this patch so the fields became available in Entity metatadata Wrappers and RestWS. So I have not tested it with other API's. I'd however expect that it should work. Could you describe a bit more what you are trying to do and with what modules?
have you cleared the caches? Have you enabled the picture field in admin/config/people/accounts ?

Please be as detailed as possible.

doors’s picture

I am using the Search API and Search API Views modules to create search pages. I have created the indexes within Search API and created the view but when I added the "Indexed Node: Author" relationship (See Image 1) which brings in the Author fields I wasn't seeing the "User:Picture" field in the Fields listing. I applied the patch and the fields come up now (Image 2).

On the page the view is attached the Image filed is still not coming up and you can see from Image 3 that firebug shows the container fields for the Picture Image but iot just does not have the inner image tag leading to the Author's picture image.

I have cleared the cache countless times.

Yes the picture field is enabled as all other views uses this field to show the user image.

doors’s picture

@Neograph734 I just realized something. All the profile images were using the default avatar and not an uploaded avatar from the user. Once I uploaded an avatar to an account that picture cam up but the default images are not shown.

Could you help to rectify why the default image is not showing?

neograph734’s picture

It was not showing because I have not thought of it. However when you change line 398 in entity/modules/callbacks.inc from

return isset($account->picture) ? $account->picture : NULL;

to

return isset($account->picture) ? $account->picture : variable_get('user_picture_default', '');

it should work.

neograph734’s picture

Oh wait, probably not. I'll have to write some more code to do that. I'll do some tests and get back to you.

neograph734’s picture

After looking into this I believe it is not related to this patch.

Drupal generates the picture in template_preprocess_user_picture() and then it displays it in user-picture.tpl.

These templates and processors are only used when displaying either the 'Rendered user', or the 'Rendered user picture'. (The last one in Views is called: 'User: Picture The user's picture, if allowed.' and I expect that one only to be available when a full user object is loaded (Search API loads only a selection of fields)).

What you could do is use Views' no result behavior, and when the user's picture is empty, rewrite the output to <img src="path/to/default/picture" alt="Default user picture">. You could use some replacement patterns to get the user's name in the alt attribute.

Hope this helps.

neograph734’s picture

Title: EntityMetadataWrapper is missing user properties » EntityMetadataWrapper is missing user properties for pictures, passwords, timezones and signatures
Status: Needs work » Needs review
StatusFileSize
new11.64 KB

Well, I guess I finally have them all working.

The following is now possible:

$user = entity_metadata_wrapper('user', $uid);

$user->password->set("newpassword");
$user->timezone->set("Europe/Amsterdam");
//
// Signatures work like node bodies;
$user->signature->value->set("signature");
$user->signature->format->set("filtered_html");
//
// Create a picture here
$data = 'binary image datastring';

$picture = file_save_data($data, 'temporary://picture.png', FILE_EXISTS_RENAME);
$picture->status = 0; // Set the file temporary so the user_save function will take it.
$picture = file_save($picture);
//
// Save the picture
$user->picture->set($picture);
//
// Save the user
$user->save();
neograph734’s picture

StatusFileSize
new11.61 KB

Turned out the picture validation was still commented out due debugging. Also fixed some trailing whitespaces.

dsdeiz’s picture

I haven't totally tested it but probably just a few corrections. :D

+++ b/modules/callbacks.inc
@@ -388,6 +405,19 @@ function entity_metadata_user_set_properties($account, $name, $value) {
+      // Handle these separately as it can happen that a user only specifies one value
+      if(!empty($value['value']))
+        $account->signature = $value['value'];
+      if(!empty($value['format']))
+        $account->signature_format = $value['format'];
+      break;

Probably just coding standards.

Will try to help the best I can to test it once I get the time. Awesome job btw!

neograph734’s picture

Thanks for the feedback, however I don't quite get what you mean. The signature as you describe here is like I have it. Or do you want me to remove the password?

dsdeiz’s picture

Oh, my bad. I was in a hurry when I looked at it. It's very minimal :D Anyway probably just braces on the if statements as mentioned here.

neograph734’s picture

StatusFileSize
new11.6 KB

Good point. Since the module is built with ternary operators, I've decided to use those here as well.

neograph734’s picture

StatusFileSize
new11.29 KB

Just found out that my small change from 'pass' to 'password' was causing all passwords to get double hashed, so nobody could log in anymore. So that has been reverted in this patch.

pol’s picture

I'm waiting for this patch too, I need to access default's user profile picture.

neograph734’s picture

Did you test this and confirm it worked? Then the status of the ticket can be set to 'reviewed and tested by the community' and we might get it in.

pol’s picture

Haven't the chance to test it yet unfortunately.

Reading the patch and tests, it seems to be very good !

tklawsuc’s picture

I could not get the code in #32 to work. The file gets created and stored in the file_managed table but not in the user's picture. I did some debugging and found that the picture value being passed to user_save in entity_metadata_user_save (callbacks.inc) is the fid instead of the stdClass that is expected. As a result the picture is not set for the user and remains as a temporary file with status 0 in the file_manage table.

The only way I could get this to work is to set the picture value on the user object (loaded with user_load) to the file object and then load the user into the entity_metadata_wrapper, set some other data and save the wrapper.

neograph734’s picture

You can do it in function entity_metadata_user_save in modules/callbacks.inc
I once had this there, but my project did not require pictures anymore. Guess I rolled the patch from the wrong repository.

I'll reroll a patch including this in a few days.

/**
 * Callback to save a user account.
 */
function entity_metadata_user_save($account) {
  // Load the user picture file object
  $account->picture = file_load($account->picture);
  
  $edit = (array) $account;
  // Don't save the hashed password as password.
  unset($edit['pass']);
  user_save($account, $edit);
}
tklawsuc’s picture

Thanks! I added a check to only load file if picture has a value and it works great.

if (!empty($account->picture)) $account->picture = file_load($account->picture);

One note if creating a new user, you will need to save the user object first before you can set the picture. Without this extra step of saving the object, not only was the picture missing but I also got an error when loading the picture in a view:

Notice: Trying to get property of non-object in views_handler_field_user_picture->render()

//create user object 
$user = entity_create('user', $values); //$values is an array of initial user values like name, mail, init, etc.
//save the user so it has a uid
$user = user_save($user);
//load up the entity wrapper
$wrapper = entity_metadata_wrapper('user', $user);
//save picture file - $picture_data is the binary data of the image
$picture = file_save_data($picture_data, 'temporary://agent.jpg', FILE_EXISTS_RENAME);  
$picture->status = 0;
$picture = file_save($picture);
$wrapper->picture->set($picture);
//set other fields and then save the entity
$wrapper->save();

Hope this helps.

neograph734’s picture

I noticed that earlier, and I suppose it is a flaw in Drupal (you can't set a picture during registratration as well). We could also handle that in entity_metadata_user_save, but I guess it is a bit hackish... It would be user friendly though...

I suppose it would become something like this, but I can't test right now:

/**
 * Callback to save a user account.
 */
function entity_metadata_user_save($account) {
  $picture = $account->picture;
  if($account->is_new) {
    $account->picture = '';
    $edit = (array) $account;
    // Don't save the hashed password as password.
    unset($edit['pass']);
    user_save($account, $edit);
  }

  // Load the user picture file object
  $account->picture = file_load($picture);
  
  $edit = (array) $account;
  // Don't save the hashed password as password.
  unset($edit['pass']);
  user_save($account, $edit);
}
neograph734’s picture

StatusFileSize
new1.01 KB
new12.08 KB

It should be working without the additional save now.

The full example from #32 should be working now.

yesct’s picture

d.o issue #2516770: REST API is not providing user picture mentions this issue as a possible fix.

edaa’s picture

StatusFileSize
new11.76 KB
/**
 * Callback for validating user picture files.
 */
function entity_metadata_user_validate_picture($picture, $context) {
  // Allow NULL values.
  if (!isset($picture)) {
    return TRUE;
  }
  // Check if the file is in fact a picture and if it is set temporarily
  if (!count(file_validate_is_image($picture)) === 0 || !$picture->status == 0) {
    return FALSE;
  }
  return TRUE;
}

!count(file_validate_is_image($picture)) === 0 always returns FALSE and !$picture->status == 0 also need to correct, see PHP Operator Precedence.

Changes:
!count(file_validate_is_image($picture)) === 0 --> count(file_validate_is_image($picture))
!$picture->status == 0 --> $picture->status != 0

Status: Needs review » Needs work

The last submitted patch, 49: 2224645-49-missing_user_properties.patch, failed testing.

neograph734’s picture

Thanks for taking the time to correct some issues edwardaa. The patch attached to #47 was my latest attempt and that seems to be working (though our suggestions could improve it).
Did you create your patch based on that one?

Status: Needs work » Needs review

Status: Needs review » Needs work

The last submitted patch, 49: 2224645-49-missing_user_properties.patch, failed testing.

edaa’s picture

Status: Needs work » Needs review
StatusFileSize
new1.47 KB
new12.64 KB

Same as #49, but add some changes with entity.test:
1. Move created image file to user picture directory (e.g., public://pictures/...)
2. Return an image file for user picture when call createValue() in testCRUDfunctions() method

neograph734’s picture

If I am not mistaken, the user_save function already moved the image to public://pictures (and renamed them if they existed). This was the reason for me to keep the pictures in temp and let user_save handle the rest, so the pictures folder won't contain two duplicates of every file.

In my opinion the tests should follow this behavior. But you might want to check above, as it has been a while sinds I worked on this.

edaa’s picture

StatusFileSize
new1.14 KB
new12.15 KB

sorry, it's my mistake, new patch rolled back user picture.

edaa’s picture

StatusFileSize
new635 bytes
new12.24 KB

Check picture before saving, now following example works:

$user = user_load($uid);

// Create a picture
$data = 'binary image data string';
$picture = file_save_data($data, 'temporary://picture.png', FILE_EXISTS_RENAME);
$picture->status = 0; // Set the file temporary so the user_save function will take it.
$picture = file_save($picture);

$user->$picture = $picture;

$wrapper = entity_metadata_wrapper('user', $uid);
$wrapper->timezone->set("Europe/Amsterdam");
$wrapper->save();
edaa’s picture

StatusFileSize
new676 bytes
new12.29 KB

Refine #59 code to avoid potential 'Undefined variable' notice.

neograph734’s picture

Great, it would be nice to hear something from the maintainer(s) about wheter or not they would want to commit this.

heylookalive’s picture

+1 on needing this!

drunken monkey’s picture

If the code works for you and also looks fine, please set the status to "Reviewed & tested by the community". That greatly increases the chances a module maintainer will look at it.

heylookalive’s picture

Status: Needs review » Reviewed & tested by the community

K.

annya’s picture

#60 works great! Thanks!

ifrik’s picture

#60 works for me as well.

edaa’s picture

StatusFileSize
new12.29 KB
new420 bytes

Fix typo: change 'password' to 'pass'.

edaa’s picture

Status: Reviewed & tested by the community » Needs review
edaa’s picture

Status: Needs review » Reviewed & tested by the community
heylookalive’s picture

Any chance of committing this? :)

NerOcrO’s picture

StatusFileSize
new12.83 KB

Hi guys,
I added a new field: init.

Status: Reviewed & tested by the community » Needs work

The last submitted patch, 71: 2224645-71-missing_user_properties.patch, failed testing.

Anonymous’s picture

Status: Needs work » Reviewed & tested by the community

2224645-71-missing_user_properties.patch applies cleanly, does what it says, & rectifies the problem of the missing User entity property info. RTBC. It would be great to have this committed.

socialnicheguru’s picture

Status: Reviewed & tested by the community » Needs work

on admin/people
selected 'changed value'
got a WSOD
Call to undefined function entity_property_timezone_options() in modules/all/views_bulk_operations/actions/modify.action.inc on line 206

Where is 'entity_property_timezone_options' function defined?

dawehner’s picture

I think we should leave out the password from this issue. Passwords have to be handled with extra care, given it could be exposed via restWS and what not.
All the other information are not sensitive and conceptually less tricky to implement in the first place (the password has to be hashed for example before saving).
For the password case we should research how D8 solved it exactly and do some kind of parallel implementation of that, if possible.

markhalliwell’s picture

Title: EntityMetadataWrapper is missing user properties for pictures, passwords, timezones and signatures » EntityMetadataWrapper missing "picture", "timezone" & "signature" properties for user entity

Agreed. Password should be handled separately and tested thoroughly (regardless that there is existing tests).

@drumm also voiced this concern #2516770-2: REST API is not providing user picture and I'd rather d.o not be blocked by this issue.

markhalliwell’s picture

Anonymous’s picture

Why not also?:

case 'language':
case 'status':
case 'data':
case 'last_access':
case 'last_login':
  return $is_own_account;
aron novak’s picture

StatusFileSize
new12.29 KB

It might happen that for picture, the file Id is numeric, but not int, see https://pasteboard.co/GOA8Vqp.png from a debugging session.
I went ahead and updated https://www.drupal.org/files/issues/2224645-67-missing_user_properties.p... (not https://www.drupal.org/files/issues/2224645-71-missing_user_properties.p..., it seems init is not wanted).

lunatic’s picture

StatusFileSize
new12.31 KB

extending the last patch it seems only logical that integers should be accepted for picture property in the validate function

something like:

 /**
 * Callback for validating user picture files.
 */
function entity_metadata_user_validate_picture($picture, $context) {
  // Allow NULL values.
  if (!isset($picture) || is_int($picture)) {
    return TRUE;
  }
  // Check if the file is in fact a picture and if it is set temporarily
  if (count(file_validate_is_image($picture)) || $picture->status != 0) {
    return FALSE;
  }
  return TRUE;
}

updated patch attached

naidim’s picture

Sorry, wrong thread.