[ Index ]

PHP Cross Reference of Drupal 6 (yi-drupal)

title

Body

[close]

/sites/all/modules/ctools/includes/ -> wizard.inc (source)

   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  }


Generated: Mon Jul 9 18:01:44 2012 Cross-referenced by PHPXref 0.7