'hidden', '#value' => $step, ); // This is purely cosmetic. We just say which page we're on. This // probably wants removing (or at least pretty-ing up!) $form['display'] = array( '#type' => 'markup', '#value' => "Page $step", ); // These are *very* important. 'multistep' tells Drupal to store // extra information between form steps. 'tree' tells drupal to // preserve the multi-dimensional form information we're using. // 'tree' isn't completely necessary in this example, but for // big/complex forms, it's pretty obligatory! $form['#multistep'] = TRUE; $form['#tree'] = TRUE; // This is where we actually produce the form on each step of the // wizard. Naturally, it's possible to have as many steps as needed // here. It's not obligatory, but useful to separate each page in // a separate key in for $form. switch($step) { case 1: $form['pageone']['itemone'] = array( '#type' => 'textfield', '#default_value' => 'page 1 item', ); break; case 2: $form['pagetwo']['itemtwo'] = array( '#type' => 'textfield', '#default_value' => 'page 2 item', ); break; case 3: $form['pagethree']['itemthree'] = array( '#type' => 'textfield', '#default_value' => 'page 3 item', ); break; } // This is important. If we're on the final step, // we tell drupal to use the normal redirect functionality. // That means the browser goes to whatever page after the // final submit. All previous steps don't redirect, so just // post back to this form. if($step == WIZARD_FINAL_STEP) { $form['#redirect'] = NULL; } else { $form['#redirect'] = FALSE; } // Now insert any previous form values... We need to remember // what has gone on previously. It's possible this could go into // $_SESSION instead, which is probably a good idea if there's // a lot of data captured in the wizard. This code only re-inserts // multi-dimensional form values (which are used in the switch/case // above). This leaves all non multi-dimensional out of the // post/repost cycle (saving a bit of mess in the form) if(!is_null($form_values)) { foreach (array_keys($form_values) as $pagenum) { if(is_array($form_values[$pagenum])) { foreach ($form_values[$pagenum] as $key => $value) { // Don't overwrite any form elements that are set above. // This could happen if the user presses "back"; it basically // makes elements disappear, which we don't want! if(!isset($form[$pagenum][$key])) { $form[$pagenum][$key] = array( '#type' => 'hidden', '#value' => $value, ); } } } } } // Insert a button. We use a 'submit', which causes the form // to be sent to Drupal, which puts it through validation AND // submission routines. Use a 'button' to avoid the submit // phase (for all but the last page of your form!). The // wizard uses the submit phase to provide the 'cancel' facility, // so be careful. if($step > 1) { $form['previous'] = array( '#type' => 'submit', '#value' => t(WIZARD_PREVIOUS), ); } $form['next'] = array( '#type' => 'submit', '#value' => t(WIZARD_NEXT), ); $form['cancel'] = array( '#type' => 'submit', '#value' => t(WIZARD_CANCEL), ); return $form; } function _my_module_wizard_validate($form_id, $form_values) { // If the user presses 'cancel' or 'back', we should do no further // validation. Also, if they press 'cancel' we should actually // goto the finish page, because the 'submit' stage won't be called // if the user hasn't filled in one of the mandatory fields. In fact, // in that case, Drupal's built in form validation will have set // errors that we don't need to show the user. if($form_values['op'] == t(WIZARD_CANCEL)) { // Clear errors from Drupal's built in validation... drupal_get_messages('error'); // Tell the user we've cancelled drupal_set_message('Wizard cancelled.'); // Now go to the 'finish page' drupal_goto(WIZARD_FINISH_REDIRECT); return; } else if($form_values['op'] == t(WIZARD_PREVIOUS)) { // Clear messages, and do no further validation drupal_get_messages('error'); return; } // Do whatever validation here. It's probably a good idea to do a // switch/case on the wizard step. It may be a good idea to validate // everything on each call, as that will catch anyone hacking the // form with directly injected form posts, although at slightly // more processing. } function _my_module_wizard_submit($form_id, $form_values) { // If the user presses 'back' or 'cancel' don't do any submission work... if($form_values['op'] == t(WIZARD_PREVIOUS) || $form_values['op'] == t(WIZARD_CANCEL)) { // Don't do any submission work here, it's not relevant return FALSE; } // Process the form values. In this example, we only do something // when we reach the end of the wizard. Our example just displays // the form values on whatever page we redirect to. if(isset($form_values['step']) && $form_values['step'] == WIZARD_FINAL_STEP) { if(!is_null($form_values)) { foreach (array_keys($form_values) as $pagenum) { if(is_array($form_values[$pagenum])) { foreach ($form_values[$pagenum] as $key => $value) { drupal_set_message("Got page $pagenum key $key = $value"); } } } } // Now send the browser to the 'finish page'. return WIZARD_FINISH_REDIRECT; } // If we haven't processed the form and completed fully, we have // to return FALSE so that Drupal redisplays our form. return FALSE; } ?>