Index: inform.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/inform/inform.install,v
retrieving revision 1.4
diff -u -p -r1.4 inform.install
--- inform.install	13 Oct 2009 13:15:02 -0000	1.4
+++ inform.install	24 Jan 2011 02:12:10 -0000
@@ -1,37 +1,116 @@
 <?php
-// $Id: inform.install,v 1.4 2009/10/13 13:15:02 JeremyFrench Exp $
+
 /**
  * @file
  * Install file for the Inform module.
  */
 
 /**
+ * Implementation of hook_requirements().
+ */
+function inform_requirements($phase) {
+  $requirements = array();
+
+  if ($phase == 'runtime') {
+    if (class_exists('SoapClient')) {
+      $requirements['soap'] = array(
+        'value' => t('Installed'),
+        'severity' => REQUIREMENT_OK,
+        'description' => t('The Inform module relies on the PHP soap extension which is installed.')
+      );
+    }
+    else {
+      $requirements['soap'] = array(
+        'value' => t('Not installed'),
+        'severity' => REQUIREMENT_ERROR,
+        'description' => t('The Inform module relies on the PHP soap extension which is not installed.')
+      );
+    }
+    $requirements['soap']['title'] = t('SOAP library');
+  }
+
+  return $requirements;
+}
+
+
+/**
  * Implementation of hook_schema().
  */
 function inform_schema() {
-  $schema['inform_node_update'] = array(
-    'description' => 'table for tracking inform tagging of nodes',
+  $schema = array();
+  
+  $schema['inform_topics'] = array(
+    'description' => 'Stores topics, entities and industries from the Inform API.',
     'fields' => array(
+      'id' => array(
+        'description' => 'Primary identifier for an Inform topic.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'name' => array(
+        'description' => 'The name of the topic as received from the Inform API.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+      ),
+      'display_name' => array(
+        'description' => 'The editable name of the topic, overrides name if present.',
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'type' => array(
+        'description' => 'The type of topic - topic, entity or industry.',
+        'type' => 'int',
+        'size' => 'small',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      )
+    ),
+    'primary key' => array('id'),
+    'unique keys' => array(
+      'name' => array('name'),
+    ),
+  );
+
+  $schema['inform_topics_nodes'] = array(
+    'description' => 'Pivot table to join nodes with their corresponding Inform topics.',
+    'fields' => array(
+      'inform_topic_id' => array(
+        'description' => 'Primary identifier for an Inform topic',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
       'nid' => array(
         'description' => 'The primary identifier for a node.',
         'type' => 'int',
         'unsigned' => TRUE,
-        'not null' => TRUE
+        'not null' => TRUE,
       ),
-      'timestamp' => array(
-        'description' => 'when the node was last tagged by inform',
-        'type' => 'datetime',
-        'not null' => FALSE,
+      'score' => array(
+        'description' => 'The score of this topic in relation to this node.',
+        'type' => 'int',
+        'not null' => TRUE,
+        'default' => 0,
       ),
-      'disregard' => array(
-        'description' => 'if 1 then ignore this node in inform queries',
+      'high_score' => array(
+        'description' => 'Whether or not this node\'s score exceeds the minimum required.',
         'type' => 'int',
+        'size' => 'tiny',
         'not null' => TRUE,
         'default' => 0,
       ),
     ),
-    'primary key' => array('nid'),
+    'primary key' => array('inform_topic_id', 'nid'),
+    'indexes' => array(
+      'nid' => array('nid'),
+      'high_score_inform_topic_id' => array('high_score', 'inform_topic_id'),
+    ),
   );
+
   return $schema;
 }
 
@@ -39,53 +118,6 @@ function inform_schema() {
  * Implementation of hook_install().
  */
 function inform_install() {
-  $ret = array();
-  $vid = db_result(db_query("SELECT vid FROM {vocabulary} WHERE module='inform'"));
-  if (!$vid) {
-    $vocabulary = array(
-      'name' => t('Inform'),
-      'multiple' => '1',
-      'required' => '0',
-      'hierarchy' => '1',
-      'relations' => '0',
-      'module' => 'inform',
-      'nodes' => array('article' => 1), 
-    );
-    taxonomy_save_vocabulary($vocabulary);
-    $vid = $vocabulary['vid'];
-  }
-  variable_set('inform_vocabulary', $vid);
-  // Set the other admin values as defaults (where applicable).
-  variable_set('inform_related_minimum_score', 40);
-  variable_set('inform_my_minimum_score', 50);
-  variable_set('inform_terms_count', 5);
-  variable_set('inform_defer_tagging', 0);
-  variable_set('inform_tag_age', 50);
-  variable_set('inform_batch_size', 50);
-
-  $term = array(
-    'name' => 'TOPIC',
-    'vid' => $vid,
-  );
-  taxonomy_save_term($term);
-
-  $term2 = array(
-    'name' => 'INDUSTRY',
-    'vid' => $vid,
-  );
-  taxonomy_save_term($term2);
-
-  $inform_score_column = array(
-    'description' => t('This field is used by the inform module to assign a relevance score to an association.'),
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-    'default' => 0,
-    'initial' => 0,
-    'size' => 'tiny',
-    'length' => 2,
-  );
-  db_add_field($ret, 'term_node', 'inform_score', $inform_score_column);
   drupal_install_schema('inform');
 }
 
@@ -93,20 +125,13 @@ function inform_install() {
  * Implementation of hook_uninstall().
  */
 function inform_uninstall() {
-  $ret = array();
-  if ($vid = variable_get('inform_vocabulary', FALSE)) {
-    taxonomy_del_vocabulary($vid);
-    variable_del('inform_vocabulary');
-  }
+  drupal_uninstall_schema('inform');
+  variable_del('inform_progress_changed');
+  variable_del('inform_progress_nid');
+  variable_del('inform_process_on_update');
+  variable_del('inform_minimum_score');
+  variable_del('inform_batch_size');
+  variable_del('inform_content_types');
   variable_del('inform_url');
   variable_del('inform_itoken');
-  variable_del('inform_related_minimum_score');
-  variable_del('inform_my_minimum_score');
-  variable_del('inform_terms_count');
-  variable_del('inform_defer_tagging');
-  variable_del('inform_tag_age');
-  variable_del('inform_batch_size');
-  
-  drupal_uninstall_schema('inform');
-  db_drop_field($ret, 'term_node', 'inform_score');
 }
Index: inform.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/inform/inform.module,v
retrieving revision 1.6
diff -u -p -r1.6 inform.module
--- inform.module	26 Oct 2009 09:47:33 -0000	1.6
+++ inform.module	24 Jan 2011 02:12:10 -0000
@@ -5,590 +5,376 @@
  * Drupal integration with Inform Technologies' web services.
  */
 
-define("INFORM_BLOCK_TAGS", 0);
-
 // Inform has industry, entity and topic as magic words, so we should treat them
 // the same way.
-define("INFORM_TERM_INDUSTRY", "INDUSTRY");
-define("INFORM_TERM_TOPIC", "TOPIC");
-define("INFORM_TERM_ENTITY", "ENTITY");
-
-/**
- * Implementation of hook_schema_alter().
- */
-function inform_schema_alter(&$schema) {
-  $schema['term_node']['fields']['inform_score'] = array(
-    'description' => t('This field is used by the Inform module to assign a relevance score to an association'),
-    'type' => 'int',
-    'unsigned' => TRUE,
-    'not null' => TRUE,
-    'default' => 0,
-    'initial' => 0,
-    'size' => 'tiny',
-    'length' => 2,
-  );
-}
+define('INFORM_TERM_INDUSTRY', 0);
+define('INFORM_TERM_TOPIC', 1);
+define('INFORM_TERM_ENTITY', 2);
+
 /** 
  * Implementation of hook_cron().
+ *
  * If deferred processing is enabled then use cron rather than node_api save
- * to tag the articles.
+ * to tag the nodes.
  */
 function inform_cron() {
-  $defer = variable_get('inform_defer_tagging', 0);
-  $batch_size = variable_get('inform_batch_size', 50);
-  $days = variable_get('inform_tag_age', 50);
-  if ($defer ==1) {
-    for ($i = 0; $i < $batch_size; $i++) {
-      $node = _inform_get_next_node_to_tag($days, $batch_size);
-      // Break out of loop if there is no node to tag.
-      if ($node == NULL) {
-        break;
-      }
-      _inform_tag_node($node);
-    }
+  if (variable_get('inform_cron_enabled', FALSE)) {
+    _inform_run_batches();
   }
 }
 
+/** 
+ * Process a batch of nodes using the Inform API.
+ */
+function _inform_run_batches() {
+  _inform_run_batch();
+  _inform_run_batch('nid', TRUE);
+}
+
 /**
- * Take a top level item as returned by the Inform web service, for example
- * industries, and tag the items contained within the array that is returned.
- *
- * @param node $node
- *   The node to which the tags apply.
- * @param string $name
- *   The name of the top level item (this should also be a top level taxonomy
- *   item in the Inform vocabulary).
- * @param mixed $tag_array
- *   The array of items to tag, if it is a single item we will convert it to
- *   an array.
+ * Implementation of hook_views_api().
+ */
+function inform_views_api() {
+  return array('api' => 2.0);
+}
+
+/**
+ * Implementation of hook_nodeapi().
  *
- * @return boolean
- *   False if there was a problem with the tagging.
+ * Processes nodes using the Inform API when they are saved/updated.
  */
-function _inform_extract_top_level_item($node, $name, $tag_array) {
-  if ($name != INFORM_TERM_ENTITY) {
-    $top_level_tid = _inform_get_taxonomy_top_level_tid_by_name($name);
-  }
-  else {
-    $top_level_tid = -1;
-  }
-  $vid = variable_get('inform_vocabulary', FALSE);
-  if ($top_level_tid == 0) {
-    drupal_set_message(t('Could not find a unique  $name top level item in the Inform vocabulary.', array('$name' => check_plain($name))), 'error');
-    return FALSE;
-  }
-  if (!is_array($tag_array)) {
-    $tag_array = array($tag_array);
+function inform_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  switch ($op) {
+    case 'insert':
+    case 'update':
+      if ($node->status && variable_get('inform_process_on_update', FALSE)) {
+        inform_process_node($node);
+        drupal_set_message("Inform processed: {$node->nid}");
+      }
+      
+      if (!$node->status) {
+        // Remove.
+        db_query("DELETE FROM {inform_topics_nodes} WHERE nid = %d", $node->nid);
+      }
+      
+      break;
   }
+}
+
+function _inform_run_batch($column = 'changed', $loop = FALSE) {
+  $nodes = _inform_get_next_batch($column, $loop);
+  watchdog('Inform', 'processing '. count($nodes) .' nodes.');
 
-  foreach ($tag_array as $tag_item) {
-    // Get the top level term if it is present.
-    if (isset($tag_item->Category)) {
-      $top_tid = _inform_get_top_level_entity_tid($tag_item->Category);
+  $batch_size = variable_get('inform_batch_size', 50);
+  $is_same_changed_case = ($column == 'changed' && 
+                           count($nodes) == $batch_size && 
+                           $nodes[0]->changed == $nodes[$batch_size - 1]->changed);
+
+  foreach ($nodes as $node) {
+    watchdog('Inform', "processing {$node->title} (nid: {$node->nid}).");
+    inform_process_node(node_load($node->nid));
+    
+    // Save our progress.
+    if ($loop && count($nodes) < variable_get('inform_batch_size', 50)) {
+      $progress = 0;
+      watchdog('Inform', "looping column: $column");
     }
     else {
-      $top_tid = $top_level_tid;
+      $progress = $node->{$column};
     }
-    if ($top_tid != -1) {
-      // Check the term exists.
-      $possibilities = taxonomy_get_term_by_name($tag_item->Name);
-
-      // tid match, if any.
-      $term_tid = NULL;
-      foreach ($possibilities as $possibility) {
-        if ($possibility->vid == $vid) {
-          $term_tid = $possibility->tid;
-        }
-      }
-
-      //Check if term is blacklisted
-      if (!_inform_check_blacklist($tag_item->Name)) {
-        if (!$term_tid) {
-          $edit = array('vid' => $vid, 'name' => $tag_item->Name, 'parent' => $top_tid);
-          $status = taxonomy_save_term($edit);
-          $term_tid = $edit['tid'];
-        }
-        db_query('DELETE FROM {term_node} WHERE nid = %d AND vid = %d AND tid = %d', $node->nid, $node->vid, $term_tid);
-        db_query('INSERT INTO {term_node} (nid, vid, tid, inform_score) VALUES (%d, %d, %d, %d)', $node->nid, $node->vid, $term_tid, $tag_item->Score);
-      }
-      db_query('DELETE FROM {inform_node_update} WHERE nid = %d', $node->nid);
-      db_query('INSERT INTO {inform_node_update} (nid,timestamp) VALUES (%d,SYSDATE())', $node->nid);
+    
+    variable_set('inform_progress_'. $column, $progress);
+    
+    if ($is_same_changed_case) {
+      variable_set('inform_progress_changed_nid', $node->nid);
     }
   }
+  
+  watchdog('Inform', 'done.');
 }
 
 /**
- * Pass a node's content to the Inform web service for tagging.
+ * Get the next set of nodes to process.
  *
- * @param object $node
- *   The node to tag.
+ * Uses variables 'inform_progress' and 'inform_batch_size' to determine
+ * which nodes and how many should be in the batch.
  */
-function _inform_tag_node($node) {
-  $inform_service_url = variable_get('inform_url', FALSE);
-  $inform_itoken = variable_get('inform_itoken', FALSE);
-  $vid = variable_get('inform_vocabulary', FALSE);
-  if ($inform_service_url && $inform_itoken) {
-    // Check that this node is to be tagged
-    $result = db_query("SELECT disregard from {inform_node_update} where nid = %d", $node->nid);
-    if ($result->num_rows==0 or db_result($result) == 0) {
-      $article_text = $node->title . $node->body;
-      $article_text = str_replace("\r\n", "<p>", $article_text);
-      $client = new SoapClient(trim($inform_service_url), array());
-      $params = array(
-        'iToken' => $inform_itoken,
-        'sArticleText' => $article_text,
-        'sSearchPrefix' => '',
-      );
-      $object_result = $client->ExtractAll($params);
-      if (!is_soap_fault($object_result)) {
-        // Clear the Inform tags for the node.
-        db_query('DELETE tn FROM {term_node} tn INNER JOIN {term_data} td ON tn.tid = td.tid WHERE nid = %d  AND tn.vid = %d AND td.vid = %d', $node->nid, $node->vid , $vid);
-        // Industries
-        if (isset($object_result->ExtractAllResult->Industries->Industry)) {
-          _inform_extract_top_level_item($node, INFORM_TERM_INDUSTRY, $object_result->ExtractAllResult->Industries->Industry);
-        }
-        // Topics
-        if (isset($object_result->ExtractAllResult->Topics->Topic)) {
-          _inform_extract_top_level_item($node, INFORM_TERM_TOPIC, $object_result->ExtractAllResult->Topics->Topic);
-        }
-        // Entities
-        if (isset($object_result->ExtractAllResult->Entities->Entity)) {
-          _inform_extract_top_level_item($node, INFORM_TERM_ENTITY, $object_result->ExtractAllResult->Entities->Entity);
-        }
-        db_query('DELETE FROM {inform_node_update} WHERE nid = %d', $node->nid);
-        db_query('INSERT INTO {inform_node_update} (nid,timestamp) VALUES (%d,SYSDATE())', $node->nid);
-        
-      }
-      else {
-        drupal_set_message(t('Fault contacting Inform web service. faultcode: $fault_code faultstring: $fault_string',
-          array(
-                '$fault_code' => check_plain($object_result->faultcode),
-                '$fault_string' => check_plain($object_result->faultstring),
-            )
-          ),
-          'error');
-        drupal_set_message(t('Tagging from Inform has not been done.'), 'error');
-      }
+function _inform_get_next_batch($column = 'changed', $loop = FALSE) {
+  $nodes = array();
+  
+  // Batch settings.
+  $progress = variable_get('inform_progress_' . $column, 0);
+  $batch_size = variable_get('inform_batch_size', 50);
+  $inform_types = preg_split('/\s+/', variable_get('inform_content_types', 'page'));
+  
+  // Query for the next batch of nodes.
+  watchdog('Inform', "Getting next batch, starting from progress=[$progress]");
+  $query = "
+    SELECT   nid, title, changed 
+    FROM     {node} 
+    WHERE    type IN (". db_placeholders($inform_types, 'text') .") 
+    AND      $column >= %d 
+    AND      status = 1 
+    ORDER BY $column ASC";
+  $args = array_merge($inform_types, (array) $progress);
+  $result = db_query_range($query, $args, 0, $batch_size);
+
+  while ($node = db_fetch_object($result)) {
+    $nodes[] = $node;
+  }
+
+  // The "changed" field is not guaranteed to be unique, migration and 
+  // views-batch-update may cause many nodes to have the same "changed",
+  // and if there are more than batch_size nodes the processing will get
+  // stuck and just repeat the first batch_size nodes of that group of nodes
+  // with the same "changed".  This takes care of that case.
+  if ($column == 'changed' && count($nodes) == $batch_size && 
+      $nodes[0]->changed == $nodes[$batch_size - 1]->changed) {
+    $progress_changed_nid = variable_get('inform_progress_changed_nid', 0);
+    watchdog('Inform', "Getting next batch, starting from progress=[$progress], start nid = [$progress_changed_nid]");
+    $query = "
+      SELECT   nid, title, changed 
+      FROM     {node} 
+      WHERE    type IN (". db_placeholders($inform_types, 'text') .") 
+      AND      changed >= %d 
+      AND      nid >= %d
+      AND      status = 1 
+      ORDER BY changed ASC, nid ASC";
+    $args = array_merge($inform_types, array($progress, $progress_changed_nid));
+    $result = db_query_range($query, $args, 0, $batch_size);
+
+    $nodes = array();
+    while ($node = db_fetch_object($result)) {
+      $nodes[] = $node;
     }
   }
-}
 
-/**
- * Check if a term is blacklisted
- * 
- *  @param string $name
- *    Name of taxonomy item.
- *    
- *  @return boolean 
- *    TRUE if term is blacklisted  
- */
-function _inform_check_blacklist($name) {
-  //blacklisted items should be in the blacklist array
-  return isset($blacklist_array[$name]);
+  return $nodes;
 }
 
 /**
- * Get the top level taxonomy items for a given name.
+ * Process a node using the Inform API.
  *
- * @param string $name
- *   Name of the taxonomy item.
+ * Makes a SOAP request to the inform API which returns
+ * an object describing topics, entities and industries
+ * relating to the node content.
  *
- * @return integer
- *   tid of the Inform item with that name.
+ * If debug is TRUE, return a string containing the text
+ * sent to Inform, and results.
  */
-function _inform_get_taxonomy_top_level_tid_by_name($name) {
-  $vid = variable_get('inform_vocabulary', FALSE);
-  $query =  db_query("
-    SELECT td.tid
-    FROM {term_data} td, {term_hierarchy} th
-    WHERE th.tid = td.tid
-    AND td.vid = %d
-    AND td.name = '%s'
-    AND  th.parent = 0", $vid, $name);
-  return db_result($query);
-}
+function inform_process_node($node, $debug = FALSE) {
+  static $client = NULL;
+  $debug_ret = '';  // String to return in debug mode
 
-/**
- * Get or create the top level entity tid for a given term.
- *
- * @param $category string
- *   Name of the category as given by Inform.
- *
- * @returns number
- *   tid of the category.
- */
-function _inform_get_top_level_entity_tid($category) {
-  $toplevel_term_exists = FALSE;
-  $vid = variable_get('inform_vocabulary', FALSE);
-  $categoryterms = taxonomy_get_term_by_name($category);
-  if (count($categoryterms) > 0) {
-    $toplevel_term_exists = TRUE;
-    $toplevel_tid = $categoryterms[0]->tid;
-  }
-  else {
-    $new_toplevel_term = array(
-      'name' => $category,
-      'vid' => $vid,
-      'parent' =>  array()
-    );
-    taxonomy_save_term($new_toplevel_term);
-    $toplevel_tid = $new_toplevel_term['tid'];
+  // Inform API.
+  $inform_service_url = variable_get('inform_url', FALSE);
+  $inform_itoken = variable_get('inform_itoken', FALSE);
+  
+  if ($inform_service_url === FALSE) {
+    watchdog("Inform", "ERROR: Inform API url missing or invalid.");
+    return;
   }
-  return $toplevel_tid;
-}
-
-
-/** 
- * Validator for batch processing form.
- */
-
-function _inform_valdate_batch($form, &$form_state) {
-  if (strlen($form_state['values']['age_check'])==0 and not(is_numeric($form_state['values']['age_check']))) {
-    form_set_error('age_check', t('If selecting a number of days old it should be a whole number or blank.'));
+  
+  if (is_null($client)) {
+    // Without the UTF8 parameter, some nodes fail to process through Inform.
+    // The parameter tells the SoapClient what character encoding to translate
+    // from; SoapClient translates everything to UTF8 internally.  UTF8 should
+    // be the default encoding for Drupal 6.
+    $client = new SoapClient($inform_service_url, array('encoding' => 'UTF8'));
   }
   
+  $params = array(
+    'iToken' => $inform_itoken,
+    'sArticleTitle' => strip_tags($node->title),
+    'sArticleText' => strip_tags($node->body),
+    'sSearchPrefix' => '',
+  );
+
+  // Let other modules alter the data before we send it to Inform.
+  drupal_alter('inform_request', $params, $node);
   
-  if (strlen($form_state['values']['max_batch'])==0 and not(is_numeric($form_state['values']['max_batch']))) {
-    form_set_error('max_batch', t('The batch size must be blank or a whole number.'));
+  if ($debug) {
+    $debug_ret = "Title Sent to Inform:\n\n" . $params['sArticleTitle'] . "\nEnd of Title\n\n";
+    $debug_ret .= "Body Sent to Inform:\n\n" . $params['sArticleText'] . "\nEnd of Text\n\n";
   }
-}
-
-/**
- * Submit handler for batch processing form. 
- */
-
-function _inform_submit_batch($values) {
-  //Get days
-  $max_days = $values['age_check']['#value'];
-  //Get max batch size
-  $max_batch = $values['max_batch']['#value'];
-  $batch = array(
-      'title' => t('Batch tagging articles.'),
-      'init_message' => t('Getting articles to tag.'),
-      'error_message' => t('Tagging failed.'),
-      'progress_message' => '',
-      'operations' => array(
-        array('_inform_batch_process', array($max_days, $max_batch)), 
-      ),
-      'finished' => '_inform_batch_finish',
-    );
-    batch_set($batch);
-}
-
-
-
-/**
- * Get the next nid that requires tagging.
- * This should be used by any function which tags more than one article
- * @param integer $number_to_fetch
- *   The number of items to fetch.
- * @param boolean $clear
- *   Clear static variables.
- *
- * @return node
- *   item that requires tagging, NULL if no nodes left to tag.  
- */
+  
+  try {
+    $result = $client->ExtractAllWithTitle($params);
 
-function _inform_get_next_node_to_tag($days = NULL, $number_to_fetch = 10, $clear = FALSE) {
-  static $result;
-  if ($days == NULL) {
-    $days = variable_get('inform_tag_age', 50);
-  }
-  //Perform the query if needed.
-  if ($clear || !$result) {
-    $result = db_query_range("SELECT n.nid FROM {node} n
-                              LEFT OUTER JOIN {inform_node_update} inu 
-                                ON n.nid = inu.nid
-                              INNER JOIN {vocabulary_node_types} vnt 
-                                ON n.type = vnt.type 
-                              WHERE 
-                                (
-                                  (
-                                    disregard is null
-                                  AND
-                                    timestamp is null
-                                  )
-                                OR
-                                  (
-                                    disregard =0 
-                                  AND 
-                                    inu.timestamp < date_sub(sysdate(), interval %d day)
-                                  )
-                                )
-                              ORDER BY n.nid DESC",
-                              array($days), 0, $number_to_fetch);
-  }
-
-  $have_result = FALSE;
-  // Get the next node to process.
-  while (!$have_result)  {
-    $row = db_fetch_array($result);
-    // Get a new result set if there are no rows available in current query.
-    if (!$row) {
-      // if $clear is set and there are no available rows, then there are no
-      // rows left in the database, return 0
-      if ($clear) {
-        return NULL;
-      } 
-      else {
-        // If there are no rows set and clear is not set then try to fetch 
-        // some more results. Should only every recurse one level deep.
-        return _inform_get_next_node_to_tag($days, $number_to_fetch, TRUE);
-      }
+    if (!is_soap_fault($result)) {
+      $results = $result->ExtractAllResult;
       
-    } 
-    else {
-      // There are rows in the current query we can use
-    }
-    $node = node_load($row['nid'], NULL, TRUE);
-    // If the node has "problem with headline" for the headline or no body then don't tag and set ignore to true. 
-    if ($node->title == "problem with headline" or $node->body == "") {
-      $inform_node_update_check = db_query("SELECT nid FROM {inform_node_update} WHERE nid = %d", array($row['nid']));
-      if (db_fetch_object($inform_node_update_check)) {
-        // Update the row to now ignore.
-        db_query("UPDATE {inform_node_update} SET disregard = 1 WHERE nid = %d", array($row['nid']));        
+      if (property_exists($results->Entities, 'Entity')) {
+        $entities = is_array($results->Entities->Entity) ? $results->Entities->Entity : array($results->Entities->Entity);
+        foreach ($entities as $entity) {
+          inform_save_topic($entity, INFORM_TERM_ENTITY, $node);
+        }
       }
-      else {
-        // Insert a row for ignore.
-        db_query("INSERT INTO {inform_node_update} (nid,timestamp,disregard) VALUES (%d,sysdate(),1)", array($row['nid']));
+  
+      if (property_exists($results->Industries, 'Industry')) {
+        $industries = is_array($results->Industries->Industry) ? $results->Industries->Industry : array($results->Industries->Industry);
+        foreach ($industries as $industry) {
+          inform_save_topic($industry, INFORM_TERM_INDUSTRY, $node);
+        }
+      }
+  
+      if (property_exists($results->Topics, 'Topic')) {
+        $topics = is_array($results->Topics->Topic) ? $results->Topics->Topic : array($results->Topics->Topic);
+        foreach ($topics as $topic) {
+          inform_save_topic($topic, INFORM_TERM_TOPIC, $node);
+        }
       }
     }
     else {
-      $have_result = TRUE;
-    }  
-  }
-  return $node;    
-}
-
-/**
- * Batch processing 
- */
-function _inform_batch_process($days, $batch_size, &$context) {
-  if (empty($context['sandbox'])) {
-    $context['sandbox']['progress'] = 0;
-    $context['sandbox']['total'] = min($batch_size, db_result(db_query('SELECT count(*) from {node} n left outer join {inform_node_update} inu on n.nid = inu.nid inner join {vocabulary_node_types} vnt on n.type = vnt.type where inu.timestamp is null or (inu.timestamp < date_sub(sysdate(), interval %d day) and disregard =0)', array($days))));
-  }
-
-  for ($i = 0; $i < $batch_size; $i++) {
-    $node = _inform_get_next_node_to_tag($days, $batch_size);
-    // Break out of loop if there is no node to tag.
-    if ($node == NULL) {
-      break;
-    }
-    _inform_tag_node($node);
-    $context['results'][] = $node->nid;
-    $context['sandbox']['progress']++;
-    $context['message'] = t("tagging.. $title", array('$title' => check_plain($node->title)));
-    if ($context['sandbox']['progress'] < $context['sandbox']['total']) {
-      $context['finished'] =  $context['sandbox']['progress'] / $context['sandbox']['total'];
+      watchdog("Inform", "error processing node (nid: {$node->nid}).");
     }
+  } 
+  catch (Exception $e) {
+    watchdog('Inform', "SOAP Exception for node: {$node->nid}: " . $e->getMessage());
   }
-}
-function _inform_batch_finish($success, $results, $operations) {
-  if ($success) {
-    $message = t('$count processed.', array('$count' => count($results)));
-  }
-  else {
-    $error_operation = reset($operations);
-    $message = t('An error occurred while processing');
-  }
-  drupal_set_message($message);
-}
-
 
+  return $debug_ret;
+}
 
 /**
- * Implementation of hook_nodeapi().
- *
- * Will send the data to Inform for processing on save.
+ * Save a topic, creating it if it does not exist.  If a $node
+ * is provided, that node is associated to the topic.
  */
-function inform_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
-  switch ($op) {
-    case 'insert':
-    case 'update':
-      // If defer mode is on then we shouldn't tag.
-      $defer = variable_get('inform_defer_tagging', 0);
-      // If we are in batch mode we shouldn't tag.
-      $batch_mode =sizeof(batch_get());
-      // If the vocabulary is not assigned to the node type we shouldn't tag
-      $assigned_vocab = db_result(db_query("SELECT count(*) FROM {vocabulary_node_types} vnt WHERE vnt.type = '%s'", array($node->type)));
-      if (!$defer && !$batch_mode && $assigned_vocab) {
-        $status = _inform_tag_node($node);
-      }
-      break;
+function inform_save_topic($inform_topic, $type = INFORM_TERM_TOPIC, $node = NULL) {
+  // Warn and exit on an empty inform_topic.  Why does this happen ?  Is it a bug 
+  // in Inform ?  Nodes on which is has happened: 8319406.
+  if (empty($inform_topic->Name) || !is_numeric($inform_topic->Score)) {
+    watchdog('Inform','inform_save_topic(): exiting due to empty inform_topic = ['. print_r($inform_topic, TRUE) .'], node = ['. print_r($node, TRUE) .']');
+    return FALSE;
   }
-}
 
-/**
- * Implementation of hook_block().
- */
-function inform_block($op = 'list', $delta = 0, $edit = array()) {
-  if ($op == 'list') {
-    $blocks = array();
-    // Only show the tag based items if the Inform tagging URL has been set.
-    if (variable_get('inform_url', "") != "") {
-      $blocks[INFORM_BLOCK_TAGS]['info'] = t('Show what Inform has tagged this item with' );
-      $blocks[INFORM_BLOCK_TAGS]['cache'] = BLOCK_CACHE_PER_PAGE;
-    }
-    return $blocks;
+  // Insert the topic if it doesn't exist.
+  $topic = db_fetch_object(db_query("SELECT * FROM {inform_topics} WHERE name = '%s'", $inform_topic->Name));
+  if (!$topic) {
+    db_query("INSERT INTO {inform_topics} (name, type) VALUES ('%s', %d)", $inform_topic->Name, $type);
+    $topic = (object) array(
+      'id' => db_last_insert_id("inform_topics", "id"),
+      'name' => $inform_topic->Name,
+      'type' => $type,
+    );
+    
+    // Let other modules react to the newly created topic.
+    module_invoke_all('inform_topicapi', $topic, 'insert', $node);
   }
-  elseif ($op == 'view') {
-    $block = array();
-    switch ($delta) {
-      case INFORM_BLOCK_TAGS:
-        $subjects = inform_related_subjects();
-        if (sizeof($subjects) > 0) {
-          $block['subject'] = t('Related subjects');
-          $block['content'] = theme('item_list', $subjects);
-        }
-      break;
+  
+  if ($node) {
+    $minimum_score = variable_get('inform_minimum_score', 0);
+    $high_score = $inform_topic->Score > $minimum_score ? 1 : 0;
+    
+    $topic_node = db_fetch_object(db_query("SELECT * FROM {inform_topics_nodes} WHERE inform_topic_id = %d AND nid = %d", $topic->id, $node->nid));
+    
+    // If the relationship doesn't exist, add it.
+    if (!$topic_node) {
+      db_query("INSERT INTO {inform_topics_nodes} (inform_topic_id, nid, score, high_score) VALUES (%d, %d, %d, %d)", $topic->id, $node->nid, $inform_topic->Score, $high_score);
     }
-    return $block;
-  }
-}
-
-/**
- * Implementation of hook_help().
- */
-function inform_help($path, $arg) {
-  $output = '';
-  switch ($path) {
-    case 'admin/help#inform':
-      $output = '<p>' . t('Inform stuff') . '</p>';
-      break;
-    case 'admin/settings/inform':
-      $output = '<p>' . t('Inform is a taxonomic tagging web service. The Inform module provides an interface to this and will automatically tag your content on save/update if you have the appropriate API key. The module then provides a number of blocks which can be used to expose these tags.') . '</p>';
-      break;
+    else {
+      // If the relationship does exist, make sure the information is up to date.
+      if ($topic_node->score != $inform_topic->Score) {
+        db_query("UPDATE {inform_topics_nodes} SET score = %d, high_score = %d WHERE inform_topic_id = %d AND nid = %d", $inform_topic->Score, $high_score, $topic->id, $node->nid);
+      }
+    }
+    
+    // Let other modules react to adding a node to the topic. 
+    module_invoke_all('inform_topicapi', $topic, 'update', $node);
   }
-  return $output;
+  
+  return TRUE;
 }
 
 /**
  * Implementation of hook_menu().
  */
 function inform_menu() {
-  
+  $items = array();
+    
   $items['admin/settings/inform'] = array(
     'title' => 'Inform settings',
     'description' => 'Manage your Inform installation',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('inform_admin_settings'),
-    'access arguments' => array('administer site configuration')
+    'access arguments' => array('administer site configuration'),
+    'type' => MENU_NORMAL_ITEM,
   );
-
-  $items['admin/content/inform'] = array(
-    'title' => 'Inform batch process',
-    'description' => 'Send your content to inform as part of a batch process, to get your whole site tagged',
+  
+  $items['admin/build/inform/topics/%inform_topic'] = array(
+    'title' => 'Edit Inform topic',
+    'description' => 'Edit the properties of an Inform topic',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('inform_batch_settings'),
-    'access arguments' => array('administer site configuration')
+    'page arguments' => array('inform_edit_topic_form', 4),
+    'access arguments' => array('administer site configuration'),
+    'title callback' => 'inform_edit_topic_title',
+    'title arguments' => array(4),
+    'type' => MENU_NORMAL_ITEM,
   );
-
-
+  
   return $items;
 }
 
 /**
- * Block functions.
- */
-
-/**
- * Render the related subjects for the page. At the moment this just supports
- * nodes, and topics but in future it may work on , comment pages and user pages.
+ * Menu wildcard loader for %inform_topic.
  */
-function inform_related_subjects($object = NULL) {
-  if ($object==NULL) {
-    $object = menu_get_item();
-    
-  }
-  
-  $cutoff = variable_get('inform_related_minimum_score', NULL);
-  $vid = variable_get('inform_vocabulary', NULL);
-  $count = variable_get('inform_terms_count', NULL);
-
-  //If the object is a menu object for a node then treat it as a node
-  if (is_array($object) && isset($object['map']) && $object['map'][0] == 'node') {
-    $object = $object['map'][1];
-  }
-  
-  if (is_array($object) && isset($object['map']) && $object['map'][0] == 'taxonomy') {
-    $tid = $object['map'][2];
-    $result = db_query_range("SELECT {term_data}.tid as term_id,
-                                    coalesce(ts.name,{term_data}.name) as name,
-                                    sum(nt1.inform_score)
-                             FROM {term_data}
-                             INNER JOIN {term_node} nt1 ON nt1.tid = {term_data}.tid
-                         INNER JOIN {term_node} nt2 on nt1.nid = nt2.nid
-                             LEFT OUTER JOIN term_synonym ts ON ts.tid = term_data.tid
-                             WHERE nt2.tid = %d
-                             AND {term_data}.tid != nt2.tid
-                             GROUP BY {term_data}.tid, coalesce(ts.name,{term_data}.name)
-                             ORDER BY sum(nt1.inform_score) desc", array($tid), 0, $count);
-  }
-  elseif (isset($object->nid)) {
-    $check_type_query = db_query("SELECT n.nid from {node} n JOIN {vocabulary_node_types} vnt on n.type = vnt.type where n.nid = %d", array($object->nid));
-    //If this node type can have inform taxonomy then query and show
-    if (db_fetch_object($check_type_query)) {
-      // Don't use the standard taxonomy functions for this block as
-      // the Inform module has settings which are needed in the query.
-      $result = db_query_range("
-        SELECT td.tid as term_id, coalesce(ts.name,td.name) as name, tn.inform_score
-        FROM {term_data} td
-        JOIN {term_node} tn on tn.tid = td.tid
-        LEFT OUTER JOIN {term_synonym} ts on ts.tid = tn.tid
-        WHERE td.vid = %d
-        AND tn.nid = %d
-        AND tn.inform_score > %d
-        AND tn.inform_score != %d
-        ORDER BY inform_score DESC", array($vid, $object->nid, $cutoff), 0, $count);
-
-    }
-  }
-
-  if (isset($result)) {
-    $links = array();
-    while ($row = db_fetch_array($result)) {
-      $links[] = array(data => l($row['name'], 'taxonomy/term/' . $row['term_id']));
-    }
-    return $links;
-  }
+function inform_topic_load($topic_id) {
+  return db_fetch_object(db_query("SELECT * FROM {inform_topics} WHERE id = %d", $topic_id));
 }
 
-
-/**
- * Form API functions.
- */
-
 /**
- * Display the form for batch proccessing settings.
+ * Edit an inform topic.
  */
-
-function inform_batch_settings() {
+function inform_edit_topic_form(&$form_state, $topic) {
   $form = array();
-  $form['age_check'] = array(
-    '#type' => 'textfield',
-    '#title' => t('tag age'),
-    '#default_value' => variable_get('inform_tag_age', 50),
-    '#description' => t('Look for nodes which are havn\'t been tagged in at least this number of days.')
+  
+  $form['id'] = array(
+    '#type' => 'value',
+    '#value' => $topic->id,
   );
-  $form['max_batch'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Max batch size'),
-    '#default_value' => variable_get('inform_batch_size', 50),
-    '#description' => t('The maxamum size of the batch.')
+  
+  $form['name'] = array(
+    '#type' => 'value',
+    '#value' => $topic->name,
   );
   
+  $form['display_name'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Display name'),
+    '#default_value' => $topic->display_name,
+    '#description' => t('A name to display that overrides the name given by Inform'),
+  );
   
   $form['submit'] = array(
     '#type' => 'submit',
     '#value' => t('Save'),
   );
-  $form["#validate"] = array('_inform_validate_batch' => array());
-  $form['#submit'] = array('_inform_submit_batch');
+
   return $form;
 }
 
 /**
+ * Title callback for Inform topic edit pages.
+ */
+function inform_edit_topic_title($topic) {
+  return t('Editing display name for topic: ') . $topic->name;
+}
+
+/**
+ * Form submit hook to handle editing topic names manually.
+ */
+function inform_edit_topic_form_submit($form, &$form_state) {
+  $values = $form_state['values'];
+  db_query("UPDATE {inform_topics} SET display_name = '%s' WHERE id = %d", $values['display_name'], $values['id']);
+  
+  // Let other modules react to changing the topic
+  $topic = inform_topic_load($values['id']);
+  module_invoke_all('inform_topicapi', $topic, 'update');
+ 
+  drupal_set_message(t("The topic '@name' has been updated.", array('@name' => $values['name'])));
+}
+
+/**
  * Display the admin settings form.
  */
 function inform_admin_settings() {
@@ -605,71 +391,54 @@ function inform_admin_settings() {
     '#default_value' => variable_get('inform_itoken', ''),
     '#description' => t('The iToken used to authenticate with your Inform account'),
   );
-
-  $form['inform_terms_count'] = array(
+  
+  $form['inform_batch_size'] = array(
     '#type' => 'textfield',
-    '#title' => t('Item count'),
-    '#default_value' => variable_get('inform_terms_count', 5),
-    '#size' => 3,
-    '#description' => t('The maximum number of similar items to display'),
+    '#title' => t('Batch size'),
+    '#default_value' => variable_get('inform_batch_size', 50),
+    '#size' => 2,
+    '#description' => t('The default size of batches processed in each cron run. If set to 0 cron mode will be ineffective.'),
   );
-
-  $form['inform_my_minimum_score'] = array(
+  
+  $form['inform_minimum_score'] = array(
     '#type' => 'textfield',
-    '#title' => t('Current node minimum score'),
-    '#default_value' => variable_get('inform_my_minimum_score', 40),
-    '#size' => 2,
-    '#description' => t('The lowest score my node must score in a category to get compared with others'),
+    '#title' => t('Minimum Score'),
+    '#default_value' => variable_get('inform_minimum_score', 50),
+    '#size' => 3,
+    '#description' => t('The minimum score required for nodes to be linked to a topic.'),
   );
 
-  $form['inform_related_minimum_score'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Related node minimum score'),
-    '#default_value' => variable_get('inform_related_minimum_score', 50),
-    '#size' => 2,
-    '#description' => t('The lowest score the related node must score in a category to get compared with the current node'),
+  $form['inform_content_types'] = array(
+    '#type' => 'textarea',
+    '#title' => t('Content Types'),
+    '#default_value' => variable_get('inform_content_types', ''),
+    '#description' => t('The content types to process with Inform, separated by spaces.'),
   );
   
-  $form['inform_defer_tagging'] = array(
+  $form['inform_process_on_update'] = array(
     '#type' => 'checkbox',
-    '#title' => t('Defer tagging of articles'),
-    '#default_value' => variable_get('inform_defer_tagging', 0),
-    '#size' => 2,
-    '#description' => t('With this option on, tagging will not be done on a node save, but via cron and the batch process options.'),
+    '#title' => t('Process On Update'),
+    '#default_value' => variable_get('inform_process_on_update', ''),
+    '#description' => t('When enabled content will be processed by the Inform API when it is updated/saved.'),
   );
 
-  $form['inform_tag_age'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Retagging age'),
-    '#default_value' => variable_get('inform_tag_age', 50),
-    '#size' => 2,
-    '#description' => t('The age in days to wait before trying to re tag articles, set as 0 to disable re tagging.'),
+  $form['#validate'] = array(
+    'inform_admin_settings_validate',
   );
-  
-  $form['inform_batch_size'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Batch size'),
-    '#default_value' => variable_get('inform_batch_size', 50),
-    '#size' => 2,
-    '#description' => t('The default size of batches processed in each cron run. If set to 0 cron mode will be ineffective.'),
+
+  $form['#submit'] = array(
+    'inform_admin_settings_submit',
   );
   
-  
-  
-  $form["#validate"] = array(
-    'inform_admin_settings_validate',
-  );
   return system_settings_form($form);
 }
 
-
-
 /**
  * Validate the admin settings form.
  */
 function inform_admin_settings_validate($form, &$form_state) {
-  if (strlen($form_state['values']['inform_url'])==0) {
-    form_set_error('inform_url', t('You must specify the extract URL'));
+  if (!$form_state['values']['inform_url']) {
+    form_set_error('inform_url', t('You must specify the Inform API URL'));
   }
   else {
     // Note: This will not fail gracefully with Xdebug on. 
@@ -681,131 +450,124 @@ function inform_admin_settings_validate(
     }
   }
   
-  if (strlen($form_state['values']['inform_itoken'])==0) {
+  if (!$form_state['values']['inform_itoken']) {
     form_set_error('inform_itoken', t('You must specify the iToken'));
   }
+  
   if (!is_numeric($form_state['values']['inform_itoken'])) {
     form_set_error('inform_itoken', t('The iToken must be a number'));
   }
-  if (!is_numeric($form_state['values']['inform_my_minimum_score'])) {
-    form_set_error('inform_my_minimum_score', t('Current node minimum score must be a number'));
-  }
-  if (!is_numeric($form_state['values']['inform_related_minimum_score'])) {
-    form_set_error('inform_related_minimum_score', t('Related node minimum score must be a number'));
-  }
-  if (!is_numeric($form_state['values']['inform_terms_count'])) {
-    form_set_error('inform_terms_count', t('Item count must be a number'));
-  }
-
-}
 
-/**
- * Functions for editing of nodes.
- */
-
-/**
- * Implementation of hook_form_alter().
- */
- 
-function inform_form_alter(&$form, $form_state, $form_id) {
-  // Remove the inform taxonomy as it gets to big to be 
-  // used effectivly.
-  $vid = variable_get('inform_vocabulary', NULL);
-  if (isset($form['taxonomy'][$vid])) {
-    unset($form['taxonomy'][$vid]);
+  if ($form_state['values']['inform_minimum_score'] < 0 || $form_state['values']['inform_minimum_score'] > 100) {
+    form_set_error('inform_itoken', t('The minimum score must be a number between 0 and 100.'));
   }
 }
 
 /**
- * Implementation of hook_form_FORM_ID_alter() for taxonomy_form_term
- * Change the term form with inform specific settings
- * @param $form
- * @param $form_state
- * @param $form_id
- * @return null
- */
-function inform_form_taxonomy_form_term_alter(&$form, $form_state) {
-  $vid = variable_get('inform_vocabulary', FALSE);
-  if ($form['#vocabulary']['vid'] == $vid) {
-    // Remove the delete button as it will have no effect on iform (terms will
-    // keep coming back.
-    unset($form['delete']);
-    // Add a blacklist button 
-    $form['blacklist'] = array(
-      '#type' => 'submit',
-      '#value' => 'Blacklist',
-    );
-    // Add a custom function to the submit action
-    $form['#submit'][] = inform_form_term_submit;
-  }
-}
-
-/**
- * Implementation of hook_form_FORM_ID_alter() for taxonomy_form_vocabulary
- * Change the vocabulary form with inform specific settings
- * @param $form
- * @param $form_state
- * @param $form_id
- * @return null
+ * Submit callback for the inform admin settings form.
  */
-function inform_form_taxonomy_form_vocabulary_alter(&$form, $form_state) {
-  $vid = variable_get('inform_vocabulary', FALSE);
-  $blacklist = variable_get('inform_blacklist', array());
-  // Alter only the inform vocabulary.
-  if ($form['vid']['#value'] == $vid && sizeof($blacklist) > 0) {
-     // Add the blacklisted terms in a multi select box.
-    $form['blacklist'] = array(
-    '#type' => 'fieldset',
-    '#title' => t('Blacklist'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-  );
-  $form['blacklist']['blacklist_terms'] = array('#type' => 'checkboxes',
-    '#title' => t('Blacklisted terms'),
-    '#default_value' => array_keys($blacklist),
-    '#options' => array_map('check_plain', $blacklist),
-    '#description' => t('These terms are blacklisted and will not be used by the inform module, uncheck to remove from blacklist'),
-  );
-  $form['#submit'][] = "inform_reset_blacklist";
+function inform_admin_settings_submit($form, &$form_state) {
+  // If the minimum score has changed, we need to update the database.
+  $minimum_score = $form_state['values']['inform_minimum_score'];
+  if ($minimum_score != variable_get('inform_minimum_score', 0)) {
+    db_query("UPDATE {inform_topics_nodes} SET high_score = IF(score >= %d, 1, 0)", $minimum_score);
   }
-  // Put the buttons at the bottom.
-  $form['submit']['#weight'] = 10;
-  $form['delete']['#weight'] = 10;
 }
 
 /**
- * Add a term to blacklist before deleting
- * @param $form
- * @param $form_state
- * @return unknown_type
+ * Get all topics for a specific node.
  */
-
-function inform_form_term_submit($form, &$form_state) {
-  if ($form_state['clicked_button']['#value'] == t('Blacklist')) {
-    $blacklist = variable_get('inform_blacklist', array());
-    $blacklist[$form_state['values']['name']] = $form_state['values']['name'];
-    variable_set('inform_blacklist',$blacklist);
-    include_once(drupal_get_path('module', 'taxonomy') . '/taxonomy.admin.inc');
-    taxonomy_term_confirm_delete_submit($form, &$form_state);
+function inform_get_topics_for_node($nid) {
+  $topics = array();
+  
+  $result = db_query("
+    SELECT it.*, itn.score FROM inform_topics it
+    LEFT JOIN inform_topics_nodes itn ON it.id = itn.inform_topic_id
+    WHERE itn.nid = %d
+    ORDER BY it.type DESC, itn.score DESC", $nid);
+    
+  while ($topic = db_fetch_object($result)) {
+    $topics[] = $topic;
   }
+  
+  return $topics;
 }
 
+	
 /**
- * Reset the blacklist after a vocabulary update
- * @param $form
- * @param $form_state
- * @return unknown_type
- */
+ * Implementation of hook_views_data().		
+ */
+function inform_views_data() {
+  $data['inform_topics']['table']['group'] = t('Inform');
+  
+  $data['inform_topics']['table']['base'] = array(
+    'field' => 'id',
+    'title' => t('Topic'),
+    'help' => t('An Inform topic'),
+    'weight' => -10,
+  );
+
+  $data['inform_topics']['id'] = array(
+    'title' => t('Topic ID'),
+    'help' => t('The ID of the topic.'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_numeric',
+      'allow empty' => TRUE,
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_numeric',
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+  );
+
+  $data['inform_topics']['name'] = array(
+    'title' => t('Topic name'),
+    'help' => t('Topic name'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+      'help' => t('Topic name.'),
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+      'help' => t('Topic name.'),
+      'many to one' => TRUE,
+    ),
+  );
+  
+  $data['inform_topics']['display_name'] = array(
+    'title' => t('Topic display name'),
+    'help' => t('Topic display name'),
+    'field' => array(
+      'handler' => 'views_handler_field',
+      'click sortable' => TRUE,
+    ),
+    'sort' => array(
+      'handler' => 'views_handler_sort',
+    ),
+    'filter' => array(
+      'handler' => 'views_handler_filter_string',
+      'help' => t('Topic display name.'),
+    ),
+    'argument' => array(
+      'handler' => 'views_handler_argument_string',
+      'help' => t('Topic display name.'),
+      'many to one' => TRUE,
+    ),
+  );
+  
+  return $data;
+}
 
-function inform_reset_blacklist($form, &$form_state) {
-  if (isset($form_state['values']['blacklist_terms'])) {
-    $blacklist_terms = $form_state['values']['blacklist_terms'];
-    $blacklist = array();
-    foreach ($blacklist_terms as $key => $value) {
-      if ($value != "0") {
-        $blacklist[$key] = $value;
-      } 
-    }
-    variable_set('inform_blacklist', $blacklist);
-  }
-}
\ No newline at end of file
