Steps to reproduce:

1. Create a form with two controls separated by a page break. Have the second control only display if the first is set to a certain value.
2. View the form.
3. Put a value in the first form control that would cause the second form control to be hidden.
4. Click next page.

What happens: The form submits.
Expected behavior: A page is shown with buttons for previous page and submit.

This problem was also observed in 7.x-3.18, so it was not introduced by the new conditionals system.

UPDATE:
There are a number of closely-related problem:

  1. A form without preview ending in a hidden page unexpectedly commits (the main issue).
  2. A form WITH preview generates the wrong label for the Prev button when clicking back from the preview to a hidden page, and raises a PHP notice.
  3. A form WITH preview on the preview page goes to the wrong page when you click Prev. It does not skip a hidden page the way it would if it weren't on the preview page.
  4. #2220679: Cannot submit if the webform ends in a page break
  5. A form with page 1 hidden cannot be back-up to when no other possible previous page is available.

Comments

quicksketch’s picture

So the last page would contain nothing at all but a submit button? I think ending the form immediately *is* the intended behavior. If you want to have a confirmation page before the form is completed, you can add another page at the end of the form that exists regardless of the conditionals.

liam morland’s picture

The problem is that the button says "next page" but the behavior is to submit.

I see the problem: doing it as I suggest means an extra click and a blank page. Perhaps it could behave as I describe above when JS is off. When it is on, the next page button turns into a submit button when the conditions are such that it will behave as one.

quicksketch’s picture

But we run into quite a few problems with the approach, we don't know if the next page is going to be required or not, especially since conditionals from earlier pages could determine if the next pages are necessary. We'd have to re-implement the entire conditional system for all pages in JavaScript just to change the button label. I'm not sure the overhead here is feasible.

liam morland’s picture

The JS on a given page would only have to check fields on that page since it could be primed with the results from previous pages. Still, it would mean implementing the conditionals in JS or using Ajax to access server-side conditional calculations. I agree that this is a problem.

The key issue is the mis-match between button label and behavior. Particularly on forms with several pages, users may expect to be able to go back and forth between pages without submitting.

liam morland’s picture

liam morland’s picture

Another solution would be to make the default text for "Next Page" be "Submit". It is less of a surprise for a Submit button to take the user to another page of the form vs. a Next Page button submitting. As well, it is still correct: The user is submitting the form on that page, just not the entire form.

quicksketch’s picture

This whole thing is going to get a lot worse when #258696: Multipage form - Page n of n (progress or percentage complete) is added. Considering you may have a 4 page form but 2 of them may get skipped, it's not going to help much what the label is if the progress bar tells you you're on step 2 of 4 but then it suddenly submits.

I think generally the easiest and most common solution to this problem is having a confirmation/summary page (#435232: Summary/review/preview page before final submission in multistep forms). It's not a problem suddenly skipping steps as long as there is some final warning before the form is completely submitted.

liam morland’s picture

I agree. I think it would be OK to skip from page 2/4 to 4/4, even if page 4 is blank except for a submit button.

I've seen many multi-page forms, such as my bank, where page 4/4 is the confirmation page after submitting. Page 3/4 is the page with the submit button. I think they do this to make sure people actually submit instead of seeing 3/3, in this case, and thinking they are done when in fact they still have to submit.

Another approach is to use collapsible fieldsets which automatically open and close as one moves through the form. Greyhound.com is like this for buying bus tickets. It works much like a multi-page form including Previous and Next, but one can at any point go back and change a previous "page" (fieldset).

dragon123’s picture

Version: 7.x-4.x-dev » 6.x-3.18

So is there a permanent solution for this behaviour of webform. Need help...

liam morland’s picture

Version: 6.x-3.18 » 7.x-4.x-dev
liam morland’s picture

danchadwick’s picture

I think the right fix for this is to force a preview when this situation occurs. I'm investigating to see if this can be implemented reasonably. In this way, at least the unexpected page is a standard webform page.

danchadwick’s picture

Issue summary: View changes
liam morland’s picture

@#12: That sounds like a good solution.

danchadwick’s picture

Issue summary: View changes

  • DanChadwick committed 4f413c4 on 7.x-4.x
    Issue #1781490 by DanChadwick: Fixed hidden conditional pages causing...
  • DanChadwick committed 445ccd6 on 8.x-4.x
    Issue #1781490 by DanChadwick: Fixed hidden conditional pages causing...
danchadwick’s picture

Status: Active » Fixed
StatusFileSize
new10.31 KB

I have a rather big patch to solve the issues around navigating conditional pages. The theory of operation is:

1) Rather than checking the node's webform['preview'], the form stores whether a preview page should be displayed.
2) Rather than iterating over the actual components and trying to handle all the special cases of preview (regular or forced) and backing up onto a hidden page 1, bogus faux components are added before and after the actual components. The regularizes the loop, treating the special cases similarly to regular components.

I have tested this with hidden pages as the first, a middle, and the last page. I have tested with with and without preview. I used the progress bar to help see where webform thinks it is. Still, this should receive wider testing before a stable release.

Committed to 7.x-4.x and 8.x.

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.

zuernbernhard’s picture

Hello Dan,

i had to comment out 3 Lines from your Patch because a Multistep Webform (multiple Pages) always showed up "Preview" when the user entered the last step-page even though preview was deactivatet in the webforms config.

/**
 * Handle the processing of pages and conditional logic.
 */
function webform_client_form_pages($form, &$form_state) {
  $node = node_load($form_state['values']['details']['nid']);

  // Multistep forms may not have any components on the first page.
  if (!isset($form_state['values']['submitted'])) {
    $form_state['values']['submitted'] = array();
  }

  // Move special settings to storage.
  if (isset($form_state['webform']['component_tree'])) {
    $form_state['storage']['component_tree'] = $form_state['webform']['component_tree'];
    $form_state['storage']['page_count'] = $form_state['webform']['page_count'];
    $form_state['storage']['page_num'] = $form_state['webform']['page_num'];
    $form_state['storage']['preview'] = $form_state['webform']['preview'];
  }

  // Flatten trees within the submission.
  $form_state['values']['submitted'] = _webform_client_form_submit_flatten($node, $form_state['values']['submitted']);

  // Perform post processing by components.
  _webform_client_form_submit_process($node, $form_state['values']['submitted']);

  // Assume the form is completed unless the page logic says otherwise.
  $form_state['webform_completed'] = TRUE;

  // Check for a multi-page form that is not yet complete.
  $submit_op = !empty($form['actions']['submit']['#value']) ? $form['actions']['submit']['#value'] : t('Submit');
  $draft_op = !empty($form['actions']['draft']['#value']) ? $form['actions']['draft']['#value'] : t('Save Draft');
  if (!in_array($form_state['values']['op'], array($submit_op, $draft_op, '__AUTOSAVE__'))) {
    // Store values from the current page in the form state storage.
    if (is_array($form_state['values']['submitted'])) {
      foreach ($form_state['values']['submitted'] as $key => $val) {
        $form_state['storage']['submitted'][$key] = $val;
      }
    }

    // Update form state values with those from storage.
    if (isset($form_state['storage']['submitted'])) {
      foreach ($form_state['storage']['submitted'] as $key => $val) {
        $form_state['values']['submitted'][$key] = $val;
      }
    }

    // Set the page number.
    if (!isset($form_state['storage']['page_num'])) {
      $form_state['storage']['page_num'] = 1;
    }
    if (end($form_state['clicked_button']['#parents']) == 'next') {
      $forward = 1;
    }
    elseif (end($form_state['clicked_button']['#parents']) == 'previous') {
      $forward = 0;
    }
    $current_page = $form_state['storage']['page_num'];

    if (isset($forward)) {
      // Find the
      //    1) previous/next non-empty page, or
      //    2) the preview page, or
      //    3) the preview page, forcing its display if the form would unexpectedly submit, or
      //    4) page 1 even if empty, if no other previous page would be shown
      $preview_page_num = $form_state['storage']['page_count'] + (int)!$form_state['webform']['preview'];
      $page_zero = array(
        array(
          'type' => 'faux',
          'page_num' => 0,
        ),
        array(
          'type' => 'pagebreak',
          'page_num' => 1,
        ),
      );
      $page_preview = array(
        array(
          'type' => 'pagebreak',
          'page_num' => $preview_page_num,
        ),
         array(
          'type' => 'faux',
          'page_num' => $preview_page_num,
        ),
      );
      $components = array_merge($page_zero, $node->webform['components'], $page_preview);
      if (!$forward) {
        $components = array_reverse($components);
      }
      foreach ($components as $component) {
        if ($component['type'] == 'pagebreak') {
          if ($forward
               ? ($component['page_num'] > $form_state['storage']['page_num'])
               : ($component['page_num'] <= $form_state['storage']['page_num'])){
            $previous_pagebreak = $component;
          }
        }
        elseif (isset($previous_pagebreak)) {
          // If a component is shown on this page, advance to this page.
          $page_num = $previous_pagebreak['page_num'] + $forward - 1;
          if ($component['page_num'] == $page_num &&
              ($component['type'] == 'faux' || _webform_client_form_rule_check($node, $component, $page_num, $form_state['values']['submitted']) == WEBFORM_CONDITIONAL_INCLUDE)) {
            if ($component['type'] == 'faux') {
              if ($forward) {
                if (!$form_state['webform']['preview']) {
                  // Force a preview to avert an unintended submission via Next.
//                  $form_state['webform']['preview'] = TRUE;
//                  $form_state['storage']['preview'] = TRUE;
//                  $form_state['storage']['page_count']++;
                }
              }
              else {
                $page_num = max (1, $page_num);
              }
            }
            $form_state['storage']['page_num'] = $page_num;
            break; // LOOP EXIT
          }
        }
      }

    }

    // The form is done if the page number is greater than the page count.
    $form_state['webform_completed'] = $form_state['storage']['page_num'] > $form_state['storage']['page_count'];
  }

  // Merge any stored submission data for multistep forms.
  if (isset($form_state['storage']['submitted'])) {
    $original_values = is_array($form_state['values']['submitted']) ? $form_state['values']['submitted'] : array();
    unset($form_state['values']['submitted']);

    foreach ($form_state['storage']['submitted'] as $key => $val) {
      $form_state['values']['submitted'][$key] = $val;
    }
    foreach ($original_values as $key => $val) {
      $form_state['values']['submitted'][$key] = $val;
    }

    // Remove the variable so it doesn't show up in the additional processing.
    unset($original_values);
  }

  // Inform the submit handlers that a draft will be saved.
  $form_state['save_draft'] = in_array($form_state['values']['op'], array($draft_op, '__AUTOSAVE__')) ||
                              ($node->webform['auto_save'] && !$form_state['webform_completed'] && user_is_logged_in());

  // Determine what we need to do on the next page.
  if (!empty($form_state['save_draft']) || !$form_state['webform_completed']) {
    // Rebuild the form and display the current (on drafts) or next page.
    $form_state['rebuild'] = TRUE;
  }
  else {
    // Remove the form state storage now that we're done with the pages.
    $form_state['rebuild'] = FALSE;
    unset($form_state['storage']);
  }
}
zuernbernhard’s picture

StatusFileSize
new85.08 KB

At the moment we use a vanilla Drupal 7.32 with Webform 4.1. I installed webform_share a few minutes ago to export the webform if anybody needs ressources for testing.

zuernbernhard’s picture

Status: Closed (fixed) » Active

Reopened (see last 2 comments)

danchadwick’s picture

@zuernBernhard -- When conditions hide the last page, the preview page should be shown to avoid an unintentional submit of the form. Are you saying that when the last page IS shown, you get a preview anyhow?

danchadwick’s picture

Status: Active » Closed (fixed)

Closing as I believe my answer #22 resolves the issue.

thechanceg’s picture

Short of hacking the module, is there any way to disable this feature if skipping a page and submitting the form is the intended action?

danchadwick’s picture

Or ensure the last page isn't blank.