Projet

Général

Profil

Paste
Télécharger (48,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / order / commerce_order.module @ dbb0c974

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines the core Commerce order entity and API functions to manage orders and
6
 * interact with them.
7
 */
8

    
9
/**
10
 * Implements hook_entity_info().
11
 */
12
function commerce_order_entity_info() {
13
  $return = array(
14
    'commerce_order' => array(
15
      'label' => t('Commerce Order', array(), array('context' => 'a drupal commerce order')),
16
      'controller class' => 'CommerceOrderEntityController',
17
      'locking mode' => 'pessimistic',
18
      'base table' => 'commerce_order',
19
      'revision table' => 'commerce_order_revision',
20
      'load hook' => 'commerce_order_load',
21
      'uri callback' => 'commerce_order_uri',
22
      'label callback' => 'commerce_order_label',
23
      'fieldable' => TRUE,
24
      'entity keys' => array(
25
        'id' => 'order_id',
26
        'revision' => 'revision_id',
27
        'bundle' => 'type',
28
      ),
29
      'bundle keys' => array(
30
        'bundle' => 'type',
31
      ),
32
      'bundles' => array(
33
        'commerce_order' => array(
34
          'label' => t('Order', array(), array('context' => 'a drupal commerce order')),
35
        ),
36
      ),
37
      'view modes' => array(
38
        'administrator' => array(
39
          'label' => t('Administrator'),
40
          'custom settings' => FALSE,
41
        ),
42
        'customer' => array(
43
          'label' => t('Customer'),
44
          'custom settings' => FALSE,
45
        ),
46
      ),
47
      'token type' => 'commerce-order',
48
      'metadata controller class' => '',
49
      'access callback' => 'commerce_entity_access',
50
      'access arguments' => array(
51
        'user key' => 'uid',
52
        'access tag' => 'commerce_order_access',
53
      ),
54
      'permission labels' => array(
55
        'singular' => t('order'),
56
        'plural' => t('orders'),
57
      ),
58

    
59
      // // Prevent Redirect alteration of the order form.
60
      'redirect' => FALSE,
61
    ),
62
  );
63

    
64
  return $return;
65
}
66

    
67
/**
68
 * Entity uri callback: gives modules a chance to specify a path for an order.
69
 */
70
function commerce_order_uri($order) {
71
  // Allow modules to specify a path, returning the first one found.
72
  foreach (module_implements('commerce_order_uri') as $module) {
73
    $uri = module_invoke($module, 'commerce_order_uri', $order);
74

    
75
    // If the implementation returned data, use that now.
76
    if (!empty($uri)) {
77
      return $uri;
78
    }
79
  }
80

    
81
  return NULL;
82
}
83

    
84
/**
85
 * Entity label callback: returns the label for an individual order.
86
 */
87
function commerce_order_label($entity, $entity_type) {
88
  return t('Order @number', array('@number' => $entity->order_number));
89
}
90

    
91
/**
92
 * Implements hook_hook_info().
93
 */
94
function commerce_order_hook_info() {
95
  $hooks = array(
96
    'commerce_order_state_info' => array(
97
      'group' => 'commerce',
98
    ),
99
    'commerce_order_state_info_alter' => array(
100
      'group' => 'commerce',
101
    ),
102
    'commerce_order_status_info' => array(
103
      'group' => 'commerce',
104
    ),
105
    'commerce_order_status_info_alter' => array(
106
      'group' => 'commerce',
107
    ),
108
    'commerce_order_uri' => array(
109
      'group' => 'commerce',
110
    ),
111
    'commerce_order_view' => array(
112
      'group' => 'commerce',
113
    ),
114
    'commerce_order_presave' => array(
115
      'group' => 'commerce',
116
    ),
117
    'commerce_order_update' => array(
118
      'group' => 'commerce',
119
    ),
120
    'commerce_order_insert' => array(
121
      'group' => 'commerce',
122
    ),
123
    'commerce_order_delete' => array(
124
      'group' => 'commerce',
125
    ),
126
  );
127
  return $hooks;
128
}
129

    
130
/**
131
 * Implements hook_enable().
132
 */
133
function commerce_order_enable() {
134
  commerce_order_configure_order_type();
135
}
136

    
137
/**
138
 * Implements hook_modules_enabled().
139
 */
140
function commerce_order_modules_enabled($modules) {
141
  commerce_order_configure_order_fields($modules);
142

    
143
  // Reset the order state and status static caches in case a newly enabled
144
  // module has provided new states or statuses.
145
  commerce_order_states_reset();
146
  commerce_order_statuses_reset();
147
}
148

    
149
/**
150
 * Ensures the line item field is present on the default order bundle.
151
 */
152
function commerce_order_configure_order_type($type = 'commerce_order') {
153
  // Look for or add a line item reference field to the order type.
154
  $field_name = 'commerce_line_items';
155
  commerce_activate_field($field_name);
156
  field_cache_clear();
157

    
158
  $field = field_info_field($field_name);
159
  $instance = field_info_instance('commerce_order', $field_name, $type);
160

    
161
  if (empty($field)) {
162
    $field = array(
163
      'field_name' => $field_name,
164
      'type' => 'commerce_line_item_reference',
165
      'cardinality' => FIELD_CARDINALITY_UNLIMITED,
166
      'entity_types' => array('commerce_order'),
167
      'translatable' => FALSE,
168
      'locked' => TRUE,
169
    );
170
    $field = field_create_field($field);
171
  }
172

    
173
  if (empty($instance)) {
174
    $instance = array(
175
      'field_name' => $field_name,
176
      'entity_type' => 'commerce_order',
177
      'bundle' => $type,
178
      'label' => t('Line items'),
179
      'settings' => array(),
180
      'widget' => array(
181
        'type' => 'commerce_line_item_manager',
182
        'weight' => -10,
183
      ),
184
      'display' => array(),
185
    );
186

    
187
    // Set the default display formatters for various view modes.
188
    foreach (array('default', 'customer', 'administrator') as $view_mode) {
189
      $instance['display'][$view_mode] = array(
190
        'label' => 'hidden',
191
        'type' => 'commerce_line_item_reference_view',
192
        'weight' => -10,
193
      );
194
    }
195

    
196
    field_create_instance($instance);
197
  }
198

    
199
  // Add the order total price field.
200
  commerce_price_create_instance('commerce_order_total', 'commerce_order', $type, t('Order total'), -8, FALSE, array('type' => 'commerce_price_formatted_components'));
201

    
202
  // Add the customer profile reference fields for each profile type.
203
  foreach (commerce_customer_profile_types() as $customer_profile_type => $profile_type) {
204
    commerce_order_configure_customer_profile_type($customer_profile_type, $profile_type['name'], $type);
205
  }
206
}
207

    
208
/**
209
 * Configure the customer profile reference fields for the specified order type.
210
 *
211
 * @param $customer_profile_type
212
 *   The machine-name of the customer profile type to reference.
213
 * @param $label
214
 *   The label to use for the profile type's reference field.
215
 * @param $order_type
216
 *   The machine-name of the order type to add fields to.
217
 */
218
function commerce_order_configure_customer_profile_type($customer_profile_type, $label, $order_type = 'commerce_order') {
219
  // Add the customer profile reference fields for each profile type.
220
  $field_name = 'commerce_customer_' . $customer_profile_type;
221

    
222
  // First check to ensure this field doesn't already exist and was just inactive
223
  // because of the profile defining module being disabled previously.
224
  commerce_activate_field($field_name);
225
  field_cache_clear();
226

    
227
  $field = field_info_field($field_name);
228
  $instance = field_info_instance('commerce_order', $field_name, $order_type);
229

    
230
  if (empty($field)) {
231
    $field = array(
232
      'field_name' => $field_name,
233
      'type' => 'commerce_customer_profile_reference',
234
      'cardinality' => 1,
235
      'entity_types' => array('commerce_order'),
236
      'translatable' => FALSE,
237
      'settings' => array(
238
        'profile_type' => $customer_profile_type,
239
      ),
240
    );
241
    $field = field_create_field($field);
242
  }
243

    
244
  if (empty($instance)) {
245
    $instance = array(
246
      'field_name' => $field_name,
247
      'entity_type' => 'commerce_order',
248
      'bundle' => $order_type,
249
      'label' => $label,
250
      'widget' => array(
251
        'type' => 'commerce_customer_profile_manager',
252
        'weight' => -5,
253
      ),
254
      'display' => array(),
255
    );
256

    
257
    // Set the default display formatters for various view modes.
258
    foreach (array('default', 'customer', 'administrator') as $view_mode) {
259
      $instance['display'][$view_mode] = array(
260
        'label' => 'above',
261
        'type' => 'commerce_customer_profile_reference_display',
262
        'weight' => -5,
263
      );
264
    }
265

    
266
    field_create_instance($instance);
267

    
268
    variable_set('commerce_customer_profile_' . $customer_profile_type . '_field', $field_name);
269
  }
270
}
271

    
272
/**
273
 * Configures the customer profile reference fields attached to the default
274
 * order type when modules defining customer profile types are enabeld after the
275
 * Order module.
276
 *
277
 * @param $modules
278
 *   An array of module names whose customer profile reference fields should be
279
 *   configured; if left NULL, will default to all modules that implement
280
 *   hook_commerce_customer_profile_type_info().
281
 */
282
function commerce_order_configure_order_fields($modules = NULL) {
283
  // If no modules array is passed, recheck the customer profile reference
284
  // fields to all customer profile types defined by enabled modules.
285
  if (empty($modules)) {
286
    $modules = module_implements('commerce_customer_profile_type_info');
287
  }
288

    
289
  // Loop through all the enabled modules.
290
  foreach ($modules as $module) {
291
    // If the module implements hook_commerce_customer_profile_type_info()...
292
    if (module_hook($module, 'commerce_customer_profile_type_info')) {
293
      $profile_types = module_invoke($module, 'commerce_customer_profile_type_info');
294

    
295
      // Loop through and configure its customer profile types.
296
      foreach ($profile_types as $profile_type) {
297
        commerce_order_configure_customer_profile_type($profile_type['type'], $profile_type['name']);
298
      }
299
    }
300
  }
301
}
302

    
303
/**
304
 * Implements hook_views_api().
305
 */
306
function commerce_order_views_api() {
307
  return array(
308
    'api' => 3,
309
    'path' => drupal_get_path('module', 'commerce_order') . '/includes/views',
310
  );
311
}
312

    
313
/**
314
 * Implements hook_permission().
315
 */
316
function commerce_order_permission() {
317
  return commerce_entity_access_permissions('commerce_order') + array(
318
    'configure order settings' => array(
319
      'title' => t('Configure order settings'),
320
      'description' => t('Allows users to configure order settings for the store.'),
321
      'restrict access' => TRUE,
322
    ),
323
  );
324
}
325

    
326
/**
327
 * Implements hook_commerce_checkout_pane_info().
328
 */
329
function commerce_order_commerce_checkout_pane_info() {
330
  $checkout_panes = array();
331

    
332
  $checkout_panes['account'] = array(
333
    'title' => t('Account information'),
334
    'file' => 'includes/commerce_order.checkout_pane.inc',
335
    'base' => 'commerce_order_account_pane',
336
    'page' => 'checkout',
337
    'weight' => -5,
338
  );
339

    
340
  return $checkout_panes;
341
}
342

    
343
/**
344
 * Implements hook_commerce_order_state_info().
345
 */
346
function commerce_order_commerce_order_state_info() {
347
  $order_states = array();
348

    
349
  $order_states['canceled'] = array(
350
    'name' => 'canceled',
351
    'title' => t('Canceled'),
352
    'description' => t('Orders in this state have been canceled through some user action.'),
353
    'weight' => -10,
354
    'default_status' => 'canceled',
355
  );
356
  $order_states['pending'] = array(
357
    'name' => 'pending',
358
    'title' => t('Pending'),
359
    'description' => t('Orders in this state have been created and are awaiting further action.'),
360
    'weight' => 0,
361
    'default_status' => 'pending',
362
  );
363
  $order_states['completed'] = array(
364
    'name' => 'completed',
365
    'title' => t('Completed'),
366
    'description' => t('Orders in this state have been completed as far as the customer is concerned.'),
367
    'weight' => 10,
368
    'default_status' => 'completed',
369
  );
370

    
371
  return $order_states;
372
}
373

    
374
/**
375
 * Implements hook_commerce_order_status_info().
376
 */
377
function commerce_order_commerce_order_status_info() {
378
  $order_statuses = array();
379

    
380
  $order_statuses['canceled'] = array(
381
    'name' => 'canceled',
382
    'title' => t('Canceled'),
383
    'state' => 'canceled',
384
  );
385

    
386
  $order_statuses['pending'] = array(
387
    'name' => 'pending',
388
    'title' => t('Pending'),
389
    'state' => 'pending',
390
  );
391
  $order_statuses['processing'] = array(
392
    'name' => 'processing',
393
    'title' => t('Processing'),
394
    'state' => 'pending',
395
    'weight' => 5,
396
  );
397

    
398
  $order_statuses['completed'] = array(
399
    'name' => 'completed',
400
    'title' => t('Completed'),
401
    'state' => 'completed',
402
  );
403

    
404
  return $order_statuses;
405
}
406

    
407
/**
408
 * Implements hook_field_attach_form().
409
 *
410
 * This function adds a property to the customer profiles stored in a form array
411
 * on order edit forms. The order module will use this to bypass considering
412
 * that order when determining if the user should be able to delete a profile
413
 * from that order's edit form.
414
 */
415
function commerce_order_field_attach_form($entity_type, $entity, &$form, &$form_state, $langcode) {
416
  // If this is an order edit form...
417
  if ($entity_type == 'commerce_order') {
418
    // Get an array of customer profile reference fields attached to products.
419
    $fields = commerce_info_fields('commerce_customer_profile_reference', 'commerce_order');
420

    
421
    // Loop over each child element of the form.
422
    foreach (element_children($form) as $key) {
423
      // If the current element is for a customer profile reference field...
424
      if (in_array($key, array_keys($fields))) {
425
        // Loop over each of its child items...
426
        foreach (element_children($form[$key][$form[$key]['#language']]) as $delta) {
427
          foreach (element_children($form[$key][$form[$key]['#language']][$delta]) as $subdelta) {
428
            // Extract the customer profile from the widget form element.
429
            $profile = $form[$key][$form[$key]['#language']][$delta][$subdelta]['profile']['#value'];
430

    
431
            // Add the order context to the profile stored in the field element.
432
            $profile->entity_context = array(
433
              'entity_type' => 'commerce_order',
434
              'entity_id' => $entity->order_id,
435
            );
436

    
437
            // Add a uid if it is empty and the order has one.
438
            if (empty($profile->uid) && !empty($entity->uid)) {
439
              $profile->uid = $entity->uid;
440
            }
441

    
442
            // If this means this profile can now be deleted and the reference
443
            // field widget includes a remove element...
444
            if ($profile->profile_id && commerce_customer_profile_can_delete($profile) &&
445
              !empty($form[$key][$form[$key]['#language']][$delta][$subdelta]['remove'])) {
446
              // Update its remove element's title accordingly.
447
              $form[$key][$form[$key]['#language']][$delta][$subdelta]['remove']['#title'] = t('Delete this profile');
448
            }
449
          }
450
        }
451
      }
452
    }
453
  }
454
}
455

    
456
/**
457
 * Implements hook_commerce_customer_profile_can_delete().
458
 */
459
function commerce_order_commerce_customer_profile_can_delete($profile) {
460
  // Look for any non-cart order with a reference field targeting the profile.
461
  foreach (commerce_info_fields('commerce_customer_profile_reference') as $field_name => $field) {
462
    // Use EntityFieldQuery to look for orders referencing this customer profile
463
    // and do not allow the delete to occur if one exists.
464
    $query = new EntityFieldQuery();
465

    
466
    $query
467
      ->addTag('commerce_order_commerce_customer_profile_can_delete')
468
      ->entityCondition('entity_type', 'commerce_order', '=')
469
      ->fieldCondition($field_name, 'profile_id', $profile->profile_id, '=')
470
      ->count();
471

    
472
    // Add a condition on the order status if there are cart order statuses.
473
    $statuses = array_keys(commerce_order_statuses(array('cart' => TRUE)));
474

    
475
    if (!empty($statuses)) {
476
      $query->propertyCondition('status', $statuses, 'NOT IN');
477
    }
478

    
479
    // If the profile includes an order context property, we know this was added
480
    // by the Order module as an order ID to skip in the deletion query.
481
    if (!empty($profile->entity_context['entity_id']) && $profile->entity_context['entity_type'] == 'commerce_order') {
482
      $query->propertyCondition('order_id', $profile->entity_context['entity_id'], '!=');
483
    }
484

    
485
    if ($query->execute() > 0) {
486
      return FALSE;
487
    }
488
  }
489

    
490
  return TRUE;
491
}
492

    
493
/**
494
 * Implements hook_commerce_customer_profile_presave().
495
 *
496
 * The Order module prevents customer profile deletion for any profile that is
497
 * referenced by a non-cart order via hook_commerce_customer_profile_can_delete().
498
 * The same logic applies when updating a customer profile to determine whether
499
 * or not the update should be allowed on the same profile or if it should be
500
 * handled as a clone of the original profile. This ensures that editing a
501
 * profile on a separate order or through the admin UI after completing an order
502
 * will not cause the original order to lose essential data.
503
 */
504
function commerce_order_commerce_customer_profile_presave($profile) {
505
  // Only perform this check when updating an existing profile.
506
  if (!empty($profile->profile_id)) {
507
    $original_profile = $profile->original;
508
    $field_change = FALSE;
509

    
510
    // Compare against the field values.
511
    foreach (field_info_instances('commerce_customer_profile', $profile->type) as $field_name => $instance) {
512
      $langcode = field_language('commerce_customer_profile', $profile, $field_name);
513

    
514
      // Make sure that empty fields have a consistent structure.
515
      if (empty($profile->{$field_name})) {
516
        $profile->{$field_name}[$langcode] = array();
517
      }
518

    
519
      if (empty($original_profile->{$field_name})) {
520
        $original_profile->{$field_name}[$langcode] = array();
521
      }
522

    
523
      // Extract the items from the field value that need to be compared.
524
      $items = $profile->{$field_name}[$langcode];
525
      $original_items = $original_profile->{$field_name}[$langcode];
526

    
527
      // If the number of items has changed, mark the field change.
528
      if (count($items) != count($original_items)) {
529
        $field_change = TRUE;
530
        break;
531
      }
532

    
533
      // Loop over each item in the field value.
534
      foreach ($items as $delta => $item) {
535
        // Find the supposedly matching original item.
536
        $original_item = $original_items[$delta];
537

    
538
        // Compare columns common to both arrays.
539
        $item = array_intersect_key($item, $original_item);
540
        $original_item = array_intersect_key($original_item, $item);
541

    
542
        if ($item != $original_item) {
543
          $field_change = TRUE;
544
          break;
545
        }
546

    
547
        // Provide special handling for the addressfield to accommodate the fact
548
        // that the field as loaded from the database may contain additional
549
        // keys that aren't included in the profile being saved because their
550
        // corresponding form elements weren't included on the edit form.
551
        // If those additional keys contain data, the field needs to be
552
        // marked as changed.
553
        if ($field_name == 'commerce_customer_address') {
554
          foreach ($original_items[$delta] as $key => $value) {
555
            if (!in_array($key, array_keys($item)) && !empty($value)) {
556
              $field_change = TRUE;
557
              break 2;
558
            }
559
          }
560
        }
561
      }
562
    }
563

    
564
    // If we did in fact detect significant changes and this profile has been
565
    // referenced by a non-cart order...
566
    if ($field_change && !commerce_order_commerce_customer_profile_can_delete($profile)) {
567
      // Update various properties of the profile so it gets saved as new.
568
      $profile->profile_id = '';
569
      $profile->revision_id = '';
570
      $profile->created = REQUEST_TIME;
571
      $profile->log = '';
572
    }
573
  }
574
}
575

    
576
/**
577
 * Implements hook_form_FORM_ID_alter().
578
 *
579
 * When a profile is being edited via its standalone form, display a message
580
 * that tells the user the profile is going to be cloned if it already being
581
 * referenced by a non-cart order.
582
 */
583
function commerce_order_form_commerce_customer_customer_profile_form_alter(&$form, &$form_state) {
584
  if (!empty($form_state['customer_profile']->profile_id) && !commerce_order_commerce_customer_profile_can_delete($form_state['customer_profile'])) {
585
    $form['clone_message'] = array(
586
      '#markup' => '<div class="messages status">' . t('Because this profile is currently referenced by an order, if you change any field values it will save as a clone of the current profile instead of updating this profile itself. This is to maintain the integrity of customer data as entered on the order(s).') . '</div>',
587
      '#weight' => -100,
588
    );
589

    
590
    // Add the original profile ID to the form state so our custom submit handler
591
    // can display a message when the clone is saved.
592
    $form_state['original_profile_id'] = $form_state['customer_profile']->profile_id;
593
    $form['actions']['submit']['#submit'][] = 'commerce_order_customer_profile_form_submit';
594
  }
595
}
596

    
597
/**
598
 * Submit callback: display a message when a profile is cloned.
599
 */
600
function commerce_order_customer_profile_form_submit($form, &$form_state) {
601
  if ($form_state['original_profile_id'] != $form_state['customer_profile']->profile_id) {
602
    drupal_set_message(t('The customer profile was cloned to prevent data loss on orders referencing the profile. It is now profile @profile_id.', array('@profile_id' => $form_state['customer_profile']->profile_id)));
603
  }
604
}
605

    
606
/**
607
 * Implements hook_form_FORM_ID_alter().
608
 *
609
 * When a profile is being deleted, display a message on the confirmation form
610
 * saying how many times the it has been referenced in all orders.
611
 */
612
function commerce_order_form_commerce_customer_customer_profile_delete_form_alter(&$form, &$form_state) {
613
  $items = array();
614

    
615
  // Check the data in every customer profile reference field.
616
  foreach (commerce_info_fields('commerce_customer_profile_reference') as $field_name => $field) {
617
    // Query for any entity referencing the deleted profile in this field.
618
    $query = new EntityFieldQuery();
619
    $query->fieldCondition($field_name, 'profile_id', $form_state['customer_profile']->profile_id, '=');
620
    $result = $query->execute();
621

    
622
    // If results were returned...
623
    if (!empty($result)) {
624
      // Loop over results for each type of entity returned.
625
      foreach ($result as $entity_type => $data) {
626
        if (($count = count($data)) > 0) {
627
          // For order references, display a message about the inability of the
628
          // customer profile to be deleted and disable the submit button.
629
          if ($entity_type == 'commerce_order') {
630
            // Load the referencing order.
631
            $order = reset($data);
632
            $order = commerce_order_load($order->order_id);
633

    
634
            // Only exit here if the order is in a non-cart status.
635
            if (!in_array($order->status, array_keys(commerce_order_statuses(array('cart' => TRUE))))) {
636
              $description = t('This customer profile is referenced by Order @order_number and therefore cannot be deleted. Disable it instead.', array('@order_number' => $order->order_number));
637

    
638
              $form['description']['#markup'] .= '<p>' . $description . '</p>';
639
              $form['actions']['submit']['#disabled'] = TRUE;
640
              return;
641
            }
642
          }
643

    
644
          // Load the entity information.
645
          $entity_info = entity_get_info($entity_type);
646

    
647
          // Add a message regarding the references.
648
          $items[] = t('@entity_label: @count', array('@entity_label' => $entity_info['label'], '@count' => $count));
649
        }
650
      }
651
    }
652
  }
653

    
654
  if (!empty($items)) {
655
    $form['description']['#markup'] .= '<p>' . t('This customer profile is referenced by the following entities: !entity_list', array('!entity_list' => theme('item_list', array('items' => $items)))) . '</p>';
656
  }
657
}
658

    
659
/**
660
 * Returns the name of the specified order type or all names keyed by type if no
661
 *   type is specified.
662
 *
663
 * For Drupal Commerce 1.0, the decision was made to support order types at the
664
 * database level but not to introduce their complexity into the UI. To that end
665
 * order "types" (i.e. bundles) may only be defined by altering the entity info.
666
 *
667
 * This function merely traverses the bundles array looking for data instead of
668
 * relying on a special hook.
669
 *
670
 * @param $type
671
 *   The order type whose name should be returned; corresponds to the bundle key
672
 *     in the order entity definition.
673
 *
674
 * @return
675
 *   Either the specified name, defaulting to the type itself if the name is not
676
 *   found, or an array of all names keyed by type if no type is passed in.
677
 */
678
function commerce_order_type_get_name($type = NULL) {
679
  $names = array();
680

    
681
  $entity = entity_get_info('commerce_order');
682

    
683
  foreach ($entity['bundles'] as $key => $value) {
684
    $names[$key] = $value['label'];
685
  }
686

    
687
  if (empty($type)) {
688
    return $names;
689
  }
690

    
691
  if (empty($names[$type])) {
692
    return check_plain($type);
693
  }
694
  else {
695
    return $names[$type];
696
  }
697
}
698

    
699
/**
700
 * Wraps commerce_order_type_get_name() for the Entity module.
701
 */
702
function commerce_order_type_options_list() {
703
  return commerce_order_type_get_name();
704
}
705

    
706
/**
707
 * Returns an initialized order object.
708
 *
709
 * @param $uid
710
 *   The uid of the owner of the order.
711
 * @param $status
712
 *   Optionally the order status of the new order.
713
 * @param $type
714
 *   The type of the order; defaults to the standard 'commerce_order' type.
715
 *
716
 * @return
717
 *   An order object with all default fields initialized.
718
 */
719
function commerce_order_new($uid = 0, $status = NULL, $type = 'commerce_order') {
720
  // If no status was specified, use the default Pending status.
721
  if (!isset($status)) {
722
    $order_state = commerce_order_state_load('pending');
723
    $status = $order_state['default_status'];
724
  }
725

    
726
  return entity_get_controller('commerce_order')->create(array(
727
    'uid' => $uid,
728
    'status' => $status,
729
    'type' => $type,
730
  ));
731
}
732

    
733
/**
734
 * Saves an order.
735
 *
736
 * @param $order
737
 *   The full order object to save. If $order->order_id is empty, a new order
738
 *     will be created.
739
 *
740
 * @return
741
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
742
 */
743
function commerce_order_save($order) {
744
  return entity_get_controller('commerce_order')->save($order);
745
}
746

    
747
/**
748
 * Loads an order by ID.
749
 */
750
function commerce_order_load($order_id) {
751
  $orders = commerce_order_load_multiple(array($order_id), array());
752
  return $orders ? reset($orders) : FALSE;
753
}
754

    
755
/**
756
 * Loads an order by number.
757
 */
758
function commerce_order_load_by_number($order_number) {
759
  $orders = commerce_order_load_multiple(array(), array('order_number' => $order_number));
760
  return $orders ? reset($orders) : FALSE;
761
}
762

    
763
/**
764
 * Loads multiple orders by ID or based on a set of matching conditions.
765
 *
766
 * @see entity_load()
767
 *
768
 * @param $order_ids
769
 *   An array of order IDs.
770
 * @param $conditions
771
 *   An array of conditions to filter loaded orders by on the {commerce_order}
772
 *   table in the form 'field' => $value. Specifying a revision_id will load the
773
 *   requested revision of the order identified either by a similar condition or
774
 *   the $order_ids array assuming the revision_id is valid for the order_id.
775
 * @param $reset
776
 *   Whether to reset the internal order loading cache.
777
 *
778
 * @return
779
 *   An array of order objects indexed by order_id.
780
 */
781
function commerce_order_load_multiple($order_ids = array(), $conditions = array(), $reset = FALSE) {
782
  return entity_load('commerce_order', $order_ids, $conditions, $reset);
783
}
784

    
785
/**
786
 * Determines whether or not the given order object represents the latest
787
 * revision of the order.
788
 *
789
 * @param $order
790
 *   A fully loaded order object.
791
 *
792
 * @return
793
 *   Boolean indicating whether or not the order object represents the latest
794
 *   revision of the order.
795
 */
796
function commerce_order_is_latest_revision($order) {
797
  $query = new EntityFieldQuery();
798
  $query
799
    ->entityCondition('entity_type', 'commerce_order', '=')
800
    ->propertyCondition('order_id', $order->order_id, '=')
801
    ->propertyCondition('revision_id', $order->revision_id, '=');
802
  $result = $query->execute();
803

    
804
  return (!empty($result)) ? TRUE : FALSE;
805
}
806

    
807
/**
808
 * Deletes an order by ID.
809
 *
810
 * @param $order_id
811
 *   The ID of the order to delete.
812
 *
813
 * @return
814
 *   TRUE on success, FALSE otherwise.
815
 */
816
function commerce_order_delete($order_id) {
817
  return commerce_order_delete_multiple(array($order_id));
818
}
819

    
820
/**
821
 * Deletes multiple orders by ID.
822
 *
823
 * @param $order_ids
824
 *   An array of order IDs to delete.
825
 *
826
 * @return
827
 *   TRUE on success, FALSE otherwise.
828
 */
829
function commerce_order_delete_multiple($order_ids) {
830
  return entity_get_controller('commerce_order')->delete($order_ids);
831
}
832

    
833
/**
834
 * Checks order access for various operations.
835
 *
836
 * @param $op
837
 *   The operation being performed. One of 'view', 'update', 'create' or
838
 *   'delete'.
839
 * @param $order
840
 *   Optionally an order to check access for.
841
 * @param $account
842
 *   The user to check for. Leave it to NULL to check for the current user.
843
 */
844
function commerce_order_access($op, $order = NULL, $account = NULL) {
845
  return commerce_entity_access($op, $order, $account, 'commerce_order');
846
}
847

    
848
/**
849
 * Implements hook_query_TAG_alter().
850
 */
851
function commerce_order_query_commerce_order_access_alter(QueryAlterableInterface $query) {
852
  // Look for an order base table to pass to the query altering function or else
853
  // assume we don't have the tables we need to establish order related altering
854
  // right now.
855
  foreach ($query->getTables() as $table) {
856
    if ($table['table'] === 'commerce_order') {
857
      commerce_entity_access_query_alter($query, 'commerce_order', $table['alias']);
858
      break;
859
    }
860
  }
861
}
862

    
863
/**
864
 * Rules integration access callback.
865
 */
866
function commerce_order_rules_access($type, $name) {
867
  if ($type == 'event' || $type == 'condition') {
868
    return commerce_order_access('view');
869
  }
870
}
871

    
872
/**
873
 * Implements hook_commerce_order_insert().
874
 */
875
function commerce_order_commerce_order_insert($order) {
876
  // Save the order number.
877
  // TODO: Provide token support for order number patterns.
878

    
879
  if (empty($order->order_number)) {
880
    $order->order_number = $order->order_id;
881

    
882
    db_update('commerce_order')
883
      ->fields(array('order_number' => $order->order_number))
884
      ->condition('order_id', $order->order_id)
885
      ->execute();
886
    db_update('commerce_order_revision')
887
      ->fields(array('order_number' => $order->order_number))
888
      ->condition('order_id', $order->order_id)
889
      ->execute();
890
  }
891
}
892

    
893
/**
894
 * Implements hook_commerce_line_item_access().
895
 *
896
 * Line items have order_id properties, but since there is no dependency from
897
 * the Line Item module to Order, we perform access checks for line items
898
 * attached to orders through this hook.
899
 */
900
function commerce_order_commerce_line_item_access($op, $line_item, $account) {
901
  // If the line item references an order...
902
  if ($order = commerce_order_load($line_item->order_id)) {
903
    // Return the account's access to update the order.
904
    return commerce_order_access('update', $order, $account);
905
  }
906
}
907

    
908
/**
909
 * Performs token replacement on an order number for valid tokens only.
910
 *
911
 * TODO: This function currently limits acceptable Tokens to Order ID with no
912
 * ability to use Tokens for the Fields attached to the order. That might be
913
 * fine for a core Token replacement, but we should at least open the
914
 * $valid_tokens array up to other modules to enable various Tokens for use.
915
 *
916
 * @param $order_number
917
 *   The raw order number string including any tokens as entered.
918
 * @param $order
919
 *   An order object used to perform token replacement on the number.
920
 *
921
 * @return
922
 *   The number with tokens replaced or FALSE if it included invalid tokens.
923
 */
924
function commerce_order_replace_number_tokens($order_number, $order) {
925
  // Build an array of valid order number tokens.
926
  $valid_tokens = array('order-id');
927

    
928
  // Ensure that only valid tokens were used.
929
  $invalid_tokens = FALSE;
930

    
931
  foreach (token_scan($order_number) as $type => $token) {
932
    if ($type !== 'order') {
933
      $invalid_tokens = TRUE;
934
    }
935
    else {
936
      foreach (array_keys($token) as $value) {
937
        if (!in_array($value, $valid_tokens)) {
938
          $invalid_tokens = TRUE;
939
        }
940
      }
941
    }
942
  }
943

    
944
  // Register the error if an invalid token was detected.
945
  if ($invalid_tokens) {
946
    return FALSE;
947
  }
948

    
949
  return $order_number;
950
}
951

    
952
/**
953
 * Validates an order number string for acceptable characters.
954
 *
955
 * @param $order_number
956
 *   The order number string to validate.
957
 *
958
 * @return
959
 *   TRUE or FALSE indicating whether or not the order number contains valid
960
 *     characters.
961
 */
962
function commerce_order_validate_number_characters($order_number) {
963
  return preg_match('!^[A-Za-z0-9_-]+$!', $order_number);
964
}
965

    
966
/**
967
 * Checks to see if a given order number already exists for another order.
968
 *
969
 * @param $order_number
970
 *   The string to match against existing order numbers.
971
 * @param $order_id
972
 *   The ID of the order the number is for; an empty value represents the number
973
 *     is meant for a new order.
974
 *
975
 * @return
976
 *   TRUE or FALSE indicating whether or not the number exists for another
977
 *     order.
978
 */
979
function commerce_order_validate_number_unique($order_number, $order_id) {
980
  // Look for an ID of an order matching the supplied number.
981
  if ($match_id = db_query('SELECT order_id FROM {commerce_order} WHERE order_number = :order_number', array(':order_number' => $order_number))->fetchField()) {
982
    // If this number is supposed to be for a new order or an order other than
983
    // the one that matched...
984
    if (empty($order_id) || $match_id != $order_id) {
985
      return FALSE;
986
    }
987
  }
988

    
989
  return TRUE;
990
}
991

    
992
/**
993
 * Returns an array of all the order states keyed by name.
994
 *
995
 * Order states can only be defined by modules but may have settings overridden
996
 * that are stored in the database (weight and the default status). When this
997
 * function is first called, it will load all the states as defined by
998
 * hook_commerce_order_state_info() and update them based on the data in the
999
 * database. The final array will be cached for subsequent calls.
1000
 */
1001
function commerce_order_states() {
1002
  // First check the static cache for an order states array.
1003
  $order_states = &drupal_static(__FUNCTION__);
1004

    
1005
  // If it did not exist, fetch the statuses now.
1006
  if (empty($order_states)) {
1007
    $order_states = module_invoke_all('commerce_order_state_info');
1008

    
1009
    // Give other modules a chance to alter the order states.
1010
    drupal_alter('commerce_order_state_info', $order_states);
1011

    
1012
    uasort($order_states, 'drupal_sort_weight');
1013
  }
1014

    
1015
  return $order_states;
1016
}
1017

    
1018
/**
1019
 * Resets the cached list of order states.
1020
 */
1021
function commerce_order_states_reset() {
1022
  $order_states = &drupal_static('commerce_order_states');
1023
  $order_states = NULL;
1024
}
1025

    
1026
/**
1027
 * Returns an order state object.
1028
 *
1029
 * @param $name
1030
 *   The machine readable name string of the state to return.
1031
 *
1032
 * @return
1033
 *   The fully loaded state object or FALSE if not found.
1034
 */
1035
function commerce_order_state_load($name) {
1036
  $order_states = commerce_order_states();
1037

    
1038
  if (isset($order_states[$name])) {
1039
    return $order_states[$name];
1040
  }
1041

    
1042
  return FALSE;
1043
}
1044

    
1045
/**
1046
 * Returns the human readable title of any or all order states.
1047
 *
1048
 * @param $name
1049
 *   Optional parameter specifying the name of the order state whose title
1050
 *     should be return.
1051
 *
1052
 * @return
1053
 *   Either an array of all order state titles keyed by name or a string
1054
 *     containing the human readable title for the specified state. If a state
1055
 *     is specified that does not exist, this function returns FALSE.
1056
 */
1057
function commerce_order_state_get_title($name = NULL) {
1058
  $order_states = commerce_order_states();
1059

    
1060
  // Return a state title if specified and it exists.
1061
  if (!empty($name)) {
1062
    if (isset($order_states[$name])) {
1063
      return $order_states[$name]['title'];
1064
    }
1065
    else {
1066
      // Return FALSE if it does not exist.
1067
      return FALSE;
1068
    }
1069
  }
1070

    
1071
  // Otherwise turn the array values into the status title only.
1072
  foreach ($order_states as $key => $value) {
1073
    $order_states[$key] = $value['title'];
1074
  }
1075

    
1076
  return $order_states;
1077
}
1078

    
1079
/**
1080
 * Wraps commerce_order_state_get_title() for use by the Entity module.
1081
 */
1082
function commerce_order_state_options_list() {
1083
  return commerce_order_state_get_title();
1084
}
1085

    
1086
/**
1087
 * Returns an array of some or all of the order statuses keyed by name.
1088
 *
1089
 * @param $conditions
1090
 *   An array of conditions to filter the returned list by; for example, if you
1091
 *   specify 'state' => 'cart' in the array, then only order statuses in the
1092
 *   cart state would be included.
1093
 *
1094
 * @return
1095
 *   The array of order status objects, keyed by status name.
1096
 */
1097
function commerce_order_statuses($conditions = array()) {
1098
  // First check the static cache for an order statuses array.
1099
  $order_statuses = &drupal_static(__FUNCTION__);
1100

    
1101
  // If it did not exist, fetch the statuses now.
1102
  if (!isset($order_statuses)) {
1103
    $order_statuses = module_invoke_all('commerce_order_status_info');
1104

    
1105
    // Merge in defaults.
1106
    foreach ($order_statuses as $name => $order_status) {
1107
      // Set some defaults for the checkout pane.
1108
      $defaults = array(
1109
        'cart' => FALSE,
1110
        'weight' => 0,
1111
        'status' => TRUE,
1112
      );
1113
      $order_status += $defaults;
1114

    
1115
      $order_statuses[$name] = $order_status;
1116
    }
1117

    
1118
    // Give other modules a chance to alter the order statuses.
1119
    drupal_alter('commerce_order_status_info', $order_statuses);
1120

    
1121
    uasort($order_statuses, 'drupal_sort_weight');
1122
  }
1123

    
1124
  // Apply conditions to the returned statuses if specified.
1125
  if (!empty($conditions)) {
1126
    $matching_statuses = array();
1127

    
1128
    foreach ($order_statuses as $name => $order_status) {
1129
      // Check the status against the conditions array to determine whether to
1130
      // add it to the return array or not.
1131
      $valid = TRUE;
1132

    
1133
      foreach ($conditions as $property => $value) {
1134
        // If the current value for the specified property on the status does
1135
        // not match the filter value...
1136
        if ($order_status[$property] != $value) {
1137
          // Do not add it to the temporary array.
1138
          $valid = FALSE;
1139
        }
1140
      }
1141

    
1142
      if ($valid) {
1143
        $matching_statuses[$name] = $order_status;
1144
      }
1145
    }
1146

    
1147
    return $matching_statuses;
1148
  }
1149

    
1150
  return $order_statuses;
1151
}
1152

    
1153
/**
1154
 * Resets the cached list of order statuses.
1155
 */
1156
function commerce_order_statuses_reset() {
1157
  $order_statuses = &drupal_static('commerce_order_statuses');
1158
  $order_statuses = NULL;
1159
}
1160

    
1161
/**
1162
 * Returns an order status object.
1163
 *
1164
 * @param $name
1165
 *   The machine readable name string of the status to return.
1166
 *
1167
 * @return
1168
 *   The fully loaded status object or FALSE if not found.
1169
 */
1170
function commerce_order_status_load($name) {
1171
  $order_statuses = commerce_order_statuses();
1172

    
1173
  if (isset($order_statuses[$name])) {
1174
    return $order_statuses[$name];
1175
  }
1176

    
1177
  return FALSE;
1178
}
1179

    
1180
/**
1181
 * Returns the human readable title of any or all order statuses.
1182
 *
1183
 * @param $name
1184
 *   Optional parameter specifying the name of the order status whose title
1185
 *     to return.
1186
 *
1187
 * @return
1188
 *   Either an array of all order status titles keyed by the status_id or a
1189
 *     string containing the human readable title for the specified status. If a
1190
 *     status is specified that does not exist, this function returns FALSE.
1191
 */
1192
function commerce_order_status_get_title($name = NULL) {
1193
  $order_statuses = commerce_order_statuses();
1194

    
1195
  // Return a status title if specified and it exists.
1196
  if (!empty($name)) {
1197
    if (isset($order_statuses[$name])) {
1198
      return $order_statuses[$name]['title'];
1199
    }
1200
    else {
1201
      // Return FALSE if it does not exist.
1202
      return FALSE;
1203
    }
1204
  }
1205

    
1206
  // Otherwise turn the array values into the status title only.
1207
  foreach ($order_statuses as $key => $value) {
1208
    $order_statuses[$key] = $value['title'];
1209
  }
1210

    
1211
  return $order_statuses;
1212
}
1213

    
1214
/**
1215
 * Wraps commerce_order_status_get_title() for use by the Entity module.
1216
 */
1217
function commerce_order_status_options_list() {
1218
  // Build an array of order status options grouped by order state.
1219
  $options = array();
1220

    
1221
  foreach (commerce_order_state_get_title() as $name => $title) {
1222
    foreach (commerce_order_statuses(array('state' => $name)) as $order_status) {
1223
      $options[check_plain($title)][$order_status['name']] = check_plain($order_status['title']);
1224
    }
1225
  }
1226

    
1227
  return $options;
1228
}
1229

    
1230
/**
1231
 * Updates the status of an order to the specified status.
1232
 *
1233
 * While there is no explicit Rules event or hook devoted to an order status
1234
 * being updated, you can use the commerce_order_update event / hook to check
1235
 * for a changed order status by comparing $order->original->status to the
1236
 * $order->status. If they are different, this will alert you that the order
1237
 * status for the given order was just changed.
1238
 *
1239
 * @param $order
1240
 *   The fully loaded order object to update.
1241
 * @param $name
1242
 *   The machine readable name string of the status to update to.
1243
 * @param $skip_save
1244
 *   TRUE to skip saving the order after updating the status; used when the
1245
 *     order would be saved elsewhere after the update.
1246
 * @param $revision
1247
 *   TRUE or FALSE indicating whether or not a new revision should be created
1248
 *   for the order if it is saved as part of the status update. If missing or
1249
 *   NULL, the value configured in "Order settings" is used.
1250
 * @param $log
1251
 *   If a new revision is created for the update, the log message that will be
1252
 *     used for the revision.
1253
 *
1254
 * @return
1255
 *   The updated order.
1256
 */
1257
function commerce_order_status_update($order, $name, $skip_save = FALSE, $revision = NULL, $log = '') {
1258
  if (!isset($revision)) {
1259
    $revision = variable_get('commerce_order_auto_revision', TRUE);
1260
  }
1261

    
1262
  // Do not update the status if the order is already at it.
1263
  if ($order->status != $name) {
1264
    $order->status = $name;
1265

    
1266
    if (!$skip_save) {
1267
      // If the status update should create a new revision, update the order
1268
      // object to reflect this and include a log message.
1269
      if ($revision) {
1270
        $order->revision = TRUE;
1271
        $order->log = $log;
1272
      }
1273

    
1274
      commerce_order_save($order);
1275
    }
1276
  }
1277

    
1278
  return $order;
1279
}
1280

    
1281
/**
1282
 * Calculates the order total, updating the commerce_order_total field data in
1283
 * the order object this function receives.
1284
 *
1285
 * @param $order
1286
 *   The order object whose order total should be calculated.
1287
 *
1288
 * @see commerce_line_items_total()
1289
 */
1290
function commerce_order_calculate_total($order) {
1291
  $order_wrapper = entity_metadata_wrapper('commerce_order', $order);
1292

    
1293
  // First determine the currency to use for the order total.
1294
  $currency_code = commerce_default_currency();
1295
  $currencies = array();
1296

    
1297
  // Populate an array of how many line items on the order use each currency.
1298
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
1299
    // If the current line item actually no longer exists...
1300
    if (!$line_item_wrapper->value()) {
1301
      // Remove the reference from the order and continue to the next value.
1302
      $order_wrapper->commerce_line_items->offsetUnset($delta);
1303
      continue;
1304
    }
1305

    
1306
    $line_item_currency_code = $line_item_wrapper->commerce_total->currency_code->value();
1307

    
1308
    if (!in_array($line_item_currency_code, array_keys($currencies))) {
1309
      $currencies[$line_item_currency_code] = 1;
1310
    }
1311
    else {
1312
      $currencies[$line_item_currency_code]++;
1313
    }
1314
  }
1315

    
1316
  reset($currencies);
1317

    
1318
  // If only one currency is present on the order, use that to calculate the
1319
  // order total.
1320
  if (count($currencies) == 1) {
1321
    $currency_code = key($currencies);
1322
  }
1323
  elseif (in_array(commerce_default_currency(), array_keys($currencies))) {
1324
    // Otherwise use the site default currency if it's in the order.
1325
    $currency_code = commerce_default_currency();
1326
  }
1327
  elseif (count($currencies) > 1) {
1328
    // Otherwise use the first currency on the order. We do this instead of
1329
    // trying to determine the most dominant currency for now because using the
1330
    // first currency leaves the option open for a UI based module to let
1331
    // customers reorder the items in the cart by currency to get the order
1332
    // total in a different currency. The currencies array still contains useful
1333
    // data, though, should we decide to expand on the count by currency approach.
1334
    $currency_code = key($currencies);
1335
  }
1336

    
1337
  // Initialize the order total with the selected currency.
1338
  $order_wrapper->commerce_order_total->amount = 0;
1339
  $order_wrapper->commerce_order_total->currency_code = $currency_code;
1340

    
1341
  // Reset the data array of the order total field to only include a
1342
  // base price component, set the currency code from any line item.
1343
  $base_price = array(
1344
    'amount' => 0,
1345
    'currency_code' => $currency_code,
1346
    'data' => array(),
1347
  );
1348

    
1349
  $order_wrapper->commerce_order_total->data = commerce_price_component_add($base_price, 'base_price', $base_price, TRUE);
1350

    
1351
  // Then loop over each line item and add its total to the order total.
1352
  $amount = 0;
1353

    
1354
  foreach ($order_wrapper->commerce_line_items as $delta => $line_item_wrapper) {
1355
    // Convert the line item's total to the order's currency for totalling.
1356
    $component_total = commerce_price_component_total($line_item_wrapper->commerce_total->value());
1357

    
1358
    // Add the totals.
1359
    $amount += commerce_currency_convert(
1360
      $component_total['amount'],
1361
      $component_total['currency_code'],
1362
      $currency_code
1363
    );
1364

    
1365
    // Combine the line item total's component prices into the order total.
1366
    $order_wrapper->commerce_order_total->data = commerce_price_components_combine(
1367
      $order_wrapper->commerce_order_total->value(),
1368
      $line_item_wrapper->commerce_total->value()
1369
    );
1370
  }
1371

    
1372
  // Update the order total price field with the final total amount.
1373
  $order_wrapper->commerce_order_total->amount = round($amount);
1374
}
1375

    
1376
/**
1377
 * Callback for getting order properties.
1378
 * @see commerce_order_entity_property_info()
1379
 */
1380
function commerce_order_get_properties($order, array $options, $name) {
1381
  switch ($name) {
1382
    case 'owner':
1383
      return $order->uid;
1384
    case 'view_url':
1385
      return url('user/' . $order->uid . '/orders/' . $order->order_id, $options);
1386
    case 'admin_url':
1387
      return url('admin/commerce/orders/' . $order->order_id, $options);
1388
    case 'edit_url':
1389
      return url('admin/commerce/orders/' . $order->order_id . '/edit', $options);
1390
    case 'state':
1391
      $order_status = commerce_order_status_load($order->status);
1392
      return $order_status['state'];
1393
    case 'mail_username':
1394
      // To prepare an e-mail address to be a username, we trim any potential
1395
      // leading and trailing spaces and replace simple illegal characters with
1396
      // hyphens. More could be done with additional illegal characters if
1397
      // necessary, but those typically won't pass e-mail validation anyways.
1398
      // We also limit the username to the maximum length for usernames.
1399
      // @see user_validate_name()
1400
      $username = preg_replace('/[^\x{80}-\x{F7} a-z0-9@_.\'-]/i', '-', trim($order->mail));
1401
      // Remove the e-mail host name so usernames are not valid email adresses.
1402
      // Since usernames are considered public information in Drupal, we must
1403
      // not leak e-mail adresses through usernames.
1404
      $username = preg_replace('/@.*$/', '', $username);
1405
      $username = substr($username, 0, USERNAME_MAX_LENGTH);
1406
      return commerce_order_unique_username($username);
1407
  }
1408
}
1409

    
1410
/**
1411
 * Takes a base username and returns a unique version of the username.
1412
 *
1413
 * @param $base
1414
 *   The base from which to construct a unique username.
1415
 *
1416
 * @return
1417
 *   A unique version of the username using appended numbers to avoid duplicates
1418
 *   if the base is already in use.
1419
 */
1420
function commerce_order_unique_username($base) {
1421
  $username = $base;
1422
  $i = 1;
1423

    
1424
  while (db_query('SELECT 1 FROM {users} WHERE name = :name', array(':name' => $username))->fetchField()) {
1425
    // Ensure the username does not exceed the maximum character limit.
1426
    if (strlen($base . $i) > USERNAME_MAX_LENGTH) {
1427
      $base = substr($base, 0, strlen($base) - strlen($i));
1428
    }
1429

    
1430
    $username = $base . $i++;
1431
  }
1432

    
1433
  return $username;
1434
}
1435

    
1436
/**
1437
 * Callback for setting order properties.
1438
 * @see commerce_order_entity_property_info()
1439
 */
1440
function commerce_order_set_properties($order, $name, $value) {
1441
  if ($name == 'owner') {
1442
    $order->uid = $value;
1443
  }
1444
}
1445

    
1446
/**
1447
 * Implements hook_entity_query_alter().
1448
 */
1449
function commerce_order_entity_query_alter($query) {
1450
  // If we're performing an entity query to orders using a property condition on
1451
  // the order state pseudo-column property, we need to alter the condition to
1452
  // compare against the statuses in the specified state instead.
1453
  if (isset($query->entityConditions['entity_type']) && $query->entityConditions['entity_type']['value'] == 'commerce_order') {
1454
    foreach ($query->propertyConditions as &$condition) {
1455
      // If the current condition was against the non-existent 'state' column...
1456
      if ($condition['column'] == 'state' && !empty($condition['value'])) {
1457
        // Get all the statuses available for this state.
1458
        $statuses = commerce_order_statuses(array('state' => $condition['value']));
1459
        $condition['column'] = 'status';
1460

    
1461
        if (count($statuses)) {
1462
          $condition['value'] = array_keys($statuses);
1463
          $condition['operator'] = (count($statuses) > 1) ? 'IN' : '=';
1464
        }
1465
        else {
1466
          // Do not return any orders for a non-existent state.
1467
          $condition['value'] = NULL;
1468
        }
1469
      }
1470
    }
1471
  }
1472
}
1473

    
1474
/**
1475
 * Implements hook_preprocess_views_view().
1476
 *
1477
 * When the line item summary and order total area handlers are present on Views
1478
 * forms, it is natural for them to appear above the submit buttons that Views
1479
 * creates for the form. This hook checks for their existence in the footer area
1480
 * and moves them if necessary.
1481
 */
1482
function commerce_order_preprocess_views_view(&$vars) {
1483
  $view = $vars['view'];
1484

    
1485
  // Determine if the line item summary or order total area handler is present
1486
  // on the View.
1487
  $has_handler = FALSE;
1488

    
1489
  foreach ($view->footer as $area) {
1490
    if ($area instanceof commerce_line_item_handler_area_line_item_summary || $area instanceof commerce_order_handler_area_order_total) {
1491
      $has_handler = TRUE;
1492
    }
1493
  }
1494

    
1495
  // If one of the handlers is present and the View in question is a form...
1496
  if ($has_handler && views_view_has_form_elements($view)) {
1497
    // Move the footer area into a row in the View positioned just above the
1498
    // form's submit buttons.
1499
    $vars['rows']['footer'] = array(
1500
      '#type' => 'markup',
1501
      '#markup' => $vars['footer'],
1502
      '#weight' => 99,
1503
    );
1504
    $vars['footer'] = '';
1505
  }
1506
}