diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index 5a5ba966143a2c6fe6cdd9eae5b614ab1ec5445f..631e2268b56ce66c3a1da29d42c23b6d42c601eb 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,6 +3,7 @@
Feeds 6.x xxxxxxxxxxxxxxxxxxxxxx
--------------------------------
+- #637334 mongolito404, elliotttf et. al.: Mapper for Content Taxonomy.
- #953638 joshuajabbour: FeedsTermProcessor looks for feed_nid in wrong object.
- #954080 mlsamuelson: mimedetect_mime() expects a file object.
- #608844 Will White: Optionally delete data items with feed nodes.
diff --git a/mappers/content_taxonomy.inc b/mappers/content_taxonomy.inc
new file mode 100644
index 0000000000000000000000000000000000000000..4ae126cbf53ea6102c3be1a2dff02ce1332964fe
--- /dev/null
+++ b/mappers/content_taxonomy.inc
@@ -0,0 +1,120 @@
+ $field) {
+ if(in_array($field['type'], array('content_taxonomy'))) {
+ $name = isset($field['widget']['label']) ? $field['widget']['label'] : $field_name;
+ $targets[$field_name] = array(
+ 'name' => $name,
+ 'callback' => 'content_taxonomy_feeds_set_target',
+ 'description' => t('The CCK %name field of the node (!type).', array('%name' => $name, '!type' => $field['type'])),
+ );
+ }
+ }
+ }
+}
+
+/**
+ * Callback for mapping. Here is where the actual mapping happens.
+ *
+ * @param $node
+ * Reference to the node object we are working on.
+ *
+ * @param $vid
+ * The selected content_taxonomy CCK field.
+ *
+ * @param $terms
+ * Given terms as array. If a string is used, it is converted to an array
+ * using taxonomy_terms_parse_string($terms)->tids
.
+ *
+ * @see taxonomy_terms_parse_string().
+ *
+ */
+function content_taxonomy_feeds_set_target(&$node, $field_name, $terms) {
+ static $fields = array();
+
+ $field = content_fields($field_name, $node->type);
+
+ // Parse string for multiple tags (comma separated)
+ if(is_string($terms)) {
+ $terms = split(',', $terms);
+ }
+
+ // Return if there are no or empty terms.
+ if (!is_array($terms) || empty($terms)) {
+ return;
+ }
+
+ $tags = $field['widget']['type'] == 'content_taxonomy_autocomplete' && $field['widget']['new_terms'];
+ $multiple = $field['widget']['multiple'];
+
+ foreach ($terms as $k => $term_name) {
+ $term_name = trim($term_name);
+ if ($terms_found = content_taxonomy_get_term_by_name_vid($term_name, $field['vid'])) {
+ // If any terms are found add them to the field by found tid.
+ foreach($terms_found as $term_found) {
+ if(!is_array($node->$field_name)) $node->$field_name = array();
+ array_push($node->$field_name, array('value' => $term_found['tid']));
+ if ($multiple != 0 && count($node->$field_name) >= $multiple) {
+ // If the vocab is not for multiple tags break after the first hit.
+ break;
+ }
+ }
+ }
+ else if ($tags) {
+ // If the field is configured for free tagging, create a new term
+ $edit = array('vid' => $field['vid'], 'name' => $term_name);
+ if ($field['widget']['extra_parent']) {
+ $edit['parent'] = $field['widget']['extra_parent'];
+ }
+ taxonomy_save_term($edit);
+ if (!is_array($node->$field_name)) {
+ $node->$field_name = array();
+ }
+ array_push($node->$field_name, array('value' => $edit['tid']));
+ }
+ if ($multiple != 0 && count($node->$field_name) >= $multiple) {
+ // If the vocab is not for multiple tags break after a first term has been added.
+ break;
+ }
+ }
+}
+
+/**
+ * Try to map a string to an existing term by name and vocabulary id.
+ *
+ * Provides a case-insensitive and trimmed mapping, to maximize the
+ * likelihood of a successful match limited by a vocabulary id.
+ *
+ * @param $name
+ * Name of the term to search for.
+ *
+ * @param $vid
+ * The vocabulary's ID.
+ *
+ * @return
+ * An array of matching term objects.
+ */
+function content_taxonomy_get_term_by_name_vid($name, $vid) {
+ $db_result = db_query(db_rewrite_sql("SELECT t.tid, t.name FROM {term_data} t WHERE LOWER(t.name) = LOWER('%s') AND vid=%d", 't', 'tid'), trim($name), $vid);
+ $result = array();
+ while ($term = db_fetch_array($db_result)) {
+ $result[] = $term;
+ }
+ return $result;
+}
diff --git a/tests/feeds/content-taxonomy.csv b/tests/feeds/content-taxonomy.csv
new file mode 100644
index 0000000000000000000000000000000000000000..c28bf8c1703c62a46a6498de8747554e682a5e10
--- /dev/null
+++ b/tests/feeds/content-taxonomy.csv
@@ -0,0 +1,2 @@
+"title","created","tags","categories","body"
+"Lorem ipsum",1251936720,"lorem, ipsum, dolor, sit, amet","consectetuer","Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat."
diff --git a/tests/feeds_mapper_content_taxonomy.test b/tests/feeds_mapper_content_taxonomy.test
new file mode 100644
index 0000000000000000000000000000000000000000..93c4abaab775e7be036ff58589f54d9e75672d4f
--- /dev/null
+++ b/tests/feeds_mapper_content_taxonomy.test
@@ -0,0 +1,191 @@
+content mapper.
+ */
+class FeedsMapperContentTaxonomyTestCase extends FeedsMapperTestCase {
+
+ public static function getInfo() {
+ return array(
+ 'name' => t('Mapper: Content Taxonomy'),
+ 'description' => t('Test Feeds Mapper support for Content Taxonomy CCK fields. Requires CCK and Content Taxonomy module.'),
+ 'group' => t('Feeds'),
+ );
+ }
+
+ /**
+ * Set up test.
+ */
+ public function setUp() {
+ // Call parent setup with the required module
+ parent::setUp(
+ 'feeds', 'feeds_ui', 'job_scheduler', 'ctools', 'content',
+ 'taxonomy', 'content_taxonomy', 'content_taxonomy_autocomplete', 'content_taxonomy_options'
+ );
+
+ // Create user and login
+ $this->drupalLogin($this->drupalCreateUser(
+ array(
+ 'administer content types',
+ 'administer feeds',
+ 'administer nodes',
+ 'administer site configuration',
+ )
+ ));
+ }
+
+ /**
+ * Basic test loading a single entry CSV file.
+ */
+ public function test() {
+
+ // Create vocabularies
+ $vocabularies = array(
+ 'tags' => array(
+ 'name' => $this->randomName(),
+ 'tags' => true,
+ ),
+ 'categories' => array(
+ 'name' => $this->randomName(),
+ 'tags' => false,
+ )
+ );
+ foreach ($vocabularies as &$vocabulary) {
+ taxonomy_save_vocabulary($vocabulary);
+ }
+
+ // Create terms
+ $terms = array(
+ array(
+ 'name' => 'foo',
+ 'vid' => $vocabularies['tags']['vid'],
+ 'weight' => 0,
+ ),
+ array(
+ 'name' => 'lorem',
+ 'vid' => $vocabularies['tags']['vid'],
+ 'weight' => 0,
+ ),
+ array(
+ 'name' => 'ipsum',
+ 'vid' => $vocabularies['tags']['vid'],
+ 'weight' => 0,
+ ),
+ array(
+ 'name' => 'bar',
+ 'vid' => $vocabularies['categories']['vid'],
+ 'weight' => 0,
+ ),
+ 'consectetuer' => array(
+ 'name' => 'consectetuer',
+ 'vid' => $vocabularies['categories']['vid'],
+ 'weight' => 0,
+ ),
+ );
+ foreach ($terms as &$term) {
+ taxonomy_save_term($term);
+ }
+
+ // Create content type
+ $typename = $this->createContentType(NULL, array(
+ 'tags' => array(
+ 'type' => 'content_taxonomy',
+ 'widget' => 'content_taxonomy_autocomplete',
+ 'settings' => array(
+ 'new_terms' => 'insert',
+ 'multiple' => '1',
+ 'vid' => $vocabularies['tags']['vid'],
+ ),
+ ),
+ 'categories' => array(
+ 'type' => 'content_taxonomy',
+ 'widget' => 'content_taxonomy_select',
+ 'settings' => array(
+ 'multiple' => '1',
+ 'vid' => $vocabularies['categories']['vid'],
+ ),
+ ),
+ ));
+
+ // Create importer configuration
+ $this->createImporterConfiguration('Content Taxonomy CSV', 'csv'); // Create a default importer configuration
+ $this->setSettings('csv', NULL, array('content_type' => '','import_period' => FEEDS_SCHEDULE_NEVER,)); // Importer setting
+ $this->setPlugin('csv', 'FeedsFileFetcher'); //Set fetcher
+ $this->setPlugin('csv', 'FeedsCSVParser'); //Set parser
+ $this->setSettings('csv', 'FeedsNodeProcessor', array('content_type' => $typename)); // Processor settings
+ $this->addMappings('csv', array(
+ array(
+ 'source' => 'title',
+ 'target' => 'title'
+ ),
+ array(
+ 'source' => 'created',
+ 'target' => 'created'
+ ),
+ array(
+ 'source' => 'body',
+ 'target' => 'body'
+ ),
+ array(
+ 'source' => 'tags',
+ 'target' => 'field_tags'
+ ),
+ array(
+ 'source' => 'categories',
+ 'target' => 'field_categories'
+ ),
+ ));
+
+ // Import CSV file.
+ $this->importFile('csv', $this->absolutePath() . '/tests/feeds/content-taxonomy.csv');
+ $this->assertText('Created 1 ' . $typename . ' node.');
+
+ // Check that the tags were stored correctly
+ $this->drupalGet('node/1/edit');
+ $this->assertCCKFieldValue('tags', array('lorem', 'ipsum', 'dolor', 'sit', 'amet'));
+
+ // Check that the category values were stored to the database correctly
+ $this->assertEqual(
+ $terms['consectetuer']['tid'],
+ db_result(db_query("SELECT field_categories_value FROM {content_field_categories} WHERE nid=1")),
+ t('Found expected content_taxonomy value')
+ );
+ }
+
+ protected function selectFieldWidget($field_name, $field_type) {
+ if ($field_type == 'content_taxonomy') {
+ return 'content_taxonomy_select';
+ } else {
+ return parent::selectFieldWidget($field_name, $field_type);
+ }
+ }
+
+ protected function getFormFieldsNames($field_name, $index) {
+ switch ($field_name) {
+ case 'tags':
+ return array("field_{$field_name}[value]");
+ case 'categories':
+ return array("field_{$field_name}[value][]");
+ default:
+ return parent::getFormFieldsNames($field_name, $index);
+ }
+ }
+
+ protected function getFormFieldsValues($field_name, $value) {
+ switch($field_name) {
+ case 'tags':
+ if (is_array($value)) {
+ // @todo sort tags by weight before joining
+ $value = join(', ', $value);
+ }
+ return array($value);
+ case 'categories':
+ // @todo return tid(s) from $value
+ default:
+ return parent::getFormFieldsValues($field_name, $value);
+ }
+ }
+}