From 6f438a92d0540fe990e0ea6e15652982329ff0ac Mon Sep 17 00:00:00 2001
From: Alex Barth <alex_b@53995.no-reply.drupal.org>
Date: Sat, 5 Dec 2009 01:38:27 +0000
Subject: [PATCH] #623424 Kars-T, Eugen Mayer, alex_b: Mapper for Taxonomy.

---
 CHANGELOG.txt                    |   1 +
 mappers/taxonomy.inc             | 105 +++++++++++++++++
 tests/feeds_mapper_taxonomy.test | 186 +++++++++++++++++++++++++++++++
 3 files changed, 292 insertions(+)
 create mode 100644 mappers/taxonomy.inc
 create mode 100644 tests/feeds_mapper_taxonomy.test

diff --git a/CHANGELOG.txt b/CHANGELOG.txt
index fd7c307e..78fc0957 100644
--- a/CHANGELOG.txt
+++ b/CHANGELOG.txt
@@ -3,6 +3,7 @@
 Feeds 6.x 1.0 xxxx, 200x-xx-xx
 ------------------------------
 
+- #623424 Kars-T, Eugen Mayer, alex_b: Mapper for Taxonomy.
 - #649552 rsoden: Provide variable for data table name.
 - #631962 velosol, alex_b: FeedsNodeProcessor: Update when changed.
 - #623452 mongolito404: Port basic test infrastructure for mappers, test for
diff --git a/mappers/taxonomy.inc b/mappers/taxonomy.inc
new file mode 100644
index 00000000..c9370a5a
--- /dev/null
+++ b/mappers/taxonomy.inc
@@ -0,0 +1,105 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Mapper that exposes a node's taxonomy vocabularies as mapping targets.
+ */
+
+/**
+ * Implementation of hook_feeds_node_processor_targets_alter().
+ *
+ * @see FeedsNodeProcessor::getMappingTargets().
+ */
+function taxonomy_feeds_node_processor_targets_alter(&$targets, $content_type) {
+  foreach (taxonomy_get_vocabularies($content_type) as $vocab) {
+    $description = t('The !name vocabulary of the node. If this is a "Tags" vocabulary, any new terms will be created on import. Otherwise only existing terms will be used. If this is not a "Tags" vocabulary and not a "Multiple select" vocabulary, only the first available term will be created. See !settings.', array('!name' => $vocab->name, '!settings' => l(t('vocabulary settings'), 'admin/content/taxonomy/edit/vocabulary/'. $vocab->vid, array('query' => 'destination='. $_GET['q']))));
+
+    $targets['taxonomy:'. $vocab->vid] = array(
+      'name' => "Taxonomy: ". $vocab->name,
+      'callback' => 'taxonomy_feeds_set_target',
+      'description' => $description,
+    );
+  }
+}
+
+/**
+ * Callback for mapping. Here is where the actual mapping happens.
+ *
+ * @param $node
+ *   Reference to the node object we are working on.
+ *
+ * @param $key
+ *   A key as added to the $targets array by
+ *   taxonomy_feeds_node_processor_targets_alter().
+ *
+ * @param $terms
+ *   Given terms as array.
+ *
+ * Add the given terms to the node object so the taxonomy module can add them
+ * on node_save().
+ */
+function taxonomy_feeds_set_target(&$node, $key, $terms) {
+
+  // Return if there are no terms.
+  if (empty($terms)) {
+    return;
+  }
+
+  // Load target vocabulary to check, if it has the "tags" flag.
+  $vocab_id = (int) str_replace('taxonomy:', '', $key);
+  $vocab = taxonomy_vocabulary_load($vocab_id);
+
+  // Cast a given single string to an array so we can use it.
+  if (!is_array($terms)) {
+    $terms = array($terms);
+  }
+
+  if ($vocab->tags) {
+    // Simply add a comma separated list to the node for a "tags" vocabulary.
+    $node->taxonomy['tags'][$vocab->vid] = implode(',', $terms);
+  }
+  else {
+    foreach ($terms as $term_name) {
+      // Check if a term already exists.
+      if ($terms_found = taxonomy_get_term_by_name_vid($term_name, $vocab->vid)) {
+        // If any terms are found add them to the node's taxonomy by found tid.
+        foreach ($terms_found AS $term_found) {
+          $node->taxonomy[$vocab->vid][$term_found->tid] = $term_found->tid;
+          if (!$vocab->multiple) {
+            break;
+          }
+        }
+        // If the vocab is not for multiple tags break after the first hit.
+        if (!$vocab->multiple) {
+          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 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_object($db_result)) {
+    $result[] = $term;
+  }
+  return $result;
+}
diff --git a/tests/feeds_mapper_taxonomy.test b/tests/feeds_mapper_taxonomy.test
new file mode 100644
index 00000000..1a43038e
--- /dev/null
+++ b/tests/feeds_mapper_taxonomy.test
@@ -0,0 +1,186 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Test case for taxonomy field mapper mappers/taxonomy.inc.
+ */
+
+require_once(drupal_get_path('module', 'feeds') . '/tests/feeds_mapper_test.inc');
+
+/**
+ * Class for testing Feeds <em>content</em> mapper.
+ */
+class FeedsMapperTaxonomyTestCase extends FeedsMapperTestCase {
+
+  public static function getInfo() {
+    return array(
+      'name' => t('Mapper: Taxonomy'),
+      'description' => t('Test Feeds Mapper support for Taxonomy.'),
+      'group' => t('Feeds'),
+    );
+  }
+
+  /**
+   * Set up the test.
+   */
+  function setUp() {
+    // Call parent setup with required modules.
+    parent::setUp('feeds', 'feeds_ui', 'ctools', 'taxonomy');
+
+    // Create user and login.
+    $this->drupalLogin($this->drupalCreateUser(
+        array(
+          'administer content types',
+          'administer feeds',
+          'administer nodes',
+          'administer site configuration',
+          'administer taxonomy',
+        )
+    ));
+  }
+
+  /**
+   * Basic test loading an RSS feed.
+   */
+  function test() {
+
+    // Add a new taxonomy vocabulary, add to story content type.
+    $edit = array(
+      'name' => 'Tags',
+      'tags' => TRUE,
+      'nodes[story]' => TRUE,
+    );
+    $this->drupalPost('admin/content/taxonomy/add/vocabulary', $edit, 'Save');
+
+    // Create a feed, include mapping to taxonomy.
+    $this->createFeedConfiguration('Syndication', 'syndication');
+    $this->addMappings('syndication',
+      array(
+        array(
+          'source' => 'title',
+          'target' => 'title',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'description',
+          'target' => 'body',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'timestamp',
+          'target' => 'created',
+          'unique' => FALSE,
+        ),
+        array(
+          'source' => 'url',
+          'target' => 'url',
+          'unique' => TRUE,
+        ),
+        array(
+          'source' => 'guid',
+          'target' => 'guid',
+          'unique' => TRUE,
+        ),
+        array(
+          'source' => 'tags',
+          'target' => 'taxonomy:1',
+        ),
+      )
+    );
+
+    // Aggregate feed node with "Tag" vocabulary.
+    $nid = $this->createFeedNode();
+    // Assert 10 items aggregated after creation of the node.
+    $this->assertText('Created 10 Story nodes.');
+    // There should be 30 terms and 44 term-node relations.
+    $this->assertEqual(30, db_result(db_query('SELECT count(*) FROM {term_data}')), 'Found correct number of terms.');
+    $this->assertEqual(44, db_result(db_query('SELECT count(*) FROM {term_node}')), 'Found correct number of term-node relations.');
+
+    // Take a look at the actual terms on frontpage.
+    $this->drupalGet('node');
+    $terms = array(
+      'authentication',
+      'custom mapping',
+      'data visualization',
+      'Drupal',
+      'Drupal planet',
+      'faceted search',
+      'GeoDC',
+      'graphs',
+      'interface',
+      'intranet',
+      'localization',
+      'localization client',
+      'localization server',
+      'map-basec browser',
+      'mapbox',
+      'microfinance',
+      'MIX Market',
+      'open atrium',
+      'open data',
+      'open source',
+      'Peru',
+      'salesforce',
+      'siteminder',
+      'siteminder module',
+      'software freedom day',
+      'translation',
+      'translation server',
+      'usability',
+      'Washington DC',
+      'World Bank',
+    );
+    foreach ($terms as $term) {
+      $term = check_plain($term);
+      $this->assertPattern('/<a href="(.*?)\/taxonomy\/term\/([0-9]*?)"(.*)>'. $term .'<\/a>/', 'Found '. $term);
+    }
+
+    // Delete all items, all associations are gone.
+    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
+    $this->assertText('Deleted 10 nodes.');
+    $this->assertEqual(30, db_result(db_query('SELECT count(*) FROM {term_data}')), 'Found correct number of terms.');
+    $this->assertEqual(0, db_result(db_query('SELECT count(*) FROM {term_node}')), 'Found correct number of term-node relations.');
+
+    // Remove "Tag" setting, import again.
+    $edit = array(
+      'tags' => FALSE,
+    );
+    $this->drupalPost('admin/content/taxonomy/edit/vocabulary/1', $edit, 'Save');
+    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
+    $this->assertText('Created 10 Story nodes.');
+
+    // We should only get one term-node association per node.
+    $this->assertEqual(30, db_result(db_query('SELECT count(*) FROM {term_data}')), 'Found correct number of terms.');
+    $this->assertEqual(10, db_result(db_query('SELECT count(*) FROM {term_node}')), 'Found correct number of term-node relations.');
+
+    // Delete all items.
+    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
+
+    // Set vocabulary to multiple terms, import again.
+    $edit = array(
+      'multiple' => TRUE,
+    );
+    $this->drupalPost('admin/content/taxonomy/edit/vocabulary/1', $edit, 'Save');
+    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
+    $this->assertText('Created 10 Story nodes.');
+
+    // We should get all term-node associations again.
+    $this->assertEqual(30, db_result(db_query('SELECT count(*) FROM {term_data}')), 'Found correct number of terms.');
+    $this->assertEqual(44, db_result(db_query('SELECT count(*) FROM {term_node}')), 'Found correct number of term-node relations.');
+
+    // Delete all items.
+    $this->drupalPost('node/'. $nid .'/delete-items', array(), 'Delete');
+
+    // Remove a term, import again.
+    $this->drupalPost('admin/content/taxonomy/edit/term/1', array(), 'Delete');
+    $this->drupalPost(NULL, array(), 'Delete');
+    $this->assertText('Deleted term');
+    $this->drupalPost('node/'. $nid .'/import', array(), 'Import');
+    $this->assertText('Created 10 Story nodes.');
+
+    // This term should now be missing from term-node associations.
+    $this->assertEqual(29, db_result(db_query('SELECT count(*) FROM {term_data}')), 'Found correct number of terms.');
+    $this->assertEqual(39, db_result(db_query('SELECT count(*) FROM {term_node}')), 'Found correct number of term-node relations.'. db_result(db_query('SELECT count(*) FROM {term_node}')));
+  }
+}
-- 
GitLab