diff --git a/feeds.api.php b/feeds.api.php index 14a8555..c139da1 100644 --- a/feeds.api.php +++ b/feeds.api.php @@ -271,10 +271,10 @@ function my_source_get_source(FeedsSource $source, FeedsParserResult $result, $k * Remove with caution. * @param $entity_type * The entity type of the target, for instance a 'node' entity. - * @param $bundle_name + * @param $bundle * The bundle name for which to alter targets. */ -function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { if ($entity_type == 'node') { $targets['my_node_field'] = array( 'name' => t('My custom node field'), @@ -292,6 +292,17 @@ function hook_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_nam 'callback' => 'my_module_set_target2', 'real_target' => 'my_node_field_two', // Specify real target field on node. ); + $targets['my_node_field3'] = array( + 'name' => t('My third custom node field'), + 'description' => t('Description of what my third custom node field does.'), + 'callback' => 'my_module_set_target3', + + // Set optional_unique to TRUE and specify unique_callbacks to allow the + // target to be unique. Existing entities can be updated based on unique + // targets. + 'optional_unique' => TRUE, + 'unique_callbacks' => array('my_module_mapper_unique'), + ); } } @@ -369,5 +380,41 @@ function my_module_form_callback($mapping, $target, $form, $form_state) { } /** + * Example of the unique_callbacks specified in + * hook_feeds_processor_targets_alter(). + * + * @param FeedsSource $source + * The Feed source. + * @param string $entity_type + * Entity type for the entity to be processed. + * @param string $bundle + * Bundle name for the entity to be processed. + * @param string $target + * A string identifying the unique target on the entity. + * @param array $values + * The unique values to be checked. + * + * @return int + * The existing entity id, or 0 if not found. + * + * @see hook_feeds_processor_targets_alter() + * @see FeedsProcessor::existingEntityId() + */ +function my_module_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) { + list($field_name, $column) = explode(':', $target . ':value'); + // Example for if the target is a field. + $query = new EntityFieldQuery(); + $result = $query + ->entityCondition('entity_type', $entity_type) + ->entityCondition('bundle', $bundle) + ->fieldCondition($field_name, $column, $values) + ->execute(); + + if (!empty($result[$entity_type])) { + return key($result[$entity_type]); + } +} + +/** * @} */ diff --git a/feeds.info b/feeds.info index eb7387b..6e32601 100644 --- a/feeds.info +++ b/feeds.info @@ -38,6 +38,7 @@ files[] = tests/feeds_mapper_field.test files[] = tests/feeds_mapper_file.test files[] = tests/feeds_mapper_path.test files[] = tests/feeds_mapper_profile.test +files[] = tests/feeds_mapper_unique.test files[] = tests/feeds_mapper.test files[] = tests/feeds_mapper_config.test files[] = tests/feeds_fetcher_file.test diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index 833cd2c..dde65b9 100755 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -738,10 +738,10 @@ abstract class FeedsProcessor extends FeedsPlugin { * * @param FeedsSource $source * The source information about this import. - * @param $result + * @param FeedsParserResult $result * A FeedsParserResult object. * - * @return + * @return int * The serial id of an entity if found, 0 otherwise. */ protected function existingEntityId(FeedsSource $source, FeedsParserResult $result) { @@ -751,23 +751,40 @@ abstract class FeedsProcessor extends FeedsPlugin { ->condition('entity_type', $this->entityType()) ->condition('id', $source->id); - // Iterate through all unique targets and test whether they do already - // exist in the database. + $targets = &drupal_static('FeedsProcessor::existingEntityId', array()); + if (!isset($targets[$this->id])) { + $targets[$this->id] = $this->getMappingTargets(); + } + + $entity_id = 0; + + // Iterate through all unique targets and test whether they already exist in + // the database. foreach ($this->uniqueTargets($source, $result) as $target => $value) { - switch ($target) { - case 'url': - $entity_id = $query->condition('url', $value)->execute()->fetchField(); - break; - case 'guid': - $entity_id = $query->condition('guid', $value)->execute()->fetchField(); - break; + if ($target === 'guid' || $target === 'url') { + $entity_id = $query->condition($target, $value)->execute()->fetchField(); } - if (isset($entity_id)) { - // Return with the content id found. + + if (!$entity_id && !empty($targets[$this->id][$target]['unique_callbacks'])) { + if (!is_array($value)) { + $value = array($value); + } + + foreach ($targets[$this->id][$target]['unique_callbacks'] as $callback) { + if (is_callable($callback) && $entity_id = call_user_func_array($callback, array($source, $this->entityType(), $this->bundle(), $target, $value))) { + // Stop at the first unique ID returned by a callback. + break; + } + } + } + + // Return with the content id found. + if ($entity_id) { return $entity_id; } } - return 0; + + return $entity_id; } diff --git a/tests/feeds_mapper_unique.test b/tests/feeds_mapper_unique.test new file mode 100644 index 0000000..46b48dd --- /dev/null +++ b/tests/feeds_mapper_unique.test @@ -0,0 +1,77 @@ + 'Unique target callbacks', + 'description' => 'Test unique target callbacks in mappers.', + 'group' => 'Feeds', + ); + } + + /** + * Test mapping target "unique_callbacks". + */ + public function test() { + // Create content type. + $typename = $this->createContentType(array(), array('alpha' => 'text')); + + // Create two nodes. Put unique value into field field_alpha. + $node1 = $this->drupalCreateNode(array( + 'type' => $typename, + 'field_alpha' => array( + LANGUAGE_NONE => array( + 0 => array( + 'value' => 'Ut wisi', + ), + ), + ), + )); + $node2 = $this->drupalCreateNode(array( + 'type' => $typename, + 'field_alpha' => array( + LANGUAGE_NONE => array( + 0 => array( + 'value' => 'Lorem', + ), + ), + ), + )); + + // Create and configure importer. + $this->createImporterConfiguration('Syndication', 'syndication'); + $this->setPlugin('syndication', 'FeedsFileFetcher'); + $this->setPlugin('syndication', 'FeedsCSVParser'); + $this->setSettings('syndication', 'FeedsNodeProcessor', array('bundle' => $typename, 'update_existing' => 2)); + $this->addMappings('syndication', array( + 0 => array( + 'source' => 'title', + 'target' => 'title', + ), + 1 => array( + 'source' => 'alpha', + 'target' => 'test_unique_target', + 'unique' => TRUE, + ), + )); + + // Import CSV file. + $this->importFile('syndication', $this->absolutePath() . '/tests/feeds/content.csv'); + $this->assertText('Updated 2 nodes'); + + // Ensure the updated nodes have the expected title now. + $node1 = node_load($node1->nid, NULL, TRUE); + $this->assertEqual('Ut wisi enim ad minim veniam', $node1->title, 'Node 1 has the expected title.'); + $node2 = node_load($node2->nid, NULL, TRUE); + $this->assertEqual('Lorem ipsum', $node2->title, 'Node 2 has the expected title.'); + } + +} diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module index 399708b..fe25704 100644 --- a/tests/feeds_tests.module +++ b/tests/feeds_tests.module @@ -104,7 +104,7 @@ function feeds_tests_files_remote() { /** * Implements hook_feeds_processor_targets_alter(). */ -function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bundle_name) { +function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bundle) { $targets['test_target'] = array( 'name' => t('Test Target'), 'description' => t('This is a test target.'), @@ -112,6 +112,14 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun 'summary_callback' => 'feeds_tests_mapper_summary', 'form_callback' => 'feeds_tests_mapper_form', ); + + $targets['test_unique_target'] = array( + 'name' => t('Test unique target'), + 'description' => t('This is a unique test target.'), + 'callback' => 'feeds_tests_mapper_set_target', + 'optional_unique' => TRUE, + 'unique_callbacks' => array('feeds_tests_mapper_unique'), + ); } /** @@ -205,3 +213,21 @@ function feeds_tests_mapper_form($mapping, $target, $form, $form_state) { ), ); } + +/** + * Callback for unique_callbacks for test_target mapper. + * + * @see feeds_tests_feeds_processor_targets_alter() + */ +function feeds_tests_mapper_unique(FeedsSource $source, $entity_type, $bundle, $target, array $values) { + $query = new EntityFieldQuery(); + $result = $query + ->entityCondition('entity_type', $entity_type) + ->entityCondition('bundle', $bundle) + ->fieldCondition('field_alpha', 'value', $values) + ->execute(); + + if (!empty($result[$entity_type])) { + return key($result[$entity_type]); + } +}