diff --git a/feeds.api.php b/feeds.api.php index 14a8555..c930972 100644 --- a/feeds.api.php +++ b/feeds.api.php @@ -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,38 @@ function my_module_form_callback($mapping, $target, $form, $form_state) { } /** + * Example of the unique_callbacks specified in + * hook_feeds_processor_targets_alter(). + * + * @param string $target + * A string identifying the unique target on the entity. + * @param mixed $value + * The unique value to be checked. + * @param string $entity_type + * Entity type for the entity to be processed. + * @param string $bundle_name + * Bundle name for the entity to be processed. + * + * @return int|string|null + * Feeds processor existing entity ID. + * Or NULL if no existing entity is found. + * + * @see hook_feeds_processor_targets_alter() + * @see FeedsProcessor::existingEntityId() + */ +function my_module_mapper_unique($target, $value, $entity_type, $bundle_name) { + // Example for if the target is a field. + $query = new EntityFieldQuery(); + $result = $query->entityCondition('entity_type', $entity_type) + ->entityCondition('bundle', $bundle_name) + ->fieldCondition($target, 'value', $value, '=') + ->execute(); + if (isset($result[$entity_type])) { + $ids = array_keys($result[$entity_type]); + return reset($ids); + } +} + +/** * @} */ diff --git a/plugins/FeedsProcessor.inc b/plugins/FeedsProcessor.inc index e73b1ee..ec8f454 100755 --- a/plugins/FeedsProcessor.inc +++ b/plugins/FeedsProcessor.inc @@ -766,7 +766,7 @@ abstract class FeedsProcessor extends FeedsPlugin { if (isset($targets[$target]['unique_callbacks'])) { $callbacks = $targets[$target]['unique_callbacks']; foreach ($callbacks as $callback) { - if (function_exists($callback) && $entity_id = $callback($target, $value, $this->entityType(), $this->bundle())) { + if (is_callable($callback) && $entity_id = call_user_func_array($callback, array($target, $value, $this->entityType(), $this->bundle()))) { // Stop at the first unique ID returned by a callback. break; } diff --git a/tests/feeds_mapper_unique.test b/tests/feeds_mapper_unique.test new file mode 100644 index 0000000..8afaac7 --- /dev/null +++ b/tests/feeds_mapper_unique.test @@ -0,0 +1,101 @@ +content mapper. + */ +class FeedsMapperUniqueTestCase extends FeedsMapperTestCase { + public static function getInfo() { + return array( + 'name' => 'Mapper: Unique target callbacks', + 'description' => 'Test unique target callbacks in mappers.', + 'group' => 'Feeds', + ); + } + + /** + * Test mapping target "unique_callbacks". + */ + public function test() { + // Set unique callback. + variable_set('feeds_test_unique_callbacks', array( + array('FeedsMapperUniqueTestCase', 'getExistingEntityId') + )); + + // 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_target', + 'unique' => TRUE, + 'textfield' => 'required', + ), + )); + + // 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. + node_load($node1->nid, NULL, TRUE); + $this->assertEqual('Ut wisi enim ad minim veniam', $node1->title, 'Node 1 has the expected title'); + node_load($node2->nid, NULL, TRUE); + $this->assertEqual('Lorem ipsum', $node2->title, 'Node 2 has the expected title'); + } + + /** + * Callback for unique_callbacks for test_target mapper. + * + * @see feeds_tests_feeds_processor_targets_alter() + */ + public static function getExistingEntityId($target, $value, $entity_type, $bundle_name) { + $query = new EntityFieldQuery(); + $result = $query->entityCondition('entity_type', $entity_type) + ->entityCondition('bundle', $bundle_name) + ->fieldCondition('field_alpha', 'value', $value, '=') + ->execute(); + if (isset($result[$entity_type])) { + $ids = array_keys($result[$entity_type]); + return reset($ids); + } + } +} diff --git a/tests/feeds_tests.module b/tests/feeds_tests.module index 399708b..a038535 100644 --- a/tests/feeds_tests.module +++ b/tests/feeds_tests.module @@ -112,6 +112,13 @@ function feeds_tests_feeds_processor_targets_alter(&$targets, $entity_type, $bun 'summary_callback' => 'feeds_tests_mapper_summary', 'form_callback' => 'feeds_tests_mapper_form', ); + + // Set unique callbacks for FeedsMapperUniqueTestCase. + $callbacks = variable_get('feeds_test_unique_callbacks'); + if (!empty($callbacks)) { + $targets['test_target']['optional_unique'] = TRUE; + $targets['test_target']['unique_callbacks'] = $callbacks; + } } /**