source: sipes/modules_contrib/ctools/includes/wizard.inc @ a8b1f3f

stableversion-3.0
Last change on this file since a8b1f3f was 2efe680, checked in by José Gregorio Puentes <jpuentes@…>, 8 años ago

se actualizo el modulo

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