Index: webform.module
===================================================================
--- webform.module	(revision 55)
+++ webform.module	(revision 59)
@@ -103,6 +103,14 @@
         'access' => true,
         'type' => MENU_CALLBACK,
       );
+      $items[]= array(
+        'path' => 'node/'. $node->nid .'/draft_saved',
+        'title' => t('webform'),
+        'callback' => '_webform_draft_confirmation',
+        'callback arguments' => array(arg(1)),
+        'access' => true,
+        'type' => MENU_CALLBACK,
+      );
       $items[] = array(
         'path' => 'node/'. $node->nid .'/results',
         'title' => t('Results'),
@@ -226,7 +234,7 @@
   }
 
   // Insert the Webform.
-  db_query("INSERT INTO {webform} (nid, confirmation, redirect_post, submit_limit, submit_interval, email, email_from_name, email_from_address, email_subject, additional_validate, additional_submit) VALUES (%d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->confirmation, $node->redirect_post, $node->submit_limit, $node->submit_interval, $node->email, $node->email_from_name, $node->email_from_address, $node->email_subject, $node->additional_validate, $node->additional_submit);
+  db_query("INSERT INTO {webform} (nid, confirmation, redirect_post, submit_limit, submit_interval, email, email_from_name, email_from_address, email_subject, additional_validate, additional_submit, allow_draft) VALUES (%d, '%s', %d, %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d)", $node->nid, $node->confirmation, $node->redirect_post, $node->submit_limit, $node->submit_interval, $node->email, $node->email_from_name, $node->email_from_address, $node->email_subject, $node->additional_validate, $node->additional_submit, $node->allow_draft);
 
   // Insert the components into the database.
   if (is_array($node->webformcomponents) && !empty($node->webformcomponents)) {
@@ -795,6 +803,13 @@
     ),
   );
 
+  // Allow save draft
+  $form['advanced']['allow_draft'] = array(
+    '#type' => 'checkbox',
+    '#title' => t('Allow Users to Save a Draft'),
+    '#default_value' => $node->allow_draft,
+  );
+  
   if (user_access('use PHP for additional processing')) {
     $form['advanced']['additional_validate'] = array(
       '#type' => 'textarea',
@@ -1179,7 +1194,23 @@
     }
   }
 
-  $output = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled);
+  // Check if this user has a draft for this webform
+  $is_draft = false;
+  if ($node->allow_draft && $user->uid != 0)
+  {
+    // Draft found - display form w/ draft data for further submission editing
+    if ($_draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid))
+    {
+      $submission = _webform_fetch_submission($_draft_sid, $node->nid);
+      $enabled = true;
+      $is_draft = true;
+      if (empty($_POST))
+      {
+        drupal_set_message('Your previous draft was found. You may continue with your submission.', 'status');
+      }
+    }
+  }
+  $output = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled, $is_draft);
 
   // Remove the surrounding <form> tag if this is a preview.
   if ($preview) {
@@ -1206,8 +1237,10 @@
  *   editing.
  * @param $form_values
  *   The current form values of a submission, used in multipage webforms.
+ * @param $is_draft
+ *   Optional. Set to true if displaying a draft.
  */
-function webform_client_form(&$node, $submission = array(), $enabled = false, $form_values = NULL) {
+function webform_client_form(&$node, $submission = array(), $enabled = false, $is_draft = false, $form_values = NULL) {
   global $user;
 
   _webform_load_components(); // Load all the components.
@@ -1225,41 +1258,44 @@
 
   // Set a header for navigating results.
   if ($submission && user_access('access webform results')) {
-    // Add CSS to display submission info. Don't preprocess because this CSS file is used rarely.
-    drupal_add_css(drupal_get_path('module', 'webform') .'/webform.css', 'module', 'all', FALSE);
+    if (! $is_draft)
+    {
+      // Add CSS to display submission info. Don't preprocess because this CSS file is used rarely.
+      drupal_add_css(drupal_get_path('module', 'webform') .'/webform.css', 'module', 'all', FALSE);
+  
+      $previous = db_result(db_query('SELECT MAX(sid) FROM {webform_submissions} WHERE nid = %d AND sid < %d', array($node->nid, $submission['sid'])));
+      $next = db_result(db_query('SELECT MIN(sid) FROM {webform_submissions} WHERE nid = %d AND sid > %d', array($node->nid, $submission['sid'])));
+  
+      $form['navigation'] = array(
+        '#prefix' => '<div class="webform-submission-navigation">',
+        '#suffix' => '</div>',
+      );
+      $form['navigation']['previous'] = array(
+        '#value' => $previous ? l(t('Previous submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $previous, array('class' => 'webform-submission-previous')) : '<span class="webform-submission-previous">'. t('Previous submission') .'</span>',
+      );
+      $form['navigation']['next'] = array(
+        '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $next, array('class' => 'webform-submission-next')) : '<span class="webform-submission-next">'. t('Next submission') .'</span>',
+      );
 
-    $previous = db_result(db_query('SELECT MAX(sid) FROM {webform_submissions} WHERE nid = %d AND sid < %d', array($node->nid, $submission['sid'])));
-    $next = db_result(db_query('SELECT MIN(sid) FROM {webform_submissions} WHERE nid = %d AND sid > %d', array($node->nid, $submission['sid'])));
-
-    $form['navigation'] = array(
-      '#prefix' => '<div class="webform-submission-navigation">',
-      '#suffix' => '</div>',
-    );
-    $form['navigation']['previous'] = array(
-      '#value' => $previous ? l(t('Previous submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $previous, array('class' => 'webform-submission-previous')) : '<span class="webform-submission-previous">'. t('Previous submission') .'</span>',
-    );
-    $form['navigation']['next'] = array(
-      '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $next, array('class' => 'webform-submission-next')) : '<span class="webform-submission-next">'. t('Next submission') .'</span>',
-    );
-
-    $form['submission_info'] = array(
-      '#title' => t('Submission Information'),
-      '#type' => 'fieldset',
-      '#collapsible' => FALSE,
-    );
-    $account = user_load(array('uid' => $submission['uid']));
-    $form['submission_info']['user_picture'] = array(
-      '#value' => theme('user_picture', $account),
-    );
-    $form['submission_info']['submitted'] = array(
-      '#value' => '<div>'. t('Submitted by !name', array('!name' => theme('username', $account))) .'</div>',
-    );
-    $form['submission_info']['time'] = array(
-      '#value' => '<div>'. format_date($submission['submitted'], 'large') .'</div>',
-    );
-    $form['submission_info']['ip_address'] = array(
-      '#value' => '<div>'. $submission['remote_addr'] .'</div>',
-    );
+      $form['submission_info'] = array(
+        '#title' => t('Submission Information'),
+        '#type' => 'fieldset',
+        '#collapsible' => FALSE,
+      );
+      $account = user_load(array('uid' => $submission['uid']));
+      $form['submission_info']['user_picture'] = array(
+        '#value' => theme('user_picture', $account),
+      );
+      $form['submission_info']['submitted'] = array(
+        '#value' => '<div>'. t('Submitted by !name', array('!name' => theme('username', $account))) .'</div>',
+      );
+      $form['submission_info']['time'] = array(
+        '#value' => '<div>'. format_date($submission['submitted'], 'large') .'</div>',
+      );
+      $form['submission_info']['ip_address'] = array(
+        '#value' => '<div>'. $submission['remote_addr'] .'</div>',
+      );
+    }
   }
 
   // Add a theme function for this form.
@@ -1341,12 +1377,22 @@
           '#weight' => 1000,
         );
       }
+      // Save draft button
+      if ($node->allow_draft && $user->uid != 0)
+      {
+        $form['draftbutton'] = array(
+          '#type' => 'submit',
+          '#value' => t('Save Draft'),
+          '#weight' => 999,
+        );
+        $form['#no_validate_on'] = 'op=Save Draft';
+      }
     }
 
     // Recursively add components to the form. Microweights keep thins in webform order.
     $microweight = 0.001;
     foreach ($component_tree['children'] as $cid => $component) {
-      _webform_client_form_add_component($cid, $component, $form['submitted'], $form, $submission, $page_num, $enabled);
+      _webform_client_form_add_component($cid, $component, $form['submitted'], $form, $submission, $page_num, $enabled, (int) $node->allow_draft && $user->uid != 0);
       $form['submitted'][$component['form_key']]['#weight'] += $microweight;
       $microweight += 0.001;
     }
@@ -1381,7 +1427,7 @@
   return $form;
 }
 
-function _webform_client_form_add_component($cid, $component, &$parent_fieldset, &$form, $submission, $page_num, $enabled = false) {
+function _webform_client_form_add_component($cid, $component, &$parent_fieldset, &$form, $submission, $page_num, $enabled = false, $allow_draft = 0) {
   // Load with submission information if necessary.
   if (!empty($submission) && !$enabled) {
     // This component is display only, with the value set according information
@@ -1409,10 +1455,37 @@
       }
     }
   }
+
+  // Save draft feature: If the webform is being built during a submission, and draft save has been requested,
+  // disable required fields to prevent validation warning. 
+  // TODO: This leaves the form elements in the rendered page without the 'required' markers. At the moment, that
+  // undesirable result is circumvented by having save-draft submissions redirect to a status page w/o the form.
+  // Also, this only disables 'required' field validation. Need to test/develop for other validation.
+  $validate = true;
+  if (isset($form['#no_validate_on']))
+  {
+    list($no_validate_key, $no_validate_value) = explode('=', $form['#no_validate_on']);
+    if ($_POST[$no_validate_key] == $no_validate_value)
+    {
+      $validate = false;        
+    }
+  }
+  
+  if (! $validate)
+  {
+    $parent_fieldset[$component['form_key']]['#required'] = 0;
+    unset($form['#redirect']);
+  }      
   if (is_array($component['children'])) {
     $microweight = 0.001;
     foreach ($component['children'] as $scid => $subcomponent) {
       _webform_client_form_add_component($scid, $subcomponent, $parent_fieldset[$component['form_key']], $form, $submission, $page_num, $enabled);
+      // Add save draft buttons to end of fieldset, if draft is allowed.
+      // TODO: Is there a better place to do this?      
+      if ($allow_draft /*&& $enabled*/ && $component['type'] == 'fieldset' && $component['page_num'] == $page_num)
+      {
+        $parent_fieldset[$component['form_key']]["draftbutton_$cid"] = array('#type' => 'submit', '#value' => t('Save Draft'), '#weight' => 999);
+      }
       $parent_fieldset[$component['form_key']][$subcomponent['form_key']]['#weight'] += $microweight;
       $microweight += 0.001;
     }
@@ -1465,7 +1538,7 @@
   $node = node_load(array('nid' => $form_values['details']['nid']));
   $session_key = 'webform_form_'. $node->nid;
 
-  if ($form_values['op'] != t('Submit')) {
+  if ($form_values['op'] != t('Submit') && $form_values['op'] != t('Save Draft')) {
     // This is a multi-page form that is not yet complete.
     // Copy values stored during previous steps into $_POST because they are needed in form_builder() to repopulate the form.
     if (is_array($_SESSION[$session_key])) {
@@ -1504,16 +1577,26 @@
     eval("?>". $node->additional_submit);
   }
 
+  // Check if user is submitting as a draft
+  $is_draft = ($node->allow_draft && $form_values['op'] == t('Save Draft')) ? 1 : 0;
+
   // Save the submission to the database.
   if (!$form_values['details']['sid']) {
     // No sid was found thus insert it in the datatabase.
-    $sid = _webform_save_submission($node, $form_values['submitted']);
+    $sid = _webform_save_submission($node, $form_values['submitted'], $is_draft);
   }
   else {
     // Sid was found thus update the existing sid in the datatbase.
-    $sid = _webform_update_submission($node, $form_values['details']['sid'], $form_values['submitted']);
+    $sid = _webform_update_submission($node, $form_values['details']['sid'], $form_values['submitted'], $is_draft);
   }
 
+  // If saving a draft, return redirect to confirmation page (no email processing).
+  if ($node->allow_draft && $form_values['op'] == t('Save Draft'))
+  {
+    $redirect = array('node/'. $node->nid .'/draft_saved', 'sid='. $sid);
+    return $redirect;
+  }
+
   // Check if this form is sending an email.
   if (isset($node->email) && !$form_values['details']['sid']) {
     $node->email = strip_tags($node->email);
@@ -1658,7 +1741,7 @@
     foreach ($fieldset as $form_key => $value) {
       $cid = webform_get_cid($node, $form_key, $parent);
 
-      if (is_array($value) && $node->webformcomponents[$cid]['type'] == 'fieldset') {
+      if ($cid && is_array($value) && $node->webformcomponents[$cid]['type'] == 'fieldset') {
         _webform_client_form_submit_flatten($node, $value, $form, $cid);
         unset($form[$form_key]);
         unset($form[$cid]);
@@ -1666,7 +1749,10 @@
       else {
         // The order here is significant!
         unset($form[$form_key]);
-        $form[$cid] = $value;
+        if ($cid)
+        {
+          $form[$cid] = $value;
+        }
       }
     }
   }
@@ -1692,6 +1778,25 @@
 }
 
 /**
+ * Prints the confirmation message after a successful draft submission.
+ */
+function _webform_draft_confirmation($nid) {
+  if ($node = node_load($nid)) {
+    if (node_access('view', $node)) {
+      drupal_set_title($node->title);
+      drupal_set_message(t('Draft saved.'));
+      return theme('webform_draft_confirmation', $node);
+    }
+    else {
+      drupal_access_denied();
+    }
+  }
+  else {
+    drupal_set_message(t("No node with the id '%nid' could be found", array('%nid' => $nid)));
+    drupal_not_found();
+  }
+}
+/**
  * Themable function for webform submission confirmation.
  * 
  * @param $node
@@ -1707,6 +1812,18 @@
 }
 
 /**
+ * Themable function for webform draft confirmation
+ */
+function theme_webform_draft_confirmation($node) {
+  $node->body = check_markup('Your draft has been successfully saved. ', $node->format, FALSE);
+  $node->links['webform_back'] = array(
+    'href' => 'node/'. $node->nid,
+    'title' => t('Go back to the form'),
+  );
+  return theme('node', $node, FALSE, TRUE);
+}
+
+/**
  * Filters all special tokens provided by webform, such as %post and %profile.
  */
 function _webform_filtervalues($string, $strict = TRUE) {
@@ -1758,12 +1875,12 @@
   }
 }
 
-function _webform_save_submission($node, $submitted) {
+function _webform_save_submission($node, $submitted, $is_draft = 0) {
   global $user;
 
   $sid = db_next_id('{webform_submissions}_sid');
 
-  db_query("INSERT INTO {webform_submissions} (nid, sid, uid, submitted, remote_addr) "." VALUES (%d, %d, %d, %d, '%s')", $node->nid, $sid, $user->uid, time(), $_SERVER['REMOTE_ADDR']);
+  db_query("INSERT INTO {webform_submissions} (nid, sid, uid, submitted, remote_addr, is_draft) "." VALUES (%d, %d, %d, %d, '%s', %d)", $node->nid, $sid, $user->uid, time(), $_SERVER['REMOTE_ADDR'], $is_draft);
 
   foreach ($submitted as $cid => $value) {
     if (is_array($value)) {
@@ -2029,13 +2146,27 @@
   }
 }
 
-function _webform_update_submission($node, $sid, $submitted) {
+function _webform_update_submission($node, $sid, $submitted, $is_draft = 0) {
   global $user;
   //update submission by first deleting and then inserting it to the database
   db_query("DELETE FROM {webform_submissions} WHERE sid = %d", $sid);
-  db_query("INSERT INTO {webform_submissions} (nid, sid, uid, submitted, remote_addr) "." VALUES (%d, %d, %d, %d, '%s')", $node->nid, $sid, $user->uid, time(), $_SERVER['REMOTE_ADDR']);
+  db_query("INSERT INTO {webform_submissions} (nid, sid, uid, submitted, remote_addr, is_draft) "." VALUES (%d, %d, %d, %d, '%s', %d)", $node->nid, $sid, $user->uid, time(), $_SERVER['REMOTE_ADDR'], $is_draft);
+
   // update the submission data by first removing all this submissions data
-  db_query("DELETE FROM {webform_submitted_data} WHERE sid = %d", $sid);
+  // Save draft: If is draft, only delete data for components submitted, to preserve any data 
+  // from form pages not visited in this draft submission. 
+  if ($is_draft)
+  {
+    $_submitted_cids = array();
+    foreach ($submitted as $cid => $value)
+    {
+      $_submitted_cids[] = $cid;
+    }
+    db_query("DELETE FROM {webform_submitted_data} WHERE sid = %d AND cid IN (" . implode(', ', $_submitted_cids) . ")", $sid);
+  } else {
+    db_query("DELETE FROM {webform_submitted_data} WHERE sid = %d", $sid);
+  }
+  
   // and then re-ad it to the database
   foreach ($submitted as $cid => $value) {
     if (is_array($value)) {
@@ -2051,4 +2182,4 @@
   }
 
   return $sid;
-}
+}
\ No newline at end of file
Index: webform.inc
===================================================================
--- webform.inc	(revision 55)
+++ webform.inc	(revision 59)
@@ -175,6 +175,24 @@
 }
 
 /**
+ * Check if current user has a draft of this webform, and return the sid
+ * @param $nid 
+ * @return 
+ *    Mixed: (int) sid of the form, or (bool) false
+ */
+function _webform_fetch_draft_sid($nid, $uid)
+{
+  $query = 'SELECT sid FROM {webform_submissions} WHERE nid = %d AND uid = %d AND is_draft = 1 ORDER BY submitted DESC';
+  $res = db_query($query, $nid, $uid);
+  $row = db_fetch_array($res);
+  if (isset($row['sid']))
+  {
+    return (int) $row['sid'];
+  }
+  return false;
+}
+
+/**
  * Theme the contents of emails sent by webform.
  * 
  * @param $form_values
