Index: skeleton_template.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton_template.inc,v
retrieving revision 1.12
diff -u -r1.12 skeleton_template.inc
--- skeleton_template.inc	31 Jan 2009 22:35:12 -0000	1.12
+++ skeleton_template.inc	11 Mar 2009 22:06:28 -0000
@@ -209,160 +209,156 @@
 }
 
 /**
- * Implements hook_form_alter()
+ * Alter the node edit form to make it suitable for a skeleton template.
  *
- * This function lets us use the default node type form to create a template.
- * There may be some issues with its handling of array data, like checkboxes
- * radios, and other odd CCK field types.
- *
- * Note that it does not support file uploads.
+ * @param $form
+ *   The form array to alter.
+ * @param $form_state
+ *   The state of the current form.
+ * @param $form_id
+ *   The ID of the current form, usually node-type_node_form.
  */
-function skeleton_form_alter(&$form, $form_state, $form_id) {
-  module_load_include('inc', 'skeleton', 'skeleton_token');
-  // only alter forms handled through the skeleton interface
-  $form_type = explode('_', $form_id);
-  $form_node = $form_type[0] .'_node_form';
-  if ($form_node == $form_id && arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'template' && arg(5) == 'edit') {
-    $template = skeleton_template_load(arg(4));
-    if (!empty($template->node_data)) {
-      $node = $template->node_data;
-    }
-    // handles some form elements (radios mostly) that don't get set correctly
-    foreach ($form as $key => $value) {
-      if (isset($node[$key]['key']) && isset($form[$key]['key']['#default_value'])) {
-        $form[$key]['key']['#default_value'] = $node[$key]['key'];
-      }
+function skeleton_alter_node_form(&$form, $form_state, $form_id) {
+  $template = skeleton_template_load(arg(4));
+  if (!empty($template->node_data)) {
+    $node = $template->node_data;
+  }
+  // handles some form elements (radios mostly) that don't get set correctly
+  foreach ($form as $key => $value) {
+    if (isset($node[$key]['key']) && isset($form[$key]['key']['#default_value'])) {
+      $form[$key]['key']['#default_value'] = $node[$key]['key'];
     }
-    // publishing options are handled oddly -- this may affect all checkbox types
-    foreach ($form['options'] as $key => $value) {
-      if (isset($node[$key])) {
-        $form['options'][$key]['#default_value'] = $node[$key];
-      }
+  }
+  // publishing options are handled oddly -- this may affect all checkbox types
+  foreach ($form['options'] as $key => $value) {
+    if (isset($node[$key])) {
+      $form['options'][$key]['#default_value'] = $node[$key];
     }
-    // handle taxonomies -- this may apply to all select elements
-    if (!empty($form['taxonomy'])) {
-      foreach ($form['taxonomy'] as $key => $value) {
-        if (is_numeric($key)) {
-          $form['taxonomy'][$key]['#default_value'] = $node['taxonomy'][$key];
-        }
+  }
+  // handle taxonomies -- this may apply to all select elements
+  if (!empty($form['taxonomy'])) {
+    foreach ($form['taxonomy'] as $key => $value) {
+      if (is_numeric($key)) {
+        $form['taxonomy'][$key]['#default_value'] = $node['taxonomy'][$key];
       }
     }
-    $form['template_id'] = array('#type' => 'value', '#value' => $template->template_id);
-    $form['type'] = array('#type' => 'value', '#value' => $template->node_type);
-    $form['skeleton_template'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Template information'),
-      '#collapsible' => TRUE,
-      '#weight' => -100
-    );
-    $form['skeleton_template']['template'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Template name'),
-      '#default_value' => $template->template,
-      '#description' => t('The unique name to identify this template.'),
-      '#required' => TRUE
-    );
-    $options = node_get_types('names');
-    $form['skeleton_template']['node_type'] = array(
-      '#type' => 'select',
-      '#title' => t('Node type'),
-      '#value' => $template->node_type,
-      '#options' => $options,
-      '#description' => t('The node type for this template. <em>This value cannot be edited.</em>'),
-      '#disabled' => TRUE
-    );
+  }
+  $form['template_id'] = array('#type' => 'value', '#value' => $template->template_id);
+  $form['type'] = array('#type' => 'value', '#value' => $template->node_type);
+  $form['skeleton_template'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Template information'),
+    '#collapsible' => TRUE,
+    '#weight' => -100
+  );
+  $form['skeleton_template']['template'] = array(
+    '#type' => 'textfield',
+    '#title' => t('Template name'),
+    '#default_value' => $template->template,
+    '#description' => t('The unique name to identify this template.'),
+    '#required' => TRUE
+  );
+  $options = node_get_types('names');
+  $form['skeleton_template']['node_type'] = array(
+    '#type' => 'select',
+    '#title' => t('Node type'),
+    '#value' => $template->node_type,
+    '#options' => $options,
+    '#description' => t('The node type for this template. <em>This value cannot be edited.</em>'),
+    '#disabled' => TRUE
+  );
+  $form['skeleton_template']['template_id'] = array('#type' => 'value', '#value' => $template->template_id);
+
+  // Add token help information below the skeleton fieldset.
+  $form['skeleton_tokens'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Skeleton replacement tokens'),
+    '#description' => t('These tokens will be replaced in the title and the body when instantiating a skeleton outline'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => -99,
+  );
+  $form['skeleton_tokens']['tokens'] = array(
+    '#value' => _skeleton_build_token_help('skeleton'),
+  );
+  $form['node_tokens'] = array(
+    '#type' => 'fieldset',
+    '#title' => t('Node replacement tokens'),
+    '#description' => t('These tokens will be replaced in the title and the body when instantiating a skeleton outline'),
+    '#collapsible' => TRUE,
+    '#collapsed' => TRUE,
+    '#weight' => -98,
+  );
+  $form['node_tokens']['tokens'] = array(
+    '#value' => _skeleton_build_token_help('node'),
+  );
 
-    // Add token help information below the skeleton fieldset.
-    $form['skeleton_tokens'] = array(
-      '#type' => 'fieldset',
-      '#title' => t('Skeleton replacement tokens'),
-      '#description' => t('These tokens will be replaced in the title and the body when instantiating a skeleton outline'),
-      '#collapsible' => TRUE,
-      '#collapsed' => TRUE,
-      '#weight' => -99,
-    );
-    $form['skeleton_tokens']['tokens'] = array(
-      '#value' => _skeleton_build_token_help('skeleton'),
-    );
-    $form['node_tokens'] = array(
+  // unset the values we don't want or cannot save
+  $unset = array('parent', 'weight', 'menu', 'book', 'path', 'attachments', 'field_file', 'revision_information', 'log', 'buttons', '#after_build', '#submit');
+  foreach ($unset as $item) {
+    unset($form[$item]);
+  }
+  if (module_exists('nodeaccess') && user_access('grant node permissions')) {
+    $form['skeleton_template_grants'] = array(
       '#type' => 'fieldset',
-      '#title' => t('Node replacement tokens'),
-      '#description' => t('These tokens will be replaced in the title and the body when instantiating a skeleton outline'),
+      '#title' => t('Node access rules'),
       '#collapsible' => TRUE,
       '#collapsed' => TRUE,
-      '#weight' => -98,
-    );
-    $form['node_tokens']['tokens'] = array(
-      '#value' => _skeleton_build_token_help('node'),
+      '#weight' => 100,
+      '#theme' => 'skeleton_na_form_element'
     );
-
-    // unset the values we don't want or cannot save
-    $unset = array('parent', 'weight', 'menu', 'book', 'path', 'attachments', 'field_file', 'revision_information', 'log', 'buttons', '#after_build', '#submit');
-    foreach ($unset as $item) {
-      unset($form[$item]);
-    }
-    if (module_exists('nodeaccess') && user_access('grant node permissions')) {
-      $form['skeleton_template_grants'] = array(
-        '#type' => 'fieldset',
-        '#title' => t('Node access rules'),
-        '#collapsible' => TRUE,
-        '#collapsed' => TRUE,
-        '#weight' => 100,
-        '#theme' => 'skeleton_na_form_element'
-      );
-      // get roles and set default permissions
-      $roles = user_roles();
-      if (isset($node['rid'])) {
-        // set the gid into the array correctly
-        $perms = $node['rid'];
-        foreach ($roles as $key => $value) {
-          $perms[$key] = $node['rid'][$key];
-          $perms[$key]['gid'] = $key;
-          $perms[$key]['name'] = $roles[$key];
-        }
-      }
-      else {
-        $perms = variable_get('nodeaccess_'. $template->node_type, array());
-      }
-      foreach ($perms as $perm) {
-        $name = $roles[$perm['gid']];
-        $roles[$perm['gid']] = $perm;
-        $roles[$perm['gid']]['name'] = $name;
-      }
-      $naform = array();
-      if (is_array($roles)) {
-        $naform['rid'] = array('#tree' => TRUE);
-        $allowed = variable_get('nodeaccess-roles', array());
-        foreach ($roles as $key => $field) {
-          if ($allowed[$key]) {
-            $naform['rid'][$key]['name'] = array('#type' => 'hidden', '#value' => $field['name']);
-            $naform['rid'][$key]['grant_view'] = array('#type' => 'checkbox', '#default_value' => $field['grant_view']);
-            $naform['rid'][$key]['grant_update'] = array('#type' => 'checkbox', '#default_value' => $field['grant_update']);
-            $naform['rid'][$key]['grant_delete'] = array('#type' => 'checkbox', '#default_value' => $field['grant_delete']);
-          }
+    // get roles and set default permissions
+    $roles = user_roles();
+    if (isset($node['rid'])) {
+      // set the gid into the array correctly
+      $perms = $node['rid'];
+      foreach ($roles as $key => $value) {
+        $perms[$key] = $node['rid'][$key];
+        $perms[$key]['gid'] = $key;
+        $perms[$key]['name'] = $roles[$key];
+      }
+    }
+    else {
+      $perms = variable_get('nodeaccess_'. $template->node_type, array());
+    }
+    foreach ($perms as $perm) {
+      $name = $roles[$perm['gid']];
+      $roles[$perm['gid']] = $perm;
+      $roles[$perm['gid']]['name'] = $name;
+    }
+    $naform = array();
+    if (is_array($roles)) {
+      $naform['rid'] = array('#tree' => TRUE);
+      $allowed = variable_get('nodeaccess-roles', array());
+      foreach ($roles as $key => $field) {
+        if ($allowed[$key]) {
+          $naform['rid'][$key]['name'] = array('#type' => 'hidden', '#value' => $field['name']);
+          $naform['rid'][$key]['grant_view'] = array('#type' => 'checkbox', '#default_value' => $field['grant_view']);
+          $naform['rid'][$key]['grant_update'] = array('#type' => 'checkbox', '#default_value' => $field['grant_update']);
+          $naform['rid'][$key]['grant_delete'] = array('#type' => 'checkbox', '#default_value' => $field['grant_delete']);
         }
       }
-      if (count($naform['rid']) > 1) {
-        $form['skeleton_template_grants']['na'] = $naform;
-      }
-      else {
-        unset($form['skeleton_template_grants']);
-      }
     }
-    $form['buttons']['delete'] = array(
-      '#type' => 'submit',
-      '#value' => t('Delete template'),
-      '#weight' => 200,
-      '#submit' => array('skeleton_template_delete_redirect'),
-    );
-    $form['#submit'][] = 'skeleton_edit_template_form_submit';
-    $form['#validate'][] = 'skeleton_edit_template_form_validate';
-    $form['buttons']['submit'] = array(
-      '#type' => 'submit',
-      '#value' => t('Update template'),
-      '#weight' => 100
-    );
+    if (count($naform['rid']) > 1) {
+      $form['skeleton_template_grants']['na'] = $naform;
+    }
+    else {
+      unset($form['skeleton_template_grants']);
+    }
   }
+  $form['buttons']['delete'] = array(
+    '#type' => 'submit',
+    '#value' => t('Delete template'),
+    '#weight' => 200,
+    '#submit' => array('skeleton_template_delete_redirect'),
+  );
+  $form['#submit'][] = 'skeleton_edit_template_form_submit';
+  $form['#validate'][] = 'skeleton_edit_template_form_validate';
+  $form['buttons']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Update template'),
+    '#weight' => 100
+  );
 }
 
 /**
@@ -391,7 +387,6 @@
  */
 function skeleton_edit_template_form_validate($form, &$form_state) {
   // stub function
-  return;
 }
 
 /**
@@ -400,12 +395,16 @@
 function skeleton_edit_template_form_submit($form, &$form_state) {
   // these items are not stored node data
   $node = $form_state['values'];
-  $unset = array('template_id', 'template', 'node_type', 'nid', 'vid', 'teaser_js', 'created', 'changed', 'date', 'op', 'submit', 'form_token', 'form_id');
+  $unset = array('template', 'node_type', 'nid', 'vid', 'teaser_js', 'created', 'changed', 'date', 'op', 'submit', 'form_token', 'form_id');
   foreach ($unset as $item) {
     unset($node[$item]);
   }
   db_query("UPDATE {skeleton_template} SET node_data = '%s' WHERE template_id = %d", serialize($node), $form_state['values']['template_id']);
   drupal_set_message(t('Template successfully updated.'));
+
+  // Now, mark any instantiated copies of this template as eligible for
+  // updating.
+  db_query("UPDATE {skeleton_template_node} SET template_status = 'updated' WHERE template_id = %d", $form_state['values']['template_id']);
 }
 
 /**
Index: skeleton.install
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton.install,v
retrieving revision 1.5
diff -u -r1.5 skeleton.install
--- skeleton.install	7 Jan 2009 17:38:58 -0000	1.5
+++ skeleton.install	11 Mar 2009 22:06:26 -0000
@@ -20,12 +20,12 @@
     }
   }
   if ($install_pass) {
-    drupal_set_message('Installed the {skeleton}, {skeleton_data}, and {skeleton_template} tables successfully.');
-    // set the module weight low for hook_form_alter()
+    drupal_set_message('Installed the {skeleton}, {skeleton_data}, {skeleton_template}, and {skeleton_template_node} tables successfully.');
   }
   else {
     drupal_set_message(t('The installation of the skeleton module was unsuccessful.'), 'error');
   }
+  // set the module weight low for hook_form_alter()
   db_query("UPDATE {system} SET weight = 20 WHERE name = 'skeleton'");
 }
 
@@ -89,6 +89,19 @@
     ),
   );
 
+  $schema['skeleton_template_node'] = array(
+    'fields' => array(
+      'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+      'template_id' => array('type' => 'int', 'unsigned' => TRUE, 'length' => 11, 'not null' => TRUE),
+      'template_status' => array('type' => 'varchar', 'length' => '16', 'not null' => TRUE),
+      'tokens' => array('type' => 'blob', 'size' => 'big'),
+    ),
+    'primary key' => array(
+      'nid',
+      'template_id',
+    ),
+  );
+
   return $schema;
 }
 
@@ -111,3 +124,26 @@
   db_create_table($ret, 'skeleton_token', $schema['skeleton_token']);
   return $ret;
 }
+
+/**
+ * Add a table for storing template id's and supplied tokens in instantiated nodes.
+ * @return unknown_type
+ */
+function skeleton_update_6002() {
+  $ret = array();
+  $schema = array();
+  $schema['skeleton_template_node'] = array(
+    'fields' => array(
+      'nid' => array('type' => 'int', 'unsigned' => TRUE, 'not null' => TRUE),
+      'template_id' => array('type' => 'int', 'unsigned' => TRUE, 'length' => 11, 'not null' => TRUE),
+      'template_status' => array('type' => 'varchar', 'length' => '16', 'not null' => TRUE),
+      'tokens' => array('type' => 'blob', 'size' => 'big'),
+    ),
+    'primary key' => array(
+      'nid',
+      'template_id',
+    ),
+  );
+  db_create_table($ret, 'skeleton_template_node', $schema['skeleton_template_node']);
+  return $ret;
+}
Index: skeleton_instance.inc
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton_instance.inc,v
retrieving revision 1.16
diff -u -r1.16 skeleton_instance.inc
--- skeleton_instance.inc	5 Feb 2009 20:33:33 -0000	1.16
+++ skeleton_instance.inc	11 Mar 2009 22:06:27 -0000
@@ -714,8 +714,13 @@
       $node['values']['body'] = token_replace($node['values']['body'], 'node', (object)$node['values']);
       $form_state['values']['body'] = $node['values']['body'];
 
+      $node['values']['tokens'] = $form_state['values']['skeleton_tokens'];
+      $form_state['values']['tokens'] = $form_state['values']['skeleton_tokens'];
+
       $form_state['values']['language'] = $node['values']['language'];
 
+      $form_state['values']['template_id'] = $node['values']['template_id'];
+
       // We've done this template, so remove it.
       unset($nodes[0]);
     }
@@ -726,6 +731,8 @@
         'title' => $form_state['values']['title'],
         'type' => 'book',
         'body' => $form_state['values']['body'],
+        'tokens' => $form_state['values']['tokens'],
+        'template_id' => $form_state['values']['template_id'],
         'op' => t('Save'),
       ),
     );
@@ -771,6 +778,8 @@
       $node['values']['uid'] = $user->uid;
       $node['values']['name'] = $user->name;
 
+      $node['values']['tokens'] = $form_state['values']['skeleton_tokens'];
+
       $node['values']['op'] = t('Save');
       drupal_execute($node['values']['type'] .'_node_form', $node, (object)$node['values']);
       // TODO: This will probably need to be removed if this call is integrated
@@ -783,5 +792,10 @@
   else {
     form_set_error(t('This skeleton is empty.'), 'form');
   }
-  $form_state['redirect'] = 'node/' . $goto_id . '/edit';
+  if (!$form_state['values']['title']) {
+    $form_state['redirect'] = 'node/' . $goto_id;
+  }
+  else {
+    $form_state['redirect'] = 'node/' . $goto_id . '/edit';
+  }
 }
Index: skeleton.module
===================================================================
RCS file: /cvs/drupal-contrib/contributions/modules/skeleton/skeleton.module,v
retrieving revision 1.16
diff -u -r1.16 skeleton.module
--- skeleton.module	5 Feb 2009 20:33:33 -0000	1.16
+++ skeleton.module	11 Mar 2009 22:06:27 -0000
@@ -70,6 +70,14 @@
     'title' => 'Add skeleton',
     'file' => 'skeleton_admin.inc',
   );
+  $items['admin/content/skeleton/sync'] = array(
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('skeleton_sync_form'),
+    'access arguments' => array('configure skeleton outlines'),
+    'type' => MENU_LOCAL_TASK,
+    'title' => 'Synchronize',
+    'file' => 'skeleton_sync.inc',
+  );
   $items['admin/content/skeleton/token'] = array(
     'page callback' => 'drupal_get_form',
     'page arguments' => array('skeleton_token_form'),
@@ -219,6 +227,15 @@
     );
   }
 
+  // Add an "Edit source template" tab for instantiated nodes.
+  $items['node/%node/edit-skeleton-template'] = array(
+    'title' => 'Edit source template',
+    'page callback' => '_skeleton_goto_edit_template',
+    'page arguments' => array(1),
+    'access callback' => '_skeleton_template_connected',
+    'access arguments' => array(1),
+    'type' => MENU_LOCAL_TASK,
+  );
   return $items;
 }
 
@@ -309,3 +326,83 @@
   $result = db_fetch_object(db_query("SELECT * FROM {skeleton_token} WHERE token_id = %d", $token_id));
   return empty($result) ? FALSE : $result;
 }
+
+/**
+ * Implementation of hook_nodeapi()
+ */
+function skeleton_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
+  switch ($op) {
+    case 'load':
+      if ($template = db_fetch_object(db_query("SELECT template_id, tokens FROM {skeleton_template_node} WHERE nid = %d", $node->nid))) {
+        $node->skeleton_template = new stdClass();
+        $node->skeleton_template->template_id = $template->template_id;
+        $node->skeleton_template->tokens = unserialize($template->tokens);
+      }
+      break;
+    case 'prepare':
+      if ($node->skeleton_template->template_id) {
+        drupal_set_message(t('This node has not been changed since it was created from a template. Saving this node will override the default content.'));
+      }
+      break;
+    case 'insert':
+      if ($node->template_id) {
+        db_query("INSERT INTO {skeleton_template_node} (nid, template_id, template_status, tokens) VALUES (%d, %d, 'synced', %s')", $node->nid, $node->template_id, serialize($node->tokens));
+      }
+      break;
+    case 'update':
+    case 'delete':
+      if (!$node->skeleton_template->keep_connected) {
+        db_query("DELETE FROM {skeleton_template_node} WHERE nid = %d", $node->nid);
+      }
+      break;
+  }
+}
+
+/**
+ * Implements hook_form_alter()
+ *
+ * This function lets us use the default node type form to create a template.
+ * There may be some issues with its handling of array data, like checkboxes
+ * radios, and other odd CCK field types.
+ *
+ * Note that it does not support file uploads.
+ */
+function skeleton_form_alter(&$form, $form_state, $form_id) {
+  module_load_include('inc', 'skeleton', 'skeleton_token');
+  // only alter forms handled through the skeleton interface
+  $form_type = explode('_', $form_id);
+  $form_node = $form_type[0] .'_node_form';
+  if ($form_node == $form_id && arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'template' && arg(5) == 'edit') {
+    module_load_include('inc', 'skeleton', 'skeleton_template');
+    skeleton_alter_node_form($form, $form_state, $form_id);
+  }
+  else if ($form_node == $form_id && arg(0) == 'admin' && arg(2) == 'skeleton' && arg(3) == 'skeleton' && arg(5) == 'create') {
+    $form['template_id'] = array('#type' => 'hidden', '#value' => $form_state['values']['template_id']);
+    $form['tokens'] = array('#type' => 'hidden', '#value' => $form_state['values']['tokens']);
+  }  
+}
+
+/**
+ * Determine if a node has an associated template and if the current user has
+ * permission to determine such information.
+ *
+ * @param $node
+ *   The node to search for.
+ * @return
+ *   TRUE if the node has an associated template and the user can access it,
+ *   FALSE otherwise. 
+ */
+function _skeleton_template_connected($node) {
+  return user_access('configure skeleton outlines') && db_result(db_query("SELECT COUNT(1) FROM {skeleton_template_node} WHERE nid = %d", $node->nid));
+}
+
+/**
+ * Given a node, go to its associated template of it exists. 
+ * @param $node
+ *   The node which was created from a template.
+ */
+function _skeleton_goto_edit_template($node) {
+  if ($template_id = db_result(db_query("SELECT template_id FROM {skeleton_template_node} WHERE nid = %d", $node->nid))) {
+    drupal_goto('admin/content/skeleton/template/' . $template_id . '/edit');
+  }
+}
\ No newline at end of file
Index: skeleton_sync.inc
===================================================================
RCS file: skeleton_sync.inc
diff -N skeleton_sync.inc
--- /dev/null	1 Jan 1970 00:00:00 -0000
+++ skeleton_sync.inc	1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * Syncronize form. Contains lists of updated, added, and deleted templates.
+ *
+ * @param $form_state
+ *   The state of the form.
+ * @return
+ *   The form array.
+ */
+function skeleton_sync_form($form_state) {
+  $form = array();
+  $form['help'] = array(
+    '#prefix' => '<p>',
+    '#suffix' => '</p>',
+    '#value' => t('Use this form to syncronize changed templates with pages which have all ready been created.'),
+  );
+
+  // Build the list of pages which have had content changes.
+  $updated = array();
+  $result = db_query("SELECT stn.nid, stn.template_id, st.template FROM {skeleton_template_node} stn INNER JOIN {skeleton_template} st ON stn.template_id = st.template_id WHERE template_status = '%s'", 'updated');
+  while ($eligble_node = db_fetch_object($result)) {
+    $node = node_load($eligble_node->nid);
+    $book = node_load($node->book['bid']);
+    $updated[$node->nid] = t('<a href="@node-url">%title</a>, in the <a href="@book-url">%book-title</a> book, created from the <a href="@template-url">%template</a> template.', array('@node-url' => url('node/' . $node->nid), '%title' => $node->title, '@book-url' => url('node/' . $book->nid), '%book-title' => $book->title, '%template' => $eligble_node->template, '@template-url' => url('admin/content/skeleton/template/' . $eligble_node->template_id . '/view')));
+  }
+  if (!empty($updated)) {
+    $form['nodes'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Select the nodes you wish to update'),
+      '#options' => $updated,
+    );
+    $form['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Synchronize'),
+    );
+  }
+  else {
+    $form['no_nodes'] = array(
+      '#prefix' => '<p>',
+      '#suffix' => '</p>',
+      '#value' => t('There are no nodes eligible for synchronizing.')
+    );
+  }
+
+  // TODO: Show a seperate list for new templates added to a skeleton.
+
+  // TODO: Show a seperate list for templates removed from a skeleton.
+
+  return $form;
+}
+
+/**
+ * Submit handler for the syncronize form.
+ *
+ * @param $form
+ *   The form being submitted.
+ *
+ * @param $form_state
+ *   The state of the form.
+ */
+function skeleton_sync_form_submit($form, $form_state) {
+  $batch = array(
+    'title' => t('Updating content'),
+    'init_message' => t('Content updating is starting.'),
+    'progress_message' => t('Processed @current out of @total.'),
+    'error_message' => t('There was an error while updating content.'),
+    'file' => drupal_get_path('module', 'skeleton'). '/skeleton_sync.inc',
+  );
+  foreach($form_state['values']['nodes'] as $nid) {
+    $batch['operations'][] = array('skeleton_sync_update_node', array($nid));
+  }
+  batch_set($batch);
+}
+
+/**
+ * Update an instantiated node based on an updated template.
+ *
+ * @param $nid
+ *   The node ID to be updated.
+ *
+ * @param &$context
+ *   The current context of the batch operation.
+ */
+function skeleton_sync_update_node($nid, &$context) {
+  module_load_include('inc', 'skeleton', 'skeleton_token');
+  $node = node_load($nid);
+  $context['message'] = t('Updating %title', array('%title' => $node->title));
+  if (empty($node->skeleton_template->template_id)) {
+    $context['results'][] = t('Failed updating <a href="@node-url">%title</a> as it does not have an associated template.', array('@node-url' => url('node/' . $node->nid), '%title' => $node->title));
+    return;
+  }
+  $template = skeleton_template_load($node->skeleton_template->template_id);
+  // Unset the node teaser. If a module has added it, it will be applied in
+  // the foreach loop.
+  unset($node->teaser);
+  $template_node = (object)$template->node_data;
+  // Don't change the author of nodes.
+  unset($template_node->uid);
+  unset($template_node->name);
+  foreach ($template_node as $key => $value) {
+    $node->$key = $value;
+  }
+  // Replace body and title tokens.
+  $node->title = token_replace($node->title, 'skeleton', $node->skeleton_template->tokens);
+  $node->body = token_replace($node->body, 'skeleton', $node->skeleton_template->tokens);
+  $node->title = token_replace($node->title, 'node', $node);
+  $node->body = token_replace($node->body, 'node', $node);
+
+  // Add a flag so that we keep this node as "unmodified".
+  $node->skeleton_template->keep_connected = TRUE;
+
+  $node = node_submit($node);
+  node_save($node);
+  db_query("UPDATE {skeleton_template_node} SET template_status = 'synced' WHERE nid = %d", $node->nid);
+  $context['results'][] = t('Updated %title', array('%title' => $node->title));
+}
