diff -ru webform/webform.install webform/webform.install
--- webform/webform.install	2009-09-28 15:40:44.000000000 -0400
+++ webform/webform.install	2009-08-19 10:30:18.000000000 -0400
@@ -81,6 +81,12 @@
         'type' => 'text',
         'not null' => TRUE,
       ),
+      'allow_draft' => array (
+         'description' => t('Can this form be saved as a draft?'),
+         'type' => 'tinyint',
+         'not null' => TRUE,
+         'default' => '0',
+      ),
     ),
     'primary key' => array('nid'),
   );
@@ -216,6 +222,12 @@
         'type' => 'varchar',
         'length' => 128,
       ),
+      'is_draft' => array (
+         'description' => t('Is this a draft of the submission?'),
+         'type' => 'tinyint',
+         'not null' => TRUE,
+         'default' => '0',
+      ),
     ),
     'unique keys' => array(
       'sid_nid' => array('sid', 'nid'),
@@ -1016,6 +1028,25 @@
 }
 
 /**
+ * Add the ability to save as draft.
+ */
+function webform_update_6206() {
+  $ret = array();
+  switch ($GLOBALS['db_type']) {
+    case 'mysqli':
+    case 'mysql':
+      $ret[] = update_sql("ALTER TABLE {webform_submissions} ADD is_draft tinyint not null default '0'");
+      $ret[] = update_sql("ALTER TABLE {webform} ADD allow_draft tinyint not null default '0'");
+      break;
+    case 'pgsql':
+      $ret[] = update_sql("ALTER TABLE {webform_submissions} ADD is_draft smallint not null default '0'");
+      $ret[] = update_sql("ALTER TABLE {webform} ADD allow_draft smallint not null default '0'");
+      break;
+  }
+
+  return $ret;
+}
+/**
  * Recursively delete all files and folders in the specified filepath, then
  * delete the containing folder.
  *
diff -ru webform/webform.module webform/webform.module
--- webform/webform.module	2009-09-28 15:41:23.000000000 -0400
+++ webform/webform.module	2009-09-28 15:47:23.000000000 -0400
@@ -87,6 +87,7 @@
   );
 
   // Node page tabs.
+
   $items['node/%webform_menu/done'] = array(
     'title' => 'Webform confirmation',
     'page callback' => '_webform_confirmation',
@@ -214,7 +215,7 @@
     'title' => 'Webform submission',
     'load arguments' => array(1),
     'page callback' => 'webform_client_form_load',
-    'page arguments' => array(1, 3, FALSE, FALSE),
+    'page arguments' => array(1, 3, FALSE, FALSE, FALSE),
     'access callback' => 'webform_submission_access',
     'access arguments' => array(1, 3, 'view'),
     'type' => MENU_CALLBACK,
@@ -223,7 +224,7 @@
     'title' => 'View',
     'load arguments' => array(1),
     'page callback' => 'webform_client_form_load',
-    'page arguments' => array(1, 3, FALSE, FALSE),
+    'page arguments' => array(1, 3, FALSE, FALSE, FALSE),
     'access callback' => 'webform_submission_access',
     'access arguments' => array(1, 3, 'view'),
     'weight' => 0,
@@ -233,7 +234,7 @@
     'title' => 'Edit',
     'load arguments' => array(1),
     'page callback' => 'webform_client_form_load',
-    'page arguments' => array(1, 3, TRUE, FALSE),
+    'page arguments' => array(1, 3, TRUE, FALSE, FALSE),
     'access callback' => 'webform_submission_access',
     'access arguments' => array(1, 3, 'edit'),
     'weight' => 1,
@@ -481,7 +482,7 @@
   }
 
   // Insert the Webform.
-  db_query("INSERT INTO {webform} (nid, confirmation, teaser, submit_text, submit_limit, submit_interval, email, email_from_name, email_from_address, email_subject, additional_validate, additional_submit) VALUES (%d, '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s')", $node->nid, $node->webform['confirmation'], $node->webform['teaser'], $node->webform['submit_text'], $node->webform['submit_limit'], $node->webform['submit_interval'], $node->webform['email'], $node->webform['email_from_name'], $node->webform['email_from_address'], $node->webform['email_subject'], $node->webform['additional_validate'], $node->webform['additional_submit']);
+  db_query("INSERT INTO {webform} (nid, confirmation, teaser, submit_text, submit_limit, submit_interval, email, email_from_name, email_from_address, email_subject, additional_validate, additional_submit, allow_draft) VALUES (%d, '%s', %d, '%s', %d, %d, '%s', '%s', '%s', '%s', '%s', '%s', %d)", $node->nid, $node->webform['confirmation'], $node->webform['teaser'], $node->webform['submit_text'], $node->webform['submit_limit'], $node->webform['submit_interval'], $node->webform['email'], $node->webform['email_from_name'], $node->webform['email_from_address'], $node->webform['email_subject'], $node->webform['additional_validate'], $node->webform['additional_submit'], $node->webform['allow_draft']);
 
   // Insert the components into the database. Used with clone.module.
   if (isset($node->webform['components']) && !empty($node->webform['components'])) {
@@ -843,6 +844,14 @@
     '#default_value' => $node->webform['submit_text'],
     '#description' => t('By default the submit button on this form will have the label <em>Submit</em>. Enter a new title here to override the default.'),
   );
+
+   // Allow save draft
+   $form['webform']['advanced']['allow_draft'] = array(
+     '#type' => 'checkbox',
+     '#title' => t('Allow users to save a draft'),
+     '#default_value' => $node->webform['allow_draft'],
+   );
+  
   if (user_access('use PHP for additional processing')) {
     $form['webform']['advanced']['additional_validate'] = array(
       '#type' => 'textarea',
@@ -1108,11 +1117,22 @@
 
   // Get a count of previous submissions by this user.
   if ($user->uid && (user_access('access own webform submissions') || user_access('access webform results') || user_access('access webform submissions'))) {
-    $submission_count = db_result(db_query('SELECT count(*) FROM {webform_submissions} WHERE nid = %d AND uid = %d', $node->nid, $user->uid));
+    $submission_count = db_result(db_query('SELECT count(*) FROM {webform_submissions} WHERE nid = %d AND uid = %d  AND is_draft = false', $node->nid, $user->uid));
   }
 
+  // Check if this user has a draft for this webform.
+  $is_draft = FALSE;
+  if ($node->webform['allow_draft'] && $user->uid != 0) {
+    // Draft found - display form with draft data for further editing.
+    if ($_draft_sid = _webform_fetch_draft_sid($node->nid, $user->uid)) {
+      include_once(drupal_get_path('module', 'webform') ."/webform_submissions.inc");
+      $submission = webform_get_submission($node->nid, $_draft_sid);
+      $enabled = TRUE;
+      $is_draft = TRUE;
+    }
+  }  
   // Render the form and generate the output.
-  $form = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled, $preview);
+  $form = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled, $preview, $is_draft);
   $output = theme('webform_view', $node, $teaser, $page, $form, $enabled);
 
   // Remove the surrounding <form> tag if this is a preview.
@@ -1392,17 +1412,20 @@
  * @param $enabled
  *   If displaying a result, specify if form elements are enabled for
  *   editing.
+ * @param $is_draft
+ *   Optional. Set to TRUE if displaying a draft.
  */
-function webform_client_form($form_state, &$node, $submission, $enabled = FALSE, $preview = FALSE) {
+function webform_client_form($form_state, &$node, $submission, $enabled = FALSE, $preview = FALSE, $is_draft = FALSE) {
+  global $user;
   module_load_include('inc', 'webform', 'webform_components');
   webform_load_components();
 
-  if (isset($submission->sid)) {
+  if (isset($submission->sid) && !$is_draft) {
     drupal_set_title(t('Submission #@sid', array('@sid' => $submission->sid)));
   }
 
   // Set a header for navigating results.
-  if ($submission && user_access('access webform results')) {
+  if ($submission && user_access('access webform results') && !$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);
 
@@ -1549,13 +1572,24 @@
           '#weight' => 1000,
         );
       }
+      
+       // Save draft button
+       if ($node->webform['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 things in webform order.
     $microweight = 0.001;
     foreach ($component_tree['children'] as $cid => $component) {
       $component_value = isset($form_state['values']['submitted'][$component['form_key']]) ? $form_state['values']['submitted'][$component['form_key']] : NULL;
-      _webform_client_form_add_component($cid, $component, $component_value, $form['submitted'], $form, $submission, $page_num, $enabled);
+      _webform_client_form_add_component($cid, $component, $component_value, $form['submitted'], $form, $submission, $page_num, $enabled, (int) $node->webform['allow_draft'] && $user->uid != 0);
       if (isset($form['submitted'][$component['form_key']])) {
         $form['submitted'][$component['form_key']]['#weight'] += $microweight;
         $microweight += 0.001;
@@ -1592,7 +1626,7 @@
   return $form;
 }
 
-function _webform_client_form_add_component($cid, $component, $component_value, &$parent_fieldset, &$form, $submission, $page_num, $enabled = FALSE) {
+function _webform_client_form_add_component($cid, $component, $component_value, &$parent_fieldset, &$form, $submission, $page_num, $enabled = FALSE, $allow_draft = 0) {
   // Load with submission information if necessary.
   if (!$enabled) {
     // This component is display only.
@@ -1632,6 +1666,22 @@
       }
     }
   }
+  
+  // Save drafts: If the webform is being built during a submission, and draft save
+  // has been requested, prevent validation warning by marking the fields already validated
+  $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']]['#validated'] = true;
+    unset($form['#redirect']);
+  }
+  
   if (isset($component['children']) && is_array($component['children'])) {
     $microweight = 0.001;
     foreach ($component['children'] as $scid => $subcomponent) {
@@ -1667,7 +1717,7 @@
 
   // Check for a multi-page form that is not yet complete.
   $submit_op = empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'];
-  if ($form_state['values']['op'] != $submit_op) {
+  if ($form_state['values']['op'] != $submit_op && $form_state['values']['op'] != t('Save Draft')) {
     // Checkboxes need post-processing to maintain their values.
     _webform_client_form_submit_process($node, $form_state['values']['submitted'], array('select', 'grid'));
 
@@ -1742,10 +1792,13 @@
     eval('?>'. $node->webform['additional_submit']);
   }
 
+  // Check if user is submitting as a draft
+  $is_draft = ($node->webform['allow_draft'] && $form_state['values']['op'] == t('Save Draft')) ? 1 : 0;
+
   // Save the submission to the database.
   if (empty($form_state['values']['details']['sid'])) {
     // No sid was found thus insert it in the datatabase.
-    $form_state['values']['details']['sid'] = webform_submission_insert($node, $form_state['values']['submitted']);
+    $form_state['values']['details']['sid'] = webform_submission_insert($node, $form_state['values']['submitted'], $is_draft);
     $form_state['values']['details']['is_new'] = TRUE;
 
     // Set a cookie including the server's submission time.
@@ -1758,12 +1811,19 @@
   }
   else {
     // Sid was found thus update the existing sid in the datatbase.
-    webform_submission_update($node, $form_state['values']['details']['sid'], $form_state['values']['submitted']);
+    webform_submission_update($node, $form_state['values']['details']['sid'], $form_state['values']['submitted'], $is_draft);
     $form_state['values']['details']['is_new'] = FALSE;
   }
 
   $sid = $form_state['values']['details']['sid'];
 
+  // If saving a draft, return redirect to confirmation page (no email processing).
+  if ($is_draft) {
+    drupal_set_message(t('Draft saved.'));
+    $form_state['redirect'] = false;
+    return;
+  }
+
   // Check if this form is sending an email.
   if ((!empty($node->webform['email']) || !empty($node->webform['additional_emails'])) && $form_state['values']['details']['is_new']) {
 
@@ -2001,6 +2061,20 @@
 }
 
 /**
+ * Check if current user has a draft of this webform, and return the sid.
+ */
+function _webform_fetch_draft_sid($nid, $uid) {
+  $query = 'SELECT * 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;
+}
+
+/**
  * Prepare to theme the fields portion of the e-mails sent by webform.
  *
  * This function calls itself recursively to maintain the tree structure of
diff -ru webform/webform_report.inc webform/webform_report.inc
--- webform/webform_report.inc	2009-09-28 15:40:44.000000000 -0400
+++ webform/webform_report.inc	2009-08-19 10:30:18.000000000 -0400
@@ -124,8 +124,11 @@
 
   $rows = array();
   foreach ($submissions as $sid => $submission) {
+    $draftstring = "";
+    if ($submission->is_draft)
+       $draftstring = " (draft)";
     $row = array(
-      $sid,
+      $sid.$draftstring,
       format_date($submission->submitted, 'small'),
     );
     if (user_access('access webform results')) {
diff -ru webform/webform_submissions.inc webform/webform_submissions.inc
--- webform/webform_submissions.inc	2009-09-28 15:40:44.000000000 -0400
+++ webform/webform_submissions.inc	2009-08-19 10:30:18.000000000 -0400
@@ -9,9 +9,24 @@
  * @author Nathan Haug <nate@lullabot.com>
  */
 
-function webform_submission_update($node, $sid, $submitted) {
+function webform_submission_update($node, $sid, $submitted, $is_draft = 0) {
+  global $user;
   // Update the submission data by first removing all this submissions data.
-  db_query('DELETE FROM {webform_submitted_data} WHERE sid = %d', $sid);
+  db_query("DELETE FROM {webform_submissions} WHERE sid = %d", $sid);
+  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);
+
+  // If is draft, only delete data for components submitted, to
+  // preserve any data from form pages not visited in this 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);
+  }
+
   // Then re-add it to the database.
   foreach ($submitted as $cid => $value) {
     // Don't save pagebreaks as submitted data.
@@ -34,10 +49,10 @@
   return $sid;
 }
 
-function webform_submission_insert($node, $submitted) {
+function webform_submission_insert($node, $submitted, $is_draft = 0) {
   global $user;
 
-  $result = db_query("INSERT INTO {webform_submissions} (nid, uid, submitted, remote_addr) VALUES (%d, %d, %d, '%s')", $node->nid, $user->uid, time(), ip_address());
+  $result = db_query("INSERT INTO {webform_submissions} (nid, uid, submitted, remote_addr, is_draft) "." VALUES (%d, %d, %d, '%s', %d)", $node->nid, $user->uid, time(), ip_address(), $is_draft);
 
   $sid = db_last_insert_id('webform_submissions', 'sid');
 
@@ -179,6 +194,7 @@
       $submissions[$row->sid]->uid = $row->uid;
       $submissions[$row->sid]->name = $row->name;
       $submissions[$row->sid]->status = $row->status;
+      $submissions[$row->sid]->is_draft = $row->is_draft;
     }
     $submissions[$row->sid]->data[$row->cid]['value'][$row->no] = $row->data;
     $previous = $row->sid;
@@ -267,7 +283,7 @@
   $query = 'SELECT count(*) '.
            'FROM {webform_submissions} '.
            "WHERE (( 0 = %d AND remote_addr = '%s') OR (uid > 0 AND uid = %d)) ".
-           'AND submitted > %d AND nid = %d';
+           'AND submitted > %d AND nid = %d AND is_draft = false';
 
   // Fetch all the entries from the database within the submit interval with this username and IP.
   $num_submissions_database = db_result(db_query($query, $user->uid, ip_address(), $user->uid, ($node->webform['submit_interval'] != -1) ? (time() - $node->webform['submit_interval']) : $node->webform['submit_interval'], $node->nid));
