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
|
}
|