Index: uuid_features.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uuid_features/uuid_features.module,v
retrieving revision 1.1
diff -u -p -w -r1.1 uuid_features.module
--- uuid_features.module	26 Jul 2010 22:16:15 -0000	1.1
+++ uuid_features.module	3 Feb 2011 12:17:46 -0000
@@ -25,6 +25,12 @@ function uuid_features_features_api() {
       'default_file' => FEATURES_DEFAULTS_INCLUDED,
       'file' => drupal_get_path('module', 'uuid_features') .'/includes/uuid_term.features.inc',
     ),
+    'uuid_book' => array(
+      'name' => t('Book'),
+      'default_hook' => 'uuid_features_default_book',
+      'default_file' => FEATURES_DEFAULTS_INCLUDED,
+      'file' => drupal_get_path('module', 'uuid_features') .'/includes/uuid_book.features.inc',
+    ),
   );
 
   // Depends on http://drupal.org/node/808690
Index: includes/uuid_book.features.inc
===================================================================
RCS file: includes/uuid_book.features.inc
diff -N includes/uuid_book.features.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/uuid_book.features.inc	3 Feb 2011 12:17:46 -0000
@@ -0,0 +1,95 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * Features hooks for the uuid_book features component.
+ * Select a book adds all book nodes to the pipe so they can be processed as
+ * normal nodes with book processing handled by uuid_node_feature hooks. Unfortunately
+ * order is not preserved so a little post load processing is required.
+ */
+
+/**
+ * Implementation of hook_features_export_options().
+ */
+function uuid_book_features_export_options() {
+  $options = array();
+
+  // use nid as no uuid for books.
+  $book_nids = array();
+
+  $books = book_get_books();
+
+  foreach (book_get_books() as $book) {
+    $book_nids[] = $book['nid'];
+  }
+
+  $query = "SELECT n.nid, n.title, n.type, u.uuid
+    FROM {node} n INNER JOIN {uuid_node} u
+    ON n.nid = u.nid  WHERE n.nid IN (". db_placeholders($book_nids, 'varchar') .") ORDER BY n.title ASC";
+  $result = db_query($query, $book_nids);
+  while ($node = db_fetch_object($result)) {
+    $options[$node->uuid] = $node->title;
+  }
+
+  return $options;
+}
+
+/**
+ * Traverse the book tree to get all nodes uuids in the book.
+ *
+ * @param $tree
+ *   A subtree of the book menu hierarchy, rooted at the current page.
+ * @return
+ *   An array of book node uuids.
+ */
+function uuid_book_features_export_traverse($tree) {
+  $book_uuids = array();
+
+  foreach ($tree as $data) {
+    // Note- access checking is already performed when building the tree.
+    if ($node = node_load($data['link']['nid'], FALSE)) {
+      $book_uuids[] = $node->uuid;
+
+      $children = '';
+      if ($data['below']) {
+        $book_uuids = array_merge($book_uuids, uuid_book_features_export_traverse($data['below']));
+      }
+    }
+  }
+  return $book_uuids;
+}
+
+/**
+ * Implementation of hook_features_export().
+ */
+function uuid_book_features_export($data, &$export, $module_name = '') {
+  $pipe = array();
+
+  $export['dependencies']['uuid_features'] = 'uuid_features';
+
+  $book_uuids = array();
+
+  foreach ($data as $uuid) {
+    $node = node_get_by_uuid($uuid);
+
+    if (isset($node->book)) {
+      $tree = book_menu_subtree_data($node->book);
+      $book_uuids = array_merge($book_uuids, uuid_book_features_export_traverse($tree));
+    }
+
+    $export['features']['uuid_book'][$uuid] = $uuid;
+  }
+
+  if(!empty($book_uuids)) {
+    if(!empty($pipe['uuid_node'])) {
+      $pipe['uuid_node'] = array_merge($pipe['uuid_node'], $book_uuids);
+    }
+    else
+    {
+      $pipe['uuid_node'] = $book_uuids;
+    }
+  }
+
+  return $pipe;
+}
Index: includes/uuid_node.features.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/uuid_features/includes/uuid_node.features.inc,v
retrieving revision 1.1.2.3
diff -u -p -w -r1.1.2.3 uuid_node.features.inc
Index: includes/modules/book.inc
===================================================================
RCS file: includes/modules/book.inc
diff -N includes/modules/book.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ includes/modules/book.inc	3 Feb 2011 12:17:46 -0000
@@ -0,0 +1,149 @@
+<?php
+// $Id$
+
+/**
+ * @file
+ * uuid_node hooks on behalf of the book module.
+ */
+
+/**
+ * Implementation of hook_uuid_node_features_export_render_alter().
+ *
+ * For root book pages - set bid to new. For child pages set a parent_uuid
+ * to use on rebuild. Clear all other book properties.
+ */
+function book_uuid_node_features_export_render_alter(&$export, $node, $module) {
+  if(isset($node->book) && isset($node->book['bid']) && isset($node->book['nid'])) {
+    $node_book_export = array();
+    // $export->book = array();
+    if($node->book['bid'] == $node->book['nid']) {
+      $node_book_export['bid'] = 'new';
+    }
+    elseif(isset($node->book['plid'])) {
+      $parent_menu_link = book_link_load($node->book['plid']);
+      $parent_node = node_load($parent_menu_link['nid']);
+      $node_book_export['parent_uuid'] = $parent_node->uuid;
+      $node_book_export['weight'] = $node->book['weight'];
+    }
+
+    $export->book = $node_book_export;
+  }
+}
+
+/**
+ * Implementation of hook_uuid_node_features_rebuild_alter().
+ * Replace book parent uuid's with the valid book parameters. As there is no way
+ * to guarantee the correct top-down hierarchical order - collect orphan
+ * book nodes for post node rebuild processing.
+ */
+function book_uuid_node_features_rebuild_alter(&$node, $module) {
+  if(isset($node->book)) {
+    if(isset($node->book['parent_uuid'])) {
+      $parent_node = node_get_by_uuid($node->book['parent_uuid']);
+
+      if($parent_node && isset($parent_node->book['mlid'])) {
+        // set up node as for add child page
+        $node->book['bid'] = $parent_node->book['bid'];
+        $node->book['plid'] = $parent_node->book['mlid'];
+        $node->book['menu_name'] = $parent_node->book['menu_name'];
+        $node->book['weight'] = $node->book['weight'];
+      }
+      else {
+        // add to pending
+        _book_uuid_node_features_pending($node);
+      }
+    }
+  }
+  $existing = node_get_by_uuid($node->uuid);
+  if(!empty($existing)) {
+    $node->book['mlid'] = $existing->book['mlid'];
+  }
+}
+
+/**
+ * Implementation of hook_uuid_node_features_rebuild_complete().
+ * Ensure complete book structure has been set up.
+ */
+function book_uuid_node_features_rebuild_complete($nodes, $module) {
+  // reset the node cache
+  node_load(FALSE, NULL, TRUE);
+
+  $pending = _book_uuid_node_features_pending();
+  if(!empty($pending)) {
+    // sort so that parent nodes come before child nodes
+    $pending = _book_uuid_node_features_pending_sort($pending);
+  }
+
+  foreach($pending as $uuid => $book) {
+    $book_page_node = node_get_by_uuid($uuid);
+    $parent_node = node_get_by_uuid($book['parent_uuid']);
+
+    // set up node as for add child page
+    $book_page_node->book['bid'] = $parent_node->book['bid'];
+    $book_page_node->book['plid'] = $parent_node->book['mlid'];
+    $book_page_node->book['menu_name'] = $parent_node->book['menu_name'];
+    $book_page_node->book['weight'] = $book['weight'];
+
+    node_save($book_page_node);
+    node_load($book_page_node->nid, NULL, TRUE);
+ }
+}
+
+/**
+ * Any pages with no parent page are put here to be processed later.
+ */
+function _book_uuid_node_features_pending($node = NULL) {
+  static $pending = array();
+
+  if(!is_null($node)) {
+    $pending[$node->uuid] = $node->book;
+  }
+  else {
+    return $pending;
+  }
+}
+
+/**
+ * Sort callback to put parents before children.
+ */
+function _book_uuid_node_features_pending_sort($pending) {
+  $pending_by_parent = array();
+
+  // group by parent
+  foreach($pending as $uuid => $book) {
+    $parent_uuid = $book['parent_uuid'];
+    if(empty($pending_by_parent)) {
+      $pending_by_parent[$parent_uuid] = array();
+    }
+
+    $pending_by_parent[$parent_uuid][$uuid] = $book;
+  }
+
+  $sorted_pending = array();
+
+  // find top-level parents and get all ancestors in order
+  foreach($pending_by_parent as $parent_uuid => $books) {
+    if(empty($pending[$parent_uuid])) {
+      // this is a top-level pending - recurse from here
+      $sorted_pending = array_merge($sorted_pending, _book_uuid_node_features_pending_get_ancestors($parent_uuid, $pending_by_parent));
+    }
+  }
+
+  return $sorted_pending;
+}
+
+/**
+ * Recursively collect ancestor nodes in top-down order.
+ */
+function _book_uuid_node_features_pending_get_ancestors($parent_uuid, $pending_by_parent) {
+  $sorted_pending = array();
+
+  if(!empty($pending_by_parent[$parent_uuid])) {
+    foreach($pending_by_parent[$parent_uuid] as $uuid => $book) {
+      $sorted_pending[$uuid] = $book;
+      $sorted_pending = array_merge($sorted_pending, _book_uuid_node_features_pending_get_ancestors($uuid, $pending_by_parent));
+    }
+  }
+
+  return $sorted_pending;
+}
