1
|
<?php
|
2
|
|
3
|
/**
|
4
|
* @file
|
5
|
* Defines the core Commerce line item entity and API functions interact with
|
6
|
* line items on orders.
|
7
|
*/
|
8
|
|
9
|
/**
|
10
|
* Implements hook_entity_info().
|
11
|
*/
|
12
|
function commerce_line_item_entity_info() {
|
13
|
$return = array(
|
14
|
'commerce_line_item' => array(
|
15
|
'label' => t('Commerce Line item'),
|
16
|
'controller class' => 'CommerceLineItemEntityController',
|
17
|
'base table' => 'commerce_line_item',
|
18
|
'fieldable' => TRUE,
|
19
|
'entity keys' => array(
|
20
|
'id' => 'line_item_id',
|
21
|
'bundle' => 'type',
|
22
|
'label' => 'line_item_id', // TODO: Update to use a custom callback.
|
23
|
),
|
24
|
'bundle keys' => array(
|
25
|
'bundle' => 'type',
|
26
|
),
|
27
|
'bundles' => array(),
|
28
|
'load hook' => 'commerce_line_item_load',
|
29
|
'view modes' => array(
|
30
|
'display' => array(
|
31
|
'label' => t('Display'),
|
32
|
'custom settings' => FALSE,
|
33
|
),
|
34
|
),
|
35
|
'access callback' => 'commerce_line_item_access',
|
36
|
'access arguments' => array(
|
37
|
'access tag' => 'commerce_line_item_access',
|
38
|
),
|
39
|
'metadata controller class' => '',
|
40
|
'token type' => 'commerce-line-item',
|
41
|
'permission labels' => array(
|
42
|
'singular' => t('line item'),
|
43
|
'plural' => t('line items'),
|
44
|
),
|
45
|
|
46
|
// Prevent Redirect alteration of the line item form.
|
47
|
'redirect' => FALSE,
|
48
|
),
|
49
|
);
|
50
|
|
51
|
$return['commerce_line_item']['bundles'] = array();
|
52
|
foreach (commerce_line_item_type_get_name() as $type => $name) {
|
53
|
$return['commerce_line_item']['bundles'][$type] = array(
|
54
|
'label' => $name,
|
55
|
);
|
56
|
}
|
57
|
|
58
|
return $return;
|
59
|
}
|
60
|
|
61
|
/**
|
62
|
* Implements hook_hook_info().
|
63
|
*/
|
64
|
function commerce_line_item_hook_info() {
|
65
|
$hooks = array(
|
66
|
'commerce_line_item_type_info' => array(
|
67
|
'group' => 'commerce',
|
68
|
),
|
69
|
'commerce_line_item_type_info_alter' => array(
|
70
|
'group' => 'commerce',
|
71
|
),
|
72
|
'commerce_line_item_summary_link_info' => array(
|
73
|
'group' => 'commerce',
|
74
|
),
|
75
|
'commerce_line_item_summary_link_info_alter' => array(
|
76
|
'group' => 'commerce',
|
77
|
),
|
78
|
'commerce_line_item_access' => array(
|
79
|
'group' => 'commerce',
|
80
|
),
|
81
|
'commerce_line_item_update' => array(
|
82
|
'group' => 'commerce',
|
83
|
),
|
84
|
'commerce_line_item_insert' => array(
|
85
|
'group' => 'commerce',
|
86
|
),
|
87
|
'commerce_line_item_delete' => array(
|
88
|
'group' => 'commerce',
|
89
|
),
|
90
|
'commerce_line_item_rebase_unit_price' => array(
|
91
|
'group' => 'commerce',
|
92
|
),
|
93
|
);
|
94
|
|
95
|
return $hooks;
|
96
|
}
|
97
|
|
98
|
|
99
|
/**
|
100
|
* Implements hook_form_alter().
|
101
|
*
|
102
|
* Alter the views form with functionality specific to line items.
|
103
|
* This form currently only supports line items from a single order, and it
|
104
|
* determines which order the line items are for based on a Views argument.
|
105
|
*/
|
106
|
function commerce_line_item_form_alter(&$form, &$form_state, $form_id) {
|
107
|
$line_item_form = FALSE;
|
108
|
// Is this a views form?
|
109
|
if (strpos($form_id, 'views_form_') === 0) {
|
110
|
$view = $form_state['build_info']['args'][0];
|
111
|
// Does the view contain one of the line item edit fields?
|
112
|
foreach ($view->field as $field_name => $field) {
|
113
|
if ($field instanceof commerce_line_item_handler_field_edit_delete || $field instanceof commerce_line_item_handler_field_edit_quantity) {
|
114
|
$line_item_form = TRUE;
|
115
|
}
|
116
|
}
|
117
|
}
|
118
|
// This is not the form we are looking for.
|
119
|
if (!$line_item_form) {
|
120
|
return;
|
121
|
}
|
122
|
// Require the existence of an order_id argument (and its value).
|
123
|
if (empty($view->argument['order_id']) || empty($view->argument['order_id']->value)) {
|
124
|
return;
|
125
|
}
|
126
|
|
127
|
$form['#attached']['css'][] = drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item.theme.css';
|
128
|
$form['#attached']['js'][] = drupal_get_path('module', 'commerce_line_item') . '/commerce_line_item.js';
|
129
|
$form['#submit'][] = 'commerce_line_item_line_item_views_form_submit';
|
130
|
|
131
|
// Wrap the form in a div so we can add CSS and javascript behaviors to it.
|
132
|
$form['#prefix'] = '<div class="commerce-line-item-views-form">';
|
133
|
$form['#suffix'] = '</div>';
|
134
|
|
135
|
// Add an additional class to the actions wrapper.
|
136
|
$form['actions']['#attributes']['class'][] = 'commerce-line-item-actions';
|
137
|
|
138
|
// Load the order from the Views argument.
|
139
|
$order = commerce_order_load($view->argument['order_id']->value[0]);
|
140
|
$form_state['order'] = $order;
|
141
|
}
|
142
|
|
143
|
/**
|
144
|
* Implements hook_field_extra_fields().
|
145
|
*/
|
146
|
function commerce_line_item_field_extra_fields() {
|
147
|
$extra = array();
|
148
|
|
149
|
foreach (commerce_line_item_types() as $type => $line_item_type) {
|
150
|
$extra['commerce_line_item'][$type] = array(
|
151
|
'form' => array(
|
152
|
'label' => array(
|
153
|
'label' => t('Line item label'),
|
154
|
'description' => t('Line item module label form element'),
|
155
|
'weight' => -10,
|
156
|
),
|
157
|
'quantity' => array(
|
158
|
'label' => t('Quantity'),
|
159
|
'description' => t('Line item module quantity form element'),
|
160
|
'weight' => -5,
|
161
|
),
|
162
|
),
|
163
|
'display' => array(
|
164
|
'label' => array(
|
165
|
'label' => t('Line item label'),
|
166
|
'description' => t('Short descriptive label for the line item'),
|
167
|
'weight' => -10,
|
168
|
),
|
169
|
'quantity' => array(
|
170
|
'label' => t('Quantity'),
|
171
|
'description' => t('Quantity associated with this line item'),
|
172
|
'weight' => -5,
|
173
|
),
|
174
|
),
|
175
|
);
|
176
|
}
|
177
|
|
178
|
return $extra;
|
179
|
}
|
180
|
|
181
|
/**
|
182
|
* Implements hook_field_access().
|
183
|
*/
|
184
|
function commerce_line_item_field_access($op, $field, $entity_type, $entity, $account) {
|
185
|
// Block field edit access to line item fields that are computed only.
|
186
|
if ($op == 'edit' && $entity_type == 'commerce_line_item') {
|
187
|
// Build an array of computed field names.
|
188
|
$computed_fields = array('commerce_total');
|
189
|
|
190
|
if (in_array($field['field_name'], $computed_fields)) {
|
191
|
return FALSE;
|
192
|
}
|
193
|
}
|
194
|
}
|
195
|
|
196
|
/**
|
197
|
* Implements hook_theme().
|
198
|
*/
|
199
|
function commerce_line_item_theme() {
|
200
|
return array(
|
201
|
'commerce_line_item_manager' => array(
|
202
|
'render element' => 'form',
|
203
|
),
|
204
|
'commerce_line_item_summary' => array(
|
205
|
'variables' => array('quantity_raw' => NULL, 'quantity_label' => NULL, 'quantity' => NULL, 'total_raw' => NULL, 'total' => NULL, 'links' => NULL, 'view' => NULL),
|
206
|
'path' => drupal_get_path('module', 'commerce_line_item') . '/theme',
|
207
|
'template' => 'commerce-line-item-summary',
|
208
|
),
|
209
|
);
|
210
|
}
|
211
|
|
212
|
/**
|
213
|
* Submit handler used when clicking the remove button.
|
214
|
*/
|
215
|
function commerce_line_item_line_item_views_delete_form_submit($form, &$form_state) {
|
216
|
drupal_set_message(t('Line item removed.'));
|
217
|
}
|
218
|
|
219
|
/**
|
220
|
* Submit handler used when clicking the update button.
|
221
|
*/
|
222
|
function commerce_line_item_line_item_views_form_submit($form, &$form_state) {
|
223
|
drupal_set_message(t('Line items saved.'));
|
224
|
}
|
225
|
|
226
|
/**
|
227
|
* Adds the necessary CSS for the line item summary template.
|
228
|
*/
|
229
|
function template_preprocess_commerce_line_item_summary(&$variables) {
|
230
|
drupal_add_css(drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item.theme.css');
|
231
|
}
|
232
|
|
233
|
/**
|
234
|
* Implements hook_views_api().
|
235
|
*/
|
236
|
function commerce_line_item_views_api() {
|
237
|
return array(
|
238
|
'api' => 3,
|
239
|
'path' => drupal_get_path('module', 'commerce_line_item') . '/includes/views',
|
240
|
);
|
241
|
}
|
242
|
|
243
|
/**
|
244
|
* Implements hook_enable().
|
245
|
*/
|
246
|
function commerce_line_item_enable() {
|
247
|
commerce_line_item_configure_line_item_types();
|
248
|
}
|
249
|
|
250
|
/**
|
251
|
* Implements hook_modules_enabled().
|
252
|
*/
|
253
|
function commerce_line_item_modules_enabled($modules) {
|
254
|
commerce_line_item_configure_line_item_fields($modules);
|
255
|
}
|
256
|
|
257
|
/**
|
258
|
* Configures line item types defined by enabled modules.
|
259
|
*/
|
260
|
function commerce_line_item_configure_line_item_types() {
|
261
|
foreach (commerce_line_item_types() as $line_item_type) {
|
262
|
commerce_line_item_configure_line_item_type($line_item_type);
|
263
|
}
|
264
|
}
|
265
|
|
266
|
/**
|
267
|
* Configures a line item type by adding default price fields and then calling
|
268
|
* its configuration callback.
|
269
|
*
|
270
|
* @param $line_item_type
|
271
|
* The fully loaded line item type array to configure.
|
272
|
*/
|
273
|
function commerce_line_item_configure_line_item_type($line_item_type) {
|
274
|
// Add the default price fields to the line item type.
|
275
|
$weight = 0;
|
276
|
|
277
|
foreach (array('commerce_unit_price' => t('Unit price'), 'commerce_total' => t('Total')) as $field_name => $label) {
|
278
|
commerce_price_create_instance($field_name, 'commerce_line_item', $line_item_type['type'], $label, $weight++);
|
279
|
}
|
280
|
|
281
|
// If this line item type specifies a configuration callback...
|
282
|
if ($callback = commerce_line_item_type_callback($line_item_type, 'configuration')) {
|
283
|
// Invoke it now.
|
284
|
$callback($line_item_type);
|
285
|
}
|
286
|
}
|
287
|
|
288
|
/**
|
289
|
* Configures line item types defined by other modules that are enabled after
|
290
|
* the Line Item module.
|
291
|
*
|
292
|
* @param $modules
|
293
|
* An array of module names whose line item type fields should be configured;
|
294
|
* if left NULL, will default to all modules that implement
|
295
|
* hook_commerce_line_item_type_info().
|
296
|
*/
|
297
|
function commerce_line_item_configure_line_item_fields($modules = NULL) {
|
298
|
// If no modules array is passed, recheck the fields for all line item types
|
299
|
// defined by enabled modules.
|
300
|
if (empty($modules)) {
|
301
|
$modules = module_implements('commerce_line_item_type_info');
|
302
|
}
|
303
|
|
304
|
// Reset the line item type cache to get types added by newly enabled modules.
|
305
|
commerce_line_item_types_reset();
|
306
|
|
307
|
// Loop through all the enabled modules.
|
308
|
foreach ($modules as $module) {
|
309
|
// If the module implements hook_commerce_line_item_type_info()...
|
310
|
if (module_hook($module, 'commerce_line_item_type_info')) {
|
311
|
// Loop through and configure the line item types defined by the module.
|
312
|
foreach (module_invoke($module, 'commerce_line_item_type_info') as $type => $line_item_type) {
|
313
|
// Load the line item type to ensure we have callbacks set.
|
314
|
$line_item_type = commerce_line_item_type_load($type);
|
315
|
commerce_line_item_configure_line_item_type($line_item_type);
|
316
|
}
|
317
|
}
|
318
|
}
|
319
|
}
|
320
|
|
321
|
/**
|
322
|
* Implements hook_permission().
|
323
|
*/
|
324
|
function commerce_line_item_permission() {
|
325
|
$permissions = array(
|
326
|
'administer line item types' => array(
|
327
|
'title' => t('Administer line item types'),
|
328
|
'description' => t('View and configure fields attached to module defined line item types.'),
|
329
|
'restrict access' => TRUE,
|
330
|
),
|
331
|
'administer line items' => array(
|
332
|
'title' => t('Administer line items'),
|
333
|
'description' => t('Update and delete any line item on the site.'),
|
334
|
'restrict access' => TRUE,
|
335
|
),
|
336
|
);
|
337
|
|
338
|
return $permissions;
|
339
|
}
|
340
|
|
341
|
/**
|
342
|
* Implements hook_field_attach_delete().
|
343
|
*
|
344
|
* When an entity is deleted, this hook is invoked so any attached fields can
|
345
|
* do necessary clean-up. Because line items can't exist apart from a line item
|
346
|
* reference field, this function checks the entity being deleted for any
|
347
|
* referenced line items that are orphaned and deletes them.
|
348
|
*/
|
349
|
function commerce_line_item_field_attach_delete($entity_type, $entity) {
|
350
|
$wrapper = entity_metadata_wrapper($entity_type, $entity);
|
351
|
$entity_info = entity_get_info($entity_type);
|
352
|
|
353
|
// If the entity being deleted has a bundle...
|
354
|
if (!empty($entity_info['entity keys']['bundle'])) {
|
355
|
// Extract the bundle name using the specified property.
|
356
|
$property = $entity_info['entity keys']['bundle'];
|
357
|
$bundle = $entity->{$property};
|
358
|
}
|
359
|
else {
|
360
|
// Otherwise use the entity type as the bundle name.
|
361
|
$bundle = $entity_type;
|
362
|
}
|
363
|
|
364
|
// Find any line item reference fields on this entity and delete any orphan
|
365
|
// referenced line items.
|
366
|
$line_item_ids = array();
|
367
|
|
368
|
foreach (field_info_instances($entity_type, $bundle) as $field_name => $field) {
|
369
|
// Only examine line item reference fields using the manager widget.
|
370
|
if ($field['widget']['type'] == 'commerce_line_item_manager') {
|
371
|
// Build an array containing the line item IDs referenced by this field,
|
372
|
// accommodating both single and multi-value fields.
|
373
|
$referenced_line_item_ids = array();
|
374
|
|
375
|
if ($wrapper->{$field_name} instanceof EntityListWrapper) {
|
376
|
foreach ($wrapper->{$field_name} as $delta => $line_item_wrapper) {
|
377
|
$referenced_line_item_ids[] = $line_item_wrapper->line_item_id->value();
|
378
|
}
|
379
|
}
|
380
|
elseif (!is_null($wrapper->{$field_name}->value())) {
|
381
|
$referenced_line_item_ids[] = $wrapper->{$field_name}->line_item_id->value();
|
382
|
}
|
383
|
|
384
|
// Loop over each line item referenced on this field...
|
385
|
foreach ($referenced_line_item_ids as $line_item_id) {
|
386
|
// To determine if it's still an orphan (i.e. no other entities reference
|
387
|
// this line item through any line item reference field).
|
388
|
$orphan = TRUE;
|
389
|
|
390
|
// Loop over each line item reference field to look for references.
|
391
|
foreach (commerce_info_fields('commerce_line_item_reference') as $key => $value) {
|
392
|
$query = new EntityFieldQuery();
|
393
|
$query->fieldCondition($key, 'line_item_id', $line_item_id, '=')->count();
|
394
|
|
395
|
// If some entity still references this line item through this field...
|
396
|
if ($query->execute() > 0) {
|
397
|
// It is not an orphan.
|
398
|
$orphan = FALSE;
|
399
|
}
|
400
|
}
|
401
|
|
402
|
// If this line item is an orphan, add it to the array of line items to
|
403
|
// be deleted.
|
404
|
if ($orphan) {
|
405
|
$line_item_ids[] = $line_item_id;
|
406
|
}
|
407
|
}
|
408
|
}
|
409
|
}
|
410
|
|
411
|
// Delete the line items if any were found.
|
412
|
if (!empty($line_item_ids)) {
|
413
|
commerce_line_item_delete_multiple($line_item_ids);
|
414
|
}
|
415
|
}
|
416
|
|
417
|
/**
|
418
|
* Returns an array of line item type arrays keyed by type.
|
419
|
*/
|
420
|
function commerce_line_item_types() {
|
421
|
// First check the static cache for a line item types array.
|
422
|
$line_item_types = &drupal_static(__FUNCTION__);
|
423
|
|
424
|
// If it did not exist, fetch the types now.
|
425
|
if (!isset($line_item_types)) {
|
426
|
$line_item_types = module_invoke_all('commerce_line_item_type_info');
|
427
|
drupal_alter('commerce_line_item_type_info', $line_item_types);
|
428
|
|
429
|
foreach ($line_item_types as $type => &$line_item_type) {
|
430
|
$defaults = array(
|
431
|
'type' => $type,
|
432
|
'product' => FALSE,
|
433
|
'base' => $type,
|
434
|
'callbacks' => array(),
|
435
|
);
|
436
|
|
437
|
$line_item_type += $defaults;
|
438
|
|
439
|
// Merge in default callbacks.
|
440
|
foreach (array('configuration', 'title', 'add_form', 'add_form_submit') as $callback) {
|
441
|
if (!isset($line_item_type['callbacks'][$callback])) {
|
442
|
$line_item_type['callbacks'][$callback] = $line_item_type['base'] . '_' . $callback;
|
443
|
}
|
444
|
}
|
445
|
}
|
446
|
}
|
447
|
|
448
|
return $line_item_types;
|
449
|
}
|
450
|
|
451
|
/**
|
452
|
* Returns a single line item type array.
|
453
|
*
|
454
|
* @param $type
|
455
|
* The machine-readable name of the line item type.
|
456
|
*
|
457
|
* @return
|
458
|
* The specified line item type array or FALSE if it does not exist.
|
459
|
*/
|
460
|
function commerce_line_item_type_load($type) {
|
461
|
$line_item_types = commerce_line_item_types();
|
462
|
|
463
|
return isset($line_item_types[$type]) ? $line_item_types[$type] : FALSE;
|
464
|
}
|
465
|
|
466
|
/**
|
467
|
* Resets the cached list of line item types.
|
468
|
*/
|
469
|
function commerce_line_item_types_reset() {
|
470
|
$line_item_types = &drupal_static('commerce_line_item_types');
|
471
|
$line_item_types = NULL;
|
472
|
entity_info_cache_clear();
|
473
|
}
|
474
|
|
475
|
/**
|
476
|
* Returns the human readable name of any or all line item types.
|
477
|
*
|
478
|
* @param $type
|
479
|
* Optional parameter specifying the type whose name to return.
|
480
|
*
|
481
|
* @return
|
482
|
* Either an array of all line item type names keyed by the machine name or a
|
483
|
* string containing the human readable name for the specified type. If a
|
484
|
* type is specified that does not exist, this function returns FALSE.
|
485
|
*/
|
486
|
function commerce_line_item_type_get_name($type = NULL) {
|
487
|
$line_item_types = commerce_line_item_types();
|
488
|
|
489
|
// Return a type name if specified and it exists.
|
490
|
if (!empty($type)) {
|
491
|
if (isset($line_item_types[$type])) {
|
492
|
return $line_item_types[$type]['name'];
|
493
|
}
|
494
|
else {
|
495
|
// Return FALSE if it does not exist.
|
496
|
return FALSE;
|
497
|
}
|
498
|
}
|
499
|
|
500
|
// Otherwise turn the array values into the type name only.
|
501
|
$line_item_type_names = array();
|
502
|
|
503
|
foreach ($line_item_types as $key => $value) {
|
504
|
$line_item_type_names[$key] = $value['name'];
|
505
|
}
|
506
|
|
507
|
return $line_item_type_names;
|
508
|
}
|
509
|
|
510
|
/**
|
511
|
* Wraps commerce_line_item_type_get_name() for the Entity module.
|
512
|
*/
|
513
|
function commerce_line_item_type_options_list() {
|
514
|
return commerce_line_item_type_get_name();
|
515
|
}
|
516
|
|
517
|
/**
|
518
|
* Title callback: return the human-readable line item type name.
|
519
|
*/
|
520
|
function commerce_line_item_type_title($line_item_type) {
|
521
|
return $line_item_type['name'];
|
522
|
}
|
523
|
|
524
|
/**
|
525
|
* Returns a path argument from a line item type.
|
526
|
*/
|
527
|
function commerce_line_item_type_to_arg($type) {
|
528
|
return $type;
|
529
|
}
|
530
|
|
531
|
/**
|
532
|
* Returns the specified callback for the given line item type if one exists.
|
533
|
*
|
534
|
* @param $line_item_type
|
535
|
* The line item type array.
|
536
|
* @param $callback
|
537
|
* The callback function to return, one of:
|
538
|
* - configuration
|
539
|
* - title
|
540
|
* - add_form
|
541
|
* - add_form_validate
|
542
|
* - add_form_submit
|
543
|
*
|
544
|
* @return
|
545
|
* A string containing the name of the callback function or FALSE if it could
|
546
|
* not be found.
|
547
|
*/
|
548
|
function commerce_line_item_type_callback($line_item_type, $callback) {
|
549
|
// If the specified callback function exists, return it.
|
550
|
if (!empty($line_item_type['callbacks'][$callback]) &&
|
551
|
function_exists($line_item_type['callbacks'][$callback])) {
|
552
|
return $line_item_type['callbacks'][$callback];
|
553
|
}
|
554
|
|
555
|
// Otherwise return FALSE.
|
556
|
return FALSE;
|
557
|
}
|
558
|
|
559
|
/**
|
560
|
* Returns an initialized line item object.
|
561
|
*
|
562
|
* @param $type
|
563
|
* The machine-readable type of the line item.
|
564
|
* @param $order_id
|
565
|
* The ID of the order the line item belongs to (if available).
|
566
|
*
|
567
|
* @return
|
568
|
* A line item object with all default fields initialized.
|
569
|
*/
|
570
|
function commerce_line_item_new($type = '', $order_id = 0) {
|
571
|
return entity_get_controller('commerce_line_item')->create(array(
|
572
|
'type' => $type,
|
573
|
'order_id' => $order_id,
|
574
|
));
|
575
|
}
|
576
|
|
577
|
/**
|
578
|
* Saves a line item.
|
579
|
*
|
580
|
* @param $line_item
|
581
|
* The full line item object to save.
|
582
|
*
|
583
|
* @return
|
584
|
* SAVED_NEW or SAVED_UPDATED depending on the operation performed.
|
585
|
*/
|
586
|
function commerce_line_item_save($line_item) {
|
587
|
return entity_get_controller('commerce_line_item')->save($line_item);
|
588
|
}
|
589
|
|
590
|
/**
|
591
|
* Loads a line item by ID.
|
592
|
*/
|
593
|
function commerce_line_item_load($line_item_id) {
|
594
|
$line_items = commerce_line_item_load_multiple(array($line_item_id), array());
|
595
|
return $line_items ? reset($line_items) : FALSE;
|
596
|
}
|
597
|
|
598
|
/**
|
599
|
* Loads multiple line items by ID or based on a set of matching conditions.
|
600
|
*
|
601
|
* @see entity_load()
|
602
|
*
|
603
|
* @param $line_item_ids
|
604
|
* An array of line item IDs.
|
605
|
* @param $conditions
|
606
|
* An array of conditions on the {commerce_line_item} table in the form
|
607
|
* 'field' => $value.
|
608
|
* @param $reset
|
609
|
* Whether to reset the internal line item loading cache.
|
610
|
*
|
611
|
* @return
|
612
|
* An array of line item objects indexed by line_item_id.
|
613
|
*/
|
614
|
function commerce_line_item_load_multiple($line_item_ids = array(), $conditions = array(), $reset = FALSE) {
|
615
|
return entity_load('commerce_line_item', $line_item_ids, $conditions, $reset);
|
616
|
}
|
617
|
|
618
|
/**
|
619
|
* Deletes a line item by ID.
|
620
|
*
|
621
|
* @param $line_item_id
|
622
|
* The ID of the line item to delete.
|
623
|
*
|
624
|
* @return
|
625
|
* TRUE on success, FALSE otherwise.
|
626
|
*/
|
627
|
function commerce_line_item_delete($line_item_id) {
|
628
|
return commerce_line_item_delete_multiple(array($line_item_id));
|
629
|
}
|
630
|
|
631
|
/**
|
632
|
* Deletes multiple line items by ID.
|
633
|
*
|
634
|
* @param $line_item_ids
|
635
|
* An array of line item IDs to delete.
|
636
|
*
|
637
|
* @return
|
638
|
* TRUE on success, FALSE otherwise.
|
639
|
*/
|
640
|
function commerce_line_item_delete_multiple($line_item_ids) {
|
641
|
return entity_get_controller('commerce_line_item')->delete($line_item_ids);
|
642
|
}
|
643
|
|
644
|
/**
|
645
|
* Deletes any references to the given line item.
|
646
|
*/
|
647
|
function commerce_line_item_delete_references($line_item) {
|
648
|
// Check the data in every line item reference field.
|
649
|
foreach (commerce_info_fields('commerce_line_item_reference') as $field_name => $field) {
|
650
|
// Query for any entity referencing the deleted line item in this field.
|
651
|
$query = new EntityFieldQuery();
|
652
|
$query->fieldCondition($field_name, 'line_item_id', $line_item->line_item_id, '=');
|
653
|
$result = $query->execute();
|
654
|
|
655
|
// If results were returned...
|
656
|
if (!empty($result)) {
|
657
|
// Loop over results for each type of entity returned.
|
658
|
foreach ($result as $entity_type => $data) {
|
659
|
// Load the entities of the current type.
|
660
|
$entities = entity_load($entity_type, array_keys($data));
|
661
|
|
662
|
// Loop over each entity and remove the reference to the deleted line item.
|
663
|
foreach ($entities as $entity_id => $entity) {
|
664
|
commerce_entity_reference_delete($entity, $field_name, 'line_item_id', $line_item->line_item_id);
|
665
|
|
666
|
entity_save($entity_type, $entity);
|
667
|
}
|
668
|
}
|
669
|
}
|
670
|
}
|
671
|
}
|
672
|
|
673
|
/**
|
674
|
* Determines access to perform an operation on a particular line item.
|
675
|
*
|
676
|
* @param $op
|
677
|
* The operation to perform on the line item, either 'update' or 'delete'.
|
678
|
* @param $line_item
|
679
|
* The line item object in question.
|
680
|
* @param $account
|
681
|
* The user account whose access should be checked; defaults to the current
|
682
|
* user if left NULL.
|
683
|
*
|
684
|
* @return
|
685
|
* TRUE or FALSE indicating whether or not access should be granted.
|
686
|
*/
|
687
|
function commerce_line_item_access($op, $line_item, $account = NULL) {
|
688
|
global $user;
|
689
|
$account = isset($account) ? $account : clone($user);
|
690
|
|
691
|
// If the user has the administration permission, return TRUE now.
|
692
|
if (user_access('administer line items', $account)) {
|
693
|
return TRUE;
|
694
|
}
|
695
|
|
696
|
// For users who don't have the general administration permission, we have to
|
697
|
// determine access to update or delete a given line item through a connection
|
698
|
// to an Order.
|
699
|
if (!empty($line_item->order_id) && module_exists('commerce_order')) {
|
700
|
$order = commerce_order_load($line_item->order_id);
|
701
|
return commerce_order_access($op, $order, $account);
|
702
|
}
|
703
|
|
704
|
// Issue a blanket refusal of access in the event the order module is not
|
705
|
// enabled, as we have no other way of determining line item access outside of
|
706
|
// the 'administer line items' permission.
|
707
|
return FALSE;
|
708
|
}
|
709
|
|
710
|
/**
|
711
|
* Implements hook_query_TAG_alter().
|
712
|
*
|
713
|
* Implement access control on line items. This is different from other entities
|
714
|
* because the access to a line item is totally delegated to its order.
|
715
|
*/
|
716
|
function commerce_line_item_query_commerce_line_item_access_alter(QueryAlterableInterface $query) {
|
717
|
// Read the meta-data from the query.
|
718
|
if (!$account = $query->getMetaData('account')) {
|
719
|
global $user;
|
720
|
$account = $user;
|
721
|
}
|
722
|
|
723
|
// If the user has the administration permission, nothing to do.
|
724
|
if (user_access('administer line items', $account)) {
|
725
|
return;
|
726
|
}
|
727
|
|
728
|
// Join the line items to their orders.
|
729
|
if (module_exists('commerce_order')) {
|
730
|
$tables = &$query->getTables();
|
731
|
|
732
|
// Look for an existing commerce_order table.
|
733
|
foreach ($tables as $table) {
|
734
|
if ($table['table'] === 'commerce_order') {
|
735
|
$order_alias = $table['alias'];
|
736
|
break;
|
737
|
}
|
738
|
}
|
739
|
|
740
|
// If not found, attempt a join against the first table.
|
741
|
if (!isset($order_alias)) {
|
742
|
reset($tables);
|
743
|
$base_table = key($tables);
|
744
|
$order_alias = $query->innerJoin('commerce_order', 'co', '%alias.order_id = ' . $base_table . '.order_id');
|
745
|
}
|
746
|
|
747
|
// Perform the access control on the order.
|
748
|
commerce_entity_access_query_alter($query, 'commerce_order', $order_alias);
|
749
|
}
|
750
|
else {
|
751
|
// The user has access to no line item.
|
752
|
$query->where('1 = 0');
|
753
|
}
|
754
|
}
|
755
|
|
756
|
/**
|
757
|
* Returns the title of a line item based on its type.
|
758
|
*
|
759
|
* Titles are returned sanitized and so do not need to be sanitized again prior
|
760
|
* to display.
|
761
|
*
|
762
|
* @param $line_item
|
763
|
* The line item object whose title should be returned.
|
764
|
*
|
765
|
* @return
|
766
|
* The type-dependent title of the line item.
|
767
|
*/
|
768
|
function commerce_line_item_title($line_item) {
|
769
|
// Find the line item type's title callback.
|
770
|
$line_item_type = commerce_line_item_type_load($line_item->type);
|
771
|
$title_callback = commerce_line_item_type_callback($line_item_type, 'title');
|
772
|
|
773
|
return $title_callback ? $title_callback($line_item) : '';
|
774
|
}
|
775
|
|
776
|
/**
|
777
|
* Implements hook_field_info().
|
778
|
*/
|
779
|
function commerce_line_item_field_info() {
|
780
|
return array(
|
781
|
'commerce_line_item_reference' => array(
|
782
|
'label' => t('Line item reference'),
|
783
|
'description' => t('This field stores the ID of a related line item as an integer value.'),
|
784
|
'settings' => array(),
|
785
|
'instance_settings' => array(),
|
786
|
'default_widget' => 'commerce_line_item_manager',
|
787
|
'default_formatter' => 'commerce_line_item_reference_view',
|
788
|
'property_type' => 'commerce_line_item',
|
789
|
'property_callbacks' => array('commerce_line_item_property_info_callback'),
|
790
|
),
|
791
|
);
|
792
|
}
|
793
|
|
794
|
/**
|
795
|
* Implements hook_field_validate().
|
796
|
*
|
797
|
* Possible error codes:
|
798
|
* - 'invalid_line_item_id': line_item_id is not valid for the field (not a
|
799
|
* valid line item ID).
|
800
|
*/
|
801
|
function commerce_line_item_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
|
802
|
$translated_instance = commerce_i18n_object('field_instance', $instance);
|
803
|
|
804
|
// Extract line_item_ids to check.
|
805
|
$line_item_ids = array();
|
806
|
|
807
|
// First check non-numeric line_item_id's to avoid losing time with them.
|
808
|
foreach ($items as $delta => $item) {
|
809
|
if (is_array($item) && !empty($item['line_item_id'])) {
|
810
|
if (is_numeric($item['line_item_id'])) {
|
811
|
$line_item_ids[] = $item['line_item_id'];
|
812
|
}
|
813
|
else {
|
814
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
815
|
'error' => 'invalid_line_item_id',
|
816
|
'message' => t('%name: you have specified an invalid line item for this field.', array('%name' => $translated_instance['label'])),
|
817
|
);
|
818
|
}
|
819
|
}
|
820
|
}
|
821
|
|
822
|
// Prevent performance hog if there are no ids to check.
|
823
|
if ($line_item_ids) {
|
824
|
$line_items = commerce_line_item_load_multiple($line_item_ids);
|
825
|
|
826
|
foreach ($items as $delta => $item) {
|
827
|
if (is_array($item)) {
|
828
|
if (!empty($item['line_item_id']) && !isset($line_items[$item['line_item_id']])) {
|
829
|
$errors[$field['field_name']][$langcode][$delta][] = array(
|
830
|
'error' => 'invalid_line_item_id',
|
831
|
'message' => t('%name: you have specified an invalid line item for this reference field.', array('%name' => $translated_instance['label'])),
|
832
|
);
|
833
|
}
|
834
|
}
|
835
|
}
|
836
|
}
|
837
|
}
|
838
|
|
839
|
/**
|
840
|
* Implements hook_field_is_empty().
|
841
|
*/
|
842
|
function commerce_line_item_field_is_empty($item, $field) {
|
843
|
// line_item_id = 0 is empty too, which is exactly what we want.
|
844
|
return empty($item['line_item_id']);
|
845
|
}
|
846
|
|
847
|
/**
|
848
|
* Implements hook_field_formatter_info().
|
849
|
*/
|
850
|
function commerce_line_item_field_formatter_info() {
|
851
|
return array(
|
852
|
'commerce_line_item_reference_view' => array(
|
853
|
'label' => t('Line item View'),
|
854
|
'description' => t('Display the line items via a default View.'),
|
855
|
'field types' => array('commerce_line_item_reference'),
|
856
|
'settings' => array(
|
857
|
'view' => 'commerce_line_item_table|default',
|
858
|
),
|
859
|
),
|
860
|
);
|
861
|
}
|
862
|
|
863
|
/**
|
864
|
* Implements hook_field_formatter_settings_form().
|
865
|
*/
|
866
|
function commerce_line_item_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
|
867
|
$display = $instance['display'][$view_mode];
|
868
|
$settings = $display['settings'];
|
869
|
|
870
|
$element = array();
|
871
|
|
872
|
if ($display['type'] == 'commerce_line_item_reference_view') {
|
873
|
// Build an options array of Views available for the order contents pane.
|
874
|
$options = array();
|
875
|
|
876
|
// Generate an option list from all user defined and module defined views.
|
877
|
foreach (views_get_all_views() as $name => $view) {
|
878
|
// Only include line item Views.
|
879
|
if ($view->base_table == 'commerce_line_item') {
|
880
|
foreach ($view->display as $display_name => $display) {
|
881
|
$options[check_plain($name)][$name .'|'. $display_name] = $display->display_title;
|
882
|
}
|
883
|
}
|
884
|
}
|
885
|
|
886
|
$element['view'] = array(
|
887
|
'#type' => 'select',
|
888
|
'#title' => t('Order contents View'),
|
889
|
'#description' => t('Specify the View to use to display the line items referenced by this field.'),
|
890
|
'#options' => $options,
|
891
|
'#default_value' => $settings['view'],
|
892
|
);
|
893
|
}
|
894
|
|
895
|
return $element;
|
896
|
}
|
897
|
|
898
|
/**
|
899
|
* Implements hook_field_formatter_settings_summary().
|
900
|
*/
|
901
|
function commerce_line_item_field_formatter_settings_summary($field, $instance, $view_mode) {
|
902
|
$display = $instance['display'][$view_mode];
|
903
|
$settings = $display['settings'];
|
904
|
|
905
|
$summary = array();
|
906
|
|
907
|
if ($display['type'] == 'commerce_line_item_reference_view') {
|
908
|
// Load the View and display its information in the summary.
|
909
|
list($name, $display_name) = explode('|', $display['settings']['view']);
|
910
|
$view = views_get_view($name);
|
911
|
|
912
|
$summary = t('View: @name - @display', array('@name' => $view->name, '@display' => $view->display[$display_name]->display_title));
|
913
|
}
|
914
|
|
915
|
return $summary;
|
916
|
}
|
917
|
|
918
|
/**
|
919
|
* Implements hook_field_formatter_view().
|
920
|
*/
|
921
|
function commerce_line_item_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
|
922
|
$result = array();
|
923
|
|
924
|
// Collect the list of line item IDs.
|
925
|
$line_item_ids = array();
|
926
|
|
927
|
foreach ($items as $delta => $item) {
|
928
|
$line_item_ids[] = $item['line_item_id'];
|
929
|
}
|
930
|
|
931
|
switch ($display['type']) {
|
932
|
case 'commerce_line_item_reference_view':
|
933
|
// Extract the View and display ID from the setting.
|
934
|
list($view_id, $display_id) = explode('|', $display['settings']['view']);
|
935
|
|
936
|
$result[0] = array(
|
937
|
'#markup' => commerce_embed_view($view_id, $display_id, array(implode(',', $line_item_ids))),
|
938
|
);
|
939
|
|
940
|
break;
|
941
|
}
|
942
|
|
943
|
return $result;
|
944
|
}
|
945
|
|
946
|
/**
|
947
|
* Implements hook_field_widget_info().
|
948
|
*
|
949
|
* Defines widgets available for use with field types as specified in each
|
950
|
* widget's $info['field types'] array.
|
951
|
*/
|
952
|
function commerce_line_item_field_widget_info() {
|
953
|
$widgets = array();
|
954
|
|
955
|
// Define the creation / reference widget for line items.
|
956
|
$widgets['commerce_line_item_manager'] = array(
|
957
|
'label' => t('Line item manager'),
|
958
|
'description' => t('Use a complex widget to manager the line items referenced by this object.'),
|
959
|
'field types' => array('commerce_line_item_reference'),
|
960
|
'settings' => array(),
|
961
|
'behaviors' => array(
|
962
|
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
|
963
|
'default value' => FIELD_BEHAVIOR_NONE,
|
964
|
),
|
965
|
);
|
966
|
|
967
|
// Do not show the widget on forms; useful in cases where line item reference
|
968
|
// fields will be attached to non-order entities and managed by code.
|
969
|
$widgets['commerce_line_item_reference_hidden'] = array(
|
970
|
'label' => t('Do not show a widget'),
|
971
|
'description' => t('Will not display the line item reference field on forms. Use only if you maintain line item references some other way.'),
|
972
|
'field types' => array('commerce_line_item_reference'),
|
973
|
'settings' => array(),
|
974
|
'behaviors' => array(
|
975
|
'multiple values' => FIELD_BEHAVIOR_CUSTOM,
|
976
|
),
|
977
|
);
|
978
|
|
979
|
return $widgets;
|
980
|
}
|
981
|
|
982
|
/**
|
983
|
* Implements hook_field_widget_form().
|
984
|
*
|
985
|
* Used to define the form element for custom widgets.
|
986
|
*/
|
987
|
function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
|
988
|
// Define the complex line item reference field widget.
|
989
|
if ($instance['widget']['type'] == 'commerce_line_item_manager') {
|
990
|
$line_item_ids = array();
|
991
|
|
992
|
// Build an array of line item IDs from this field's values.
|
993
|
foreach ($items as $item) {
|
994
|
$line_item_ids[] = $item['line_item_id'];
|
995
|
}
|
996
|
|
997
|
// Load the line items for temporary storage in the form array.
|
998
|
$line_items = commerce_line_item_load_multiple($line_item_ids);
|
999
|
|
1000
|
// Update the base form element array to use the proper theme and validate
|
1001
|
// functions and to include header information for the line item table.
|
1002
|
$element += array(
|
1003
|
'#theme' => 'commerce_line_item_manager',
|
1004
|
'#element_validate' => array('commerce_line_item_manager_validate'),
|
1005
|
'#header' => array(t('Remove'), t('Title'), t('SKU'), t('Unit price'), t('Quantity'), t('Total')),
|
1006
|
'#empty' => t('No line items found.'),
|
1007
|
'line_items' => array(),
|
1008
|
);
|
1009
|
|
1010
|
if (!empty($form_state['line_item_save_warning'])) {
|
1011
|
drupal_set_message(t('New line items on this order will not be saved until the <em>Save order</em> button is clicked.'), 'warning');
|
1012
|
}
|
1013
|
|
1014
|
// Add a set of elements to the form for each referenced line item.
|
1015
|
foreach ($line_items as $line_item_id => $line_item) {
|
1016
|
// Store the original line item for later comparison.
|
1017
|
$element['line_items'][$line_item_id]['line_item'] = array(
|
1018
|
'#type' => 'value',
|
1019
|
'#value' => $line_item,
|
1020
|
);
|
1021
|
|
1022
|
// This checkbox will be overridden with a clickable delete image.
|
1023
|
$element['line_items'][$line_item_id]['remove'] = array(
|
1024
|
'#type' => 'checkbox',
|
1025
|
'#default_value' => FALSE,
|
1026
|
);
|
1027
|
|
1028
|
$element['line_items'][$line_item_id]['title'] = array(
|
1029
|
'#markup' => commerce_line_item_title($line_item),
|
1030
|
);
|
1031
|
|
1032
|
$element['line_items'][$line_item_id]['line_item_label'] = array(
|
1033
|
'#markup' => check_plain($line_item->line_item_label),
|
1034
|
);
|
1035
|
|
1036
|
// Retrieve the widget form for just the unit price.
|
1037
|
$widget_form = _field_invoke_default('form', 'commerce_line_item', $line_item, $form, $form_state, array('field_name' => 'commerce_unit_price'));
|
1038
|
|
1039
|
// Unset the title and description and add it to the line item form.
|
1040
|
$language = $widget_form['commerce_unit_price']['#language'];
|
1041
|
unset($widget_form['commerce_unit_price'][$language][0]['amount']['#title']);
|
1042
|
unset($widget_form['commerce_unit_price'][$language][0]['amount']['#description']);
|
1043
|
|
1044
|
$element['line_items'][$line_item_id]['commerce_unit_price'] = $widget_form['commerce_unit_price'];
|
1045
|
$quantity = round($line_item->quantity);
|
1046
|
|
1047
|
$element['line_items'][$line_item_id]['quantity'] = array(
|
1048
|
'#type' => 'textfield',
|
1049
|
'#datatype' => 'integer',
|
1050
|
'#default_value' => $quantity,
|
1051
|
'#size' => 4,
|
1052
|
'#maxlength' => max(4, strlen($quantity)),
|
1053
|
);
|
1054
|
|
1055
|
// Wrap the line item and add its formatted total to the form.
|
1056
|
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
|
1057
|
|
1058
|
$element['line_items'][$line_item_id]['commerce_total'] = array(
|
1059
|
'#markup' => commerce_currency_format($wrapper->commerce_total->amount->value(), $wrapper->commerce_total->currency_code->value(), $line_item),
|
1060
|
);
|
1061
|
}
|
1062
|
|
1063
|
// If the the form has been instructed to add a line item...
|
1064
|
if (!empty($form_state['line_item_add'])) {
|
1065
|
// Load the info object for the selected line item type.
|
1066
|
$line_item_type = commerce_line_item_type_load($form_state['line_item_add']);
|
1067
|
|
1068
|
// Store the line item info object in the form array.
|
1069
|
$element['actions']['line_item_type'] = array(
|
1070
|
'#type' => 'value',
|
1071
|
'#value' => $line_item_type,
|
1072
|
);
|
1073
|
|
1074
|
// If this type specifies a valid add form callback function...
|
1075
|
if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form')) {
|
1076
|
// Load in the appropriate form elements to the actions array.
|
1077
|
$element['actions'] += $callback($element, $form_state);
|
1078
|
}
|
1079
|
|
1080
|
// Add a default save button.
|
1081
|
$element['actions'] += array(
|
1082
|
'save_line_item' => array(
|
1083
|
'#type' => 'button',
|
1084
|
'#value' => !empty($line_item_type['add_form_submit_value']) ? $line_item_type['add_form_submit_value'] : t('Save'),
|
1085
|
'#limit_validation_errors' => array(array_merge($element['#field_parents'], array($field['field_name']))),
|
1086
|
'#ajax' => array(
|
1087
|
'callback' => 'commerce_line_item_manager_refresh',
|
1088
|
'wrapper' => 'line-item-manager',
|
1089
|
),
|
1090
|
),
|
1091
|
);
|
1092
|
|
1093
|
$element['actions']['cancel'] = array(
|
1094
|
'#type' => 'button',
|
1095
|
'#value' => t('Cancel'),
|
1096
|
'#limit_validation_errors' => array(),
|
1097
|
'#ajax' => array(
|
1098
|
'callback' => 'commerce_line_item_manager_refresh',
|
1099
|
'wrapper' => 'line-item-manager',
|
1100
|
),
|
1101
|
);
|
1102
|
}
|
1103
|
else {
|
1104
|
// Otherwise display the select list to add a new line item.
|
1105
|
$options = commerce_line_item_type_get_name();
|
1106
|
|
1107
|
// Only display the line item selector if line item types exist.
|
1108
|
if (!empty($options)) {
|
1109
|
$element['actions']['line_item_type'] = array(
|
1110
|
'#type' => 'select',
|
1111
|
'#options' => commerce_line_item_type_get_name(),
|
1112
|
'#prefix' => '<div class="add-line-item">',
|
1113
|
);
|
1114
|
$element['actions']['line_item_add'] = array(
|
1115
|
'#type' => 'button',
|
1116
|
'#value' => t('Add line item'),
|
1117
|
'#limit_validation_errors' => array(array_merge($element['#field_parents'], array($field['field_name']))),
|
1118
|
'#ajax' => array(
|
1119
|
'callback' => 'commerce_line_item_manager_refresh',
|
1120
|
'wrapper' => 'line-item-manager',
|
1121
|
),
|
1122
|
'#suffix' => '</div>',
|
1123
|
);
|
1124
|
}
|
1125
|
}
|
1126
|
|
1127
|
return $element;
|
1128
|
}
|
1129
|
elseif ($instance['widget']['type'] == 'commerce_line_item_reference_hidden') {
|
1130
|
return array();
|
1131
|
}
|
1132
|
}
|
1133
|
|
1134
|
/**
|
1135
|
* Returns the line item manager element for display via AJAX.
|
1136
|
*/
|
1137
|
function commerce_line_item_manager_refresh($form, $form_state) {
|
1138
|
// Reverse the array parents of the triggering element, because we know the
|
1139
|
// part of the form to return will be 3 elements up from the triggering element.
|
1140
|
$parents = array_reverse($form_state['triggering_element']['#array_parents']);
|
1141
|
return $form[$parents[3]][$form[$parents[3]]['#language']];
|
1142
|
}
|
1143
|
|
1144
|
/**
|
1145
|
* Themes the line item manager widget form element.
|
1146
|
*/
|
1147
|
function theme_commerce_line_item_manager($variables) {
|
1148
|
drupal_add_css(drupal_get_path('module', 'commerce_line_item') . '/theme/commerce_line_item.admin.css');
|
1149
|
|
1150
|
$form = $variables['form'];
|
1151
|
$rows = array();
|
1152
|
|
1153
|
// Add each line item to the table.
|
1154
|
foreach (element_children($form['line_items']) as $line_item_id) {
|
1155
|
$row = array(
|
1156
|
drupal_render($form['line_items'][$line_item_id]['remove']),
|
1157
|
drupal_render($form['line_items'][$line_item_id]['title']),
|
1158
|
drupal_render($form['line_items'][$line_item_id]['line_item_label']),
|
1159
|
drupal_render($form['line_items'][$line_item_id]['commerce_unit_price']),
|
1160
|
drupal_render($form['line_items'][$line_item_id]['quantity']),
|
1161
|
drupal_render($form['line_items'][$line_item_id]['commerce_total']),
|
1162
|
);
|
1163
|
|
1164
|
$rows[] = $row;
|
1165
|
}
|
1166
|
|
1167
|
// Setup the table's variables array and build the output.
|
1168
|
$table_variables = array(
|
1169
|
'caption' => check_plain($form['#title']),
|
1170
|
'header' => $form['#header'],
|
1171
|
'rows' => $rows,
|
1172
|
'empty' => $form['#empty'],
|
1173
|
);
|
1174
|
|
1175
|
$output = theme('table', $table_variables) . drupal_render($form['actions']);
|
1176
|
|
1177
|
return '<div id="line-item-manager" class="clearfix">' . $output . '</div>';
|
1178
|
}
|
1179
|
|
1180
|
/**
|
1181
|
* Validation callback for a commerce_line_item_manager element.
|
1182
|
*
|
1183
|
* When the form is submitted, the line item reference field stores the line
|
1184
|
* item IDs as derived from the $element['line_items'] array and updates any
|
1185
|
* referenced line items based on the extra form elements.
|
1186
|
*/
|
1187
|
function commerce_line_item_manager_validate($element, &$form_state, $form) {
|
1188
|
$value = array();
|
1189
|
|
1190
|
// Loop through the line items in the manager table.
|
1191
|
foreach (element_children($element['line_items']) as $line_item_id) {
|
1192
|
// If the line item has been marked for deletion...
|
1193
|
if ($element['line_items'][$line_item_id]['remove']['#value']) {
|
1194
|
// Delete the line item now and don't include it from the $value array.
|
1195
|
commerce_line_item_delete($line_item_id);
|
1196
|
}
|
1197
|
else {
|
1198
|
// Add the line item ID to the current value of the reference field.
|
1199
|
$value[] = array('line_item_id' => $line_item_id);
|
1200
|
|
1201
|
// Update the line item based on the values in the additional elements.
|
1202
|
$line_item = clone($element['line_items'][$line_item_id]['line_item']['#value']);
|
1203
|
|
1204
|
// Validate the quantity of each line item.
|
1205
|
$element_name = implode('][', $element['line_items'][$line_item_id]['quantity']['#parents']);
|
1206
|
$quantity = $element['line_items'][$line_item_id]['quantity']['#value'];
|
1207
|
|
1208
|
if (!is_numeric($quantity) || $quantity <= 0) {
|
1209
|
form_set_error($element_name, t('You must specify a positive number for the quantity'));
|
1210
|
}
|
1211
|
elseif ($element['line_items'][$line_item_id]['quantity']['#datatype'] == 'integer' &&
|
1212
|
(int) $quantity != $quantity) {
|
1213
|
form_set_error($element_name, t('You must specify a whole number for the quantity.'));
|
1214
|
}
|
1215
|
else {
|
1216
|
$line_item->quantity = $quantity;
|
1217
|
}
|
1218
|
|
1219
|
// Manually validate the unit price of each line item.
|
1220
|
$unit_price = $form_state['values'][$element['#field_name']][$element['#language']]['line_items'][$line_item_id]['commerce_unit_price'];
|
1221
|
$amount = $unit_price[$element['#language']][0]['amount'];
|
1222
|
$currency_code = $unit_price[$element['#language']][0]['currency_code'];
|
1223
|
|
1224
|
// Display an error message for a non-numeric unit price.
|
1225
|
if (!is_numeric($amount)) {
|
1226
|
$name = implode('][', array_merge($element['line_items'][$line_item_id]['commerce_unit_price']['#parents'], array($element['#language'], 0, 'amount')));
|
1227
|
form_set_error($name, 'You must enter a numeric value for the unit price.');
|
1228
|
}
|
1229
|
elseif ($amount <> $line_item->commerce_unit_price[$element['#language']][0]['amount'] ||
|
1230
|
$currency_code <> $line_item->commerce_unit_price[$element['#language']][0]['currency_code']) {
|
1231
|
// Otherwise update the unit price amount if it has changed.
|
1232
|
$line_item->commerce_unit_price = $unit_price;
|
1233
|
|
1234
|
// Rebuild the price components array.
|
1235
|
commerce_line_item_rebase_unit_price($line_item);
|
1236
|
}
|
1237
|
|
1238
|
// Only save if values were actually changed.
|
1239
|
if ($line_item != $element['line_items'][$line_item_id]['line_item']['#value']) {
|
1240
|
commerce_line_item_save($line_item);
|
1241
|
}
|
1242
|
}
|
1243
|
}
|
1244
|
|
1245
|
// If the "Add line item" button was clicked, store the line item type in the
|
1246
|
// $form_state for the rebuild of the $form array.
|
1247
|
if (!empty($form_state['triggering_element'])) {
|
1248
|
if ($form_state['triggering_element']['#value'] == t('Add line item')) {
|
1249
|
$form_state['line_item_add'] = $element['actions']['line_item_type']['#value'];
|
1250
|
$form_state['rebuild'] = TRUE;
|
1251
|
}
|
1252
|
else {
|
1253
|
unset($form_state['line_item_add']);
|
1254
|
|
1255
|
$parent = end($form_state['triggering_element']['#parents']);
|
1256
|
|
1257
|
// If the save button was clicked from the line item type action form...
|
1258
|
if ($parent == 'save_line_item') {
|
1259
|
$line_item_type = $element['actions']['line_item_type']['#value'];
|
1260
|
|
1261
|
// Extract an order ID from the form if present.
|
1262
|
$order_id = 0;
|
1263
|
|
1264
|
if (!empty($form_state['commerce_order'])) {
|
1265
|
$order_id = $form_state['commerce_order']->order_id;
|
1266
|
}
|
1267
|
|
1268
|
// Create the new line item.
|
1269
|
$line_item = commerce_line_item_new($line_item_type['type'], $order_id);
|
1270
|
|
1271
|
// If this type specifies a valid add form callback function...
|
1272
|
if ($callback = commerce_line_item_type_callback($line_item_type, 'add_form_submit')) {
|
1273
|
// Allow the callback to alter data onto the line item to be saved and
|
1274
|
// to return an error message if something goes wrong.
|
1275
|
$error = $callback($line_item, $element, $form_state, $form);
|
1276
|
}
|
1277
|
else {
|
1278
|
// Assume no error if the callback isn't specified.
|
1279
|
$error = FALSE;
|
1280
|
}
|
1281
|
|
1282
|
// If we didn't end up with any errors...
|
1283
|
if (empty($error)) {
|
1284
|
// Save it and add it to the line item reference field's values array.
|
1285
|
commerce_line_item_save($line_item);
|
1286
|
|
1287
|
// If the item is saved, we set a variable to notify the user the
|
1288
|
// need of saving the order.
|
1289
|
$form_state['line_item_save_warning'] = TRUE;
|
1290
|
|
1291
|
$value[] = array('line_item_id' => $line_item->line_item_id);
|
1292
|
}
|
1293
|
else {
|
1294
|
// Otherwise display the error message; note this is not using
|
1295
|
// form_set_error() on purpose, because a failed addition of a line item
|
1296
|
// doesn't affect the rest of the form submission process.
|
1297
|
drupal_set_message($error, 'error');
|
1298
|
}
|
1299
|
|
1300
|
$form_state['rebuild'] = TRUE;
|
1301
|
}
|
1302
|
elseif ($parent == 'cancel') {
|
1303
|
// If the cancel button was clicked refresh without action.
|
1304
|
$form_state['rebuild'] = TRUE;
|
1305
|
}
|
1306
|
}
|
1307
|
}
|
1308
|
|
1309
|
form_set_value($element, $value, $form_state);
|
1310
|
}
|
1311
|
|
1312
|
/**
|
1313
|
* Implements hook_field_widget_error().
|
1314
|
*/
|
1315
|
function commerce_line_item_field_widget_error($element, $error) {
|
1316
|
form_error($element, $error['message']);
|
1317
|
}
|
1318
|
|
1319
|
/**
|
1320
|
* Recalculates the price components of the given line item's unit price based
|
1321
|
* on its current amount and currency code.
|
1322
|
*
|
1323
|
* When a line item's unit price is adjusted via the line item manager widget,
|
1324
|
* its components need to be recalculated using the given price as the new base
|
1325
|
* price. Otherwise old component data will be used when calculating the total
|
1326
|
* of the order, causing it not to match with the actual line item total.
|
1327
|
*
|
1328
|
* This function recalculates components by using the new unit price amount as
|
1329
|
* the base price and allowing other modules to add additional components to the
|
1330
|
* new components array as required based on the prior components.
|
1331
|
*
|
1332
|
* @param $line_item
|
1333
|
* The line item object whose unit price components should be recalculated.
|
1334
|
* The unit price amount and currency code should already be set to their new
|
1335
|
* value.
|
1336
|
*
|
1337
|
* @see hook_commerce_line_item_rebase_unit_price()
|
1338
|
*/
|
1339
|
function commerce_line_item_rebase_unit_price($line_item) {
|
1340
|
// Prepare a line item wrapper.
|
1341
|
$wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
|
1342
|
$price = $wrapper->commerce_unit_price->value();
|
1343
|
|
1344
|
// Extract the old components array and reset the current one.
|
1345
|
$old_components = array();
|
1346
|
$component_type = 'base_price';
|
1347
|
|
1348
|
if (!empty($price['data']['components'])) {
|
1349
|
$old_components = $price['data']['components'];
|
1350
|
|
1351
|
// Find the base price component type used so line items that don't use the
|
1352
|
// base_price component can preserve their own initial component type.
|
1353
|
$base_component = reset($old_components);
|
1354
|
$component_type = $base_component['name'];
|
1355
|
|
1356
|
if (!commerce_price_component_type_load($component_type)) {
|
1357
|
$component_type = 'base_price';
|
1358
|
}
|
1359
|
}
|
1360
|
|
1361
|
// Set the current price as the new base price.
|
1362
|
$price['data']['components'] = array();
|
1363
|
$price['data'] = commerce_price_component_add($price, $component_type, $price, TRUE, FALSE);
|
1364
|
|
1365
|
// Set the unit price to the current price array.
|
1366
|
$wrapper->commerce_unit_price = $price;
|
1367
|
|
1368
|
// Give other modules a chance to add components to the array.
|
1369
|
foreach (module_implements('commerce_line_item_rebase_unit_price') as $module) {
|
1370
|
$function = $module . '_commerce_line_item_rebase_unit_price';
|
1371
|
$function($price, $old_components, $line_item);
|
1372
|
}
|
1373
|
|
1374
|
// Set the unit price once again to the price with any additional components.
|
1375
|
$wrapper->commerce_unit_price = $price;
|
1376
|
}
|
1377
|
|
1378
|
/**
|
1379
|
* Callback for getting line item properties.
|
1380
|
*
|
1381
|
* @see commerce_line_item_entity_property_info()
|
1382
|
*/
|
1383
|
function commerce_line_item_get_properties($line_item, array $options, $name) {
|
1384
|
switch ($name) {
|
1385
|
case 'order':
|
1386
|
return !empty($line_item->order_id) ? $line_item->order_id : commerce_order_new();
|
1387
|
}
|
1388
|
}
|
1389
|
|
1390
|
/**
|
1391
|
* Callback for setting line item properties.
|
1392
|
*
|
1393
|
* @see commerce_line_item_entity_property_info()
|
1394
|
*/
|
1395
|
function commerce_line_item_set_properties($line_item, $name, $value) {
|
1396
|
switch ($name) {
|
1397
|
case 'order':
|
1398
|
$line_item->order_id = $value;
|
1399
|
break;
|
1400
|
}
|
1401
|
}
|
1402
|
|
1403
|
/**
|
1404
|
* Callback to alter the property info of the reference field.
|
1405
|
*
|
1406
|
* @see commerce_line_item_field_info().
|
1407
|
*/
|
1408
|
function commerce_line_item_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
|
1409
|
$property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
|
1410
|
$property['options list'] = 'entity_metadata_field_options_list';
|
1411
|
}
|
1412
|
|
1413
|
/**
|
1414
|
* Returns the total quantity of an array of line items.
|
1415
|
*
|
1416
|
* @param $line_items
|
1417
|
* The array of line items whose quantities you want to count; also accepts an
|
1418
|
* EntityListWrapper of a line item reference field.
|
1419
|
* @param $types
|
1420
|
* An array of line item types to filter by before counting.
|
1421
|
*
|
1422
|
* @return
|
1423
|
* The total quantity of all the matching line items.
|
1424
|
*/
|
1425
|
function commerce_line_items_quantity($line_items, $types = array()) {
|
1426
|
// Sum up the quantity of all matching line items.
|
1427
|
$quantity = 0;
|
1428
|
|
1429
|
foreach ($line_items as $line_item) {
|
1430
|
if (!$line_item instanceof EntityMetadataWrapper) {
|
1431
|
$line_item = entity_metadata_wrapper('commerce_line_item', $line_item);
|
1432
|
}
|
1433
|
|
1434
|
if (empty($types) || in_array($line_item->type->value(), $types)) {
|
1435
|
$quantity += $line_item->quantity->value();
|
1436
|
}
|
1437
|
}
|
1438
|
|
1439
|
return $quantity;
|
1440
|
}
|
1441
|
|
1442
|
/**
|
1443
|
* Returns the total price amount and currency of an array of line items.
|
1444
|
*
|
1445
|
* @param $line_items
|
1446
|
* The array of line items whose quantities you want to count; also accepts an
|
1447
|
* EntityListWrapper of a line item reference field.
|
1448
|
* @param $types
|
1449
|
* An array of line item types to filter by before totaling.
|
1450
|
*
|
1451
|
* @return
|
1452
|
* An associative array of containing the total 'amount' and 'currency_code'
|
1453
|
* the line items.
|
1454
|
*
|
1455
|
* @see commerce_order_calculate_total()
|
1456
|
*/
|
1457
|
function commerce_line_items_total($line_items, $types = array()) {
|
1458
|
// First determine the currency to use for the order total. This code has
|
1459
|
// been copied and modifed from commerce_order_calculate_total(). It is worth
|
1460
|
// considering abstracting this code into a separate API function that both
|
1461
|
// functions can use.
|
1462
|
$currency_code = commerce_default_currency();
|
1463
|
$currencies = array();
|
1464
|
|
1465
|
// Populate an array of how many line items on the order use each currency.
|
1466
|
foreach ($line_items as $delta => $line_item_wrapper) {
|
1467
|
// Convert the line item to a wrapper if necessary.
|
1468
|
if (!$line_item_wrapper instanceof EntityMetadataWrapper) {
|
1469
|
$line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item_wrapper);
|
1470
|
}
|
1471
|
|
1472
|
$line_item_currency_code = $line_item_wrapper->commerce_total->currency_code->value();
|
1473
|
|
1474
|
if (!in_array($line_item_currency_code, array_keys($currencies))) {
|
1475
|
$currencies[$line_item_currency_code] = 1;
|
1476
|
}
|
1477
|
else {
|
1478
|
$currencies[$line_item_currency_code]++;
|
1479
|
}
|
1480
|
}
|
1481
|
|
1482
|
reset($currencies);
|
1483
|
|
1484
|
// If only one currency is present, use that to calculate the total.
|
1485
|
if (count($currencies) == 1) {
|
1486
|
$currency_code = key($currencies);
|
1487
|
}
|
1488
|
elseif (in_array(commerce_default_currency(), array_keys($currencies))) {
|
1489
|
// Otherwise use the site default currency if it's in the array.
|
1490
|
$currency_code = commerce_default_currency();
|
1491
|
}
|
1492
|
elseif (count($currencies) > 1) {
|
1493
|
// Otherwise use the first currency in the array.
|
1494
|
$currency_code = key($currencies);
|
1495
|
}
|
1496
|
|
1497
|
// Sum up the total price of all matching line items.
|
1498
|
$total = 0;
|
1499
|
|
1500
|
foreach ($line_items as $line_item_wrapper) {
|
1501
|
// Convert the line item to a wrapper if necessary.
|
1502
|
if (!$line_item_wrapper instanceof EntityMetadataWrapper) {
|
1503
|
$line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item_wrapper);
|
1504
|
}
|
1505
|
|
1506
|
if (empty($types) || in_array($line_item_wrapper->type->value(), $types)) {
|
1507
|
$total += commerce_currency_convert(
|
1508
|
$line_item_wrapper->commerce_total->amount->value(),
|
1509
|
$line_item_wrapper->commerce_total->currency_code->value(),
|
1510
|
$currency_code
|
1511
|
);
|
1512
|
}
|
1513
|
}
|
1514
|
|
1515
|
return array('amount' => $total, 'currency_code' => $currency_code);
|
1516
|
}
|
1517
|
|
1518
|
/**
|
1519
|
* Returns a sorted array of line item summary links.
|
1520
|
*
|
1521
|
* @see hook_commerce_line_item_summary_link_info()
|
1522
|
*/
|
1523
|
function commerce_line_item_summary_links() {
|
1524
|
// Retrieve links defined by the hook and allow other modules to alter them.
|
1525
|
$links = module_invoke_all('commerce_line_item_summary_link_info');
|
1526
|
|
1527
|
// Merge in default values for our custom properties.
|
1528
|
foreach ($links as $key => &$link) {
|
1529
|
$link += array(
|
1530
|
'weight' => 0,
|
1531
|
'access' => TRUE,
|
1532
|
);
|
1533
|
}
|
1534
|
|
1535
|
drupal_alter('commerce_line_item_summary_link_info', $links);
|
1536
|
|
1537
|
// Sort the links by weight and return the array.
|
1538
|
uasort($links, 'drupal_sort_weight');
|
1539
|
|
1540
|
return $links;
|
1541
|
}
|
1542
|
|
1543
|
/**
|
1544
|
* Implements hook_field_views_data().
|
1545
|
*/
|
1546
|
function commerce_line_item_field_views_data($field) {
|
1547
|
$data = field_views_field_default_views_data($field);
|
1548
|
|
1549
|
// Build an array of bundles the field appears on.
|
1550
|
$bundles = array();
|
1551
|
|
1552
|
foreach ($field['bundles'] as $entity => $entity_bundles) {
|
1553
|
$bundles[] = $entity . ' (' . implode(', ', $entity_bundles) . ')';
|
1554
|
}
|
1555
|
|
1556
|
$replacements = array('!field_name' => $field['field_name'], '@bundles' => implode(', ', $bundles));
|
1557
|
|
1558
|
foreach ($data as $table_name => $table_data) {
|
1559
|
foreach ($table_data as $field_name => $field_data) {
|
1560
|
if (isset($field_data['filter']['field_name']) && $field_name != 'delta') {
|
1561
|
$data[$table_name][$field_name]['relationship'] = array(
|
1562
|
'title' => t('Referenced line items'),
|
1563
|
'label' => t('Line items referenced by !field_name', $replacements),
|
1564
|
'help' => t('Relate this entity to line items referenced by its !field_name value.', $replacements) . '<br />' . t('Appears in: @bundles.', $replacements),
|
1565
|
'base' => 'commerce_line_item',
|
1566
|
'base field' => 'line_item_id',
|
1567
|
'handler' => 'views_handler_relationship',
|
1568
|
);
|
1569
|
}
|
1570
|
}
|
1571
|
}
|
1572
|
|
1573
|
return $data;
|
1574
|
}
|