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
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' => '
', + '#suffix' => '
', + ); + $form['navigation']['previous'] = array( + '#value' => $previous ? l(t('Previous submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $previous, array('class' => 'webform-submission-previous')) : ''. t('Previous submission') .'', + ); + $form['navigation']['next'] = array( + '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $next, array('class' => 'webform-submission-next')) : ''. t('Next submission') .'', + ); - $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' => '
', - '#suffix' => '
', - ); - $form['navigation']['previous'] = array( - '#value' => $previous ? l(t('Previous submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $previous, array('class' => 'webform-submission-previous')) : ''. t('Previous submission') .'', - ); - $form['navigation']['next'] = array( - '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/results/'. ($enabled ? 'edit' : 'view') .'/'. $next, array('class' => 'webform-submission-next')) : ''. t('Next submission') .'', - ); - - $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' => '
'. t('Submitted by !name', array('!name' => theme('username', $account))) .'
', - ); - $form['submission_info']['time'] = array( - '#value' => '
'. format_date($submission['submitted'], 'large') .'
', - ); - $form['submission_info']['ip_address'] = array( - '#value' => '
'. $submission['remote_addr'] .'
', - ); + $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' => '
'. t('Submitted by !name', array('!name' => theme('username', $account))) .'
', + ); + $form['submission_info']['time'] = array( + '#value' => '
'. format_date($submission['submitted'], 'large') .'
', + ); + $form['submission_info']['ip_address'] = array( + '#value' => '
'. $submission['remote_addr'] .'
', + ); + } } // 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