? .svn ? p_219181_editown.patch ? p_226907_draft.patch ? p_239343_access.patch ? p_239751_throttle.patch ? components/.svn ? po/.svn Index: webform.install =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform.install,v retrieving revision 1.14.2.16.2.15 diff -u -p -r1.14.2.16.2.15 webform.install --- webform.install 28 Feb 2008 03:21:40 -0000 1.14.2.16.2.15 +++ webform.install 31 Mar 2008 17:10:23 -0000 @@ -15,7 +15,7 @@ function webform_install() { confirmation text, teaser tinyint not null default '0', submit_text varchar(255) default NULL, - submit_limit tinyint not null default '-1', + submit_limit tinyint not null default '-1', submit_interval int not null default '-1', email varchar(255) default NULL, email_from_name varchar(255) default NULL, @@ -23,6 +23,7 @@ function webform_install() { email_subject varchar(255) default NULL, additional_validate text default NULL, additional_submit text default NULL, + allow_draft tinyint not null default '0', PRIMARY KEY (nid) ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */" ); @@ -48,6 +49,7 @@ function webform_install() { uid int(10) unsigned NOT NULL default '0', submitted int(11) NOT NULL default '0', remote_addr varchar(128), + is_draft tinyint not null default '0', PRIMARY KEY (sid), UNIQUE KEY sid_nid (sid, nid) ) TYPE=MyISAM /*!40100 DEFAULT CHARACTER SET utf8 */" @@ -80,6 +82,7 @@ function webform_install() { email_subject varchar(255) NOT NULL default '', additional_validate text default NULL, additional_submit text default NULL, + allow_draft smallint NOT NULL default '0', PRIMARY KEY (nid) )" ); @@ -105,6 +108,7 @@ function webform_install() { uid integer NOT NULL default '0', submitted integer NOT NULL default '0', remote_addr varchar(128) NOT NULL default '', + is_draft smallint NOT NULL default '0', PRIMARY KEY (sid) )" ); @@ -655,6 +659,25 @@ function webform_update_20() { } /** + * Add draft capabilities. + */ +function webform_update_21() { + $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. * Index: webform.module =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform.module,v retrieving revision 1.113.2.70.2.33 diff -u -p -r1.113.2.70.2.33 webform.module --- webform.module 30 Mar 2008 00:32:14 -0000 1.113.2.70.2.33 +++ webform.module 31 Mar 2008 17:10:23 -0000 @@ -13,7 +13,7 @@ */ /** - * Implemenation of hook_help(). + * Implementation of hook_help(). */ function webform_help($section = "admin/help#webform") { $output = ""; @@ -114,6 +114,13 @@ function webform_menu($may_cache) { 'access' => node_access('view', $node), 'type' => MENU_CALLBACK, ); + $items[]= array( + 'path' => 'node/'. $node->nid .'/draft_saved', + 'title' => t('Draft saved'), + 'callback' => '_webform_draft_confirmation', + 'callback arguments' => array(arg(1)), + 'type' => MENU_CALLBACK, + ); $items[] = array( 'path' => 'node/'. $nid .'/done', 'title' => t('Webform confirmation'), @@ -200,7 +207,7 @@ function webform_menu($may_cache) { 'path' => 'node/'. $nid .'/submission/'. $sid, 'title' => t('Webform submission'), 'callback' => 'drupal_get_form', - 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, FALSE, FALSE), + 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, FALSE, FALSE, FALSE), 'access' => webform_submission_access($node, $submission, 'view'), 'type' => MENU_CALLBACK, ); @@ -208,7 +215,7 @@ function webform_menu($may_cache) { 'path' => 'node/'. $nid .'/submission/'. $sid .'/view', 'title' => t('View'), 'callback' => 'drupal_get_form', - 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, FALSE, FALSE), + 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, FALSE, FALSE, FALSE), 'access' => webform_submission_access($node, $submission, 'view'), 'weight' => 0, 'type' => MENU_DEFAULT_LOCAL_TASK, @@ -217,7 +224,7 @@ function webform_menu($may_cache) { 'path' => 'node/'. $nid .'/submission/'. $sid .'/edit', 'title' => t('Edit'), 'callback' => 'drupal_get_form', - 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, TRUE, FALSE), + 'callback arguments' => array('webform_client_form_'. $node->nid, $node, $submission, TRUE, FALSE, FALSE), 'access' => webform_submission_access($node, $submission, 'edit'), 'weight' => 1, 'type' => MENU_LOCAL_TASK, @@ -259,7 +266,7 @@ function webform_node_info() { } /** - * Implemenation of hook_access(). + * Implementation of hook_access(). */ function webform_access($op, $node) { global $user; @@ -299,13 +306,13 @@ function webform_file_download($file) { } /** - * Implemenation of hook_insert(). + * Implementation of hook_insert(). */ function webform_insert($node) { include_once(drupal_get_path('module', 'webform') .'/webform_components.inc'); // 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. if (isset($node->webform['components']) && !empty($node->webform['components'])) { @@ -318,7 +325,7 @@ function webform_insert($node) { } /** - * Implemenation of hook_update(). + * Implementation of hook_update(). */ function webform_update($node) { // Update the webform by deleting existing data and replacing with the new. @@ -328,7 +335,7 @@ function webform_update($node) { } /** - * Implemenation of hook_delete(). + * Implementation of hook_delete(). */ function webform_delete(&$node) { db_query("DELETE FROM {webform} WHERE nid = %d", $node->nid); @@ -337,7 +344,7 @@ function webform_delete(&$node) { } /** - * Implemenation of hook_load(). + * Implementation of hook_load(). */ function webform_load($node) { $additions = new stdClass(); @@ -616,6 +623,13 @@ function webform_form(&$node, &$param) { '#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', @@ -821,7 +835,7 @@ function webform_view(&$node, $teaser = $preview = FALSE; if ($_POST['op'] == t('Preview')) { - $preview = true; + $preview = TRUE; $additions = webform_load($node); $node->webform['components'] = $additions->webform['components']; } @@ -839,7 +853,18 @@ function webform_view(&$node, $teaser = } } - $output = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled, $preview); + // 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; + } + } + $output = drupal_get_form('webform_client_form_'. $node->nid, $node, $submission, $enabled, $preview, $is_draft); // Remove the surrounding
tag if this is a preview. if ($preview) { @@ -961,14 +986,15 @@ function theme_webform_admin_settings($f * @param $submission * An array of values for the form if we're displaying a result. * @param $enabled - * If displaying a result, specify if form elements are enabled for - * editing. + * If displaying a result, specify if form elements are enabled for editing. + * @param $is_draft + * Optional. Set to TRUE if displaying a draft. * @param $form_values * The current form values of a submission, used in multipage webforms. * Note: The position of this parameter depends on all other parameters being * specified when using drupal_get_form(). */ -function webform_client_form(&$node, $submission, $enabled, $preview, $form_values = NULL) { +function webform_client_form(&$node, $submission, $enabled, $preview, $is_draft = FALSE, $form_values = NULL) { global $user; include_once(drupal_get_path('module', 'webform') .'/webform_components.inc'); webform_load_components(); @@ -983,48 +1009,50 @@ function webform_client_form(&$node, $su // 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); - - $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['submission'] = array( - '#type' => 'value', - '#value' => $submission, - ); - $form['navigation'] = array( - '#prefix' => '
', - '#suffix' => '
', - ); - $form['navigation']['previous'] = array( - '#value' => $previous ? l(t('Previous submission'), 'node/'. $node->nid .'/submission/'. $previous . ($enabled ? '/edit' : '') , array('class' => 'webform-submission-previous'), ($enabled ? 'destination=node/'. $node->nid .'/submission/'. $previous .'/edit' : NULL)) : ''. t('Previous submission') .'', - ); - $form['navigation']['next'] = array( - '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/submission/'. $next . ($enabled ? '/edit' : ''), array('class' => 'webform-submission-next'), ($enabled ? 'destination=node/'. $node->nid .'/submission/'. $next .'/edit' : NULL)) : ''. t('Next submission') .'', - ); + 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 .'/submission/'. $previous . ($enabled ? '/edit' : '') , array('class' => 'webform-submission-previous'), ($enabled ? 'destination=node/'. $node->nid .'/submission/'. $previous .'/edit' : NULL)) : ''. t('Previous submission') .'', + ); + $form['navigation']['next'] = array( + '#value' => $next ? l(t('Next submission'), 'node/'. $node->nid .'/submission/'. $next . ($enabled ? '/edit' : ''), array('class' => 'webform-submission-next'), ($enabled ? 'destination=node/'. $node->nid .'/submission/'. $next .'/edit' : NULL)) : ''. 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']['form'] = array( - '#value' => '
'. t('Form: !form', array('!form' => l($node->title, 'node/'. $node->nid))) .'
', - ); - $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'] = array( + '#type' => 'value', + '#value' => $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']['form'] = array( + '#value' => '
'. t('Form: !form', array('!form' => l($node->title, 'node/'. $node->nid))) .'
', + ); + $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. @@ -1113,12 +1141,21 @@ function webform_client_form(&$node, $su '#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 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->webform['allow_draft'] && $user->uid != 0); $form['submitted'][$component['form_key']]['#weight'] += $microweight; $microweight += 0.001; } @@ -1153,7 +1190,7 @@ function webform_client_form(&$node, $su 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 @@ -1181,6 +1218,26 @@ function _webform_client_form_add_compon } } } + + // 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) { @@ -1241,7 +1298,7 @@ function webform_client_form_submit($for $session_key = 'webform_form_'. $node->nid; // Check for a multi-page form that is not yet complete. - if ($form_values['op'] != (empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text'])) { + if ($form_values['op'] != (empty($node->webform['submit_text']) ? t('Submit') : $node->webform['submit_text']) && $form_values['op'] != t('Save Draft')) { // This is a multi-page form that is not yet complete. // Save the order of the current post into a copied array. $original_post = is_array($_POST['submitted']) ? $_POST['submitted'] : array(); @@ -1316,14 +1373,22 @@ function webform_client_form_submit($for eval("?>". $node->webform['additional_submit']); } + // Check if user is submitting as a draft + $is_draft = ($node->webform['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_submission_insert($node, $form_values['submitted']); + $sid = webform_submission_insert($node, $form_values['submitted'], $is_draft); } else { // Sid was found thus update the existing sid in the datatbase. - $sid = webform_submission_update($node, $form_values['details']['sid'], $form_values['submitted']); + $sid = webform_submission_update($node, $form_values['details']['sid'], $form_values['submitted'], $is_draft); + } + + // If saving a draft, return redirect to confirmation page (no email processing). + if ($node->webform['allow_draft'] && $form_values['op'] == t('Save Draft')) { + return array('node/'. $node->nid .'/draft_saved', 'sid='. $sid); } // Check if this form is sending an email. @@ -1456,7 +1521,7 @@ function _webform_client_form_submit_pro if (is_array($form_values)) { foreach ($form_values as $form_key => $value) { $cid = webform_get_cid($node, $form_key, $parent); - if (is_array($value) && $node->webform['components'][$cid]['type'] == 'fieldset') { + if ($cid && is_array($value) && $node->webform['components'][$cid]['type'] == 'fieldset') { _webform_client_form_submit_process($node, $form_values[$form_key], $cid); } } @@ -1490,7 +1555,9 @@ function _webform_client_form_submit_fla else { // The order here is significant! unset($form[$form_key]); - $form[$cid] = $value; + if ($cid) { + $form[$cid] = $value; + } } } } @@ -1515,7 +1582,6 @@ function _webform_confirmation($node) { * The webform node that has just been submitted. */ function theme_webform_confirmation($node, $sid) { - if (empty($node->webform['confirmation'])) { drupal_set_message(t('Thank you, your submission has been received.')); drupal_goto('node/'. $node->nid); @@ -1530,6 +1596,47 @@ function theme_webform_confirmation($nod } /** + * 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 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 e-mails sent by webform. * * @param $form_values Index: webform_submissions.inc =================================================================== RCS file: /cvs/drupal-contrib/contributions/modules/webform/webform_submissions.inc,v retrieving revision 1.1.2.8 diff -u -p -r1.1.2.8 webform_submissions.inc --- webform_submissions.inc 21 Mar 2008 21:19:57 -0000 1.1.2.8 +++ webform_submissions.inc 31 Mar 2008 17:10:23 -0000 @@ -23,10 +23,25 @@ function webform_submission_access($node } } -function webform_submission_update($node, $sid, $submitted) { - // Update the submission data by first removing all this submissions data. - db_query("DELETE FROM {webform_submitted_data} WHERE sid = %d", $sid); - // Then re-ad it to the database. +function webform_submission_update($node, $sid, $submitted, $is_draft = 0) { + global $user; + + 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) { if (is_array($value)) { $delta = 0; @@ -43,12 +58,12 @@ function webform_submission_update($node return $sid; } -function webform_submission_insert($node, $submitted) { +function webform_submission_insert($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)) {