| [ Index ] |
PHP Cross Reference of Drupal 6 (yi-drupal) |
[Summary view] [Print] [Text view]
1 <?php 2 // $Id: wizard.inc,v 1.12.2.9 2010/09/10 18:25:13 merlinofchaos Exp $ 3 4 /** 5 * @file 6 * CTools' multi-step form wizard tool. 7 * 8 * This tool enables the creation of multi-step forms that go from one 9 * form to another. The forms themselves can allow branching if they 10 * like, and there are a number of configurable options to how 11 * the wizard operates. 12 * 13 * The wizard can also be friendly to ajax forms, such as when used 14 * with the modal tool. 15 * 16 * The wizard provides callbacks throughout the process, allowing the 17 * owner to control the flow. The general flow of what happens is: 18 * 19 * Generate a form 20 * submit a form 21 * based upon button clicked, 'finished', 'next form', 'cancel' or 'return'. 22 * 23 * Each action has its own callback, so cached objects can be modifed and or 24 * turned into real objects. Each callback can make decisions about where to 25 * go next if it wishes to override the default flow. 26 */ 27 28 /** 29 * Display a multi-step form. 30 * 31 * Aside from the addition of the $form_info which contains an array of 32 * information and configuration so the multi-step wizard can do its thing, 33 * this function works a lot like ctools_build_form. 34 * 35 * Remember that the form builders for this form will receive 36 * &$form, &$form_state, NOT just &$form_state and no additional args. 37 * 38 * Do NOT use #required => TRUE with these forms as that validation 39 * cannot be skipped for the CANCEL button. 40 * 41 * @param $form_info 42 * An array of form info. @todo document the array. 43 * @param $step 44 * The current form step. 45 * @param &$form_state 46 * The form state array; this is a reference so the caller can get back 47 * whatever information the form(s) involved left for it. 48 */ 49 function ctools_wizard_multistep_form($form_info, $step, &$form_state) { 50 // allow order array to be optional 51 if (empty($form_info['order'])) { 52 foreach($form_info['forms'] as $step_id => $params) { 53 $form_info['order'][$step_id] = $params['title']; 54 } 55 } 56 57 if (!isset($step)) { 58 $keys = array_keys($form_info['order']); 59 $step = array_shift($keys); 60 } 61 62 ctools_wizard_defaults($form_info); 63 64 $form_state['step'] = $step; 65 $form_state['form_info'] = $form_info; 66 67 // Ensure we have form information for the current step. 68 if (!isset($form_info['forms'][$step])) { 69 return; 70 } 71 72 // Ensure that whatever include file(s) were requested by the form info are 73 // actually included. 74 $info = $form_info['forms'][$step]; 75 76 if (!empty($info['include'])) { 77 if (is_array($info['include'])) { 78 foreach ($info['include'] as $file) { 79 require_once './' . $file; 80 } 81 } 82 else { 83 require_once './' . $info['include']; 84 } 85 } 86 87 // This tells ctools_build_form to apply our wrapper to the form. It 88 // will give it buttons and the like. 89 $form_state['wrapper callback'] = 'ctools_wizard_wrapper'; 90 if (!isset($form_state['rerender'])) { 91 $form_state['rerender'] = FALSE; 92 } 93 94 $form_state['no_redirect'] = TRUE; 95 96 ctools_include('form'); 97 $output = ctools_build_form($info['form id'], $form_state); 98 99 if (empty($form_state['executed']) || !empty($form_state['rerender'])) { 100 if (empty($form_state['title']) && !empty($info['title'])) { 101 $form_state['title'] = $info['title']; 102 } 103 104 if (!empty($form_state['ajax render'])) { 105 // Any include files should already be included by this point: 106 return $form_state['ajax render']($form_state, $output); 107 } 108 109 // Automatically use the modal tool if set to true. 110 if (!empty($form_state['modal']) && empty($form_state['modal return'])) { 111 ctools_include('modal'); 112 113 // This overwrites any previous commands. 114 $form_state['commands'] = ctools_modal_form_render($form_state, $output); 115 } 116 } 117 118 if (!empty($form_state['executed'])) { 119 // We use the plugins get_function format because it's powerful and 120 // not limited to just functions. 121 ctools_include('plugins'); 122 123 if (isset($form_state['clicked_button']['#wizard type'])) { 124 $type = $form_state['clicked_button']['#wizard type']; 125 // If we have a callback depending upon the type of button that was 126 // clicked, call it. 127 if ($function = ctools_plugin_get_function($form_info, "$type callback")) { 128 $function($form_state); 129 } 130 131 // If the modal is in use, some special code for it: 132 if (!empty($form_state['modal']) && empty($form_state['modal return'])) { 133 if ($type != 'next') { 134 // Automatically dismiss the modal if we're not going to another form. 135 ctools_include('modal'); 136 $form_state['commands'][] = ctools_modal_command_dismiss(); 137 } 138 } 139 } 140 141 if (empty($form_state['ajax'])) { 142 // redirect, if one is set. 143 if ($form_state['redirect']) { 144 return drupal_redirect_form(array(), $form_state['redirect']); 145 } 146 } 147 else if (isset($form_state['ajax next'])) { 148 // Clear a few items off the form state so we don't double post: 149 $next = $form_state['ajax next']; 150 unset($form_state['ajax next']); 151 unset($form_state['executed']); 152 unset($form_state['post']); 153 unset($form_state['next']); 154 return ctools_wizard_multistep_form($form_info, $next, $form_state); 155 } 156 157 // If the callbacks wanted to do something besides go to the next form, 158 // it needs to have set $form_state['commands'] with something that can 159 // be rendered. 160 } 161 162 // Render ajax commands if we have any. 163 if (isset($form_state['ajax']) && !empty($form_state['commands'])) { 164 return ctools_ajax_render($form_state['commands']); 165 } 166 167 // Otherwise, return the output. 168 return $output; 169 } 170 171 /** 172 * Provide a wrapper around another form for adding multi-step information. 173 */ 174 function ctools_wizard_wrapper(&$form, &$form_state) { 175 $form_info = &$form_state['form_info']; 176 $info = $form_info['forms'][$form_state['step']]; 177 178 // Determine the next form from this step. 179 // Create a form trail if we're supposed to have one. 180 $trail = array(); 181 $previous = TRUE; 182 foreach ($form_info['order'] as $id => $title) { 183 if ($id == $form_state['step']) { 184 $previous = FALSE; 185 $class = 'wizard-trail-current'; 186 } 187 elseif ($previous) { 188 $not_first = TRUE; 189 $class = 'wizard-trail-previous'; 190 $form_state['previous'] = $id; 191 } 192 else { 193 $class = 'wizard-trail-next'; 194 if (!isset($form_state['next'])) { 195 $form_state['next'] = $id; 196 } 197 if (empty($form_info['show trail'])) { 198 break; 199 } 200 } 201 202 if (!empty($form_info['show trail'])) { 203 if (!empty($form_info['free trail'])) { 204 // ctools_wizard_get_path() returns results suitable for #redirect 205 // which can only be directly used in drupal_goto. We have to futz 206 // with it. 207 $path = ctools_wizard_get_path($form_info, $id); 208 $options = array(); 209 if (!empty($path[1])) { 210 $options['query'] = $path[1]; 211 } 212 if (!empty($path[2])) { 213 $options['fragment'] = $path[2]; 214 } 215 $title = l($title, $path[0], $options); 216 } 217 $trail[] = '<span class="' . $class . '">' . $title . '</span>'; 218 } 219 } 220 221 // Display the trail if instructed to do so. 222 if (!empty($form_info['show trail'])) { 223 ctools_add_css('wizard'); 224 $form['ctools_trail'] = array( 225 '#value' => theme(array('ctools_wizard_trail__' . $form_info['id'], 'ctools_wizard_trail'), $trail), 226 '#weight' => -1000, 227 ); 228 } 229 230 if (empty($form_info['no buttons'])) { 231 // Ensure buttons stay on the bottom. 232 $form['buttons'] = array( 233 '#prefix' => '<div class="clear-block">', 234 '#suffix' => '</div>', 235 '#weight' => 1000, 236 ); 237 238 $button_attributes = array(); 239 if (!empty($form_state['ajax']) && empty($form_state['modal'])) { 240 $button_attributes = array('class' => 'ctools-use-ajax'); 241 } 242 243 if (!empty($form_info['show back']) && isset($form_state['previous'])) { 244 $form['buttons']['previous'] = array( 245 '#type' => 'submit', 246 '#value' => $form_info['back text'], 247 '#next' => $form_state['previous'], 248 '#wizard type' => 'next', 249 '#weight' => -2000, 250 '#skip validation' => TRUE, 251 // hardcode the submit so that it doesn't try to save data. 252 '#submit' => array('ctools_wizard_submit'), 253 '#attributes' => $button_attributes, 254 ); 255 256 if (isset($form_info['no back validate']) || isset($info['no back validate'])) { 257 $form['buttons']['previous']['#validate'] = array(); 258 } 259 } 260 261 // If there is a next form, place the next button. 262 if (isset($form_state['next']) || !empty($form_info['free trail'])) { 263 $form['buttons']['next'] = array( 264 '#type' => 'submit', 265 '#value' => $form_info['next text'], 266 '#next' => !empty($form_info['free trail']) ? $form_state['step'] : $form_state['next'], 267 '#wizard type' => 'next', 268 '#weight' => -1000, 269 '#attributes' => $button_attributes, 270 ); 271 } 272 273 // There are two ways the return button can appear. If this is not the 274 // end of the form list (i.e, there is a next) then it's "update and return" 275 // to be clear. If this is the end of the path and there is no next, we 276 // call it 'Finish'. 277 278 // Even if there is no direct return path (some forms may not want you 279 // leaving in the middle) the final button is always a Finish and it does 280 // whatever the return action is. 281 if (!empty($form_info['show return']) && !empty($form_state['next'])) { 282 $form['buttons']['return'] = array( 283 '#type' => 'submit', 284 '#value' => $form_info['return text'], 285 '#wizard type' => 'return', 286 '#attributes' => $button_attributes, 287 ); 288 } 289 else if (empty($form_state['next']) || !empty($form_info['free trail'])) { 290 $form['buttons']['return'] = array( 291 '#type' => 'submit', 292 '#value' => $form_info['finish text'], 293 '#wizard type' => 'finish', 294 '#attributes' => $button_attributes, 295 ); 296 } 297 298 // If we are allowed to cancel, place a cancel button. 299 if ((isset($form_info['cancel path']) && !isset($form_info['show cancel'])) || !empty($form_info['show cancel'])) { 300 $form['buttons']['cancel'] = array( 301 '#type' => 'submit', 302 '#value' => $form_info['cancel text'], 303 '#wizard type' => 'cancel', 304 // hardcode the submit so that it doesn't try to save data. 305 '#skip validation' => TRUE, 306 '#submit' => array('ctools_wizard_submit'), 307 '#attributes' => $button_attributes, 308 ); 309 } 310 311 // Set up optional validate handlers. 312 $form['#validate'] = array(); 313 if (function_exists($info['form id'] . '_validate')) { 314 $form['#validate'][] = $info['form id'] . '_validate'; 315 } 316 if (isset($info['validate']) && function_exists($info['validate'])) { 317 $form['#validate'][] = $info['validate']; 318 } 319 320 // Set up our submit handler after theirs. Since putting something here will 321 // skip Drupal's autodetect, we autodetect for it. 322 323 // We make sure ours is after theirs so that they get to change #next if 324 // the want to. 325 $form['#submit'] = array(); 326 if (function_exists($info['form id'] . '_submit')) { 327 $form['#submit'][] = $info['form id'] . '_submit'; 328 } 329 if (isset($info['submit']) && function_exists($info['submit'])) { 330 $form['#submit'][] = $info['submit']; 331 } 332 $form['#submit'][] = 'ctools_wizard_submit'; 333 } 334 335 if (!empty($form_state['ajax'])) { 336 $params = ctools_wizard_get_path($form_state['form_info'], $form_state['step']); 337 if (count($params) > 1) { 338 $url = array_shift($params); 339 $options = array(); 340 341 $keys = array(0 => 'query', 1 => 'fragment'); 342 foreach ($params as $key => $value) { 343 if (isset($keys[$key]) && isset($value)) { 344 $options[$keys[$key]] = $value; 345 } 346 } 347 348 $params = array($url, $options); 349 } 350 $form['#action'] = call_user_func_array('url', $params); 351 } 352 353 if (isset($info['wrapper']) && function_exists($info['wrapper'])) { 354 $info['wrapper']($form, $form_state); 355 } 356 357 if (isset($form_info['wrapper']) && function_exists($form_info['wrapper'])) { 358 $form_info['wrapper']($form, $form_state); 359 } 360 } 361 362 /** 363 * On a submit, go to the next form. 364 */ 365 function ctools_wizard_submit(&$form, &$form_state) { 366 if (isset($form_state['clicked_button']['#wizard type'])) { 367 $type = $form_state['clicked_button']['#wizard type']; 368 369 // if AJAX enabled, we proceed slightly differently here. 370 if (!empty($form_state['ajax'])) { 371 if ($type == 'next') { 372 $form_state['ajax next'] = $form_state['clicked_button']['#next']; 373 } 374 } 375 else { 376 if ($type == 'cancel' && isset($form_state['form_info']['cancel path'])) { 377 $form_state['redirect'] = $form_state['form_info']['cancel path']; 378 } 379 else if ($type == 'next') { 380 $form_state['redirect'] = ctools_wizard_get_path($form_state['form_info'], $form_state['clicked_button']['#next']); 381 } 382 else if (isset($form_state['form_info']['return path'])) { 383 $form_state['redirect'] = $form_state['form_info']['return path']; 384 } 385 else if ($type == 'finish' && isset($form_state['form_info']['cancel path'])) { 386 $form_state['redirect'] = $form_state['form_info']['cancel path']; 387 } 388 } 389 } 390 } 391 392 /** 393 * Create a path from the form info and a given step. 394 */ 395 function ctools_wizard_get_path($form_info, $step) { 396 if (is_array($form_info['path'])) { 397 foreach ($form_info['path'] as $id => $part) { 398 $form_info['path'][$id] = str_replace('%step', $step, $form_info['path'][$id]); 399 } 400 return $form_info['path']; 401 } 402 else { 403 return array(str_replace('%step', $step, $form_info['path'])); 404 } 405 } 406 407 /** 408 * Set default parameters and callbacks if none are given. 409 * Callbacks follows pattern: 410 * $form_info['id']_$hook 411 * $form_info['id']_$form_info['forms'][$step_key]_$hook 412 */ 413 function ctools_wizard_defaults(&$form_info) { 414 $hook = $form_info['id']; 415 $defaults = array( 416 'show trail' => FALSE, 417 'free trail' => FALSE, 418 'show back' => FALSE, 419 'show cancel' => FALSE, 420 'show return' => FALSE, 421 'next text' => t('Continue'), 422 'back text' => t('Back'), 423 'return text' => t('Update and return'), 424 'finish text' => t('Finish'), 425 'cancel text' => t('Cancel'), 426 ); 427 428 if (!empty($form_info['free trail'])) { 429 $defaults['next text'] = t('Update'); 430 $defaults['finish text'] = t('Save'); 431 } 432 433 $form_info = $form_info + $defaults; 434 // set form callbacks if they aren't defined 435 foreach($form_info['forms'] as $step => $params) { 436 if (!$params['form id']) { 437 $form_callback = $hook . '_' . $step . '_form'; 438 $form_info['forms'][$step]['form id'] = $form_callback; 439 } 440 } 441 442 // set button callbacks 443 $callbacks = array( 444 'back callback' => '_back', 445 'next callback' => '_next', 446 'return callback' => '_return', 447 'cancel callback' => '_cancel', 448 'finish callback' => '_finish', 449 ); 450 451 foreach($callbacks as $key => $callback) { 452 // never overwrite if explicity defined 453 if (empty($form_info[$key])) { 454 $wizard_callback = $hook . $callback; 455 if (function_exists($wizard_callback)) { 456 $form_info[$key] = $wizard_callback; 457 } 458 } 459 } 460 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Mon Jul 9 18:01:44 2012 | Cross-referenced by PHPXref 0.7 |