TRUE with these forms as that validation * cannot be skipped for the CANCEL button. * * @param $form_info * An array of form info. @todo document the array. * @param $step * The current form step. * @param &$form_state * The form state array; this is a reference so the caller can get back * whatever information the form(s) involved left for it. */ function ctools_wizard_multistep_form($form_info, $step, &$form_state) { // allow order array to be optional if (empty($form_info['order'])) { foreach($form_info['forms'] as $step_id => $params) { $form_info['order'][$step_id] = $params['title']; } } if (!isset($step)) { $keys = array_keys($form_info['order']); $step = array_shift($keys); } ctools_wizard_defaults($form_info); // If automated caching is enabled, ensure that everything is as it // should be. if (!empty($form_info['auto cache'])) { // If the cache mechanism hasn't been set, default to the simple // mechanism and use the wizard ID to ensure uniqueness so cache // objects don't stomp on each other. if (!isset($form_info['cache mechanism'])) { $form_info['cache mechanism'] = 'simple::wizard::' . $form_info['id']; } // If not set, default the cache key to the wizard ID. This is often // a unique ID of the object being edited or similar. if (!isset($form_info['cache key'])) { $form_info['cache key'] = $form_info['id']; } // If not set, default the cache location to storage. This is often // somnething like 'conf'. if (!isset($form_info['cache location'])) { $form_info['cache location'] = 'storage'; } // If absolutely nothing was set for the cache area to work on if (!isset($form_state[$form_info['cache location']])) { ctools_include('cache'); $form_state[$form_info['cache location']] = ctools_cache_get($form_info['cache mechanism'], $form_info['cache key']); } } $form_state['step'] = $step; $form_state['form_info'] = $form_info; // Ensure we have form information for the current step. if (!isset($form_info['forms'][$step])) { return; } // Ensure that whatever include file(s) were requested by the form info are // actually included. $info = $form_info['forms'][$step]; if (!empty($info['include'])) { if (is_array($info['include'])) { foreach ($info['include'] as $file) { require_once DRUPAL_ROOT . '/' . $file; } } else { require_once DRUPAL_ROOT . '/' . $info['include']; } } // This tells drupal_build_form to apply our wrapper to the form. It // will give it buttons and the like. $form_state['wrapper_callback'] = 'ctools_wizard_wrapper'; if (!isset($form_state['rerender'])) { $form_state['rerender'] = FALSE; } $form_state['no_redirect'] = TRUE; $output = drupal_build_form($info['form id'], $form_state); if (empty($form_state['executed']) || !empty($form_state['rerender'])) { if (empty($form_state['title']) && !empty($info['title'])) { $form_state['title'] = $info['title']; } if (!empty($form_state['ajax render'])) { // Any include files should already be included by this point: return $form_state['ajax render']($form_state, $output); } // Automatically use the modal tool if set to true. if (!empty($form_state['modal']) && empty($form_state['modal return'])) { ctools_include('modal'); // This overwrites any previous commands. $form_state['commands'] = ctools_modal_form_render($form_state, $output); } } if (!empty($form_state['executed'])) { // We use the plugins get_function format because it's powerful and // not limited to just functions. ctools_include('plugins'); if (isset($form_state['clicked_button']['#wizard type'])) { $type = $form_state['clicked_button']['#wizard type']; // If we have a callback depending upon the type of button that was // clicked, call it. if ($function = ctools_plugin_get_function($form_info, "$type callback")) { $function($form_state); } // If auto-caching is on, we need to write the cache on next and // clear the cache on finish. if (!empty($form_info['auto cache'])) { if ($type == 'next') { ctools_include('cache'); ctools_cache_set($form_info['cache mechanism'], $form_info['cache key'], $form_state[$form_info['cache location']]); } elseif ($type == 'finish') { ctools_include('cache'); ctools_cache_clear($form_info['cache mechanism'], $form_info['cache key']); } } // Set a couple of niceties: if ($type == 'finish') { $form_state['complete'] = TRUE; } if ($type == 'cancel') { $form_state['cancel'] = TRUE; } // If the modal is in use, some special code for it: if (!empty($form_state['modal']) && empty($form_state['modal return'])) { if ($type != 'next') { // Automatically dismiss the modal if we're not going to another form. ctools_include('modal'); $form_state['commands'][] = ctools_modal_command_dismiss(); } } } if (empty($form_state['ajax'])) { // redirect, if one is set. if ($form_state['redirect']) { call_user_func_array('drupal_goto', $form_state['redirect']); } } else if (isset($form_state['ajax next'])) { // Clear a few items off the form state so we don't double post: $next = $form_state['ajax next']; unset($form_state['ajax next']); unset($form_state['executed']); unset($form_state['post']); unset($form_state['next']); return ctools_wizard_multistep_form($form_info, $next, $form_state); } // If the callbacks wanted to do something besides go to the next form, // it needs to have set $form_state['commands'] with something that can // be rendered. } // Render ajax commands if we have any. if (isset($form_state['ajax']) && isset($form_state['commands']) && empty($form_state['modal return'])) { return ajax_render($form_state['commands']); } // Otherwise, return the output. return $output; } /** * Provide a wrapper around another form for adding multi-step information. */ function ctools_wizard_wrapper($form, &$form_state) { $form_info = &$form_state['form_info']; $info = $form_info['forms'][$form_state['step']]; // Determine the next form from this step. // Create a form trail if we're supposed to have one. $trail = array(); $previous = TRUE; foreach ($form_info['order'] as $id => $title) { if ($id == $form_state['step']) { $previous = FALSE; $class = 'wizard-trail-current'; } elseif ($previous) { $not_first = TRUE; $class = 'wizard-trail-previous'; $form_state['previous'] = $id; } else { $class = 'wizard-trail-next'; if (!isset($form_state['next'])) { $form_state['next'] = $id; } if (empty($form_info['show trail'])) { break; } } if (!empty($form_info['show trail'])) { if (!empty($form_info['free trail'])) { // ctools_wizard_get_path() returns results suitable for #redirect // which can only be directly used in drupal_goto. We have to futz // with it. $path = ctools_wizard_get_path($form_info, $id); $options = array(); if (!empty($path[1])) { $options['query'] = $path[1]; } if (!empty($path[2])) { $options['fragment'] = $path[2]; } $title = l($title, $path[0], $options); } $trail[] = '' . $title . ''; } } // Display the trail if instructed to do so. if (!empty($form_info['show trail'])) { ctools_add_css('wizard'); $form['ctools_trail'] = array( '#markup' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), array('trail' => $trail)), '#weight' => -1000, ); } if (empty($form_info['no buttons'])) { // Ensure buttons stay on the bottom. $form['buttons'] = array( '#prefix' => '