Projet

Général

Profil

Paste
Télécharger (52,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / line_item / commerce_line_item.module @ 9d13637e

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
}