How does Feeds decide if it is going to create a new (or update an existing) entity? I am using Feeds to map a CSV file to user entities and have the "Update existing users" setting in place. Everything is going great, except for the Telephone field... as it currently stands, the Telephone module doesn't have support for Feeds.

There is a patch that works for saving a telephone number when creating a new user, but not when updating an existing user.

Here's a CSV example (excluding headers for simplicity) for a new user:

demo,demo@demo.com,1234567890


Here's a CSV example for updating that same user:

demo,demo@demo.com,4567890123


When changing the value of the telephone number in the CSV file, and running it through the importer again, Feeds (or someone else?) doesn't see any incoming changes to be applied, so it skips the import entirely saying:

"There are no new users."


I've been looking at the Feeds API to see what hooks are available, and thought hook_feeds_presave() would be a nice one, since it talks about "skipping" an entity, but this hook only gets fired when creating a new user.

The hook_feeds_before_update() function gets called each time, but I'm not seeing a way to indicate to Feeds whether or not a value has changed, and to proceed or skip.

I'm guessing I need to expand upon the patch with hook(s)/other to make a simple decision for Feeds, pseudo code e.g.

...
// If telephone number has changed, tell feeds its OK to update.
if ($feed_item_value != $entity-&gt;{$field_name}['und'][o]['value']) {
  $feeds-&gt;update = TRUE;
}
else {
  $feeds-&gt;update = FALSE;
}
...


How does a module, that provides a field, tell Feeds that it detects a change with the field's value and to update an entity? Or does feeds and the internal entity system automatically do this?
The Feeds module uses the FeedsProcessor object to define what it calls mapping targets for an entity to determine what fields are unique and what can be optionally be set unique in the feeds importer mapping settings UI. It then uses the method existingEntityId to determine if the mapping targets are unique.

In the case of users, the hook_feeds_plugins method for the feeds module tells feeds to use the class FeedsUserProcessor. This class has a method named getMappingTargets to say that the optional unique fields can be name and mail. In the mapping ui for the user feeds importer (admin/structure/feeds/user/mapping), you can set name and/or mail to be unique. The existingEntityId method in FeedsUserProcessor.inc determines if it has found a unique value. If so, and the importer is set to replace or update existing users, then the feeds record will be updated.

If you really wanted to work with telephone number as a unique field attached to a user, then you use implement hook_feeds_plugins, define your own FeedsProcessor and extend FeedsUserProcessor, and overwrite the getMappingTargets to add the field as an optional_unique entry, and overwrite existingEntityId to find other users with a matching telephone number.

However, it sounds like you don't need to do that. Make sure your user feeds import is set to update existing users, and that it is reading the csv file properly.

I went ahead and enabled telephone, installed the patch you mentioned, and was able to see a user be created, and then be updated with a different telephone number. My import file looked like this:

name,mail,telephone
demo,demo@demo.com,1234567890


and then this:

name,mail,telephone
demo,demo@demo.com,4567890123


And here is an export of the feed import:

$feeds_importer = new stdClass();
$feeds_importer-&gt;disabled = FALSE; /* Edit this to true to make a default feeds_importer disabled initially */
$feeds_importer-&gt;api_version = 1;
$feeds_importer-&gt;id = 'user';
$feeds_importer-&gt;config = array(
  'name' =&gt; 'User import',
  'description' =&gt; 'Import users from CSV file.',
  'fetcher' =&gt; array(
    'plugin_key' =&gt; 'FeedsFileFetcher',
    'config' =&gt; array(
      'direct' =&gt; FALSE,
      'allowed_extensions' =&gt; 'txt csv tsv xml opml',
      'directory' =&gt; 'public://feeds',
      'allowed_schemes' =&gt; array(
        0 =&gt; 'public',
      ),
    ),
  ),
  'parser' =&gt; array(
    'plugin_key' =&gt; 'FeedsCSVParser',
    'config' =&gt; array(
      'delimiter' =&gt; ',',
      'no_headers' =&gt; 0,
    ),
  ),
  'processor' =&gt; array(
    'plugin_key' =&gt; 'FeedsUserProcessor',
    'config' =&gt; array(
      'bundle' =&gt; 'user',
      'roles' =&gt; array(
        3 =&gt; 0,
      ),
      'update_existing' =&gt; '1',
      'status' =&gt; '1',
      'mappings' =&gt; array(
        0 =&gt; array(
          'source' =&gt; 'name',
          'target' =&gt; 'name',
          'unique' =&gt; 0,
        ),
        1 =&gt; array(
          'source' =&gt; 'mail',
          'target' =&gt; 'mail',
          'unique' =&gt; 1,
        ),
        2 =&gt; array(
          'source' =&gt; 'telephone',
          'target' =&gt; 'field_telephone:url',
          'unique' =&gt; FALSE,
        ),
      ),
      'defuse_mail' =&gt; 0,
      'update_non_existent' =&gt; 'skip',
      'input_format' =&gt; 'plain_text',
      'skip_hash_check' =&gt; 0,
    ),
  ),
  'content_type' =&gt; '',
  'update' =&gt; 0,
  'import_period' =&gt; '-1',
  'expire_period' =&gt; 3600,
  'import_on_create' =&gt; 1,
  'process_in_background' =&gt; 0,
);