Projet

Général

Profil

Paste
Télécharger (55,8 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / product_reference / commerce_product_reference.module @ b858700c

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines a field type for referencing products from other entities.
6
 */
7

    
8

    
9
/**
10
 * Implements hook_commerce_product_uri().
11
 */
12
function commerce_product_reference_commerce_product_uri($product) {
13
  // If the product has a display context, use its URI.
14
  if (!empty($product->display_context)) {
15
    // If the display context does not have the fully loaded entity, as on the
16
    // Add to Cart form refresh, load it now using the entity ID if present.
17
    if (empty($product->display_context['entity']) && !empty($product->display_context['entity_id'])) {
18
      $product->display_context['entity'] = entity_load_single($product->display_context['entity_type'], $product->display_context['entity_id']);
19
    }
20

    
21
    // Do not return a URI without a valid entity type or loaded entity.
22
    if (empty($product->display_context['entity_type']) || empty($product->display_context['entity'])) {
23
      // Otherwise do not return a URI from this function.
24
      return NULL;
25
    }
26

    
27
    return entity_uri($product->display_context['entity_type'], $product->display_context['entity']);
28
  }
29
}
30

    
31
/**
32
 * Implements hook_field_extra_fields().
33
 *
34
 * This implementation will define "extra fields" on node bundles with product
35
 * reference fields to correspond with availabled fields on products. These
36
 * fields will then also be present in the node view.
37
 */
38
function commerce_product_reference_field_extra_fields() {
39
  $extra = array();
40

    
41
  // Loop through the product reference fields.
42
  foreach (commerce_info_fields('commerce_product_reference') as $field_name => $field) {
43
    foreach ($field['bundles'] as $entity_type => $bundles) {
44
      if ($entity_type == 'commerce_line_item' || $entity_type == 'commerce_product') {
45
        // We do not currently support the injection of product fields into the
46
        // display of line items or other products.
47
        continue;
48
      }
49

    
50
      foreach ($bundles as $bundle_name) {
51
        // Get the instance settings for the field on this entity bundle.
52
        $instance_settings = field_info_instance($entity_type, $field['field_name'], $bundle_name);
53

    
54
        // If field injection is turned off for this instance, skip adding the
55
        // extra fields to this bundle's view modes.
56
        if (empty($instance_settings['settings']['field_injection'])) {
57
          continue;
58
        }
59

    
60
        // Attach extra fields from products that may be visible on the bundle.
61
        // We have to call commerce_product_field_extra_fields() directly
62
        // instead of using field_info_extra_fields() because of the order in
63
        // which these items are rebuilt in the cache for use by "Manage
64
        // display" tabs. Otherwise these extra fields will not appear.
65
        $product_fields = commerce_product_field_extra_fields();
66

    
67
        // Prevent notices if there are no product types defined.
68
        if (empty($product_fields['commerce_product'])) {
69
          continue;
70
        }
71

    
72
        foreach ($product_fields['commerce_product'] as $key => $value) {
73
          foreach ($value['display'] as $product_extra_field_name => $product_extra_field) {
74
            $product_extra_field['label'] = t('Product: @label', array('@label' => $product_extra_field['label']));
75

    
76
            $product_extra_field['display']['default'] = array(
77
              'weight' => 0,
78
              'visible' => FALSE,
79
            );
80

    
81
            $extra[$entity_type][$bundle_name]['display']['product:' . $product_extra_field_name] = $product_extra_field;
82
          }
83
        }
84

    
85
        // Do the same for fields on products that may be visible on the bundle.
86
        // First build a list of product types that may be referenced.
87
        $field_instance = field_info_instance($entity_type, $field_name, $bundle_name);
88
        $product_types = array_filter($field_instance['settings']['referenceable_types']);
89

    
90
        // If no product types are specified, use all product types.
91
        if (empty($product_types)) {
92
          $product_types = array_keys(commerce_product_types());
93
        }
94

    
95
        foreach ($product_types as $product_type) {
96
          foreach (field_info_instances('commerce_product', $product_type) as $product_field_name => $product_field) {
97
            $extra[$entity_type][$bundle_name]['display']['product:' . $product_field_name] = array(
98
              'label' => t('Product: @label', array('@label' => $product_field['label'])),
99
              'description' => t('Field from a referenced product.'),
100
              'weight' => $product_field['widget']['weight'],
101
              'configurable' => TRUE,
102
            );
103
          }
104
        }
105
      }
106
    }
107
  }
108

    
109
  return $extra;
110
}
111

    
112
/**
113
 * Implements hook_field_extra_fields_display_alter().
114
 *
115
 * This whole implementation is basically a hack because Drupal core does not
116
 * allow you to specify default visibility for extra fields. We don't want any
117
 * of the Product extra fields to be visible by default in referencing entities,
118
 * so we have to alter the display settings at this point until such a time as
119
 * the settings have been updated for the given bundle.
120
 */
121
function commerce_product_reference_field_extra_fields_display_alter(&$displays, $context) {
122
  // Load the bundle settings for the current bundle.
123
  $bundle_settings = field_bundle_settings($context['entity_type'], $context['bundle']);
124

    
125
  // Loop over the extra fields defined by the Product module.
126
  $product_fields = commerce_product_field_extra_fields();
127

    
128
  // Prevent notices if there are no product types defined.
129
  if (empty($product_fields['commerce_product'])) {
130
    return;
131
  }
132

    
133
  foreach ($product_fields['commerce_product'] as $key => $value) {
134
    foreach ($value['display'] as $product_extra_field_name => $product_extra_field) {
135
      // If the current extra field is represented in the $displays array...
136
      if (!empty($displays['product:' . $product_extra_field_name])) {
137
        // And no data yet exists for the extra field in the bundle settings...
138
        if (empty($bundle_settings['extra_fields']['display']['product:' . $product_extra_field_name]) ||
139
          (empty($bundle_settings['view_modes'][$context['view_mode']]['custom_settings']) && empty($bundle_settings['extra_fields']['display']['product:' . $product_extra_field_name]['default'])) ||
140
          (!empty($bundle_settings['view_modes'][$context['view_mode']]['custom_settings']) && empty($bundle_settings['extra_fields']['display']['product:' . $product_extra_field_name][$context['view_mode']]))) {
141
          // Default the extra field to be invisible.
142
          $displays['product:' . $product_extra_field_name]['visible'] = FALSE;
143
        }
144
      }
145
    }
146
  }
147
}
148

    
149
/**
150
 * Implements hook_form_FORM_ID_alter().
151
 */
152
function commerce_product_reference_form_field_ui_field_edit_form_alter(&$form, &$form_state) {
153
  // Alter the form if the user has selected to not display a widget for a
154
  // product reference field.
155
  if ($form['#instance']['widget']['type'] == 'commerce_product_reference_hidden') {
156
    // Add a help message to the top of the page.
157
    $form['hidden_product_reference_help'] = array(
158
      '#markup' => '<div class="messages status">' . t('This field has been configured to not display a widget. There is no way to enter values for this field via the user interface, so you must have some alternate way of adding data to these fields. The settings for the field will still govern what type of products can be referenced and whether or not their fields will be rendered into the referencing entity on display.') . '</div>',
159
      '#weight' => -20,
160
    );
161

    
162
    // Hide options from the form that pertain to UI based data entry.
163
    $form['instance']['description']['#access'] = FALSE;
164
    $form['instance']['required']['#access'] = FALSE;
165
    $form['instance']['default_value_widget']['#access'] = FALSE;
166
  }
167
}
168

    
169
/**
170
 * Implements hook_form_FORM_ID_alter().
171
 *
172
 * Override the field manage display screen to add a description to the fields
173
 * we embed into the target node from the product. Also implements the same hack
174
 * we had to use in commerce_product_reference_field_extra_fields_display_alter()
175
 * to default product extra fields to be hidden.
176
 */
177
function commerce_product_reference_form_field_ui_display_overview_form_alter(&$form, &$form_state) {
178
  if (!module_exists('commerce_product_ui')) {
179
    return;
180
  }
181

    
182
  $entity_type = $form['#entity_type'];
183
  $bundle = $form['#bundle'];
184
  $view_mode = $form['#view_mode'];
185

    
186
  // Load the bundle settings for the current bundle.
187
  $bundle_settings = field_bundle_settings($entity_type, $bundle);
188

    
189
  // Loop over the extra fields defined by the Product Reference module for this
190
  // entity to set help text and make sure any extra field derived from product
191
  // fields to be hidden by default.
192
  $product_fields = commerce_product_reference_field_extra_fields();
193

    
194
  if (isset($product_fields[$entity_type][$bundle])) {
195
    foreach ($product_fields[$entity_type][$bundle]['display'] as $field_name => $field) {
196
      // If the extra field has configurable settings, add a help text for it.
197
      if (!empty($field['configurable'])) {
198
        $form['fields'][$field_name]['format']['type']['#description'] = t('Modify the settings for this field on the <a href="!url">product type "manage display" configuration</a>.', array('!url' => url('admin/commerce/products/types')));
199
      }
200
      else {
201
        // Otherwise just mention it as visibility settings.
202
        $form['fields'][$field_name]['format']['type']['#description'] = t('The visibility of this field may also need to be toggled in the <a href="!url">product type "manage display" configuration</a>.', array('!url' => url('admin/commerce/products/types')));
203

    
204
        // If no data yet exists for the extra field in the bundle settings...
205
        if (empty($bundle_settings['extra_fields']['display'][$field_name]) ||
206
          (empty($bundle_settings['view_modes'][$view_mode]['custom_settings']) && empty($bundle_settings['extra_fields']['display'][$field_name]['default'])) ||
207
          (!empty($bundle_settings['view_modes'][$view_mode]['custom_settings']) && empty($bundle_settings['extra_fields']['display'][$field_name][$view_mode]))) {
208
          // Default it to be hidden.
209
          $form['fields'][$field_name]['format']['type']['#default_value'] = 'hidden';
210
        }
211
      }
212
    }
213
  }
214
}
215

    
216
/**
217
 * Implements hook_form_FORM_ID_alter().
218
 *
219
 * When a product is being deleted. Display a message on the confirmation form
220
 * saying how many times the product is referenced in all product reference
221
 * fields.
222
 */
223
function commerce_product_reference_form_commerce_product_product_delete_form_alter(&$form, &$form_state) {
224
  $items = array();
225

    
226
  // Check the data in every product reference field.
227
  foreach (commerce_info_fields('commerce_product_reference') as $field_name => $field) {
228
    // Query for any entity referencing the deleted product in this field.
229
    $query = new EntityFieldQuery();
230
    $query->fieldCondition($field_name, 'product_id', $form_state['product']->product_id, '=');
231
    $result = $query->execute();
232

    
233
    // If results were returned...
234
    if (!empty($result)) {
235
      // Loop over results for each type of entity returned.
236
      foreach ($result as $entity_type => $data) {
237
        if (($count = count($data)) > 0) {
238
          // For line item references, display a message about the inability of
239
          // the product to be deleted and disable the submit button.
240
          if ($entity_type == 'commerce_line_item') {
241
            // Load the referencing line item.
242
            $line_item = reset($data);
243
            $line_item = commerce_line_item_load($line_item->line_item_id);
244

    
245
            // Implement a soft dependency on the Order module to show a little
246
            // more information in the non-deletion message.
247
            if (!empty($line_item->order_id) && $order = commerce_order_load($line_item->order_id)) {
248
              $description = t('This product is referenced by a line item on Order @order_number and therefore cannot be deleted. Disable it instead.', array('@order_number' => $order->order_number));
249
            }
250
            else {
251
              $description = t('This product is referenced by a line item and therefore cannot be deleted. Disable it instead.');
252
            }
253

    
254
            $form['description']['#markup'] .= '<p>' . $description . '</p>';
255
            $form['actions']['submit']['#disabled'] = TRUE;
256
            return;
257
          }
258

    
259
          // Load the entity information.
260
          $entity_info = entity_get_info($entity_type);
261

    
262
          // Add a message regarding the references.
263
          $items[] = t('@entity_label: @count', array('@entity_label' => $entity_info['label'], '@count' => $count));
264
        }
265
      }
266
    }
267
  }
268

    
269
  if (!empty($items)) {
270
    $form['description']['#markup'] .= '<p>' . t('This product is referenced by the following entities: !entity_list', array('!entity_list' => theme('item_list', array('items' => $items)))) . '</p>';
271
  }
272
}
273

    
274
/**
275
 * Implements hook_commerce_product_delete().
276
 *
277
 * Remove references to this product in all product reference field contents.
278
 */
279
function commerce_product_reference_commerce_product_delete($product) {
280
  // Check the data in every product reference field.
281
  foreach (commerce_info_fields('commerce_product_reference') as $field_name => $field) {
282
    // Query for any entity referencing the deleted product in this field.
283
    $query = new EntityFieldQuery();
284
    $query->fieldCondition($field_name, 'product_id', $product->product_id, '=');
285
    $result = $query->execute();
286

    
287
    // If results were returned...
288
    if (!empty($result)) {
289
      // Loop over results for each type of entity returned.
290
      foreach ($result as $entity_type => $data) {
291
        // Load the entities of the current type.
292
        $entities = entity_load($entity_type, array_keys($data));
293

    
294
        // Loop over each entity and remove the reference to the deleted product.
295
        foreach ($entities as $entity_id => $entity) {
296
          commerce_entity_reference_delete($entity, $field_name, 'product_id', $product->product_id);
297

    
298
          // Store the changes to the entity.
299
          entity_save($entity_type, $entity);
300
        }
301
      }
302
    }
303
  }
304
}
305

    
306
/**
307
 * Implements hook_entity_view().
308
 *
309
 * This implementation adds product fields to the content array of an entity on
310
 * display if the product contains a non-empty product reference field.
311
 */
312
function commerce_product_reference_entity_view($entity, $entity_type, $view_mode, $langcode) {
313
  list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
314
  $instances = NULL;
315

    
316
  if ($entity_type == 'commerce_line_item' || $entity_type == 'commerce_product') {
317
    // We do not currently support the injection of product fields into the
318
    // display of line items or other products.
319
    return;
320
  }
321

    
322
  // An entity metadata wrapper will be loaded on demand if it is determined
323
  // this entity has a product reference field instance attached to it.
324
  $wrapper = NULL;
325

    
326
  // Loop through product reference fields to see if any exist on this entity
327
  // bundle that is either hidden or displayed with the Add to Cart form display
328
  // formatter.
329
  foreach (commerce_info_fields('commerce_product_reference', $entity_type) as $field_name => $field) {
330
    // Load the instances array only if the entity has product reference fields.
331
    if (empty($instances)) {
332
      $instances = field_info_instances($entity_type, $bundle);
333
    }
334

    
335
    if (isset($instances[$field_name])) {
336
      // Load a wrapper for the entity being viewed.
337
      if (empty($wrapper)) {
338
        $wrapper = entity_metadata_wrapper($entity_type, $entity);
339
      }
340

    
341
      // Find the default product based on the cardinality of the field.
342
      $product = NULL;
343

    
344
      if ($field['cardinality'] == 1) {
345
        $product = $wrapper->{$field_name}->value();
346
      }
347
      elseif (count($wrapper->{$field_name}) > 0) {
348
        $product = commerce_product_reference_default_product($wrapper->{$field_name}->value());
349

    
350
        // If the product is disabled, attempt to find one that is active and
351
        // use that as the default product instead.
352
        if (!empty($product) && $product->status == 0) {
353
          foreach ($wrapper->{$field_name} as $delta => $product_wrapper) {
354
            if ($product_wrapper->status->value() != 0) {
355
              $product = $product_wrapper->value();
356
              break;
357
            }
358
          }
359
        }
360
      }
361

    
362
      // If we found a product and the reference field enables field injection...
363
      if (!empty($product) && $instances[$field_name]['settings']['field_injection']) {
364
        // Add the display context for these field to the product.
365
        $product->display_context = array(
366
          'entity_type' => $entity_type,
367
          'entity_id' => $id,
368
          'entity' => $entity,
369
          'view_mode' => $view_mode,
370
          'language' => $langcode,
371
        );
372

    
373
        // Determine if the referenced product type specifies custom settings
374
        // for the reference view mode.
375
        $view_mode_settings = field_view_mode_settings('commerce_product', $product->type);
376

    
377
        if (!empty($view_mode_settings[$entity_type . '_' . $view_mode]['custom_settings'])) {
378
          $reference_view_mode = $entity_type . '_' . $view_mode;
379
        }
380
        else {
381
          $reference_view_mode = 'default';
382
        }
383

    
384
        // Loop through the fields on the referenced product's type.
385
        foreach (field_info_instances('commerce_product', $product->type) as $product_field_name => $product_field) {
386
          if (!isset($product_field['display'][$reference_view_mode])) {
387
            $reference_view_mode = 'default';
388
          }
389

    
390
          // Only prepare visible fields.
391
          if (!isset($product_field['display'][$reference_view_mode]['type']) || $product_field['display'][$reference_view_mode]['type'] != 'hidden') {
392
            // Add the product field to the entity's content array.
393
            $content_key = 'product:' . $product_field_name;
394

    
395
            $entity->content[$content_key] = field_view_field('commerce_product', $product, $product_field_name, $reference_view_mode, $langcode);
396

    
397
            // For multiple value references, add context information so the cart
398
            // form can do dynamic replacement of fields.
399
            if ($field['cardinality'] != 1) {
400
              // Construct an array of classes that will be used to theme and
401
              // target the rendered field for AJAX replacement.
402
              $classes = array(
403
                'commerce-product-field',
404
                drupal_html_class('commerce-product-field-' . $product_field_name),
405
                drupal_html_class('field-' . $product_field_name),
406
                drupal_html_class(implode('-', array($entity_type, $id, 'product', $product_field_name))),
407
              );
408

    
409
              // Add an extra class to distinguish empty product fields.
410
              if (empty($entity->content[$content_key])) {
411
                $classes[] = 'commerce-product-field-empty';
412
              }
413

    
414
              // Ensure the field's content array has a prefix and suffix key.
415
              $entity->content[$content_key] += array(
416
                '#prefix' => '',
417
                '#suffix' => '',
418
              );
419

    
420
              // Add the custom div before and after the prefix and suffix.
421
              $entity->content[$content_key]['#prefix'] = '<div class="' . implode(' ', $classes) . '">' . $entity->content[$content_key]['#prefix'];
422
              $entity->content[$content_key]['#suffix'] .= '</div>';
423
            }
424
          }
425
        }
426

    
427
        // Attach "extra fields" to the bundle representing all the extra fields
428
        // currently attached to products.
429
        foreach (field_info_extra_fields('commerce_product', $product->type, 'display') as $product_extra_field_name => $product_extra_field) {
430
          $display = field_extra_fields_get_display('commerce_product', $product->type, $reference_view_mode);
431

    
432
          // Only include extra fields that specify a theme function and that
433
          // are visible on the current view mode.
434
          if (!empty($product_extra_field['theme']) &&
435
            !empty($display[$product_extra_field_name]['visible'])) {
436
            // Add the product extra field to the entity's content array.
437
            $content_key = 'product:' . $product_extra_field_name;
438

    
439
            $variables = array(
440
              $product_extra_field_name => $product->{$product_extra_field_name},
441
              'label' => $product_extra_field['label'] . ':',
442
              'product' => $product,
443
            );
444

    
445
            $entity->content[$content_key] = array(
446
              '#markup' => theme($product_extra_field['theme'], $variables),
447
              '#attached' => array(
448
                'css' => array(drupal_get_path('module', 'commerce_product') . '/theme/commerce_product.theme.css'),
449
              ),
450
            );
451

    
452
            // For multiple value references, add context information so the cart
453
            // form can do dynamic replacement of fields.
454
            if ($field['cardinality'] != 1) {
455
              // Construct an array of classes that will be used to theme and
456
              // target the rendered field for AJAX replacement.
457
              $classes = array(
458
                'commerce-product-extra-field',
459
                drupal_html_class('commerce-product-extra-field-' . $product_extra_field_name),
460
                drupal_html_class(implode('-', array($entity_type, $id, 'product', $product_extra_field_name))),
461
              );
462

    
463
              // Add an extra class to distinguish empty fields.
464
              if (empty($entity->content[$content_key])) {
465
                $classes[] = 'commerce-product-extra-field-empty';
466
              }
467

    
468
              // Ensure the extra field's content array has a prefix and suffix key.
469
              $entity->content[$content_key] += array(
470
                '#prefix' => '',
471
                '#suffix' => '',
472
              );
473

    
474
              // Add the custom div before and after the prefix and suffix.
475
              $entity->content[$content_key]['#prefix'] = '<div class="' . implode(' ', $classes) . '">' . $entity->content[$content_key]['#prefix'];
476
              $entity->content[$content_key]['#suffix'] .= '</div>';
477
            }
478
          }
479
        }
480
      }
481
    }
482
  }
483
}
484

    
485
/**
486
 * Implements hook_views_api().
487
 */
488
function commerce_product_reference_views_api() {
489
  return array(
490
    'api' => 3,
491
    'path' => drupal_get_path('module', 'commerce_product_reference') . '/includes/views',
492
  );
493
}
494

    
495
/**
496
 * Implements hook_field_info().
497
 */
498
function commerce_product_reference_field_info() {
499
  return array(
500
    'commerce_product_reference' => array(
501
      'label' => t('Product reference'),
502
      'description' => t('This field stores the ID of a related product as an integer value.'),
503
      'settings' => array('options_list_limit' => NULL),
504
      'instance_settings' => array('referenceable_types' => array(), 'field_injection' => TRUE),
505
      'default_widget' => 'options_select',
506
      'default_formatter' => 'commerce_product_reference_title_link',
507
      'property_type' => 'commerce_product',
508
      'property_callbacks' => array('commerce_product_reference_property_info_callback'),
509
      'default_token_formatter' => 'commerce_product_reference_title_plain',
510
    ),
511
  );
512
}
513

    
514
/**
515
 * Implements hook_field_settings_form().
516
 */
517
function commerce_product_reference_field_settings_form($field, $instance, $has_data) {
518
  $settings = $field['settings'];
519
  $form = array();
520

    
521
  if ($field['type'] == 'commerce_product_reference') {
522
    $options = array();
523

    
524
    $form['options_list_limit'] = array(
525
      '#type' => 'textfield',
526
      '#title' => t('Options list limit'),
527
      '#description' => t('Limits the number of products available in field widgets with options lists; leave blank for no limit.'),
528
      '#default_value' => !empty($settings['options_list_limit']) ? $settings['options_list_limit'] : '',
529
      '#element_validate' => array('commerce_options_list_limit_validate'),
530
    );
531
  }
532

    
533
  return $form;
534
}
535

    
536
/**
537
 * Implements hook_field_instance_settings_form().
538
 */
539
function commerce_product_reference_field_instance_settings_form($field, $instance) {
540
  $settings = $instance['settings'];
541
  $form = array();
542

    
543
  $form['field_injection'] = array(
544
    '#type' => 'checkbox',
545
    '#title' => t('Render fields from the referenced products when viewing this entity.'),
546
    '#description' => t('If enabled, the appearance of product fields on this entity is governed by the display settings for the fields on the product type.'),
547
    '#default_value' => isset($settings['field_injection']) ? $settings['field_injection'] : TRUE,
548
    '#weight' => -9,
549
  );
550

    
551
  // Build an options array of the product types.
552
  $options = array();
553

    
554
  foreach (commerce_product_type_get_name() as $type => $name) {
555
    $options[$type] = check_plain($name);
556
  }
557

    
558
  $form['referenceable_types'] = array(
559
    '#type' => 'checkboxes',
560
    '#title' => t('Product types that can be referenced'),
561
    '#description' => t('If no types are selected, any type of product may be referenced.'),
562
    '#options' => $options,
563
    '#default_value' => is_array($settings['referenceable_types']) ? $settings['referenceable_types'] : array(),
564
    '#multiple' => TRUE,
565
    '#weight' => -3,
566
  );
567

    
568
  return $form;
569
}
570

    
571
/**
572
 * Implements hook_field_validate().
573
 *
574
 * Possible error codes:
575
 * - 'invalid_product_id': product_id is not valid for the field (not a valid
576
 *                         product id, or the product is not referenceable).
577
 */
578
function commerce_product_reference_field_validate($entity_type, $entity, $field, $instance, $langcode, $items, &$errors) {
579
  $translated_instance = commerce_i18n_object('field_instance', $instance);
580

    
581
  // Extract product_ids to check.
582
  $product_ids = array();
583

    
584
  // First check non-numeric product_id's to avoid losing time with them.
585
  foreach ($items as $delta => $item) {
586
    if (is_array($item) && !empty($item['product_id'])) {
587
      if (is_numeric($item['product_id'])) {
588
        $product_ids[] = $item['product_id'];
589
      }
590
      else {
591
        $errors[$field['field_name']][$langcode][$delta][] = array(
592
          'error' => 'invalid_product_id',
593
          'message' => t('%name: you have specified an invalid product for this reference field.', array('%name' => $translated_instance['label'])),
594
        );
595
      }
596
    }
597
  }
598

    
599
  // Prevent performance hog if there are no ids to check.
600
  if ($product_ids) {
601
    $refs = commerce_product_match_products($field, $instance, '', NULL, $product_ids);
602

    
603
    foreach ($items as $delta => $item) {
604
      if (is_array($item)) {
605
        if (!empty($item['product_id']) && !isset($refs[$item['product_id']])) {
606
          $errors[$field['field_name']][$langcode][$delta][] = array(
607
            'error' => 'invalid_product_id',
608
            'message' => t('%name: you have specified an invalid product for this reference field.', array('%name' => $translated_instance['label'])),
609
          );
610
        }
611
      }
612
    }
613
  }
614
}
615

    
616
/**
617
 * Implements hook_field_is_empty().
618
 */
619
function commerce_product_reference_field_is_empty($item, $field) {
620
  // product_id = 0 is empty too, which is exactly what we want.
621
  return empty($item['product_id']);
622
}
623

    
624
/**
625
 * Implements hook_field_formatter_info().
626
 */
627
function commerce_product_reference_field_formatter_info() {
628
  return array(
629
    'commerce_product_reference_sku_link' => array(
630
      'label' => t('SKU (link)'),
631
      'description' => t('Display the SKU of the referenced product as a link to the node page.'),
632
      'field types' => array('commerce_product_reference'),
633
    ),
634
    'commerce_product_reference_sku_plain' => array(
635
      'label' => t('SKU (no link)'),
636
      'description' => t('Display the SKU of the referenced product as plain text.'),
637
      'field types' => array('commerce_product_reference'),
638
    ),
639
    'commerce_product_reference_title_link' => array(
640
      'label' => t('Title (link)'),
641
      'description' => t('Display the title of the referenced product as a link to the node page.'),
642
      'field types' => array('commerce_product_reference'),
643
    ),
644
    'commerce_product_reference_title_plain' => array(
645
      'label' => t('Title (no link)'),
646
      'description' => t('Display the title of the referenced product as plain text.'),
647
      'field types' => array('commerce_product_reference'),
648
    ),
649
    'commerce_product_reference_rendered_product' => array(
650
      'label' => t('Rendered product'),
651
      'description' => t('Display the rendered products in any available view mode.'),
652
      'field types' => array('commerce_product_reference'),
653
      'settings' => array(
654
        'view_mode' => 'full',
655
        'page' => TRUE,
656
      ),
657
    ),
658
  );
659
}
660

    
661
/**
662
 * Implements hook_field_formatter_settings_form().
663
 */
664
function commerce_product_reference_field_formatter_settings_form($field, $instance, $view_mode, $form, &$form_state) {
665
  $display = $instance['display'][$view_mode];
666
  $settings = $display['settings'];
667
  $element = array();
668

    
669
  if ($display['type'] == 'commerce_product_reference_rendered_product') {
670
    $entity_info = entity_get_info('commerce_product');
671
    $options = array();
672

    
673
    if (!empty($entity_info['view modes'])) {
674
      foreach ($entity_info['view modes'] as $view_mode => $view_mode_settings) {
675
        $options[$view_mode] = $view_mode_settings['label'];
676
      }
677
    }
678

    
679
    if (count($options) > 1) {
680
      $element['view_mode'] = array(
681
        '#type' => 'select',
682
        '#title' => t('View mode'),
683
        '#options' => $options,
684
        '#default_value' => $settings['view_mode'],
685
      );
686
    }
687

    
688
    $element['page'] = array(
689
      '#type' => 'checkbox',
690
      '#title' => t('Render the product in full page mode without the title in a heading tag.'),
691
      '#default_value' => $settings['page'],
692
    );
693
  }
694

    
695
  return $element;
696
}
697

    
698
/**
699
 * Implements hook_field_formatter_settings_summary().
700
 */
701
function commerce_product_reference_field_formatter_settings_summary($field, $instance, $view_mode) {
702
  $display = $instance['display'][$view_mode];
703
  $settings = $display['settings'];
704

    
705
  $summary = array();
706

    
707
  if ($display['type'] == 'commerce_product_reference_rendered_product') {
708
    $entity_info = entity_get_info('commerce_product');
709
    $summary[] = t('View mode: @mode', array('@mode' => isset($entity_info['view modes'][$settings['view_mode']]['label']) ? $entity_info['view modes'][$settings['view_mode']]['label'] : $settings['view_mode']));
710

    
711
    if (!empty($settings['page'])) {
712
      $summary[] = t('Rendering without the title in a heading tag.');
713
    }
714
    else {
715
      $summary[] = t('Rendering with the title in a heading tag.');
716
    }
717
  }
718

    
719
  return implode('<br />', $summary);
720
}
721

    
722
/**
723
 * Implements hook_field_formatter_prepare_view().
724
 */
725
function commerce_product_reference_field_formatter_prepare_view($entity_type, $entities, $field, $instances, $langcode, &$items, $displays) {
726
  $display = reset($displays);
727

    
728
  if ($display['type'] == 'commerce_product_reference_rendered_product') {
729
    $product_ids = array();
730

    
731
    // Collect every possible entity attached to any of the entities.
732
    foreach ($entities as $id => $entity) {
733
      foreach ($items[$id] as $delta => $item) {
734
        if (isset($item['product_id'])) {
735
          $product_ids[] = $item['product_id'];
736
        }
737
      }
738
    }
739

    
740
    if ($product_ids) {
741
      $products = entity_load('commerce_product', $product_ids);
742
    }
743
    else {
744
      $products = array();
745
    }
746

    
747
    // Iterate through the entities again to attach the loaded data.
748
    foreach ($entities as $id => $entity) {
749
      $rekey = FALSE;
750

    
751
      foreach ($items[$id] as $delta => $item) {
752
        // Check whether the referenced product could be loaded and that the
753
        // user has access to it.
754
        if (isset($products[$item['product_id']]) && entity_access('view', 'commerce_product', $products[$item['product_id']])) {
755
          // Add the fully loaded entity to the items array.
756
          $items[$id][$delta]['entity'] = $products[$item['product_id']];
757
        }
758
        else {
759
          // Otherwise, unset the item since the referenced product does not
760
          // exist or is not be accessible to the user.
761
          unset($items[$id][$delta]);
762
          $rekey = TRUE;
763
        }
764
      }
765

    
766
      if ($rekey) {
767
        // Rekey the items array.
768
        $items[$id] = array_values($items[$id]);
769
      }
770
    }
771
  }
772
}
773

    
774
/**
775
 * Implements hook_field_formatter_view().
776
 */
777
function commerce_product_reference_field_formatter_view($entity_type, $entity, $field, $instance, $langcode, $items, $display) {
778
  $result = array();
779

    
780
  // Collect the list of product IDs.
781
  $product_ids = array();
782

    
783
  foreach ($items as $delta => $item) {
784
    $product_ids[$item['product_id']] = $item['product_id'];
785
  }
786

    
787
  // Exit now if we didn't find any product IDs.
788
  if (empty($product_ids)) {
789
    return;
790
  }
791

    
792
  // Load the referenced products.
793
  $products = commerce_product_load_multiple($product_ids, array('status' => 1));
794

    
795
  switch ($display['type']) {
796
    case 'commerce_product_reference_sku_link':
797
    case 'commerce_product_reference_sku_plain':
798
      foreach ($items as $delta => $item) {
799
        if (!empty($products[$item['product_id']])) {
800
          if ($display['type'] == 'commerce_product_reference_sku_link') {
801
            $result[$delta] = array(
802
              '#type' => 'link',
803
              '#title' => $products[$item['product_id']]->sku,
804
              '#href' => 'admin/commerce/products/' . $item['product_id'],
805
            );
806
          }
807
          else {
808
            $result[$delta] = array(
809
              '#markup' => check_plain($products[$item['product_id']]->sku),
810
            );
811
          }
812
        }
813
      }
814
      break;
815

    
816
    case 'commerce_product_reference_title_link':
817
    case 'commerce_product_reference_title_plain':
818
      foreach ($items as $delta => $item) {
819
        if (!empty($products[$item['product_id']])) {
820
          if ($display['type'] == 'commerce_product_reference_title_link') {
821
            $result[$delta] = array(
822
              '#type' => 'link',
823
              '#title' => $products[$item['product_id']]->title,
824
              '#href' => 'admin/commerce/products/' . $item['product_id'],
825
            );
826
          }
827
          else {
828
            $result[$delta] = array(
829
              '#markup' => check_plain($products[$item['product_id']]->title),
830
            );
831
          }
832
        }
833
      }
834
      break;
835

    
836
    case 'commerce_product_reference_rendered_product':
837
      foreach ($items as $delta => $item) {
838
        // Protect ourselves from recursive rendering.
839
        static $depth = 0;
840
        $depth++;
841

    
842
        if ($depth > 20) {
843
          throw new CommerceProductReferenceRecursiveRenderingException(t('Recursive rendering detected when rendering product (@product_id). Aborting rendering.', array('@product_id' => $item['product_id'])));
844
        }
845

    
846
        $entity = clone $item['entity'];
847
        unset($entity->content);
848
        $result[$delta] = entity_view('commerce_product', array($item['product_id'] => $entity), $display['settings']['view_mode'], $langcode, $display['settings']['page']);
849
        $depth = 0;
850
      }
851
      break;
852
  }
853

    
854
  return $result;
855
}
856

    
857
/**
858
 * Implements hook_field_widget_info().
859
 *
860
 * Defines widgets available for use with field types as specified in each
861
 * widget's $info['field types'] array.
862
 */
863
function commerce_product_reference_field_widget_info() {
864
  $widgets = array();
865

    
866
  // Define an autocomplete textfield widget for product referencing that works
867
  // like the Term Reference autocomplete widget.
868
  $widgets['commerce_product_reference_autocomplete'] = array(
869
    'label' => t('Autocomplete text field'),
870
    'description' => t('Display the list of referenceable products as a textfield with autocomplete behaviour.'),
871
    'field types' => array('commerce_product_reference'),
872
    'settings' => array(
873
      'autocomplete_match' => 'contains',
874
      'size' => 60,
875
      'autocomplete_path' => 'commerce_product/autocomplete',
876
    ),
877
    'behaviors' => array(
878
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
879
    ),
880
  );
881

    
882
  // Do not show the widget on forms; useful in cases where reference fields
883
  // have a lot of data that is maintained automatically.
884
  $widgets['commerce_product_reference_hidden'] = array(
885
    'label' => t('Do not show a widget'),
886
    'description' => t('Will not display the product reference field on forms. Use only if you maintain product references some other way.'),
887
    'field types' => array('commerce_product_reference'),
888
    'behaviors' => array(
889
      'multiple values' => FIELD_BEHAVIOR_CUSTOM,
890
    ),
891
  );
892

    
893
  return $widgets;
894
}
895

    
896
/**
897
 * Implements hook_field_widget_info_alter().
898
 */
899
function commerce_product_reference_field_widget_info_alter(&$info) {
900
  $info['options_select']['field types'][] = 'commerce_product_reference';
901
  $info['options_buttons']['field types'][] = 'commerce_product_reference';
902
}
903

    
904
/**
905
 * Implements hook_field_widget_settings_form().
906
 */
907
function commerce_product_reference_field_widget_settings_form($field, $instance) {
908
  $widget = $instance['widget'];
909
  $defaults = field_info_widget_settings($widget['type']);
910
  $settings = array_merge($defaults, $widget['settings']);
911

    
912
  $form = array();
913

    
914
  // Build the settings for the product reference autocomplete widget.
915
  if ($widget['type'] == 'commerce_product_reference_autocomplete') {
916
    $form['autocomplete_match'] = array(
917
      '#type' => 'select',
918
      '#title' => t('Autocomplete matching'),
919
      '#default_value' => $settings['autocomplete_match'],
920
      '#options' => array(
921
        'starts_with' => t('Starts with'),
922
        'contains' => t('Contains'),
923
      ),
924
      '#description' => t('Select the method used to collect autocomplete suggestions. Note that <em>Contains</em> can cause performance issues on sites with thousands of nodes.'),
925
    );
926
    $form['size'] = array(
927
      '#type' => 'textfield',
928
      '#title' => t('Size of textfield'),
929
      '#default_value' => $settings['size'],
930
      '#element_validate' => array('_element_validate_integer_positive'),
931
      '#required' => TRUE,
932
    );
933
  }
934

    
935
  return $form;
936
}
937

    
938
/**
939
 * Implements hook_field_widget_form().
940
 *
941
 * Used to define the form element for custom widgets.
942
 */
943
function commerce_product_reference_field_widget_form(&$form, &$form_state, $field, $instance, $langcode, $items, $delta, $element) {
944
  // Define the autocomplete textfield for products.
945
  if ($instance['widget']['type'] == 'commerce_product_reference_autocomplete') {
946
    $product_ids = array();
947
    $skus = array();
948

    
949
    // Build an array of product IDs from this field's values.
950
    foreach ($items as $item) {
951
      $product_ids[] = $item['product_id'];
952
    }
953

    
954
    // Load those products and loop through them to extract their SKUs.
955
    $products = commerce_product_load_multiple($product_ids);
956

    
957
    foreach ($product_ids as $product_id) {
958
      if (!empty($products[$product_id])) {
959
        $skus[] = $products[$product_id]->sku;
960
      }
961
    }
962

    
963
    return $element + array(
964
      '#type' => 'textfield',
965
      '#default_value' => implode(', ', $skus),
966
      '#autocomplete_path' => $instance['widget']['settings']['autocomplete_path'] . '/' . $instance['entity_type'] . '/' . $field['field_name'] . '/' . $instance['bundle'],
967
      '#size' => $instance['widget']['settings']['size'],
968
      '#maxlength' => 2048,
969
      '#element_validate' => array('commerce_product_reference_autocomplete_validate'),
970
    );
971
  }
972
  elseif ($instance['widget']['type'] == 'commerce_product_reference_hidden') {
973
    return array();
974
  }
975
}
976

    
977
/**
978
 * Validation callback for a commerce_product_reference autocomplete element.
979
 */
980
function commerce_product_reference_autocomplete_validate($element, &$form_state, $form) {
981
  // If a value was entered into the autocomplete...
982
  if (!empty($element['#value'])) {
983
    // Translate SKUs into product IDs.
984
    $typed_skus = drupal_explode_tags($element['#value']);
985

    
986
    $value = array();
987

    
988
    // Loop through all the entered SKUs...
989
    foreach ($typed_skus as $typed_sku) {
990
      // To see if the product actually exists...
991
      if ($product = commerce_product_load_by_sku(trim($typed_sku))) {
992
        // And store its product ID for later validation.
993
        $value[] = array('product_id' => $product->product_id);
994
      }
995
      else {
996
        form_error($element, t('Product SKU %sku does not exist.', array('%sku' => $typed_sku)));
997
      }
998
    }
999
  }
1000
  else {
1001
    $value = array();
1002
  }
1003

    
1004
  // Update the value of this element so the field can validate the product IDs.
1005
  form_set_value($element, $value, $form_state);
1006
}
1007

    
1008
/**
1009
 * Implements hook_field_widget_error().
1010
 */
1011
function commerce_product_reference_field_widget_error($element, $error) {
1012
  form_error($element, $error['message']);
1013
}
1014

    
1015
/**
1016
 * Implements hook_options_list().
1017
 */
1018
function commerce_product_reference_options_list($field, $instance = NULL) {
1019
  $options = array();
1020

    
1021
  // Look for an options list limit in the field settings.
1022
  if (!empty($field['settings']['options_list_limit'])) {
1023
    $limit = (int) $field['settings']['options_list_limit'];
1024
  }
1025
  else {
1026
    $limit = NULL;
1027
  }
1028

    
1029
  // Loop through all product matches.
1030
  foreach (commerce_product_match_products($field, $instance, '', 'contains', array(), $limit) as $product_id => $data) {
1031
    // Add them to the options list in optgroups by product type.
1032
    $name = check_plain(commerce_product_type_get_name($data['type']));
1033

    
1034
    if (!empty($instance['widget']['type']) && $instance['widget']['type'] == 'options_select') {
1035
      $options[$name][$product_id] = t('!sku: !title', array('!sku' => $data['sku'], '!title' => $data['title']));
1036
    }
1037
    else {
1038
      $options[$name][$product_id] = t('@sku: @title', array('@sku' => $data['sku'], '@title' => $data['title']));
1039
    }
1040
  }
1041

    
1042
  // Simplify the options list if only one optgroup exists.
1043
  if (count($options) == 1) {
1044
    $options = reset($options);
1045
  }
1046

    
1047
  return $options;
1048
}
1049

    
1050
/**
1051
 * Implements hook_module_implements_alter().
1052
 */
1053
function commerce_product_reference_module_implements_alter(&$implementations, $hook) {
1054
  if ($hook == 'entity_info_alter') {
1055
    // Place this module's implementation of hook_entity_info_alter() is at the
1056
    // end of the invocation list so it can react to view modes altered onto the
1057
    // product entity type from other modules like Display Suite.
1058
    $group = $implementations['commerce_product_reference'];
1059
    unset($implementations['commerce_product_reference']);
1060
    $implementations['commerce_product_reference'] = $group;
1061
  }
1062
}
1063

    
1064
/**
1065
 * Implements hook_entity_info_alter().
1066
 *
1067
 * Adds the line item and the product display-specific view modes to the product.
1068
 */
1069
function commerce_product_reference_entity_info_alter(&$entity_info) {
1070
  $entity_info['commerce_product']['view modes']['line_item'] = array(
1071
    'label' => t('Line item'),
1072
    'custom settings' => TRUE,
1073
  );
1074

    
1075
  // Query the field tables directly to avoid creating a loop in the Field API:
1076
  // it is not legal to call any of the field_info_*() in
1077
  // hook_entity_info(), as field_read_instances() calls entity_get_info().
1078
  $query = db_select('field_config_instance', 'fci', array('fetch' => PDO::FETCH_ASSOC));
1079
  $query->join('field_config', 'fc', 'fc.id = fci.field_id');
1080
  $query->fields('fci', array('entity_type'));
1081
  $query->condition('fc.type', 'commerce_product_reference');
1082
  $query->condition('fc.deleted', 0);
1083
  $query->distinct();
1084

    
1085
  foreach ($query->execute() as $instance) {
1086
    $entity_type = $instance['entity_type'];
1087

    
1088
    if (!empty($entity_info[$entity_type]['view modes'])) {
1089
      foreach ($entity_info[$entity_type]['view modes'] as $view_mode => $view_mode_info) {
1090
        $entity_info['commerce_product']['view modes'][$entity_type . '_' . $view_mode] = array(
1091
          'label' => t('@entity_type: @view_mode', array('@entity_type' => $entity_info[$entity_type]['label'], '@view_mode' => $view_mode_info['label'])),
1092

    
1093
          // UX: Enable the 'Node: teaser' mode by default, if present.
1094
          'custom settings' => $entity_type == 'node' && $view_mode == 'teaser',
1095
        );
1096
      }
1097
    }
1098
  }
1099
}
1100

    
1101
/**
1102
 * Creates a required, locked instance of a product reference field on the
1103
 * specified bundle.
1104
 *
1105
 * @param $field_name
1106
 *   The name of the field; if it already exists, a new instance of the existing
1107
 *     field will be created. For fields governed by the Commerce modules, this
1108
 *     should begin with commerce_.
1109
 * @param $entity_type
1110
 *   The type of entity the field instance will be attached to.
1111
 * @param $bundle
1112
 *   The bundle name of the entity the field instance will be attached to.
1113
 * @param $label
1114
 *   The label of the field instance.
1115
 * @param $weight
1116
 *   The default weight of the field instance widget and display.
1117
 */
1118
function commerce_product_reference_create_instance($field_name, $entity_type, $bundle, $label, $weight = 0) {
1119
  // Look for or add the specified field to the requested entity bundle.
1120
  commerce_activate_field($field_name);
1121
  field_cache_clear();
1122

    
1123
  $field = field_info_field($field_name);
1124
  $instance = field_info_instance($entity_type, $field_name, $bundle);
1125

    
1126
  if (empty($field)) {
1127
    $field = array(
1128
      'field_name' => $field_name,
1129
      'type' => 'commerce_product_reference',
1130
      'cardinality' => 1,
1131
      'entity_types' => array($entity_type),
1132
      'translatable' => FALSE,
1133
      'locked' => TRUE,
1134
    );
1135
    $field = field_create_field($field);
1136
  }
1137

    
1138
  if (empty($instance)) {
1139
    $instance = array(
1140
      'field_name' => $field_name,
1141
      'entity_type' => $entity_type,
1142
      'bundle' => $bundle,
1143

    
1144
      'label' => $label,
1145
      'required' => TRUE,
1146
      'settings' => array(),
1147

    
1148
      'widget' => array(
1149
        'type' => 'commerce_product_reference_autocomplete',
1150
        'weight' => $weight,
1151
      ),
1152

    
1153
      'display' => array(
1154
        'display' => array(
1155
          'label' => 'hidden',
1156
          'weight' => $weight,
1157
        ),
1158
      ),
1159
    );
1160
    field_create_instance($instance);
1161
  }
1162
}
1163

    
1164
/**
1165
 * Implements hook_commerce_line_item_type_info().
1166
 */
1167
function commerce_product_reference_commerce_line_item_type_info() {
1168
  $line_item_types = array();
1169

    
1170
  $line_item_types['product'] = array(
1171
    'name' => t('Product'),
1172
    'description' => t('References a product and displays it with the SKU as the label.'),
1173
    'product' => TRUE,
1174
    'add_form_submit_value' => t('Add product'),
1175
    'base' => 'commerce_product_line_item',
1176
  );
1177

    
1178
  return $line_item_types;
1179
}
1180

    
1181
/**
1182
 * Ensures the product line item type contains a product reference field.
1183
 *
1184
 * This function is called by the line item module when it is enabled or this
1185
 * module is enabled. It invokes this function using the configuration_callback
1186
 * as specified above. Other modules defining product line item types should
1187
 * use this function to ensure their types have the required fields.
1188
 *
1189
 * @param $line_item_type
1190
 *   The info array of the line item type being configured.
1191
 */
1192
function commerce_product_line_item_configuration($line_item_type) {
1193
  $type = $line_item_type['type'];
1194

    
1195
  // Create the product reference field for the line item type.
1196
  commerce_product_reference_create_instance('commerce_product', 'commerce_line_item', $type, t('Product'));
1197

    
1198
  // Look for or add a display path textfield to the product line item type.
1199
  $field_name = 'commerce_display_path';
1200
  commerce_activate_field($field_name);
1201
  field_cache_clear();
1202

    
1203
  $field = field_info_field($field_name);
1204
  $instance = field_info_instance('commerce_line_item', $field_name, $type);
1205

    
1206
  if (empty($field)) {
1207
    $field = array(
1208
      'field_name' => $field_name,
1209
      'type' => 'text',
1210
      'cardinality' => 1,
1211
      'entity_types' => array('commerce_line_item'),
1212
      'translatable' => FALSE,
1213
      'locked' => TRUE,
1214
    );
1215
    $field = field_create_field($field);
1216
  }
1217

    
1218
  if (empty($instance)) {
1219
    $instance = array(
1220
      'field_name' => $field_name,
1221
      'entity_type' => 'commerce_line_item',
1222
      'bundle' => $type,
1223
      'label' => t('Display path'),
1224
      'required' => TRUE,
1225
      'settings' => array(),
1226

    
1227
      'widget' => array(
1228
        'type' => 'text_textfield',
1229
        'weight' => 0,
1230
      ),
1231

    
1232
      'display' => array(
1233
        'display' => array(
1234
          'label' => 'hidden',
1235
          'weight' => 0,
1236
        ),
1237
      ),
1238
    );
1239
    field_create_instance($instance);
1240
  }
1241
}
1242

    
1243
/**
1244
 * Returns an appropriate title for this line item.
1245
 */
1246
function commerce_product_line_item_title($line_item) {
1247
  // Currently, just return the product's title.  However, in the future replace
1248
  // this with the product preview build mode.
1249
  if ($product = entity_metadata_wrapper('commerce_line_item', $line_item)->commerce_product->value()) {
1250
    return check_plain($product->title);
1251
  }
1252
}
1253

    
1254
/**
1255
 * Returns the elements necessary to add a product line item through a line item
1256
 * manager widget.
1257
 */
1258
function commerce_product_line_item_add_form($element, &$form_state) {
1259
  $form = array();
1260

    
1261
  $form['product_sku'] = array(
1262
    '#type' => 'textfield',
1263
    '#title' => t('Product SKU'),
1264
    '#description' => t('Enter the SKU of the product to add to the order.'),
1265
    '#autocomplete_path' => 'commerce_product/autocomplete/commerce_product/line_item_product_selector/product',
1266
    '#size' => 60,
1267
    '#maxlength' => 255,
1268
  );
1269

    
1270
  return $form;
1271
}
1272

    
1273
/**
1274
 * Adds the selected product information to a line item added via a line item
1275
 *   manager widget.
1276
 *
1277
 * @param $line_item
1278
 *   The newly created line item object.
1279
 * @param $element
1280
 *   The array representing the widget form element.
1281
 * @param $form_state
1282
 *   The present state of the form upon the latest submission.
1283
 * @param $form
1284
 *   The actual form array.
1285
 *
1286
 * @return
1287
 *   NULL if all is well or an error message if something goes wrong.
1288
 */
1289
function commerce_product_line_item_add_form_submit(&$line_item, $element, &$form_state, $form) {
1290
  // Load the selected product.
1291
  if ($product = commerce_product_load_by_sku($element['actions']['product_sku']['#value'])) {
1292
    // Populate the line item with the product data.
1293
    commerce_product_line_item_populate($line_item, $product);
1294
  }
1295
  else {
1296
    return t('You have entered an invalid product SKU.');
1297
  }
1298
}
1299

    
1300
/**
1301
 * Creates a new product line item populated with the proper product values.
1302
 *
1303
 * @param $product
1304
 *   The fully loaded product referenced by the line item.
1305
 * @param $quantity
1306
 *   The quantity to set for the product.
1307
 * @param $order_id
1308
 *   The ID of the order the line item belongs to (if available).
1309
 * @param $data
1310
 *   A data array to set on the new line item. The following information in the
1311
 *   data array may be used on line item creation:
1312
 *   - $data['context']['display_path']: if present will be used to set the line
1313
 *     item's display_path field value.
1314
 * @param $type
1315
 *   The type of product line item to create. Must be a product line item as
1316
 *   defined in the line item type info array, and the line item type must
1317
 *   include the expected product related fields. Defaults to the base product
1318
 *   line item type defined by the Product Reference module.
1319
 *
1320
 * @return
1321
 *   The fully loaded line item populated with the product data as specified.
1322
 */
1323
function commerce_product_line_item_new($product, $quantity = 1, $order_id = 0, $data = array(), $type = 'product') {
1324
  // Ensure a default product line item type.
1325
  if (empty($type)) {
1326
    $type = 'product';
1327
  }
1328

    
1329
  // Create the new line item.
1330
  $line_item = entity_create('commerce_line_item', array(
1331
    'type' => $type,
1332
    'order_id' => $order_id,
1333
    'quantity' => $quantity,
1334
    'data' => $data,
1335
  ));
1336

    
1337
  // Populate it with the product information.
1338
  commerce_product_line_item_populate($line_item, $product);
1339

    
1340
  // Return the line item.
1341
  return $line_item;
1342
}
1343

    
1344
/**
1345
 * Populates an existing product line item with the product and quantity data.
1346
 *
1347
 * @param $line_item
1348
 *   The fully loaded line item object, populated by reference.
1349
 * @param $product
1350
 *   The fully loaded product referenced by the line item.
1351
 */
1352
function commerce_product_line_item_populate($line_item, $product) {
1353
  // Set the label to be the product SKU.
1354
  $line_item->line_item_label = $product->sku;
1355

    
1356
  // Wrap the line item and product to easily set field information.
1357
  $line_item_wrapper = entity_metadata_wrapper('commerce_line_item', $line_item);
1358
  $product_wrapper = entity_metadata_wrapper('commerce_product', $product);
1359

    
1360
  // Add the product reference value to the line item for the right language.
1361
  $line_item_wrapper->commerce_product = $product->product_id;
1362

    
1363
  // Add the display URI if specified.
1364
  if (!empty($line_item->data['context']['display_path'])) {
1365
    $line_item_wrapper->commerce_display_path = $line_item->data['context']['display_path'];
1366
  }
1367
  else {
1368
    $line_item_wrapper->commerce_display_path = '';
1369
  }
1370

    
1371
  // Set the unit price on the line item object if the product has a value in
1372
  // its commerce_price field.
1373
  $line_item->commerce_unit_price = $product->commerce_price;
1374

    
1375
  if (!is_null($line_item_wrapper->commerce_unit_price->value())) {
1376
    // Add the base price to the components array.
1377
    if (!commerce_price_component_load($line_item_wrapper->commerce_unit_price->value(), 'base_price')) {
1378
      $line_item_wrapper->commerce_unit_price->data = commerce_price_component_add(
1379
        $line_item_wrapper->commerce_unit_price->value(),
1380
        'base_price',
1381
        $line_item_wrapper->commerce_unit_price->value(),
1382
        TRUE
1383
      );
1384
    }
1385
  }
1386
}
1387

    
1388
/**
1389
 * Returns an array of product line item types.
1390
 */
1391
function commerce_product_line_item_types() {
1392
  $types = array();
1393

    
1394
  foreach (commerce_line_item_types() as $type => $line_item_type) {
1395
    if (!empty($line_item_type['product'])) {
1396
      $types[] = $type;
1397
    }
1398
  }
1399

    
1400
  return $types;
1401
}
1402

    
1403
/**
1404
 * Implements hook_commerce_checkout_order_can_checkout().
1405
 */
1406
function commerce_product_reference_commerce_checkout_order_can_checkout($order) {
1407
  // Allow orders with one or more product line items to proceed to checkout.
1408
  // If there are no line items on the order, redirect away.
1409
  $wrapper = entity_metadata_wrapper('commerce_order', $order);
1410

    
1411
  if (commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()) > 0) {
1412
    return TRUE;
1413
  }
1414
}
1415

    
1416
/**
1417
 * Implements hook_commerce_product_can_delete().
1418
 */
1419
function commerce_product_reference_commerce_product_can_delete($product) {
1420
  // Use EntityFieldQuery to look for line items referencing this product and do
1421
  // not allow the delete to occur if one exists.
1422
  $query = new EntityFieldQuery();
1423

    
1424
  $query
1425
    ->addTag('commerce_product_reference_commerce_product_can_delete')
1426
    ->entityCondition('entity_type', 'commerce_line_item', '=')
1427
    ->entityCondition('bundle', commerce_product_line_item_types(), 'IN')
1428
    ->fieldCondition('commerce_product', 'product_id', $product->product_id, '=')
1429
    ->count();
1430

    
1431
  return $query->execute() == 0;
1432
}
1433

    
1434
/**
1435
 * Callback to alter the property info of the reference fields.
1436
 *
1437
 * @see commerce_product_reference_field_info().
1438
 */
1439
function commerce_product_reference_property_info_callback(&$info, $entity_type, $field, $instance, $field_type) {
1440
  $property = &$info[$entity_type]['bundles'][$instance['bundle']]['properties'][$field['field_name']];
1441
  $property['options list'] = 'entity_metadata_field_options_list';
1442
}
1443

    
1444
/**
1445
 * Returns a list of all node types that contain a product reference field.
1446
 *
1447
 * @return
1448
 *   An array of node types, keyed by type.
1449
 */
1450
function commerce_product_reference_node_types() {
1451
  $list = array();
1452
  $types = node_type_get_types();
1453
  foreach (field_info_field_map() as $field_name => $field_stub) {
1454
    if ($field_stub['type'] == 'commerce_product_reference' && !empty($field_stub['bundles']['node'])) {
1455
      foreach($field_stub['bundles']['node'] as $bundle) {
1456
        $list[$bundle] = $types[$bundle];
1457
      }
1458
    }
1459
  }
1460

    
1461
  ksort($list);
1462
  return $list;
1463
}
1464

    
1465
/**
1466
 * Implements hook_preprocess_HOOK().
1467
 */
1468
function commerce_product_reference_preprocess_entity(&$variables) {
1469
  // @todo Remove this hook implementation when a new release is created for
1470
  // Entity API to fix the default $url variable in entity.tpl.php.
1471
  // @see http://drupal.org/node/1601162
1472
  if (!isset($variables['url'])) {
1473
    $variables['url'] = FALSE;
1474
  }
1475
}
1476

    
1477
/**
1478
 * Returns the default referenced product from an array of product entities.
1479
 *
1480
 * The basic behavior for determining a default product from an array of
1481
 * referenced products is to use the first referenced product. This function
1482
 * also allows other modules to specify a different default product through
1483
 * hook_commerce_product_reference_default_delta_alter().
1484
 *
1485
 * @param $products
1486
 *   An array of product entities referenced by a product reference field.
1487
 *
1488
 * @return
1489
 *   The default product entity.
1490
 */
1491
function commerce_product_reference_default_product($products) {
1492
  // Fetch the first delta value from the array.
1493
  reset($products);
1494
  $delta = key($products);
1495

    
1496
  // Allow other modules to specify a different delta value if desired.
1497
  drupal_alter('commerce_product_reference_default_delta', $delta, $products);
1498

    
1499
  return $products[$delta];
1500
}
1501

    
1502
/**
1503
 * Exception thrown when the referenced product display formatter goes into a
1504
 * potentially infinite loop.
1505
 */
1506
class CommerceProductReferenceRecursiveRenderingException extends Exception {}