Projet

Général

Profil

Paste
Télécharger (49,5 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / customer / commerce_customer.module @ b858700c

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines the customer profile entity and API functions to manage customers and
6
 * interact with them.
7
 */
8

    
9
/**
10
 * Implements hook_entity_info().
11
 */
12
function commerce_customer_entity_info() {
13
  $return = array(
14
    'commerce_customer_profile' => array(
15
      'label' => t('Commerce Customer profile'),
16
      'controller class' => 'CommerceCustomerProfileEntityController',
17
      'base table' => 'commerce_customer_profile',
18
      'revision table' => 'commerce_customer_profile_revision',
19
      'fieldable' => TRUE,
20
      'entity keys' => array(
21
        'id' => 'profile_id',
22
        'revision' => 'revision_id',
23
        'bundle' => 'type',
24
      ),
25
      'bundle keys' => array(
26
        'bundle' => 'type',
27
      ),
28
      'bundles' => array(),
29
      'load hook' => 'commerce_customer_profile_load',
30
      'view modes' => array(
31
        // Neither of these provide a full view of the profile but rather give
32
        // the summary of field data as seen on the checkout form or in the
33
        // customer profile reference field's display formatter.
34
        'administrator' => array(
35
          'label' => t('Administrator'),
36
          'custom settings' => FALSE,
37
        ),
38
        'customer' => array(
39
          'label' => t('Customer'),
40
          'custom settings' => FALSE,
41
        ),
42
      ),
43
      'uri callback' => 'commerce_customer_profile_uri',
44
      'label callback' => 'commerce_customer_profile_label',
45
      'token type' => 'commerce-customer-profile',
46
      'metadata controller class' => '',
47
      'access callback' => 'commerce_entity_access',
48
      'access arguments' => array(
49
        'user key' => 'uid',
50
        'access tag' => 'commerce_customer_profile_access',
51
      ),
52
      'permission labels' => array(
53
        'singular' => t('customer profile'),
54
        'plural' => t('customer profiles'),
55
      ),
56

    
57
      // Prevent Redirect alteration of the customer form.
58
      'redirect' => FALSE,
59
    ),
60
  );
61

    
62
  foreach (commerce_customer_profile_type_get_name() as $type => $name) {
63
    $return['commerce_customer_profile']['bundles'][$type] = array(
64
      'label' => $name,
65
    );
66
  }
67

    
68
  return $return;
69
}
70

    
71
/**
72
 * Entity uri callback: gives modules a chance to specify a path for a customer
73
 * profile.
74
 */
75
function commerce_customer_profile_uri($profile) {
76
  // Allow modules to specify a path, returning the first one found.
77
  foreach (module_implements('commerce_customer_profile_uri') as $module) {
78
    $uri = module_invoke($module, 'commerce_customer_profile_uri', $profile);
79

    
80
    // If the implementation returned data, use that now.
81
    if (!empty($uri)) {
82
      return $uri;
83
    }
84
  }
85

    
86
  return NULL;
87
}
88

    
89
/**
90
 * Entity label callback: returns the label for an individual customer profile.
91
 */
92
function commerce_customer_profile_label($profile) {
93
  // Load the customer profile type to look find the label callback.
94
  $profile_type = commerce_customer_profile_type_load($profile->type);
95

    
96
  // Make sure we get a valid label callback.
97
  $callback = $profile_type['label_callback'];
98

    
99
  if (!function_exists($callback)) {
100
    $callback = 'commerce_customer_profile_default_label';
101
  }
102

    
103
  return $callback($profile);
104
}
105

    
106
/**
107
 * Returns the default label for a customer profile.
108
 *
109
 * @param $profile
110
 *   A fully loaded customer profile object.
111
 *
112
 * @return
113
 *   The full name of the default address if available or the profile ID.
114
 */
115
function commerce_customer_profile_default_label($profile) {
116
  $label = '';
117

    
118
  // If the profile has a default address field...
119
  if (!empty($profile->commerce_customer_address)) {
120
    // Wrap the customer profile object for easier access to its field data.
121
    $profile_wrapper = entity_metadata_wrapper('commerce_customer_profile', $profile);
122
    if (isset($profile_wrapper->commerce_customer_address->name_line)) {
123
      $label = $profile_wrapper->commerce_customer_address->name_line->value();
124
    }
125
  }
126

    
127
  // Return the profile ID if we couldn't derive a label from an address field.
128
  if (empty($label)) {
129
    $label = $profile->profile_id;
130
  }
131

    
132
  return $label;
133
}
134

    
135
/**
136
 * Implements hook_hook_info().
137
 */
138
function commerce_customer_hook_info() {
139
  $hooks = array(
140
    'commerce_customer_profile_type_info' => array(
141
      'group' => 'commerce',
142
    ),
143
    'commerce_customer_profile_type_info_alter' => array(
144
      'group' => 'commerce',
145
    ),
146
    'commerce_customer_profile_uri' => array(
147
      'group' => 'commerce',
148
    ),
149
    'commerce_customer_profile_view' => array(
150
      'group' => 'commerce',
151
    ),
152
    'commerce_customer_profile_presave' => array(
153
      'group' => 'commerce',
154
    ),
155
    'commerce_customer_profile_insert' => array(
156
      'group' => 'commerce',
157
    ),
158
    'commerce_customer_profile_update' => array(
159
      'group' => 'commerce',
160
    ),
161
    'commerce_customer_profile_delete' => array(
162
      'group' => 'commerce',
163
    ),
164
    'commerce_customer_profile_can_delete' => array(
165
      'group' => 'commerce',
166
    ),
167
  );
168

    
169
  return $hooks;
170
}
171

    
172
/**
173
 * Implements hook_enable().
174
 */
175
function commerce_customer_enable() {
176
  commerce_customer_configure_customer_types();
177
}
178

    
179
/**
180
 * Implements hook_modules_enabled().
181
 */
182
function commerce_customer_modules_enabled($modules) {
183
  commerce_customer_configure_customer_fields($modules);
184
}
185

    
186
/**
187
 * Configures customer profile types defined by enabled modules.
188
 */
189
function commerce_customer_configure_customer_types() {
190
  foreach (commerce_customer_profile_types() as $type => $profile_type) {
191
    commerce_customer_configure_customer_profile_type($profile_type);
192
  }
193
}
194

    
195
/**
196
 * Ensures the address field is present on the specified customer profile bundle.
197
 */
198
function commerce_customer_configure_customer_profile_type($profile_type) {
199
  if ($profile_type['addressfield']) {
200
    // Look for or add an address field to the customer profile type.
201
    $field_name = 'commerce_customer_address';
202
    commerce_activate_field($field_name);
203
    field_cache_clear();
204

    
205
    $field = field_info_field($field_name);
206
    $instance = field_info_instance('commerce_customer_profile', $field_name, $profile_type['type']);
207

    
208
    if (empty($field)) {
209
      $field = array(
210
        'field_name' => $field_name,
211
        'type' => 'addressfield',
212
        'cardinality' => 1,
213
        'entity_types' => array('commerce_customer_profile'),
214
        'translatable' => FALSE,
215
      );
216

    
217
      $field = field_create_field($field);
218
    }
219

    
220
    if (empty($instance)) {
221
      $instance = array(
222
        'field_name' => $field_name,
223
        'entity_type' => 'commerce_customer_profile',
224
        'bundle' => $profile_type['type'],
225
        'label' => t('Address'),
226
        'required' => TRUE,
227
        'widget' => array(
228
          'type' => 'addressfield_standard',
229
          'weight' => -10,
230
          'settings' => array(
231
            'format_handlers' => array('address', 'name-oneline'),
232
          ),
233
        ),
234
        'display' => array(),
235
      );
236

    
237
      // Set the default display formatters for various view modes.
238
      foreach (array('default', 'customer', 'administrator') as $view_mode) {
239
        $instance['display'][$view_mode] = array(
240
          'label' => 'hidden',
241
          'type' => 'addressfield_default',
242
          'weight' => -10,
243
        );
244
      }
245

    
246
      field_create_instance($instance);
247
    }
248
  }
249
}
250

    
251
/**
252
 * Configures fields referencing customer profile types defined by enabled
253
 * modules and configures the fields on those profile types if necessary.
254
 *
255
 * @param $modules
256
 *   An array of module names whose customer profile type fields should be
257
 *   configured; if left NULL, will default to all modules that implement
258
 *   hook_commerce_customer_profile_type_info().
259
 */
260
function commerce_customer_configure_customer_fields($modules = NULL) {
261
  // If no modules array is passed, recheck the fields for all customer profile
262
  // types defined by enabled modules.
263
  if (empty($modules)) {
264
    $modules = module_implements('commerce_customer_profile_type_info');
265
  }
266

    
267
  // Reset the customer profile type static cache to ensure we get types added
268
  // by newly enabled modules.
269
  commerce_customer_profile_types_reset();
270

    
271
  // Loop through all the enabled modules.
272
  foreach ($modules as $module) {
273
    // If the module implements hook_commerce_customer_profile_type_info()...
274
    if (module_hook($module, 'commerce_customer_profile_type_info')) {
275
      $profile_types = module_invoke($module, 'commerce_customer_profile_type_info');
276

    
277
      // If this profile type has been previously disabled, update any reference
278
      // fields to be active again before attempting to recreate them.
279
      $activated = FALSE;
280

    
281
      foreach ($profile_types as $type => $profile_type) {
282
        foreach (field_read_fields(array('type' => 'commerce_customer_profile_reference', 'active' => 0, 'storage_active' => 1, 'deleted' => 0), array('include_inactive' => TRUE)) as $field_name => $field) {
283
          // If this field references profiles of the re-enabled type...
284
          if ($field['settings']['profile_type'] == $type) {
285
            if (commerce_activate_field($field_name)) {
286
              $activated = TRUE;
287
            }
288
          }
289
        }
290
      }
291

    
292
      // Clear the field cache if any profile reference fields were activated.
293
      if ($activated) {
294
        field_cache_clear();
295
      }
296

    
297
      // Loop through and configure the customer profile types defined by the module.
298
      foreach ($profile_types as $type => $profile_type) {
299
        // Default the addressfield property if it isn't set.
300
        $profile_type = array_merge(array('addressfield' => TRUE), $profile_type);
301
        commerce_customer_configure_customer_profile_type($profile_type);
302
      }
303
    }
304
  }
305
}
306

    
307
/**
308
 * Implements hook_modules_disabled().
309
 */
310
function commerce_customer_modules_disabled($modules) {
311
  // Loop through all the disabled modules.
312
  foreach ($modules as $module) {
313
    // If the module implements hook_commerce_customer_profile_type_info()...
314
    if (module_hook($module, 'commerce_customer_profile_type_info')) {
315
      $profile_types = module_invoke($module, 'commerce_customer_profile_type_info');
316

    
317
      if (!empty($profile_types)) {
318
        // Disable any profiles of the types disabled.
319
        $query = db_update('commerce_customer_profile')
320
          ->fields(array('status' => 0))
321
          ->condition('type', array_keys($profile_types), 'IN')
322
          ->execute();
323

    
324
        // Ensure each profile's current revision is also disabled.
325
        $query = db_update('commerce_customer_profile_revision')
326
          ->fields(array('status' => 0))
327
          ->where('revision_id IN (SELECT revision_id FROM {commerce_customer_profile} WHERE type IN (:profile_types))', array(':profile_types' => array_keys($profile_types)))
328
          ->execute();
329

    
330
        // Loop through and disable customer profile reference fields that may
331
        // correspond to the disabled profile types.
332
        foreach ($profile_types as $type => $profile_type) {
333
          foreach (field_read_fields(array('type' => 'commerce_customer_profile_reference')) as $field_name => $field) {
334
            // If this field references profiles of the disabled type...
335
            if ($field['settings']['profile_type'] == $type) {
336
              // Set it to inactive and save it.
337
              $field['active'] = 0;
338
              field_update_field($field);
339
            }
340
          }
341
        }
342
      }
343
    }
344
  }
345
}
346

    
347
/**
348
 * Implements hook_views_api().
349
 */
350
function commerce_customer_views_api() {
351
  return array(
352
    'api' => 3,
353
    'path' => drupal_get_path('module', 'commerce_customer') . '/includes/views',
354
  );
355
}
356

    
357
/**
358
 * Implements hook_permission().
359
 */
360
function commerce_customer_permission() {
361
  $permissions = array(
362
    'administer customer profile types' => array(
363
      'title' => t('Administer customer profile types'),
364
      'description' => t('Allows users to add customer profile types and configure their fields.'),
365
      'restrict access' => TRUE,
366
    ),
367
  );
368

    
369
  $permissions += commerce_entity_access_permissions('commerce_customer_profile');
370

    
371
  return $permissions;
372
}
373

    
374
/**
375
 * Implements hook_theme().
376
 */
377
function commerce_customer_theme() {
378
  return array(
379
    'commerce_customer_profile' => array(
380
      'variables' => array('profile' => NULL, 'view_mode' => NULL),
381
    ),
382
  );
383
}
384

    
385
/**
386
 * Implements hook_commerce_customer_profile_type_info().
387
 */
388
function commerce_customer_commerce_customer_profile_type_info() {
389
  $profile_types = array();
390

    
391
  $profile_types['billing'] = array(
392
    'type' => 'billing',
393
    'name' => t('Billing information'),
394
    'description' => t('The profile used to collect billing information on the checkout and order forms.'),
395
    'help' => '',
396
  );
397

    
398
  return $profile_types;
399
}
400

    
401
/**
402
 * Implements hook_commerce_checkout_pane_info().
403
 */
404
function commerce_customer_commerce_checkout_pane_info() {
405
  $checkout_panes = array();
406
  $weight = 5;
407

    
408
  foreach (commerce_customer_profile_types() as $type => $profile_type) {
409
    // Get instance data for the customer profile reference field.
410
    $field_name = variable_get('commerce_customer_profile_' . $type . '_field', '');
411
    $instance = field_info_instance('commerce_order', $field_name, 'commerce_order');
412
    $translated_instance = commerce_i18n_object('field_instance', $instance);
413

    
414
    $checkout_panes['customer_profile_' . $type] = array(
415
      'title' => !empty($instance['label']) ? $translated_instance['label'] : $profile_type['name'],
416
      'file' => 'includes/commerce_customer.checkout_pane.inc',
417
      'base' => 'commerce_customer_profile_pane',
418
      'page' => !empty($instance) ? 'checkout' : 'disabled',
419
      'locked' => empty($instance),
420
      'weight' => isset($profile_type['checkout_pane_weight']) ? $profile_type['checkout_pane_weight'] : $weight++,
421
    );
422
  }
423

    
424
  return $checkout_panes;
425
}
426

    
427
/**
428
 * Implements hook_field_views_data().
429
 */
430
function commerce_customer_field_views_data($field) {
431
  $data = field_views_field_default_views_data($field);
432

    
433
  // Build an array of bundles the customer profile reference field appears on.
434
  $bundles = array();
435

    
436
  foreach ($field['bundles'] as $entity => $entity_bundles) {
437
    $bundles[] = $entity . ' (' . implode(', ', $entity_bundles) . ')';
438
  }
439

    
440
  $replacements = array('!field_name' => $field['field_name'], '@bundles' => implode(', ', $bundles));
441

    
442
  foreach ($data as $table_name => $table_data) {
443
    foreach ($table_data as $field_name => $field_data) {
444
      if (isset($field_data['filter']['field_name']) && $field_name != 'delta') {
445
        $data[$table_name][$field_name]['relationship'] = array(
446
          'title' => t('Referenced customer profile'),
447
          'label' => t('Customer profile referenced by !field_name', $replacements),
448
          'help' => t('Relate this entity to the customer profile referenced by its !field_name value.', $replacements) . '<br />' . t('Appears in: @bundles.', $replacements),
449
          'base' => 'commerce_customer_profile',
450
          'base field' => 'profile_id',
451
          'handler' => 'views_handler_relationship',
452
        );
453
      }
454
    }
455
  }
456

    
457
  return $data;
458
}
459

    
460
/**
461
 * Implements hook_field_delete_instance().
462
 */
463
function commerce_customer_field_delete_instance($instance) {
464
  // If the checkout module is enabled, we need to react to the deletion of a
465
  // profile reference field from the core order bundle by updating the related
466
  // checkout pane so it is no longer configured to use the deleted field.
467
  if (module_exists('commerce_checkout')) {
468
    $field = field_info_field($instance['field_name']);
469

    
470
    // Ensure the deleted field instance is a customer profile reference field
471
    // and is attached to the core commerce_order entity type / bundle.
472
    if ($field['type'] == 'commerce_customer_profile_reference' &&
473
      $instance['entity_type'] == 'commerce_order' && $instance['bundle'] == 'commerce_order') {
474
      // Loop through the customer profile types to see if there's a checkout
475
      // pane that used the deleted field.
476
      foreach (commerce_customer_profile_types() as $type => $profile_type) {
477
        if (variable_get('commerce_customer_profile_' . $type . '_field', '') == $field['field_name']) {
478
          // Unset the field variable and disable the checkout pane.
479
          variable_set('commerce_customer_profile_' . $type . '_field', '');
480

    
481
          $checkout_pane = commerce_checkout_pane_load('customer_profile_' . $type);
482
          $checkout_pane['enabled'] = FALSE;
483
          $checkout_pane['page'] = 'disabled';
484

    
485
          commerce_checkout_pane_save($checkout_pane);
486

    
487
          drupal_static_reset('commerce_checkout_panes');
488
        }
489
      }
490
    }
491
  }
492
}
493

    
494
/**
495
 * Returns an array of customer profile type arrays keyed by type.
496
 */
497
function commerce_customer_profile_types() {
498
  // First check the static cache for a profile types array.
499
  $profile_types = &drupal_static(__FUNCTION__);
500

    
501
  // If it did not exist, fetch the types now.
502
  if (!isset($profile_types)) {
503
    $profile_types = array();
504

    
505
    // Find profile types defined by hook_commerce_customer_profile_type_info().
506
    foreach (module_implements('commerce_customer_profile_type_info') as $module) {
507
      foreach (module_invoke($module, 'commerce_customer_profile_type_info') as $type => $profile_type) {
508
        // Initialize customer profile type properties if necessary.
509
        $defaults = array(
510
          'description' => '',
511
          'help' => '',
512
          'addressfield' => TRUE,
513
          'module' => $module,
514
          'label_callback' => 'commerce_customer_profile_default_label',
515
        );
516

    
517
        $profile_types[$type] = array_merge($defaults, $profile_type);
518
      }
519
    }
520

    
521
    // Last allow the info to be altered by other modules.
522
    drupal_alter('commerce_customer_profile_type_info', $profile_types);
523
  }
524

    
525
  return $profile_types;
526
}
527

    
528
/**
529
 * Loads a customer profile type.
530
 *
531
 * @param $type
532
 *   The machine-readable name of the customer profile type; accepts normal
533
 *     machine names and URL prepared machine names with underscores replaced by
534
 *     hyphens.
535
 */
536
function commerce_customer_profile_type_load($type) {
537
  $type = strtr($type, array('-' => '_'));
538
  $profile_types = commerce_customer_profile_types();
539
  return !empty($profile_types[$type]) ? $profile_types[$type] : FALSE;
540
}
541

    
542
/**
543
 * Resets the cached list of customer profile types.
544
 */
545
function commerce_customer_profile_types_reset() {
546
  $profile_types = &drupal_static('commerce_customer_profile_types');
547
  $profile_types = NULL;
548
  entity_info_cache_clear();
549
}
550

    
551
/**
552
 * Returns the human readable name of any or all customer profile types.
553
 *
554
 * @param $type
555
 *   Optional parameter specifying the type whose name to return.
556
 *
557
 * @return
558
 *   Either an array of all profile type names keyed by the machine name or a
559
 *     string containing the human readable name for the specified type. If a
560
 *     type is specified that does not exist, this function returns FALSE.
561
 */
562
function commerce_customer_profile_type_get_name($type = NULL) {
563
  $profile_types = commerce_customer_profile_types();
564

    
565
  // Return a type name if specified and it exists.
566
  if (!empty($type)) {
567
    if (isset($profile_types[$type])) {
568
      return $profile_types[$type]['name'];
569
    }
570
    else {
571
      // Return FALSE if it does not exist.
572
      return FALSE;
573
    }
574
  }
575

    
576
  // Otherwise turn the array values into the type name only.
577
  foreach ($profile_types as $key => $value) {
578
    $profile_types[$key] = $value['name'];
579
  }
580

    
581
  return $profile_types;
582
}
583

    
584
/**
585
 * Wraps commerce_customer_profile_type_get_name() for the Entity module.
586
 */
587
function commerce_customer_profile_type_options_list() {
588
  return commerce_customer_profile_type_get_name();
589
}
590

    
591
/**
592
 * Title callback: return the human-readable customer profile type name.
593
 */
594
function commerce_customer_profile_type_title($profile_type) {
595
  return $profile_type['name'];
596
}
597

    
598
/**
599
 * Returns a path argument from a customer profile type.
600
 */
601
function commerce_customer_profile_type_to_arg($type) {
602
  return $type;
603
}
604

    
605
/**
606
 * Returns an initialized customer profile object.
607
 *
608
 * @param $type
609
 *   The type of customer profile to create.
610
 * @param $uid
611
 *   The uid of the user the customer profile is for.
612
 *
613
 * @return
614
 *   A customer profile object with all default fields initialized.
615
 */
616
function commerce_customer_profile_new($type = '', $uid = 0) {
617
  return entity_get_controller('commerce_customer_profile')->create(array(
618
    'type' => $type,
619
    'uid' => $uid,
620
  ));
621
}
622

    
623
/**
624
 * Saves a customer profile.
625
 *
626
 * @param $profile
627
 *   The full customer profile object to save. If $profile->profile_id is empty,
628
 *     a new customer profile will be created.
629
 *
630
 * @return
631
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
632
 */
633
function commerce_customer_profile_save($profile) {
634
  return entity_get_controller('commerce_customer_profile')->save($profile);
635
}
636

    
637
/**
638
 * Loads a customer profile by ID.
639
 */
640
function commerce_customer_profile_load($profile_id) {
641
  $profiles = commerce_customer_profile_load_multiple(array($profile_id), array());
642
  return $profiles ? reset($profiles) : FALSE;
643
}
644

    
645
/**
646
 * Loads multiple customer profiles by ID or based on a set of conditions.
647
 *
648
 * @see entity_load()
649
 *
650
 * @param $profile_ids
651
 *   An array of customer profile IDs.
652
 * @param $conditions
653
 *   An array of conditions on the {commerce_customer_profile} table in the form
654
 *     'field' => $value.
655
 * @param $reset
656
 *   Whether to reset the internal customer profile loading cache.
657
 *
658
 * @return
659
 *   An array of customer profile objects indexed by profile_id.
660
 */
661
function commerce_customer_profile_load_multiple($profile_ids = array(), $conditions = array(), $reset = FALSE) {
662
  return entity_load('commerce_customer_profile', $profile_ids, $conditions, $reset);
663
}
664

    
665
/**
666
 * Determines whether or not the give customer profile can be deleted.
667
 *
668
 * @param $profile
669
 *   The customer profile to be checked for deletion.
670
 *
671
 * @return
672
 *   Boolean indicating whether or not the customer profile can be deleted.
673
 */
674
function commerce_customer_profile_can_delete($profile) {
675
  // Return FALSE if the given profile does not have an ID; it need not be
676
  // deleted, which is functionally equivalent to cannot be deleted as far as
677
  // code depending on this function is concerned.
678
  if (empty($profile->profile_id)) {
679
    return FALSE;
680
  }
681

    
682
  // If any module implementing hook_commerce_customer_profile_can_delete()
683
  // returns FALSE the customer profile cannot be deleted. Return TRUE if none
684
  // return FALSE.
685
  return !in_array(FALSE, module_invoke_all('commerce_customer_profile_can_delete', $profile));
686
}
687

    
688
/**
689
 * Deletes a customer profile by ID.
690
 *
691
 * @param $profile_id
692
 *   The ID of the customer profile to delete.
693
 * @param $entity_context
694
 *   An optional entity context array that specifies the entity throgh whose
695
 *   customer profile reference field the given profiles are being deleted:
696
 *   - entity_type: the type of entity
697
 *   - entity_id: the unique ID of the entity
698
 *
699
 * @return
700
 *   TRUE on success, FALSE otherwise.
701
 */
702
function commerce_customer_profile_delete($profile_id, $entity_context = array()) {
703
  return commerce_customer_profile_delete_multiple(array($profile_id), $entity_context);
704
}
705

    
706
/**
707
 * Deletes multiple customer profiles by ID.
708
 *
709
 * @param $profile_ids
710
 *   An array of customer profile IDs to delete.
711
 * @param $entity_context
712
 *   An optional entity context array that specifies the entity throgh whose
713
 *   customer profile reference field the given profiles are being deleted:
714
 *   - entity_type: the type of entity
715
 *   - entity_id: the unique ID of the entity
716
 *
717
 * @return
718
 *   TRUE on success, FALSE otherwise.
719
 */
720
function commerce_customer_profile_delete_multiple($profile_ids, $entity_context = array()) {
721
  return entity_get_controller('commerce_customer_profile')->delete($profile_ids, NULL, $entity_context);
722
}
723

    
724
/**
725
 * Implements hook_commerce_customer_profile_delete().
726
 *
727
 * Remove references to this customer profile in all customer profile reference
728
 * field contents.
729
 */
730
function commerce_customer_commerce_customer_profile_delete($profile) {
731
  // Check the data in every customer profile reference field.
732
  foreach (commerce_info_fields('commerce_customer_profile_reference') as $field_name => $field) {
733
    // Query for any entity referencing the deleted profile in this field.
734
    $query = new EntityFieldQuery();
735
    $query->fieldCondition($field_name, 'profile_id', $profile->profile_id, '=');
736
    $result = $query->execute();
737

    
738
    // If results were returned...
739
    if (!empty($result)) {
740
      // Loop over results for each type of entity returned.
741
      foreach ($result as $entity_type => $data) {
742
        // Load the entities of the current type.
743
        $entities = entity_load($entity_type, array_keys($data));
744

    
745
        // Loop over each entity and remove the reference to the deleted profile.
746
        foreach ($entities as $entity_id => $entity) {
747
          commerce_entity_reference_delete($entity, $field_name, 'profile_id', $profile->profile_id);
748

    
749
          // Store the changes to the entity.
750
          entity_save($entity_type, $entity);
751
        }
752
      }
753
    }
754
  }
755
}
756

    
757
/**
758
 * Checks customer profile access for various operations.
759
 *
760
 * @param $op
761
 *   The operation being performed. One of 'view', 'update', 'create' or
762
 *   'delete'.
763
 * @param $profile
764
 *   Optionally a profile to check access for or for the create operation the
765
 *   profile type. If nothing is given access permissions for all profiles are returned.
766
 * @param $account
767
 *   The user to check for. Leave it to NULL to check for the current user.
768
 */
769
function commerce_customer_profile_access($op, $profile = NULL, $account = NULL) {
770
  return commerce_entity_access($op, $profile, $account, 'commerce_customer_profile');
771
}
772

    
773
/**
774
 * Implements hook_query_TAG_alter().
775
 */
776
function commerce_customer_query_commerce_customer_profile_access_alter(QueryAlterableInterface $query) {
777
  return commerce_entity_access_query_alter($query, 'commerce_customer_profile');
778
}
779

    
780
/**
781
 * Implements hook_field_info().
782
 */
783
function commerce_customer_field_info() {
784
  return array(
785
    'commerce_customer_profile_reference' => array(
786
      'label' => t('Customer profile reference'),
787
      'description' => t('This field stores the ID of a related customer profile as an integer value.'),
788
      'settings' => array('profile_type' => 'billing', 'options_list_limit' => 50),
789
      'instance_settings' => array(),
790
      'default_widget' => 'options_select',
791
      'default_formatter' => 'commerce_customer_profile_reference_display',
792
      'property_type' => 'commerce_customer_profile',
793
      'property_callbacks' => array('commerce_customer_profile_property_info_callback'),
794
    ),
795
  );
796
}
797

    
798
/**
799
 * Implements hook_field_settings_form().
800
 */
801
function commerce_customer_field_settings_form($field, $instance, $has_data) {
802
  $settings = $field['settings'];
803
  $form = array();
804

    
805
  if ($field['type'] == 'commerce_customer_profile_reference') {
806
    $options = array();
807

    
808
    // Build an options array of the customer profile types.
809
    foreach (commerce_customer_profile_type_get_name() as $type => $name) {
810
      $options[$type] = check_plain($name);
811
    }
812

    
813
    $form['profile_type'] = array(
814
      '#type' => 'radios',
815
      '#title' => t('Customer profile type that can be referenced'),
816
      '#options' => $options,
817
      '#default_value' => !empty($settings['profile_type']) ? $settings['profile_type'] : 'billing',
818
      '#disabled' => $has_data,
819
    );
820

    
821
    $form['options_list_limit'] = array(
822
      '#type' => 'textfield',
823
      '#title' => t('Options list limit'),
824
      '#description' => t('Limits the number of customer profiles available in field widgets with options lists; leave blank for no limit.'),
825
      '#default_value' => !empty($settings['options_list_limit']) ? $settings['options_list_limit'] : 50,
826
      '#element_validate' => array('commerce_options_list_limit_validate'),
827
    );
828
  }
829

    
830
  return $form;
831
}
832

    
833
/**
834
 * Implements hook_field_validate().
835
 *
836
 * Possible error codes:
837
 * - 'invalid_profile_id': profile_id is not valid for the field (not a valid
838
 *   line item ID).
839
 */
840
function commerce_customer_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
841
  $translated_instance = commerce_i18n_object('field_instance', $instance);
842

    
843
  if ($field['type'] == 'commerce_customer_profile_reference') {
844
    // Extract profile_ids to check.
845
    $profile_ids = array();
846

    
847
    // First check non-numeric profile_id's to avoid losing time with them.
848
    foreach ($items as $delta => $item) {
849
      if (is_array($item) && !empty($item['profile_id'])) {
850
        if (is_numeric($item['profile_id'])) {
851
          $profile_ids[] = $item['profile_id'];
852
        }
853
        else {
854
          $errors[$field['field_name']][$langcode][$delta][] = array(
855
            'error' => 'invalid_profile_id',
856
            'message' => t('%name: you have specified an invalid customer profile for this reference field.', array('%name' => $translated_instance['label'])),
857
          );
858
        }
859
      }
860
    }
861

    
862
    // Prevent performance hog if there are no ids to check.
863
    if ($profile_ids) {
864
      $profiles = commerce_customer_profile_load_multiple($profile_ids, array('type' => $field['settings']['profile_type']));
865

    
866
      foreach ($items as $delta => $item) {
867
        if (is_array($item)) {
868
          // Check that the item specifies a profile_id and that a profile of
869
          // the proper type exists with that ID.
870
          if (!empty($item['profile_id']) && !isset($profiles[$item['profile_id']])) {
871
            $errors[$field['field_name']][$langcode][$delta][] = array(
872
              'error' => 'invalid_profile_id',
873
              'message' => t('%name: you have specified an invalid customer profile for this reference field.', array('%name' => $translated_instance['label'])),
874
            );
875
          }
876
        }
877
      }
878
    }
879
  }
880
}
881

    
882
/**
883
 * Implements hook_field_is_empty().
884
 */
885
function commerce_customer_field_is_empty($item, $field) {
886
  if ($field['type'] == 'commerce_customer_profile_reference') {
887
    // profile_id = 0 is empty too, which is exactly what we want.
888
    return empty($item['profile_id']);
889
  }
890
}
891

    
892
/**
893
 * Implements hook_field_formatter_info().
894
 */
895
function commerce_customer_field_formatter_info() {
896
  return array(
897
    'commerce_customer_profile_reference_display' => array(
898
      'label' => t('Customer profile display'),
899
      'description' => t('Display the customer profile.'),
900
      'field types' => array('commerce_customer_profile_reference'),
901
    ),
902
  );
903
}
904

    
905
/**
906
 * Implements hook_field_formatter_view().
907
 */
908
function commerce_customer_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
909
  $result = array();
910

    
911
  // Collect the list of customer profile IDs.
912
  $profile_ids = array();
913

    
914
  foreach ($items as $delta => $item) {
915
    $profile_ids[] = $item['profile_id'];
916
  }
917

    
918
  switch ($display['type']) {
919
    case 'commerce_customer_profile_reference_display':
920
      foreach ($items as $delta => $item) {
921
        $profile = commerce_customer_profile_load($item['profile_id']);
922

    
923
        if ($profile) {
924
          $content = entity_view('commerce_customer_profile', array($profile->profile_id => $profile), 'customer', $langcode);
925

    
926
          $result[$delta] = array(
927
            '#markup' => drupal_render($content),
928
          );
929
        }
930
      }
931

    
932
      break;
933
  }
934

    
935
  return $result;
936
}
937

    
938
/**
939
 * Implements hook_field_widget_info().
940
 *
941
 * Defines widgets available for use with field types as specified in each
942
 * widget's $info['field types'] array.
943
 */
944
function commerce_customer_field_widget_info() {
945
  $widgets = array();
946

    
947
  // Define the creation / reference widget for line items.
948
  $widgets['commerce_customer_profile_manager'] = array(
949
    'label' => t('Customer profile manager'),
950
    'description' => t('Use a complex widget to edit the profile referenced by this object.'),
951
    'field types' => array('commerce_customer_profile_reference'),
952
    'settings' => array(),
953
    'behaviors' => array(
954
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
955
      'default value' => FIELD_BEHAVIOR_NONE,
956
    ),
957
  );
958

    
959
  return $widgets;
960
}
961

    
962
/**
963
 * Implements hook_form_FORM_ID_alter().
964
 */
965
function commerce_customer_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
966
  // Alter the field edit form so it's obvious that customer profile manager
967
  // widgets do not support multiple values.
968
  if (empty($form['locked']) &&
969
      !empty($form['instance']) &&
970
      $form['instance']['widget']['type']['#value'] == 'commerce_customer_profile_manager') {
971
    $form['field']['cardinality']['#options'] = array('1' => '1');
972
    $form['field']['cardinality']['#description'] = t('The customer profile manager widget only supports single value editing and entry via its form.');
973
  }
974
}
975

    
976
/**
977
 * Implements hook_field_widget_info_alter().
978
 */
979
function commerce_customer_field_widget_info_alter(&$info) {
980
  if (!empty($info['options_select'])) {
981
    $info['options_select']['field types'][] = 'commerce_customer_profile_reference';
982
  }
983
}
984

    
985
/**
986
 * Implements hook_options_list().
987
 */
988
function commerce_customer_options_list($field) {
989
  $options = array();
990

    
991
  // Look for an options list limit in the field settings.
992
  if (!empty($field['settings']['options_list_limit'])) {
993
    $limit = (int) $field['settings']['options_list_limit'];
994
  }
995
  else {
996
    $limit = NULL;
997
  }
998

    
999
  // Loop through all customer matches.
1000
  foreach (commerce_customer_match_customer_profiles($field, array(), $limit) as $profile_id => $data) {
1001
    // Add them to the options list in optgroups by customer profile type.
1002
    $name = check_plain(commerce_customer_profile_type_get_name($data['type']));
1003
    $options[$name][$profile_id] = t('@profile: User @user', array('@profile' => $profile_id, '@user' => $data['uid']));
1004
  }
1005

    
1006
  // Simplify the options list if only one optgroup exists.
1007
  if (count($options) == 1) {
1008
    $options = reset($options);
1009
  }
1010

    
1011
  return $options;
1012
}
1013

    
1014
/**
1015
 * Implements hook_field_widget_form().
1016
 *
1017
 * Used to define the form element for custom widgets.
1018
 */
1019
function commerce_customer_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
1020
  // Define the complex customer profile reference field widget.
1021
  if ($instance['widget']['type'] == 'commerce_customer_profile_manager') {
1022
    $profile_type = commerce_customer_profile_type_load($field['settings']['profile_type']);
1023

    
1024
    // Do not attempt to render the widget for a non-existent profile type.
1025
    if (empty($profile_type)) {
1026
      drupal_set_message(t('Field %field_name attempted to use the non-existing customer profile type %type.', array('%field_name' => $field['field_name'], '%type' => $field['settings']['profile_type'])), 'error');
1027
      return array();
1028
    }
1029

    
1030
    // Build an array of customer profile IDs from this field's values.
1031
    $profile_ids = array();
1032

    
1033
    foreach ($items as $item) {
1034
      $profile_ids[] = $item['profile_id'];
1035
    }
1036

    
1037
    // Load the profiles for temporary storage in the form array.
1038
    $profiles = commerce_customer_profile_load_multiple($profile_ids);
1039

    
1040
    if (empty($profiles)) {
1041
      $profiles[0] = commerce_customer_profile_new($profile_type['type']);
1042
    }
1043

    
1044
    // Update the base form element array to use the proper theme and validate
1045
    // functions and to include header information for the line item table.
1046
    $element += array(
1047
      '#element_validate' => array('commerce_customer_profile_manager_validate'),
1048
      'profiles' => array('#tree' => TRUE),
1049
    );
1050

    
1051
    // Add a set of elements to the form for each referenced profile.
1052
    $key = 0;
1053

    
1054
    foreach ($profiles as $profile) {
1055
      // Add a fieldset around the profile form.
1056
      $element['profiles'][$key] = array(
1057
        '#type' => 'fieldset',
1058
        '#title' => check_plain($profile_type['name']),
1059
      );
1060

    
1061
      // Store the original customer profile for later comparison.
1062
      $element['profiles'][$key]['profile'] = array(
1063
        '#type' => 'value',
1064
        '#value' => $profile,
1065
      );
1066

    
1067
      field_attach_form('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
1068

    
1069
      // Tweak the form to remove the fieldset from the address field if there
1070
      // is only one on this profile.
1071
      $addressfields = array();
1072

    
1073
      foreach (commerce_info_fields('addressfield', 'commerce_customer_profile') as $field_name => $field) {
1074
        // First make sure this addressfield is part of the current profile.
1075
        if (!empty($element['profiles'][$key][$field_name]['#language'])) {
1076
          $langcode = $element['profiles'][$key][$field_name]['#language'];
1077

    
1078
          // Only consider this addressfield if it's represented on the form.
1079
          if (!empty($element['profiles'][$key][$field_name][$langcode])) {
1080
            $addressfields[] = array($field_name, $langcode);
1081
          }
1082
        }
1083
      }
1084

    
1085
      // Check to ensure only one addressfield was found on the form.
1086
      if (count($addressfields) == 1) {
1087
        list($field_name, $langcode) = array_shift($addressfields);
1088

    
1089
        foreach (element_children($element['profiles'][$key][$field_name][$langcode]) as $delta) {
1090
          if ($element['profiles'][$key][$field_name][$langcode][$delta]['#type'] != 'submit') {
1091
            $element['profiles'][$key][$field_name][$langcode][$delta]['#type'] = 'container';
1092
          }
1093
        }
1094

    
1095
        // Remove the default #parents array so the normal tree can do its thing.
1096
        unset($element['profiles'][$key]['#parents']);
1097
      }
1098

    
1099
      // This checkbox will be overridden with a clickable delete image.
1100
      // TODO: Make this an #ajaxy submit button.
1101
      if ($profile->profile_id) {
1102
        // Create a title for this box based on whether or not the currently
1103
        // referenced customer profile can be deleted.
1104
        if (commerce_customer_profile_can_delete($profile)) {
1105
          $title = t('Delete this profile');
1106
        }
1107
        else {
1108
          $title = t('Clear this profile');
1109
        }
1110

    
1111
        $element['profiles'][$key]['remove'] = array(
1112
          '#type' => 'checkbox',
1113
          '#title' => $title,
1114
          '#default_value' => FALSE,
1115
          '#access' => commerce_customer_profile_access('delete', $profile),
1116
          '#weight' => 100,
1117
        );
1118
      }
1119

    
1120
      $key += 1;
1121
    }
1122

    
1123
    // If the reference field is not required, unrequire any elements in the
1124
    // profile edit form.
1125
    if (!$delta == 0 || !$instance['required']) {
1126
      commerce_unrequire_form_elements($element);
1127
    }
1128

    
1129
    return $element;
1130
  }
1131
}
1132

    
1133
/**
1134
 * Validation callback for a commerce_customer_profile_manager element.
1135
 *
1136
 * When the form is submitted, the profile reference field stores the profile
1137
 * IDs as derived from the $element['profiles'] array and updates any
1138
 * referenced profiles based on the extra form elements.
1139
 */
1140
function commerce_customer_profile_manager_validate($element, &$form_state, $form) {
1141
  $value = array();
1142

    
1143
  // If the triggering element wants to limit validation errors and the form is
1144
  // not going to be submitted...
1145
  if (isset($form_state['triggering_element']['#limit_validation_errors']) && ($form_state['triggering_element']['#limit_validation_errors'] !== FALSE) && !($form_state['submitted'] && !isset($form_state['triggering_element']['#submit']))) {
1146
    // Ensure this element wasn't specifically marked for validation in the
1147
    // #limit_validation_errors sections array.
1148
    $section_match = FALSE;
1149

    
1150
    foreach ($form_state['triggering_element']['#limit_validation_errors'] as $section) {
1151
      // Because #limit_validation_errors sections force validation for any
1152
      // element that matches the section or is a child of it, we can consider
1153
      // it a match if the section completely matches the beginning of this
1154
      // element's #parents array even if #parents contains additional elements.
1155
      if (array_intersect_assoc($section, $element['#parents']) === $section) {
1156
        $section_match = TRUE;
1157
      }
1158
    }
1159

    
1160
    // Exit this validate function, because the form is going to be rebuilt and
1161
    // the data submitted may very well be incomplete.
1162
    if (!$section_match) {
1163
      form_set_value($element, array(), $form_state);
1164
      return;
1165
    }
1166
  }
1167

    
1168
  // Loop through the profiles in the manager table.
1169
  foreach (element_children($element['profiles']) as $key) {
1170
    // Update the profile based on the values in the additional elements.
1171
    $profile = clone($element['profiles'][$key]['profile']['#value']);
1172

    
1173
    // If the profile has been marked for deletion...
1174
    if ($profile->profile_id && $element['profiles'][$key]['remove']['#value']) {
1175
      // Delete the profile now if we can and don't include it in the $value array.
1176
      if (commerce_customer_profile_can_delete($profile)) {
1177
        // If another module altered in an entity context, be sure to pass it to
1178
        // the delete function.
1179
        if (!empty($profile->entity_context)) {
1180
          commerce_customer_profile_delete($profile->profile_id, $profile->entity_context);
1181
        }
1182
        else {
1183
          commerce_customer_profile_delete($profile->profile_id);
1184
        }
1185
      }
1186
    }
1187
    else {
1188
      // Notify field widgets to validate their data.
1189
      field_attach_form_validate('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
1190

    
1191
      // TODO: Trap it on error, rebuild the form with error messages.
1192
      // Notify field widgets to save the field data.
1193
      field_attach_submit('commerce_customer_profile', $profile, $element['profiles'][$key], $form_state);
1194

    
1195
      // Only save if values were actually changed.
1196
      if ($profile != $element['profiles'][$key]['profile']['#value']) {
1197
        commerce_customer_profile_save($profile);
1198
      }
1199

    
1200
      // Add the profile ID to the current value of the reference field.
1201
      $value[] = array('profile_id' => $profile->profile_id);
1202
    }
1203
  }
1204

    
1205
  form_set_value($element, $value, $form_state);
1206
}
1207

    
1208
/**
1209
 * Implements hook_field_widget_error().
1210
 */
1211
function commerce_customer_field_widget_error($element, $error) {
1212
  form_error($element, $error['message']);
1213
}
1214

    
1215
/**
1216
 * Callback to alter the property info of the reference field.
1217
 *
1218
 * @see commerce_customer_field_info().
1219
 */
1220
function commerce_customer_profile_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1221
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1222
  $property['options list'] = 'entity_metadata_field_options_list';
1223
}
1224

    
1225
/**
1226
 * Fetches an array of all customer profiles matching the given parameters.
1227
 *
1228
 * This info is used in various places (allowed values, autocomplete results,
1229
 * input validation...). Some of them only need the profile_ids, others
1230
 * profile_id + titles, others yet profile_id + titles + rendered row (for
1231
 * display in widgets).
1232
 *
1233
 * The array we return contains all the potentially needed information,
1234
 * and lets calling functions use the parts they actually need.
1235
 *
1236
 * @param $field
1237
 *   The field description.
1238
 * @param $ids
1239
 *   Optional product ids to lookup.
1240
 * @param $limit
1241
 *   If non-zero, limit the size of the result set.
1242
 *
1243
 * @return
1244
 *   An array of valid profiles in the form:
1245
 *   array(
1246
 *     profile_id => array(
1247
 *       'uid' => The user ID,
1248
 *       'rendered' => The text to display in widgets (can be HTML)
1249
 *     ),
1250
 *     ...
1251
 *   )
1252
 */
1253
function commerce_customer_match_customer_profiles($field, $ids = array(), $limit = NULL) {
1254
  $results = &drupal_static(__FUNCTION__, array());
1255

    
1256
  // Create unique id for static cache.
1257
  $cid = implode(':', array(
1258
    $field['field_name'],
1259
    implode('-', $ids),
1260
    $limit,
1261
  ));
1262

    
1263
  if (!isset($results[$cid])) {
1264
    $matches = _commerce_customer_match_customer_profiles_standard($field, $ids, $limit);
1265

    
1266
    // Store the results.
1267
    $results[$cid] = !empty($matches) ? $matches : array();
1268
  }
1269

    
1270
  return $results[$cid];
1271
}
1272

    
1273
/**
1274
 * Helper function for commerce_customer_match_customer_profiles().
1275
 *
1276
 * Returns an array of products matching the specific parameters.
1277
 */
1278
function _commerce_customer_match_customer_profiles_standard($field, $ids = array(), $limit = NULL) {
1279
  // Build the query object with the necessary fields.
1280
  $query = db_select('commerce_customer_profile', 'cp');
1281
  $profile_id_alias = $query->addField('cp', 'profile_id');
1282
  $profile_uid_alias = $query->addField('cp', 'uid');
1283
  $profile_type_alias = $query->addField('cp', 'type');
1284

    
1285
  // Add a condition to the query to filter by matching profile types.
1286
  if (!empty($field['settings']['referenceable_types']) && is_array($field['settings']['referenceable_types'])) {
1287
    $types = array_diff(array_values($field['settings']['referenceable_types']), array(0, NULL));
1288

    
1289
    // Only filter by type if some types have been specified.
1290
    if (!empty($types)) {
1291
      $query->condition('cp.type', $types, 'IN');
1292
    }
1293
  }
1294

    
1295
  if ($ids) {
1296
    // Otherwise add a profile_id specific condition if specified.
1297
    $query->condition($profile_id_alias, $ids, 'IN');
1298
  }
1299

    
1300
  // Order the results by ID and then profile type.
1301
  $query
1302
    ->orderBy($profile_id_alias)
1303
    ->orderBy($profile_type_alias);
1304

    
1305
  // Add a limit if specified.
1306
  if ($limit) {
1307
    $query->range(0, $limit);
1308
  }
1309

    
1310
  // Execute the query and build the results array.
1311
  $result = $query->execute();
1312

    
1313
  $matches = array();
1314

    
1315
  foreach ($result->fetchAll() as $profile) {
1316
    $matches[$profile->profile_id] = array(
1317
      'uid' => $profile->uid,
1318
      'type' => $profile->type,
1319
      'rendered' => t('Profile @profile_id', array('@profile_id' => $profile->profile_id)),
1320
    );
1321
  }
1322

    
1323
  return $matches;
1324
}
1325

    
1326
/**
1327
 * Callback for getting customer profile properties.
1328
 *
1329
 * @see commerce_customer_entity_property_info()
1330
 */
1331
function commerce_customer_profile_get_properties($profile, array $options, $name) {
1332
  switch ($name) {
1333
    case 'user':
1334
      return $profile->uid;
1335
  }
1336
}
1337

    
1338
/**
1339
 * Callback for setting customer profile properties.
1340
 *
1341
 * @see commerce_customer_entity_property_info()
1342
 */
1343
function commerce_customer_profile_set_properties($profile, $name, $value) {
1344
  if ($name == 'user') {
1345
    $profile->uid = $value;
1346
  }
1347
}
1348

    
1349
/**
1350
 * Element validate callback: Pertaining to the "copy profile" checkbox.
1351
 */
1352
function commerce_customer_profile_copy_validate($element, &$form_state, $form) {
1353
  $triggering_element = end($form_state['triggering_element']['#array_parents']);
1354
  $pane_id = reset($element['#array_parents']);
1355

    
1356
  // Checkbox: Off - Only invoked for the corresponding trigger element.
1357
  if ($triggering_element == 'commerce_customer_profile_copy' && $form_state['triggering_element']['#id'] == $element['#id'] && empty($element['#value'])) {
1358
    $form_state['order']->data['profile_copy'][$pane_id]['status'] = FALSE;
1359
    unset($form_state['order']->data['profile_copy'][$pane_id]['elements']);
1360
    commerce_order_save($form_state['order']);
1361
  }
1362

    
1363
  // Checkbox: On - Only invoked for the corresponding trigger element, or the
1364
  // "continue" checkout form button.
1365
  elseif ((($triggering_element == 'commerce_customer_profile_copy' && $form_state['triggering_element']['#id'] == $element['#id']) || $triggering_element == 'continue') && !empty($element['#value'])) {
1366
    $type = substr($pane_id, 17);  // Removes 'customer_profile_'
1367
    $source_id = 'customer_profile_' . variable_get('commerce_' . $pane_id . '_profile_copy_source', '');
1368
    $info = array('commerce_customer_profile', $type, $pane_id);
1369

    
1370
    // Try getting the source profile from the form_state values, if it is present on the form..
1371
    if (isset($form_state['values'][$source_id])) {
1372
      commerce_customer_profile_copy_fields($info, $form_state['input'][$pane_id], $form_state['input'][$source_id], $form_state);
1373
      commerce_customer_profile_copy_fields($info, $form_state['values'][$pane_id], $form_state['values'][$source_id], $form_state);
1374
    }
1375

    
1376
    // Otherwise, attempt to get source profile from the order object.
1377
    else {
1378
      // Check for source profile via order wrapper.
1379
      $wrapper = entity_metadata_wrapper('commerce_order', $form_state['order']);
1380
      $profile = NULL;
1381

    
1382
      if ($source_field_name = variable_get('commerce_' . $source_id . '_field', '')) {
1383
        $profile = $wrapper->{$source_field_name}->value();
1384
      }
1385
      elseif (!empty($form_state['order']->data['profiles'][$source_id])) {
1386
        $profile = commerce_customer_profile_load($form_state['order']->data['profiles'][$source_id]);
1387
      }
1388

    
1389
      if (!empty($profile)) {
1390
        commerce_customer_profile_copy_fields($info, $form_state['input'][$pane_id], $profile, $form_state);
1391
        commerce_customer_profile_copy_fields($info, $form_state['values'][$pane_id], $profile, $form_state);
1392
      }
1393
    }
1394

    
1395
    $form_state['order']->data['profile_copy'][$pane_id]['status'] = TRUE;
1396
    commerce_order_save($form_state['order']);
1397

    
1398
    // Unset any cached addressfield data for this customer profile.
1399
    if (!empty($form_state['addressfield'])) {
1400
      foreach ($form_state['addressfield'] as $key => $value) {
1401
        if (strpos($key, 'commerce_customer_profile|' . $type) === 0) {
1402
          unset($form_state['addressfield'][$key]);
1403
        }
1404
      }
1405
    }
1406
  }
1407
}
1408

    
1409
/**
1410
 * Copy field values from a source profile to a target array.
1411
 *
1412
 * @param $info
1413
 *   An array containing info for the entity type, bundle, and pane ID.
1414
 * @param $target
1415
 *   An array (typically $form_state) in which values will be copied to.
1416
 * @param $source
1417
 *   Can be either an array or object of values.
1418
 * @param $form_state
1419
 *   The form state array from the form.
1420
 */
1421
function commerce_customer_profile_copy_fields($info, &$target, $source, &$form_state) {
1422
  list($entity_type, $bundle, $pane_id) = $info;
1423
  $form_state['order']->data['profile_copy'][$pane_id]['elements'] = array();
1424

    
1425
  // Loop over all the field instances that could be attached to this entity.
1426
  foreach (field_info_instances($entity_type, $bundle) as $field_name => $instance) {
1427
    $field = NULL;
1428

    
1429
    // Extract the field value from the source object or array.
1430
    if (is_object($source) && isset($source->{$field_name})) {
1431
      $field = $source->{$field_name};
1432
    }
1433
    elseif (is_array($source) && isset($source[$field_name])) {
1434
      $field = $source[$field_name];
1435
    }
1436

    
1437
    // Loop over the source field value and copy its items to the target.
1438
    if (is_array($field)) {
1439
      foreach ($field as $langcode => $items) {
1440
        if (is_array($items)) {
1441
          $target[$field_name][$langcode] = array();
1442

    
1443
          foreach ($items as $delta => $item) {
1444
            $target[$field_name][$langcode][$delta] = $item;
1445
            $form_state['order']->data['profile_copy'][$pane_id]['elements'][$field_name][$langcode][$delta] = TRUE;
1446
          }
1447
        }
1448
        else {
1449
          $target[$field_name][$langcode] = $items;
1450
          $form_state['order']->data['profile_copy'][$pane_id]['elements'][$field_name][$langcode] = TRUE;
1451
        }
1452
      }
1453
    }
1454
  }
1455
}