Index: webform.install =================================================================== --- webform.install (revision 13791) +++ webform.install (working copy) @@ -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,24 @@ } /** + * 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; + } + +} +/** * Recursively delete all files and folders in the specified filepath, then * delete the containing folder. * Index: webform.module =================================================================== --- webform.module (revision 13791) +++ webform.module (working copy) @@ -87,6 +87,17 @@ ); // Node page tabs. + + $items['node/%webform_menu/draftsaved']= array( + 'title' => t('Draft saved'), + 'load arguments' => array(1), + 'page callback' => '_webform_draft_confirmation', + 'page arguments' => array(1, 3), + 'access callback' => 'webform_submission_access', + 'access arguments' => array(1, 3, 'view'), + 'type' => MENU_CALLBACK, + ); + $items['node/%webform_menu/done'] = array( 'title' => 'Webform confirmation', 'page callback' => '_webform_confirmation', @@ -214,7 +225,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 +234,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 +244,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 +492,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 +854,14 @@ '#default_value' => $node->webform['submit_text'], '#description' => t('By default the submit button on this form will have the label Submit. 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 +1127,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
tag if this is a preview. @@ -1392,17 +1422,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 +1582,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 +1636,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 +1676,26 @@ } } } + + // Save drafts: 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 (isset($component['children']) && is_array($component['children'])) { $microweight = 0.001; foreach ($component['children'] as $scid => $subcomponent) { @@ -1667,7 +1731,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 +1806,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 +1825,18 @@ } 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 ($node->webform['allow_draft'] && $form_state['values']['op'] == t('Save Draft')) { + return array('node/'. $node->nid .'/draft_saved', 'sid='. $sid); + } + // 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 +2074,48 @@ } /** + * 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 { + return drupal_access_denied(); + } + } +} + +/** + * 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); +} + +/** + * 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 Index: webform_report.inc =================================================================== --- webform_report.inc (revision 13791) +++ webform_report.inc (working copy) @@ -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')) { Index: webform_submissions.inc =================================================================== --- webform_submissions.inc (revision 13791) +++ webform_submissions.inc (working copy) @@ -9,9 +9,24 @@ * @author Nathan Haug */ -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));