Project

General

Profile

Paste
Download (32.4 KB) Statistics
| Branch: | Revision:

root / drupal7 / sites / all / modules / commerce / modules / checkout / commerce_checkout.module @ dbb0c974

1
<?php
2

    
3
/**
4
 * @file
5
 * Enable checkout as a multi-step form with customizable pages and a simple
6
 *   checkout pane API.
7
 */
8

    
9

    
10
/**
11
 * Implements hook_menu().
12
 */
13
function commerce_checkout_menu() {
14
  $items = array();
15

    
16
  $items['checkout/%commerce_order'] = array(
17
    'title' => 'Checkout',
18
    'page callback' => 'commerce_checkout_router',
19
    'page arguments' => array(1),
20
    'access arguments' => array('access checkout'),
21
    'type' => MENU_CALLBACK,
22
    'file' => 'includes/commerce_checkout.pages.inc',
23
  );
24
  $items['checkout/%commerce_order/%commerce_checkout_page'] = array(
25
    'title' => 'Checkout',
26
    'page callback' => 'commerce_checkout_router',
27
    'page arguments' => array(1, 2),
28
    'access arguments' => array('access checkout'),
29
    'type' => MENU_CALLBACK,
30
    'file' => 'includes/commerce_checkout.pages.inc',
31
  );
32

    
33
  $items['admin/commerce/config/checkout'] = array(
34
    'title' => 'Checkout settings',
35
    'description' => 'Customize the checkout form and configure checkout rules.',
36
    'page callback' => 'drupal_get_form',
37
    'page arguments' => array('commerce_checkout_builder_form'),
38
    'access arguments' => array('administer checkout'),
39
    'type' => MENU_NORMAL_ITEM,
40
    'file' => 'includes/commerce_checkout.admin.inc',
41
  );
42
  $items['admin/commerce/config/checkout/form'] = array(
43
    'title' => 'Checkout form',
44
    'description' => 'Build your checkout pages using module defined checkout form elements.',
45
    'type' => MENU_DEFAULT_LOCAL_TASK,
46
    'weight' => 0,
47
    'file' => 'includes/commerce_checkout.admin.inc',
48
  );
49
  $items['admin/commerce/config/checkout/rules'] = array(
50
    'title' => 'Checkout rules',
51
    'description' => 'Enable and configure checkout completion rules.',
52
    'page callback' => 'commerce_checkout_complete_rules',
53
    'access arguments' => array('administer checkout'),
54
    'type' => MENU_LOCAL_TASK,
55
    'weight' => 5,
56
    'file' => 'includes/commerce_checkout.admin.inc',
57
  );
58

    
59
  // Add the menu items for the various Rules forms.
60
  $controller = new RulesUIController();
61
  $items += $controller->config_menu('admin/commerce/config/checkout/rules');
62

    
63
  $items['admin/commerce/config/checkout/rules/add'] = array(
64
    'title' => 'Add a checkout rule',
65
    'description' => 'Adds an additional checkout completion rule configuration.',
66
    'page callback' => 'drupal_get_form',
67
    'page arguments' => array('commerce_checkout_add_complete_rule_form', 'admin/commerce/config/checkout/rules'),
68
    'access arguments' => array('administer checkout'),
69
    'file path' => drupal_get_path('module', 'rules_admin'),
70
    'file' => 'rules_admin.inc',
71
    'type' => MENU_LOCAL_ACTION,
72
  );
73

    
74
  $items['admin/commerce/config/checkout/form/pane/%commerce_checkout_pane'] = array(
75
    'title callback' => 'commerce_checkout_pane_settings_title',
76
    'title arguments' => array(6),
77
    'description' => 'Configure the settings for a checkout pane.',
78
    'page callback' => 'drupal_get_form',
79
    'page arguments' => array('commerce_checkout_pane_settings_form', 6),
80
    'access arguments' => array('administer checkout'),
81
    'file' => 'includes/commerce_checkout.admin.inc',
82
  );
83

    
84
  // If the Order UI module is installed, add a local action to it that lets an
85
  // administrator invoke the checkout completion event on the order. Modules
86
  // that define their own order edit menu item are also responsible for
87
  // defining their own local action menu items if needed.
88
  if (module_exists('commerce_order_ui')) {
89
    $items['admin/commerce/orders/%commerce_order/edit/checkout'] = array(
90
      'title' => 'Simulate checkout completion',
91
      'description' => 'Directly invokes the checkout completion rules on the order.',
92
      'page callback' => 'drupal_get_form',
93
      'page arguments' => array('commerce_checkout_complete_form', 3),
94
      'access callback' => 'commerce_checkout_complete_form_access',
95
      'access arguments' => array(3),
96
      'type' => MENU_LOCAL_ACTION,
97
      'file' => 'includes/commerce_checkout.admin.inc',
98
    );
99
  }
100

    
101
  return $items;
102
}
103

    
104
/**
105
 * Access callback: determines access to the "Simulate checkout completion"
106
 * local action.
107
 */
108
function commerce_checkout_complete_form_access($order) {
109
  // Returns TRUE if the link is enabled via the order settings form and the
110
  // user has access to update the order.
111
  return variable_get('commerce_order_simulate_checkout_link', TRUE) && commerce_order_access('update', $order);
112
}
113

    
114
/**
115
 * Implements hook_hook_info().
116
 */
117
function commerce_checkout_hook_info() {
118
  $hooks = array(
119
    'commerce_checkout_page_info' => array(
120
      'group' => 'commerce',
121
    ),
122
    'commerce_checkout_page_info_alter' => array(
123
      'group' => 'commerce',
124
    ),
125
    'commerce_checkout_pane_info' => array(
126
      'group' => 'commerce',
127
    ),
128
    'commerce_checkout_pane_info_alter' => array(
129
      'group' => 'commerce',
130
    ),
131
    'commerce_checkout_router' => array(
132
      'group' => 'commerce',
133
    ),
134
    'commerce_checkout_complete' => array(
135
      'group' => 'commerce',
136
    ),
137
  );
138

    
139
  return $hooks;
140
}
141

    
142
/**
143
 * Implements hook_permission().
144
 */
145
function commerce_checkout_permission() {
146
  $permissions = array(
147
    'administer checkout' => array(
148
      'title' => t('Administer checkout'),
149
      'description' => t('Configure checkout settings including the layout of the checkout form.'),
150
      'restrict access' => TRUE,
151
    ),
152
    'access checkout' => array(
153
      'title' => t('Access checkout'),
154
      'description' => t('Complete a purchase through the checkout form.'),
155
    ),
156
  );
157

    
158
  return $permissions;
159
}
160

    
161
/**
162
 * Implements hook_help().
163
 */
164
function commerce_checkout_help($path, $arg) {
165
  switch ($path) {
166
    case 'admin/commerce/config/checkout':
167
    case 'admin/commerce/config/checkout/form':
168
      return t('Use the table below to build your checkout form using the available checkout panes and pages defined by modules enabled on your site. You may configure the checkout pane settings using the operations links below.');
169

    
170
    case 'admin/commerce/config/checkout/rules':
171
      return t('When a customer advances to the checkout completion page, rules reacting on the <em>Completing the checkout process</em> are evaluated. Default rules handle standard tasks like updating the order status, sending order e-mails, and creating accounts for anonymous users. You can edit these or add additional rules to customize your checkout workflow.');
172
  }
173
}
174

    
175
/**
176
 * Implements hook_theme().
177
 */
178
function commerce_checkout_theme() {
179
  return array(
180
    'commerce_checkout_builder_form' => array(
181
      'render element' => 'form',
182
      'file' => 'includes/commerce_checkout.admin.inc',
183
    ),
184
    'commerce_checkout_review' => array(
185
      'render element' => 'form',
186
      'file' => 'includes/commerce_checkout.pages.inc',
187
    ),
188
    'commerce_checkout_help' => array(
189
      'variables' => array('help' => NULL),
190
      'path' => drupal_get_path('module', 'commerce_checkout') . '/theme',
191
      'template' => 'commerce-checkout-help',
192
    ),
193
    'commerce_checkout_errors_message' => array(
194
      'variables' => array('label' => NULL, 'message' => NULL),
195
      'path' => drupal_get_path('module', 'commerce_checkout') . '/theme',
196
      'template' => 'commerce-checkout-errors-message',
197
    ),
198
  );
199
}
200

    
201
/**
202
 * Implements hook_i18n_string_list().
203
 */
204
function commerce_checkout_i18n_string_list($group) {
205
  if ($group == 'commerce') {
206
    // Allow the checkout completion message to be translated.
207
    $message = variable_get('commerce_checkout_completion_message', commerce_checkout_completion_message_default());
208
    $strings['commerce']['checkout']['complete']['message'] = $message['value'];
209

    
210
    return $strings;
211
  }
212
}
213

    
214
/**
215
 * Implements hook_forms().
216
 *
217
 * Each page of the checkout form is actually a unique form as opposed to a
218
 * single multistep form. To accommodate this, we map any form ID beginning with
219
 * commerce_checkout_form_ to the same form builder assuming the remainder of
220
 * the form ID matches a valid checkout page ID.
221
 */
222
function commerce_checkout_forms($form_id, $args) {
223
  $forms = array();
224

    
225
  // All checkout page forms should be built using the same function.
226
  if (strpos($form_id, 'commerce_checkout_form_') === 0) {
227
    // Ensure the checkout page is valid.
228
    if (commerce_checkout_page_load(substr($form_id, 23))) {
229
      $forms[$form_id] = array(
230
        'callback' => 'commerce_checkout_form',
231
      );
232
    }
233
  }
234

    
235
  $forms['commerce_checkout_add_complete_rule_form'] = array(
236
    'callback' => 'rules_admin_add_reaction_rule',
237
  );
238

    
239
  return $forms;
240
}
241

    
242
/**
243
 * Implements hook_form_alter().
244
 */
245
function commerce_checkout_form_alter(&$form, &$form_state, $form_id) {
246
  if (strpos($form_id, 'views_form_commerce_cart_form_') === 0) {
247
    // Only add the Checkout button if the cart form View shows line items.
248
    $view = reset($form_state['build_info']['args']);
249

    
250
    if (!empty($view->result)) {
251
      $form['actions']['checkout'] = array(
252
        '#type' => 'submit',
253
        '#value' => t('Checkout'),
254
        '#weight' => 5,
255
        '#access' => user_access('access checkout'),
256
        '#submit' => array_merge($form['#submit'], array('commerce_checkout_line_item_views_form_submit')),
257
      );
258
    }
259
  }
260
}
261

    
262
/**
263
 * Submit handler used to redirect to the checkout page.
264
 */
265
function commerce_checkout_line_item_views_form_submit($form, &$form_state) {
266
  $order = commerce_order_load($form_state['order']->order_id);
267

    
268
  // Set the order status to the first checkout page's status.
269
  $order_state = commerce_order_state_load('checkout');
270
  $form_state['order'] = commerce_order_status_update($order, $order_state['default_status'], TRUE);
271

    
272
  // Skip saving in the status update and manually save here to force a save
273
  // even when the status doesn't actually change.
274
  if (variable_get('commerce_order_auto_revision', TRUE)) {
275
    $form_state['order']->revision = TRUE;
276
    $form_state['order']->log = t('Customer proceeded to checkout using a submit button.');
277
  }
278

    
279
  commerce_order_save($form_state['order']);
280

    
281
  // Redirect to the checkout page if specified.
282
  if ($form_state['triggering_element']['#value'] == $form['actions']['checkout']['#value']) {
283
    $form_state['redirect'] = 'checkout/' . $order->order_id;
284
  }
285
}
286

    
287
/**
288
 * Implements hook_form_FORM_ID_alter().
289
 *
290
 * Adds a checkbox to the order settings form to enable the local action on
291
 * order edit forms to simulate checkout completion.
292
 */
293
function commerce_checkout_form_commerce_order_settings_form_alter(&$form, &$form_state) {
294
  $form['commerce_order_simulate_checkout_link'] = array(
295
    '#type' => 'checkbox',
296
    '#title' => t('Enable the local action link on order edit forms to simulate checkout completion.'),
297
    '#default_value' => variable_get('commerce_order_simulate_checkout_link', TRUE),
298
    '#weight' => 20,
299
  );
300
}
301

    
302
/**
303
 * Implements hook_form_FORM_ID_alter().
304
 *
305
 * The Checkout module instantiates the Rules Admin rule configuration add form
306
 * at a particular path in the Commerce IA. It uses its own form ID to do so and
307
 * alters the form here to select the necessary Rules event.
308
 *
309
 * @see rules_admin_add_reaction_rule()
310
 */
311
function commerce_checkout_form_commerce_checkout_add_complete_rule_form_alter(&$form, &$form_state) {
312
  unset($form['settings']['help']);
313
  $form['settings']['event']['#type'] = 'value';
314
  $form['settings']['event']['#value'] = 'commerce_checkout_complete';
315
  $form['submit']['#suffix'] = l(t('Cancel'), 'admin/commerce/config/checkout/rules');
316
}
317

    
318
/**
319
 * Implements hook_commerce_order_state_info().
320
 */
321
function commerce_checkout_commerce_order_state_info() {
322
  $order_states = array();
323

    
324
  $order_states['checkout'] = array(
325
    'name' => 'checkout',
326
    'title' => t('Checkout'),
327
    'description' => t('Orders in this state have begun but not completed the checkout process.'),
328
    'weight' => -3,
329
    'default_status' => 'checkout_' . commerce_checkout_first_checkout_page(),
330
  );
331

    
332
  return $order_states;
333
}
334

    
335
/**
336
 * Implements hook_commerce_order_status_info().
337
 */
338
function commerce_checkout_commerce_order_status_info() {
339
  $order_statuses = array();
340

    
341
  // Create an order status to correspond with every checkout page.
342
  foreach (commerce_checkout_pages() as $page_id => $checkout_page) {
343
    $order_statuses['checkout_' . $page_id] = array(
344
      'name' => 'checkout_' . $page_id,
345
      'title' => t('Checkout: @page', array('@page' => $checkout_page['name'])),
346
      'state' => 'checkout',
347
      'checkout_page' => $page_id,
348
      'cart' => $checkout_page['status_cart'],
349
      'weight' => $checkout_page['weight'],
350
    );
351
  }
352

    
353
  return $order_statuses;
354
}
355

    
356
/**
357
 * Implements hook_commerce_checkout_page_info().
358
 */
359
function commerce_checkout_commerce_checkout_page_info() {
360
  $checkout_pages = array();
361

    
362
  // Define a primary checkout page as the first step.
363
  $checkout_pages['checkout'] = array(
364
    'title' => t('Checkout'),
365
    'weight' => 0,
366
  );
367

    
368
  // Define a page for reviewing the data entered during checkout.
369
  $checkout_pages['review'] = array(
370
    'name' => t('Review'),
371
    'title' => t('Review order'),
372
    'help' => t('Review your order before continuing.'),
373
    'weight' => 10,
374
  );
375

    
376
  // Define a page for checkout completion with no submit buttons on it.
377
  $checkout_pages['complete'] = array(
378
    'name' => t('Complete'),
379
    'title' => t('Checkout complete'),
380
    'weight' => 50,
381
    'status_cart' => FALSE,
382
    'buttons' => FALSE,
383
  );
384

    
385
  return $checkout_pages;
386
}
387

    
388
/**
389
 * Implements hook_commerce_checkout_pane_info().
390
 */
391
function commerce_checkout_commerce_checkout_pane_info() {
392
  $checkout_panes = array();
393

    
394
  $checkout_panes['checkout_review'] = array(
395
    'title' => t('Review'),
396
    'file' => 'includes/commerce_checkout.checkout_pane.inc',
397
    'base' => 'commerce_checkout_review_pane',
398
    'page' => 'review',
399
    'fieldset' => FALSE,
400
  );
401

    
402
  $checkout_panes['checkout_completion_message'] = array(
403
    'title' => t('Completion message'),
404
    'file' => 'includes/commerce_checkout.checkout_pane.inc',
405
    'base' => 'commerce_checkout_completion_message_pane',
406
    'page' => 'complete',
407
    'fieldset' => FALSE,
408
  );
409

    
410
  return $checkout_panes;
411
}
412

    
413
/**
414
 * Returns an array of checkout pages defined by enabled modules.
415
 *
416
 * @return
417
 *   An associative array of checkout page objects keyed by the page_id.
418
 */
419
function commerce_checkout_pages() {
420
  $checkout_pages = &drupal_static(__FUNCTION__);
421

    
422
  // If the checkout pages haven't been defined yet, do so now.
423
  if (empty($checkout_pages)) {
424
    $checkout_pages = module_invoke_all('commerce_checkout_page_info');
425
    drupal_alter('commerce_checkout_page_info', $checkout_pages);
426

    
427
    $count = 0;
428
    foreach ($checkout_pages as $page_id => $checkout_page) {
429
      $defaults = array(
430
        'page_id' => $page_id,
431
        'name' => $checkout_page['title'],
432
        'title' => '',
433
        'help' => '',
434
        'status_cart' => TRUE,
435
        'buttons' => TRUE,
436
        'back_value' => t('Go back'),
437
        'submit_value' => t('Continue to next step'),
438
        'prev_page' => NULL,
439
        'next_page' => NULL,
440
      );
441

    
442
      $checkout_pages[$page_id] += $defaults;
443

    
444
      // Set a weight that preserves the order of 0 weighted pages.
445
      if (empty($checkout_page['weight'])) {
446
        $checkout_pages[$page_id]['weight'] = $count++ / count($checkout_pages);
447
      }
448
    }
449

    
450
    uasort($checkout_pages, 'drupal_sort_weight');
451

    
452
    // Initialize the previous and next pages.
453
    $previous_page_id = NULL;
454

    
455
    foreach ($checkout_pages as &$checkout_page) {
456
      // Look for any checkout panes assigned to this page.
457
      $checkout_panes = commerce_checkout_panes(array('page' => $checkout_page['page_id'], 'enabled' => TRUE));
458

    
459
      // If this is the completion page or at least one pane was found...
460
      if ($checkout_page['page_id'] == 'complete' || !empty($checkout_panes)) {
461
        // If a page has been stored as the previous page...
462
        if ($previous_page_id) {
463
          // Set the current page's previous page and the previous page's next.
464
          $checkout_page['prev_page'] = $previous_page_id;
465
          $checkout_pages[$previous_page_id]['next_page'] = $checkout_page['page_id'];
466
        }
467

    
468
        // Set the current page as the previous page for the next iteration.
469
        $previous_page_id = $checkout_page['page_id'];
470
      }
471
    }
472
  }
473

    
474
  return $checkout_pages;
475
}
476

    
477
/**
478
 * Returns the page ID of the first checkout page sorted by weight.
479
 */
480
function commerce_checkout_first_checkout_page() {
481
  return key(commerce_checkout_pages());
482
}
483

    
484
/**
485
 * Returns a checkout page object.
486
 *
487
 * @param $page_id
488
 *   The ID of the page to return.
489
 *
490
 * @return
491
 *   The fully loaded page object or FALSE if not found.
492
 */
493
function commerce_checkout_page_load($page_id) {
494
  $checkout_pages = commerce_checkout_pages();
495

    
496
  // If a page was specified that does not exist, return FALSE.
497
  if (empty($checkout_pages[$page_id])) {
498
    return FALSE;
499
  }
500

    
501
  // Otherwise, return the specified page.
502
  return $checkout_pages[$page_id];
503
}
504

    
505
/**
506
 * Return a filtered array of checkout pane objects.
507
 *
508
 * @param $conditions
509
 *   An array of conditions to filter the returned list by; for example, if you
510
 *   specify 'enabled' => TRUE in the array, then only checkout panes with an
511
 *   enabled value equal to TRUE would be included.
512
 * @param $skip_saved_data
513
 *   A boolean that will allow the retrieval of checkout panes directly from
514
 *   code. Specifically, it will skip the cache and also prevent configured
515
 *   checkout panes data from merging.
516
 *
517
 * @return
518
 *   The array of checkout pane objects, keyed by pane ID.
519
 */
520
function commerce_checkout_panes($conditions = array(), $skip_saved_data = FALSE) {
521
  if (!$skip_saved_data) {
522
    $checkout_panes = &drupal_static(__FUNCTION__);
523
  }
524

    
525
  // Cache the saved checkout pane data if it hasn't been loaded yet.
526
  if (!isset($checkout_panes)) {
527
    if (!$skip_saved_data) {
528
      $saved_panes = db_query('SELECT * FROM {commerce_checkout_pane}')->fetchAllAssoc('pane_id', PDO::FETCH_ASSOC);
529
    }
530

    
531
    // Load panes defined by modules.
532
    $checkout_panes = array();
533

    
534
    foreach (module_implements('commerce_checkout_pane_info') as $module) {
535
      foreach (module_invoke($module, 'commerce_checkout_pane_info') as $pane_id => $checkout_pane) {
536
        $checkout_pane['pane_id'] = $pane_id;
537
        $checkout_pane['module'] = $module;
538

    
539
        // Update the pane with saved data.
540
        if (!$skip_saved_data && !empty($saved_panes[$pane_id])) {
541
          $checkout_pane = array_merge($checkout_pane, $saved_panes[$pane_id]);
542
          $checkout_pane['saved'] = TRUE;
543
        }
544

    
545
        $checkout_panes[$pane_id] = $checkout_pane;
546
      }
547
    }
548

    
549
    drupal_alter('commerce_checkout_pane_info', $checkout_panes);
550

    
551
    // Merge in defaults.
552
    foreach ($checkout_panes as $pane_id => $checkout_pane) {
553
      // Set some defaults for the checkout pane.
554
      $defaults = array(
555
        'base' => $pane_id,
556
        'name' => $checkout_pane['title'],
557
        'page' => 'checkout',
558
        'locked' => FALSE,
559
        'fieldset' => TRUE,
560
        'collapsible' => FALSE,
561
        'collapsed' => FALSE,
562
        'weight' => 0,
563
        'enabled' => TRUE,
564
        'review' => TRUE,
565
        'callbacks' => array(),
566
        'file' => '',
567
      );
568
      $checkout_pane += $defaults;
569

    
570
      // Merge in default callbacks.
571
      foreach (array('settings_form', 'checkout_form', 'checkout_form_validate', 'checkout_form_submit', 'review') as $callback) {
572
        if (!isset($checkout_pane['callbacks'][$callback])) {
573
          $checkout_pane['callbacks'][$callback] = $checkout_pane['base'] . '_' . $callback;
574
        }
575
      }
576

    
577
      $checkout_panes[$pane_id] = $checkout_pane;
578
    }
579

    
580
    // Sort the panes by their weight value.
581
    uasort($checkout_panes, 'drupal_sort_weight');
582
  }
583

    
584
  // Apply conditions to the returned panes if specified.
585
  if (!empty($conditions)) {
586
    $matching_panes = array();
587

    
588
    foreach ($checkout_panes as $pane_id => $checkout_pane) {
589
      // Check the pane against the conditions array to determine whether to add
590
      // it to the return array or not.
591
      $valid = TRUE;
592

    
593
      foreach ($conditions as $property => $value) {
594
        // If the current value for the specified property on the pane does not
595
        // match the filter value...
596
        if ($checkout_pane[$property] != $value) {
597
          // Do not add it to the temporary array.
598
          $valid = FALSE;
599
        }
600
      }
601

    
602
      if ($valid) {
603
        $matching_panes[$pane_id] = $checkout_pane;
604
      }
605
    }
606

    
607
    return $matching_panes;
608
  }
609

    
610
  return $checkout_panes;
611
}
612

    
613
/**
614
 * Resets the cached list of checkout panes.
615
 */
616
function commerce_checkout_panes_reset() {
617
  $checkout_panes = &drupal_static('commerce_checkout_panes');
618
  $checkout_panes = NULL;
619
}
620

    
621
/**
622
 * Saves a checkout pane's configuration to the database.
623
 *
624
 * @param $checkout_pane
625
 *   The fully loaded checkout pane object.
626
 *
627
 * @return
628
 *   The return value of the call to drupal_write_record() to save the checkout
629
 *     pane, either FALSE on failure or SAVED_NEW or SAVED_UPDATED indicating
630
 *     the type of query performed to save the checkout pane.
631
 */
632
function commerce_checkout_pane_save($checkout_pane) {
633
  return drupal_write_record('commerce_checkout_pane', $checkout_pane, !empty($checkout_pane['saved']) ? 'pane_id' : array());
634
}
635

    
636
/**
637
 * Loads the data for a specific checkout pane.
638
 *
639
 * @param $pane_id
640
 *   The machine readable ID of the checkout pane.
641
 *
642
 * @return
643
 *   The requested checkout pane array or FALSE if not found.
644
 */
645
function commerce_checkout_pane_load($pane_id) {
646
  // Loads the entire list of panes.
647
  $checkout_panes = commerce_checkout_panes();
648

    
649
  // Return FALSE if the pane does not exist.
650
  if (empty($checkout_panes[$pane_id])) {
651
    return FALSE;
652
  }
653

    
654
  return $checkout_panes[$pane_id];
655
}
656

    
657
/**
658
 * Return the title of a checkout pane settings form page.
659
 *
660
 * @param $checkout_pane
661
 *   The checkout pane object represented on the settings form.
662
 */
663
function commerce_checkout_pane_settings_title($checkout_pane) {
664
  return t("'!pane' checkout pane", array('!pane' => $checkout_pane['name']));
665
}
666

    
667
/**
668
 * Resets a checkout pane by pane_id to its module defined defaults.
669
 */
670
function commerce_checkout_pane_reset($pane_id) {
671
  db_delete('commerce_checkout_pane')
672
    ->condition('pane_id', $pane_id)
673
    ->execute();
674
}
675

    
676
/**
677
 * Returns the specified callback for the given checkout pane if it's available,
678
 *   loading the checkout pane include file if specified.
679
 *
680
 * @param $checkout_pane
681
 *   The checkout pane array.
682
 * @param $callback
683
 *   The callback function to return, one of:
684
 *   - settings_form
685
 *   - checkout_form
686
 *   - checkout_form_validate
687
 *   - checkout_form_submit
688
 *   - review
689
 *
690
 * @return
691
 *   A string containing the name of the callback function or FALSE if it could
692
 *     not be found.
693
 */
694
function commerce_checkout_pane_callback($checkout_pane, $callback) {
695
  // Include the checkout pane file if specified.
696
  if (!empty($checkout_pane['file'])) {
697
    $parts = explode('.', $checkout_pane['file']);
698
    module_load_include(array_pop($parts), $checkout_pane['module'], implode('.', $parts));
699
  }
700

    
701
  // If the specified callback function exists, return it.
702
  if (!empty($checkout_pane['callbacks'][$callback]) &&
703
      function_exists($checkout_pane['callbacks'][$callback])) {
704
    return $checkout_pane['callbacks'][$callback];
705
  }
706

    
707
  // Otherwise return FALSE.
708
  return FALSE;
709
}
710

    
711
/**
712
 * Checks the current user's access to the specified checkout page and order.
713
 *
714
 * @param $order
715
 *   The fully loaded order object represented on the checkout form.
716
 * @param $account
717
 *   Alternately provide an account object whose access to check instead of the
718
 *   current user.
719
 *
720
 * @return
721
 *   TRUE or FALSE indicating access.
722
 */
723
function commerce_checkout_access($order, $account = NULL) {
724
  global $user;
725

    
726
  // Default to the current user as the account whose access we're checking.
727
  if (empty($account)) {
728
    $account = clone($user);
729
  }
730

    
731
  // First, if this order doesn't belong to the account return FALSE.
732
  if ($account->uid) {
733
    if ($account->uid != $order->uid) {
734
      return FALSE;
735
    }
736
  }
737
  elseif (empty($_SESSION['commerce_cart_completed_orders']) ||
738
    !in_array($order->order_id, $_SESSION['commerce_cart_completed_orders'])) {
739
    // And then return FALSE if the anonymous user's session doesn't specify
740
    // this order ID.
741
    if (empty($_SESSION['commerce_cart_orders']) || !in_array($order->order_id, $_SESSION['commerce_cart_orders'])) {
742
      return FALSE;
743
    }
744
  }
745

    
746
  return TRUE;
747
}
748

    
749
/**
750
 * Checks access to a particular checkout page for a given order.
751
 *
752
 * @param $checkout_page
753
 *   The fully loaded checkout page object representing the current step in the
754
 *   checkout process.
755
 * @param $order
756
 *   The fully loaded order object represented on the checkout form.
757
 *
758
 * @return
759
 *   TRUE or FALSE indicating access.
760
 */
761
function commerce_checkout_page_access($checkout_page, $order) {
762
  // Load the order status object for the current order.
763
  $order_status = commerce_order_status_load($order->status);
764

    
765
  // If the order is not in a checkout status, return FALSE for any page but the
766
  // completion page unless the order is still a shopping cart.
767
  if ($order_status['state'] != 'checkout' && $checkout_page['page_id'] != 'complete') {
768
    if ($order_status['state'] == 'cart') {
769
      $checkout_pages = commerce_checkout_pages();
770
      $first_page = key($checkout_pages);
771

    
772
      if ($checkout_page['page_id'] != $first_page) {
773
        return FALSE;
774
      }
775
    }
776
    else {
777
      return FALSE;
778
    }
779
  }
780

    
781
  // If the order is still in checkout, only allow access to pages that it is
782
  // currently on or has previously completed.
783
  if ($order_status['state'] == 'checkout') {
784
    $status_checkout_page = commerce_checkout_page_load($order_status['checkout_page']);
785

    
786
    // This is the page the user is currently on.
787
    if ($status_checkout_page['page_id'] == $checkout_page['page_id']) {
788
      return TRUE;
789
    }
790

    
791
    // Prevent access to later steps of the checkout process.
792
    if ($checkout_page['weight'] > $status_checkout_page['weight']) {
793
      return FALSE;
794
    }
795

    
796
    // Check that there are back buttons in every pages between the current
797
    // page and the page the user wants to access.
798
    $pages = commerce_checkout_pages();
799
    $current_page = $status_checkout_page;
800
    do {
801
      if (!$current_page['buttons']) {
802
        return FALSE;
803
      }
804

    
805
      // Look for a previous page ID.
806
      $prev_page_id = $current_page['prev_page'];
807

    
808
      // If we cannot find one, do not allow access to the current page, causing
809
      // a redirect to the order's current status. This edge case will occur
810
      // when a checkout page is removed and a user tries to access an earlier
811
      // page for an order that was set to the removed page's status.
812
      if (empty($prev_page_id)) {
813
        return FALSE;
814
      }
815

    
816
      $current_page = $pages[$prev_page_id];
817
    }
818
    while ($current_page['page_id'] != $checkout_page['page_id']);
819
  }
820
  // We've now handled above cases where the user is trying to access a checkout
821
  // page other than the completion page for an order that is not in a checkout
822
  // status.  We then handled cases where the user is trying to access any
823
  // checkout page for orders in a checkout status.  We now turn to cases where
824
  // the user is accessing the complete page for any other order state.
825
  elseif ($checkout_page['page_id'] == 'complete') {
826
    // Don't allow completion page access for orders in the cart or canceled states.
827
    if (!commerce_checkout_complete_access($order)) {
828
      return FALSE;
829
    }
830
  }
831

    
832
  return TRUE;
833
}
834

    
835
/**
836
 * Determines if the given order should have access to the checkout completion
837
 * page based on its order status.
838
 *
839
 * @param $order
840
 *   The fully loaded order object.
841
 *
842
 * @return
843
 *   TRUE or FALSE indicating completion access.
844
 */
845
function commerce_checkout_complete_access($order) {
846
  // Load the order state array.
847
  $order_status = commerce_order_status_load($order->status);
848
  $order_state = commerce_order_state_load($order_status['state']);
849

    
850
  // Check for the checkout_complete property.
851
  return !empty($order_state['checkout_complete']);
852
}
853

    
854
/**
855
 * Implements hook_commerce_order_state_info_alter().
856
 *
857
 * Adds the checkout_complete property to order state info arrays to determine
858
 * access to the completion page for orders in the given state. By default,
859
 * neither orders in the canceled or cart states can view the completion page.
860
 */
861
function commerce_checkout_commerce_order_state_info_alter(&$order_states) {
862
  foreach ($order_states as $name => &$order_state) {
863
    if (in_array($name, array('canceled', 'cart'))) {
864
      $order_state['checkout_complete'] = FALSE;
865
    }
866
    else {
867
      $order_state['checkout_complete'] = TRUE;
868
    }
869
  }
870
}
871

    
872
/**
873
 * Returns the current checkout URI for the given order.
874
 *
875
 * @param $order
876
 *   The fully loaded order object.
877
 *
878
 * @return
879
 *   A string containing the URI to the order's current checkout page or NULL if
880
 *   the order is in a state that does not have a valid checkout URL.
881
 */
882
function commerce_checkout_order_uri($order) {
883
  $order_status = commerce_order_status_load($order->status);
884

    
885
  if ($order_status['state'] == 'checkout') {
886
    $page_id = '/' . $order_status['checkout_page'];
887
  }
888
  elseif ($order_status['state'] == 'cart') {
889
    $page_id = '';
890
  }
891
  elseif (commerce_checkout_complete_access($order)) {
892
    $page_id = '/complete';
893
  }
894
  else {
895
    return NULL;
896
  }
897

    
898
  return 'checkout/' . $order->order_id . $page_id;
899
}
900

    
901
/**
902
 * Determines whether or not the given order can proceed to checkout.
903
 *
904
 * This function operates as a confirmation rather than a falsification, meaning
905
 * that any module implementing hook_commerce_checkout_order_can_checkout() can
906
 * confirm the order may proceed to checkout.
907
 *
908
 * The default core implementation is in commerce_product_reference.module and
909
 * allows any order containing a product line item to proceed to checkout.
910
 *
911
 * More complex logic can be implemented via hook_commerce_checkout_router().
912
 *
913
 * @param $order
914
 *   The order being confirmed for checkout.
915
 *
916
 * @return
917
 *   Boolean value indicating whether or not the order can proceed to checkout.
918
 *
919
 * @see commerce_product_reference_commerce_checkout_order_can_checkout()
920
 */
921
function commerce_checkout_order_can_checkout($order) {
922
  $proceed = FALSE;
923

    
924
  // Manually invoke hook functions to use the bitwise operator that will update
925
  // the return value to TRUE if any implementation returns TRUE and ignore any
926
  // FALSE return values if it is already set to TRUE.
927
  foreach (module_implements('commerce_checkout_order_can_checkout') as $module) {
928
    $function = $module . '_commerce_checkout_order_can_checkout';
929
    $proceed |= $function($order);
930
  }
931

    
932
  return $proceed;
933
}
934

    
935
/**
936
 * Completes the checkout process for the given order.
937
 */
938
function commerce_checkout_complete($order) {
939
  rules_invoke_all('commerce_checkout_complete', $order);
940
}
941

    
942
/**
943
 * Creates a new user account with the specified parameters and notification.
944
 *
945
 * @param $name
946
 *   The new account username.
947
 * @param $mail
948
 *   The e-mail address associated with the new account.
949
 * @param $pass
950
 *   The new account password. If left empty, a password will be generated.
951
 * @param $status
952
 *   TRUE or FALSE indicating the active / blocked status of the account.
953
 * @param $notify
954
 *   TRUE or FALSE indicating whether or not to e-mail the new account details
955
 *     to the user.
956
 *
957
 * @return
958
 *   The account user object.
959
 */
960
function commerce_checkout_create_account($name, $mail, $pass, $status, $notify = FALSE) {
961
  // Setup the account fields array and save it as a new user.
962
  $fields = array(
963
    'name' => $name,
964
    'mail' => $mail,
965
    'init' => $mail,
966
    'pass' => empty($pass) ? user_password(variable_get('commerce_password_length', 8)) : $pass,
967
    'roles' => array(),
968
    'status' => $status,
969
  );
970
  $account = user_save('', $fields);
971

    
972
  // Manually set the password so it appears in the e-mail.
973
  $account->password = $fields['pass'];
974

    
975
  // Send the customer their account details if enabled.
976
  if ($notify) {
977
    // Send the e-mail through the user module.
978
    drupal_mail('user', 'register_no_approval_required', $mail, NULL, array('account' => $account), commerce_email_from());
979
  }
980

    
981
  return $account;
982
}
983

    
984
/**
985
 * Returns the default value for the checkout completion message settings form.
986
 */
987
function commerce_checkout_completion_message_default() {
988
  // If the Filtered HTML text format is available, use a default value with
989
  // links in it.
990
  if (filter_format_load('filtered_html')) {
991
    $value = 'Your order number is [commerce-order:order-number]. You can <a href="[commerce-order:url]">view your order</a> on your account page when logged in.'
992
      . "\n\n" . '<a href="[site:url]">Return to the front page.</a>';
993
    $format = 'filtered_html';
994
  }
995
  else {
996
    // Otherwise select a fallback format and use a plaint text default value.
997
    $value = 'Your order number is [commerce-order:order-number]. You can view your order on your account page when logged in.';
998
    $format = filter_fallback_format();
999
  }
1000

    
1001
  return array('value' => $value, 'format' => $format);
1002
}