Index: feeds.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.install,v
retrieving revision 1.7
diff -u -p -r1.7 feeds.install
--- feeds.install	23 Feb 2010 04:59:06 -0000	1.7
+++ feeds.install	28 Apr 2010 20:46:16 -0000
@@ -489,6 +489,39 @@ function feeds_update_6009() {
       'timestamp' => array('timestamp'),
     ),
   );
+  $schema['feeds_book_mapper'] = array(
+    'description' => t('Stores book hierarchy information about feed item nodes. Used by feeds_book_mapper.'),
+    'fields' => array(
+      'guid' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'description' => t('Unique identifier for the feed item. Primary Key with FEED_NID'),
+      ),
+      'feed_nid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => t('Node id of the owner feed, if available. Primary Key with GUID.'),
+      ),
+      'book_guid' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Unique identifier for the feed item of the current top hierarchy page.'),
+      ),
+      'parent_guid' => array(
+        'type' => 'text',
+        'not null' => TRUE,
+        'default' => '',
+        'description' => t('Unique identifier for the feed item of the current parent.'),
+      ),
+    ),
+    'primary key' => array(array('guid', 255), 'feed_nid'),
+    'indexes' => array(
+      'guid' => array(array('guid', 255)),
+    ),
+  );
+
   db_create_table($ret, 'feeds_push_subscriptions', $table);
   return $ret;
 }
Index: feeds.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/feeds/feeds.module,v
retrieving revision 1.45
diff -u -p -r1.45 feeds.module
--- feeds.module	12 Apr 2010 01:21:07 -0000	1.45
+++ feeds.module	28 Apr 2010 20:46:16 -0000
@@ -254,6 +254,7 @@ function feeds_nodeapi(&$node, $op, $for
 
   // Break out node processor related nodeapi functionality.
   _feeds_nodeapi_node_processor($node, $op);
+  _feeds_nodeapi_book_mapper_cleanup($node, $op);
 
   if ($importer_id = feeds_get_importer_id($node->type)) {
     switch ($op) {
Index: mappers/book.inc
===================================================================
RCS file: mappers/book.inc
diff -N mappers/book.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ mappers/book.inc	28 Apr 2010 20:46:16 -0000
@@ -0,0 +1,211 @@
+<?php
+// $Id$
+define('BOOK_MAX_DEPTH', 9);
+
+/**
+ * Implementation of hook_migrate_fields_node().
+ */
+function book_feeds_node_processor_targets_alter(&$targets, $content_type) {
+  if ($content_type == 'book') {
+    $targets['book_id'] = array(
+      'name' => t('Book ID'),
+      'description' => t('The ID of the top page in the book.'),
+      'callback' => 'book_feeds_set_target',
+    );
+    $targets['parent_id'] = array(
+      'name' => t('Book Parent ID'),
+      'description' => t('The ID of the parent page in the book'),
+      'callback' => 'book_feeds_set_target',
+    );
+  }
+}
+
+/**
+ * Mapper callback for book id & book parent id.
+ * Loads database with hierarchy info and updates to hierarchy.
+ */
+function book_feeds_set_target($node, $target, $value) {
+  static $mapping_counter = 0;
+  $data = array(
+    'guid' => $node->feeds_node_item->guid,
+    'feed_nid' => $node->feeds_node_item->feed_nid,
+  );
+
+  // Get previous data
+  $result = _feeds_book_record_exists($data);
+
+  if ($target == 'book_id') {
+    $result['book_guid'] = $value;
+    _feeds_book_record_exists($result, TRUE);
+  }
+  else {
+    $result['parent_guid'] = $value;
+    _feeds_book_record_exists($result, TRUE);
+  }
+
+  // Only bother the database when all mapping items have been pushed through.
+  $mapping_counter++;
+  if ($mapping_counter == 2) {
+    drupal_write_record('feeds_book_mapper', $result, array('guid', 'feed_nid'));
+    $mapping_counter = 0;
+  }
+}
+
+/**
+ * Implementation of hook_feeds_after_import().
+ */
+function book_feeds_after_import($importer, $source) {
+  if ($importer->processor instanceOf FeedsNodeProcessor && $importer->processor->config['content_type'] == 'book') {
+    _feeds_book_get_feed_nid($source->feed_nid);
+    _feeds_book_build_hierarchy();
+  }
+}
+
+/**
+ * Helper function to clean up after book mapper.
+ */
+function _feeds_nodeapi_book_mapper_cleanup($node, $op) {
+  if ($op != 'delete') {
+    return;
+  }
+  
+  // When book page is deleted, remove from table.
+  if ($node->type == 'book' && isset($node->feeds_node_item->guid)) {
+    db_query("DELETE FROM {feeds_book_mapper} WHERE guid = '%s' and feed_nid = '%d'",
+      $node->feeds_node_item->guid, $node->feeds_node_item->feed_nid);
+  }
+
+  // When importer node is deleted, remove all related entries from table.
+  elseif (feeds_get_importer_id($node->type)) {
+    db_query("DELETE FROM {feeds_book_mapper} WHERE feed_nid = '%d'", $node->nid);
+  }
+}
+
+/**
+ * Builds a book hierarchy after import.
+ *
+ * Makes every node that identifies itself as a top-level page into a book.
+ * It does this by using the Drupal-book style of looking for the incoming
+ * guid to be equivalent to the book_guid.
+ *
+ * Not bothering with node_submit, all we care about is the book hierarchy to quietly update.
+ * It would be better if we didn't need node_save at all. 
+ *
+ * For notes on updating the book hierarchy, see the README.txt.
+ */
+function _feeds_book_build_hierarchy() {
+  // Create new books.
+  $books = array();
+  $book_res = db_query("SELECT feeds.nid AS nid, feeds.guid AS guid FROM {feeds_node_item} feeds " .
+    "JOIN {feeds_book_mapper} book ON feeds.guid = book.guid WHERE book.guid = book.book_guid AND book.feed_nid = '%d'",
+    _feeds_book_get_feed_nid());
+
+  while ($book = db_fetch_object($book_res)) {
+    $book_node = node_load($book->nid);
+    // If it's not already a book, make it a book.
+    if (isset($book_node) && (empty($book_node->book) || $book_node->nid != $book_node->book['bid'])) {
+      $book_node->book = array();
+      $book_node->book['bid'] = 'new';
+      $book_node->book['module'] = 'book';
+      node_save($book_node);
+    }
+    $mlid = db_result(db_query("SELECT mlid from {book} WHERE nid='%d'", $book_node->nid));
+    $books[$book->guid] = array('mlid' => $mlid, 'bid' => $book_node->nid);
+  }
+  _feeds_book_assign_children($books);
+}
+
+/**
+ * Save children into a book outline.
+ * Recursively pulls all children of each page sent in $parents.
+ * Takes all children of depth 9 and greater and saves them to ancester
+ * at depth 8. Assumes no cycles in the hierarchy structure.
+ *
+ * @paramter $parents
+ *  Array of pages defined as array(guid => array(mlid, bid));
+ *
+ * @parameter $depth
+ *  Tracks the current depth, helps make sure hierarchies only run 9 levels deep.
+ * INTERNAL USE ONLY
+ *
+ */
+function _feeds_book_assign_children($parents, $depth = 1) {
+  foreach($parents as $p_guid => $p_info) {
+    $results = db_query("SELECT feeds.guid AS guid, feeds.nid AS nid FROM {feeds_node_item} feeds "
+      . "JOIN {feeds_book_mapper} book ON feeds.guid = book.guid "
+      . "WHERE book.parent_guid = '%s' AND book.feed_nid = '%d'", $p_guid, _feeds_book_get_feed_nid());
+
+    $children = array();
+    while ($child = db_fetch_object($results)) {
+      $child_node = node_load($child->nid);
+      if (isset($child_node) && (empty($child_node->book) || $child_node->book['plid'] != $p_info['mlid'])) {
+        $child_node->book = array();
+        $child_node->book['bid'] = $p_info['bid'];
+        $child_node->book['plid'] = $p_info['mlid'];
+        $child_node->book['weight'] = 0;
+        $child_node->book['module'] = 'book';
+        node_save($child_node);
+      }
+      // Children at the max depth cannot be parent.
+      if ($depth < BOOK_MAX_DEPTH) {
+        $mlid = db_result(db_query("SELECT mlid from {book} WHERE nid='%d'", $child_node->nid));
+        $children[$child->guid] = array('mlid' => $mlid, 'bid' => $p_info['bid']);
+      }
+      // Any child at greater than max depth will be attached to the "deepest" permitted ancestor. (Depth 8)
+      elseif ($depth >= BOOK_MAX_DEPTH) {
+        $children[$child->guid] = array('mlid' => $p_info['mlid'], 'bid' => $p_info['bid']);
+      }
+    }
+    if (!empty($children)) {
+      _feeds_book_assign_children($children, $depth+1);
+    }
+  }
+}
+
+/**
+ * Helper Function to identify if row already exists in database.
+ *
+ * @param $data
+ *  An array containing the guid and feed_nid of the current node.
+ *
+ * @param $overwrite
+ *  Overwrite the value for the current guid with the latest changes for the record.
+ * If FALSE, ignored.
+ *
+ * @return
+ *  Associative array of the current state of the record for guid, feed_nid.
+ */
+function _feeds_book_record_exists($data, $overwrite = FALSE) {
+  static $exists = array();
+
+  if ($overwrite) {
+    $exists[$data['guid']] = $data;
+    return;
+  }
+
+  if (empty($exists[$data['guid']])) {
+    $result = db_fetch_array(db_query("SELECT * FROM {feeds_book_mapper} WHERE guid='%s' AND feed_nid='%d'", array_values($data)));
+    if (empty($result)) {
+      drupal_write_record('feeds_book_mapper', $data);
+      $exists[$data['guid']] = $data;
+    }
+    else {
+      $exists[$data['guid']] = $result;
+    }
+  }
+
+  return $exists[$data['guid']];
+}
+
+/**
+ * Set/Get current feed node.
+ */
+function _feeds_book_get_feed_nid($f_nid = NULL) {
+  static $feed_nid;
+  if ($f_nid) {
+    $feed_nid = $f_nid;
+  }
+  else {
+    return $feed_nid;
+  }
+}
