Projet

Général

Profil

Paste
Télécharger (16,4 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / checkout / includes / commerce_checkout.pages.inc @ b858700c

1
<?php
2

    
3
/**
4
 * @file
5
 * The page and form callbacks for use in the checkout form.
6
 */
7

    
8

    
9
/**
10
 * Redirects invalid checkout attempts or displays the checkout form if valid.
11
 */
12
function commerce_checkout_router($order, $checkout_page = NULL) {
13
  $checkout_pages = commerce_checkout_pages();
14

    
15
  // If no checkout page is specified, default to the first one.
16
  if (empty($checkout_page)) {
17
    $checkout_page = reset($checkout_pages);
18
  }
19

    
20
  // If the user does not have access to checkout the order, return a 404. We
21
  // could return a 403, but then the user would know they've identified a
22
  // potentially valid checkout URL.
23
  if (!commerce_checkout_access($order)) {
24
    return MENU_NOT_FOUND;
25
  }
26

    
27
  // If the user is attempting to access an inaccessible page for their order,
28
  // redirect them to the proper page.
29
  if (!commerce_checkout_page_access($checkout_page, $order)) {
30
    $target_uri = commerce_checkout_order_uri($order);
31

    
32
    // Only redirect if the target page is different from the page the user was
33
    // trying to access. Otherwise give a 403 error.
34
    if (!empty($target_uri) && $target_uri !== $_GET['q']) {
35
      drupal_goto($target_uri);
36
    }
37
    else {
38
      return MENU_ACCESS_DENIED;
39
    }
40
  }
41

    
42
  // Ensure the order can proceed to checkout; if not, redirect away.
43
  if (!commerce_checkout_order_can_checkout($order)) {
44
    drupal_goto('<front>');
45
  }
46

    
47
  // Prior to displaying the checkout form, allow other modules to route the
48
  // checkout form.
49
  module_invoke_all('commerce_checkout_router', $order, $checkout_page);
50

    
51
  // Update the page title if specified.
52
  if (!empty($checkout_page['title'])) {
53
    drupal_set_title($checkout_page['title']);
54
  }
55

    
56
  return drupal_get_form('commerce_checkout_form_' . $checkout_page['page_id'], $order, $checkout_page);
57
}
58

    
59
/**
60
 * Builds the checkout form for the given order on the specified checkout page.
61
 *
62
 * @param $order
63
 *   The fully loaded order object being checked out.
64
 * @param $checkout_page
65
 *   The checkout page object representing the current step in checkout.
66
 */
67
function commerce_checkout_form($form, &$form_state, $order, $checkout_page) {
68
  global $user;
69

    
70
  $form['#attached']['css'][] = drupal_get_path('module', 'commerce_checkout') .'/theme/commerce_checkout.base.css';
71
  $form['#attached']['css'][] = drupal_get_path('module', 'commerce_checkout') .'/theme/commerce_checkout.theme.css';
72
  $form['#attached']['js'][] = drupal_get_path('module', 'commerce_checkout') . '/commerce_checkout.js';
73

    
74
  $form_state['order'] = $order;
75
  $form_state['checkout_page'] = $checkout_page;
76
  $form_state['account'] = clone($user);
77

    
78
  // Add any help text that has been defined for this checkout page.
79
  $help = filter_xss($checkout_page['help']);
80

    
81
  if (!empty($help)) {
82
    $form['help'] = array(
83
      '#markup' => theme('commerce_checkout_help', array('help' => $help)),
84
    );
85
  }
86

    
87
  // Restore form errors.
88
  if (!empty($form_state['storage']['errors'])) {
89
    $form_errors = &drupal_static('form_set_error', array());
90
    $form_errors = $form_state['storage']['errors'];
91
  }
92

    
93
  // Catch and clear already pushed messages.
94
  $previous_messages = drupal_get_messages();
95
  $show_errors_message = FALSE;
96
  $visible_panes = 0;
97

    
98
  // Add any enabled checkout panes for this page to the form.
99
  foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
100
    if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form')) {
101
      // Generate the pane form.
102
      $pane_form = $callback($form, $form_state, $checkout_pane, $order);
103

    
104
      // Combine the messages that were created during this pane's validation or
105
      // submit process with any that were created during the pane generation
106
      // and merge them into the session's current messages array.
107
      if (!empty($form_state['storage']['messages'][$pane_id])) {
108
        $_SESSION['messages'] = array_merge_recursive($form_state['storage']['messages'][$pane_id], drupal_get_messages());
109
      }
110

    
111
      // If there are messages in the session right now for this pane, theme
112
      // them into the form right above the pane itself.
113
      if (!empty($_SESSION['messages'])) {
114
        // If there are error messages and this is not the first pane on the
115
        // form, then indicate we need to show an error message at the top of
116
        // the page.
117
        if ($visible_panes > 0 && !empty($_SESSION['messages']['error'])) {
118
          $show_errors_message = TRUE;
119
        }
120

    
121
        // Rendering status messages clears the session of messages, so they
122
        // will not be visible if the user is redirected. We can at least not
123
        // render here when we detect the global variable added by Rules to
124
        // handle redirects, though modules implementing redirects will still
125
        // encounter the same problem of "lost" messages.
126
        if (!isset($GLOBALS['_rules_action_drupal_goto_do'])){
127
          $form_state['storage']['themed_messages'][$pane_id] = theme('status_messages');
128

    
129
          $pane_form[$pane_id . '_messages'] = array(
130
            '#markup' => $form_state['storage']['themed_messages'][$pane_id],
131
            '#weight' => -50,
132
          );
133
        }
134
      }
135

    
136
      // Create a fieldset for the pane and add the form data defined in the
137
      // pane's form callback.
138
      if ($pane_form) {
139
        $form[$pane_id] = $pane_form + array(
140
          '#type' => $checkout_pane['fieldset'] ? 'fieldset' : 'container',
141
          '#title' => check_plain($checkout_pane['title']),
142
          '#collapsible' => $checkout_pane['collapsible'],
143
          '#collapsed' => $checkout_pane['collapsed'],
144
          '#attributes' => array('class' => array($pane_id)),
145
          '#tree' => TRUE,
146
        );
147

    
148
        $visible_panes++;
149
      }
150
    }
151
  }
152

    
153
  // Restore general messages to the current session's messages array.
154
  $_SESSION['messages'] = array_merge_recursive(array_filter($previous_messages), drupal_get_messages());
155

    
156
  // If there are errors on the form, add a message to the top of the page.
157
  if ($show_errors_message) {
158
    $form['error_message'] = array(
159
      '#markup' => theme('commerce_checkout_errors_message', array('label' => t('Errors on form'), 'message' => t('There are errors on the page. Please correct them and resubmit the form.'))),
160
      '#weight' => -10,
161
    );
162
  }
163

    
164
  // Only add buttons to the form if the checkout page hasn't disabled them.
165
  if ($checkout_page['buttons']) {
166
    $form['buttons'] = array(
167
      '#type' => 'fieldset',
168
      '#attributes' => array('class' => array('checkout-buttons')),
169
    );
170
    $form['buttons']['continue'] = array(
171
      '#type' => 'submit',
172
      '#value' => $checkout_page['submit_value'],
173
      '#attributes' => array('class' => array('checkout-continue')),
174
      '#suffix' => '<span class="checkout-processing element-invisible"></span>',
175
      '#validate' => array('commerce_checkout_form_validate'),
176
      '#submit' => array('commerce_checkout_form_submit'),
177
    );
178

    
179
    // Add the cancel or back button where appropriate. We define button level
180
    // submit handlers because we're using hook_forms() to use this form builder
181
    // function and to avoid issues if other modules implement button level submit
182
    // handlers on these or custom checkout buttons.
183
    $button_operator = '<span class="button-operator">' . t('or') . '</span>';
184

    
185
    if (!$checkout_page['prev_page'] && !empty($checkout_page['back_value'])) {
186
      // Add an empty "Back" button value to avoid submission errors.
187
      $form['buttons']['back'] = array(
188
        '#type' => 'value',
189
        '#value' => '',
190
      );
191

    
192
      // Store the cancel redirect in the form so modules can modify it easily.
193
      $form_state['cancel_redirect'] = '<front>';
194

    
195
      $form['buttons']['cancel'] = array(
196
        '#type' => 'submit',
197
        '#value' => t('Cancel'),
198
        '#attributes' => array('class' => array('checkout-cancel')),
199
        '#submit' => array('commerce_checkout_form_cancel_submit'),
200
        '#limit_validation_errors' => array(),
201
        '#prefix' => $button_operator,
202
      );
203
    }
204
    elseif ($checkout_page['prev_page'] && !empty($checkout_page['back_value'])) {
205
      $form['buttons']['back'] = array(
206
        '#type' => 'submit',
207
        '#value' => $checkout_page['back_value'],
208
        '#attributes' => array('class' => array('checkout-back')),
209
        '#submit' => array('commerce_checkout_form_back_submit'),
210
        '#limit_validation_errors' => array(),
211
        '#prefix' => $button_operator,
212
      );
213
    }
214
  }
215

    
216
  // Remove form level validate and submit handlers.
217
  $form['#validate'] = array();
218
  $form['#submit'] = array();
219

    
220
  return $form;
221
}
222

    
223
/**
224
 * Validate handler for the continue button of the checkout form.
225
 *
226
 * This function calls the validation function of each pane, followed by
227
 * the submit function if the validation succeeded. As long as one pane
228
 * fails validation, we then ask for the form to be rebuilt. Once all the panes
229
 * are happy, we move on to the next page.
230
 */
231
function commerce_checkout_form_validate($form, &$form_state) {
232
  $checkout_page = $form_state['checkout_page'];
233

    
234
  // Load a fresh copy of the order stored in the form.
235
  $order = commerce_order_load($form_state['order']->order_id);
236

    
237
  // Catch and clear already pushed messages.
238
  $previous_messages = drupal_get_messages();
239

    
240
  // Load any pre-existing validation errors for the elements.
241
  $errors = array();
242

    
243
  foreach ((array) form_get_errors() as $element_path => $error) {
244
    list($pane_id, ) = explode('][', $element_path, 2);
245
    $errors[$pane_id][$element_path] = $error;
246
  }
247

    
248
  // Loop through the enabled checkout panes for the current page.
249
  $form_validate = TRUE;
250
  foreach (commerce_checkout_panes(array('enabled' => TRUE, 'page' => $checkout_page['page_id'])) as $pane_id => $checkout_pane) {
251
    $validate = TRUE;
252

    
253
    // If any element in the pane failed validation, we mark the pane as
254
    // unvalidated and replay the validation messages on top of it.
255
    if (!empty($errors[$pane_id])) {
256
      $validate = FALSE;
257

    
258
      foreach ($errors[$pane_id] as $element_path => $message) {
259
        if ($message) {
260
          drupal_set_message($message, 'error');
261
        }
262
      }
263

    
264
      if (isset($previous_messages['error'])) {
265
        $previous_messages['error'] = array_values(array_diff($previous_messages['error'], $errors[$pane_id]));
266
      }
267
    }
268

    
269
    // If the pane has defined a checkout form validate handler...
270
    if ($callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_validate')) {
271
      // Give it a chance to process the submitted data.
272
      $validate &= $callback($form, $form_state, $checkout_pane, $order);
273
    }
274

    
275
    // Catch and clear panes' messages.
276
    $pane_messages = drupal_get_messages();
277

    
278
    // Submit the pane if it validated.
279
    if ($validate && $callback = commerce_checkout_pane_callback($checkout_pane, 'checkout_form_submit')) {
280
      $callback($form, $form_state, $checkout_pane, $order);
281
    }
282

    
283
    // Generate status messages.
284
    $form_state['storage']['messages'][$pane_id] = array_merge_recursive($pane_messages, drupal_get_messages());
285

    
286
    // A failed pane makes the form fail.
287
    $form_validate &= $validate;
288
  }
289

    
290
  // Restore messages and form errors.
291
  $_SESSION['messages'] = array_merge_recursive(array_filter($previous_messages), drupal_get_messages());
292
  $form_errors = &drupal_static('form_set_error', array());
293
  $form_state['storage']['errors'] = $form_errors;
294
  $form_errors = array();
295

    
296
  // Save the updated order object and reset the order in the form cache to
297
  // ensure rebuilt forms use the updated order.
298
  commerce_order_save($order);
299
  $form_state['build_info']['args'][0] = $order;
300

    
301
  // If a pane failed validation or the form state has otherwise been altered to
302
  // initiate a rebuild, return without moving to the next checkout page.
303
  if (!$form_validate || $form_state['rebuild']) {
304
    $form_state['rebuild'] = TRUE;
305
  }
306
}
307

    
308
/**
309
 * Submit handler for the continue button of the checkout form.
310
 */
311
function commerce_checkout_form_submit($form, &$form_state) {
312
  $checkout_page = $form_state['checkout_page'];
313

    
314
  // Load a fresh copy of the order stored in the form.
315
  $order = commerce_order_load($form_state['order']->order_id);
316

    
317
  // If we are going to redirect with checkout pane messages stored in the form
318
  // state, they will not be displayed on a subsequent form build like normal.
319
  // Move them out of the form state messages array and into the current
320
  // session's general message array instead.
321
  if (!empty($form_state['storage']['messages'])) {
322
    foreach ($form_state['storage']['messages'] as $pane_id => $pane_messages) {
323
      $_SESSION['messages'] = array_merge_recursive($_SESSION['messages'], $pane_messages);
324
    }
325
  }
326

    
327
  // If the form was submitted via the continue button...
328
  if (end($form_state['triggering_element']['#array_parents']) == 'continue') {
329
    // If there is another checkout page...
330
    if ($checkout_page['next_page']) {
331
      // Update the order status to reflect the next checkout page.
332
      $order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Customer continued to the next checkout page via a submit button.'));
333

    
334
      // If it happens to be the complete page, process completion now.
335
      if ($checkout_page['next_page'] == 'complete') {
336
        commerce_checkout_complete($order);
337
      }
338

    
339
      // Redirect to the next checkout page.
340
      $form_state['redirect'] = 'checkout/' . $order->order_id . '/' . $checkout_page['next_page'];
341
    }
342
  }
343
}
344

    
345
/**
346
 * Special submit handler for the back button to avoid processing orders.
347
 */
348
function commerce_checkout_form_back_submit($form, &$form_state) {
349
  // If there is a previous page...
350
  if ($previous_page = commerce_checkout_page_load($form_state['checkout_page']['prev_page'])) {
351
    $order = commerce_order_load($form_state['order']->order_id);
352

    
353
    // Move the form back to that page.
354
    if ($previous_page['prev_page']) {
355
      $form_state['redirect'] = 'checkout/' . $order->order_id . '/' . $previous_page['page_id'];
356
    }
357
    else {
358
      $form_state['redirect'] = 'checkout/' . $order->order_id;
359
    }
360

    
361
    // Update the order status for the checkout step.
362
    $form_state['order'] = commerce_order_status_update($order, 'checkout_' . $previous_page['page_id'], FALSE, NULL, t('Customer returned to the previous checkout page via a submit button.'));
363
  }
364
}
365

    
366
/**
367
 * Special submit handler for the cancel button to avoid processing orders.
368
 */
369
function commerce_checkout_form_cancel_submit($form, &$form_state) {
370
  $order = commerce_order_load($form_state['order']->order_id);
371

    
372
  // Set the order status back to the first checkout page's status.
373
  $order_state = commerce_order_state_load('checkout');
374
  $form_state['order'] = commerce_order_status_update($order, $order_state['default_status'], TRUE);
375

    
376
  // Skip saving in the status update and manually save here to force a save
377
  // even when the status doesn't actually change.
378
  if (variable_get('commerce_order_auto_revision', TRUE)) {
379
    $form_state['order']->revision = TRUE;
380
    $form_state['order']->log = t('Customer manually canceled the checkout process.');
381
  }
382

    
383
  commerce_order_save($form_state['order']);
384

    
385
  drupal_set_message(t('Checkout of your current order has been canceled and may be resumed when you are ready.'));
386

    
387
  $form_state['redirect'] = $form_state['cancel_redirect'];
388
}
389

    
390
/**
391
 * Themes the optional checkout review page data.
392
 */
393
function theme_commerce_checkout_review($variables) {
394
  $form = $variables['form'];
395

    
396
  // Turn the review data array into table rows.
397
  $rows = array();
398

    
399
  foreach ($form['#data'] as $pane_id => $data) {
400
    // First add a row for the title.
401
    $rows[] = array(
402
      'data' => array(
403
        array('data' => $data['title'], 'colspan' => 2),
404
      ),
405
      'class' => array('pane-title', 'odd'),
406
    );
407

    
408
    // Next, add the data for this particular section.
409
    if (is_array($data['data'])) {
410
      // If it's an array, treat each key / value pair as a 2 column row.
411
      foreach ($data['data'] as $key => $value) {
412
        $rows[] = array(
413
          'data' => array(
414
            array('data' => $key .':', 'class' => array('pane-data-key')),
415
            array('data' => $value, 'class' => array('pane-data-value')),
416
          ),
417
          'class' => array('pane-data', 'even'),
418
        );
419
      }
420
    }
421
    else {
422
      // Otherwise treat it as a block of text in its own row.
423
      $rows[] = array(
424
        'data' => array(
425
          array('data' => $data['data'], 'colspan' => 2, 'class' => array('pane-data-full')),
426
        ),
427
        'class' => array('pane-data', 'even'),
428
      );
429
    }
430
  }
431

    
432
  return theme('table', array('rows' => $rows, 'attributes' => array('class' => array('checkout-review'))));
433
}