Projet

Général

Profil

Paste
Télécharger (28,6 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / modules / product / commerce_product.module @ dbb0c974

1
<?php
2

    
3
/**
4
 * @file
5
 * Defines the core Commerce product entity, including the entity itself, the
6
 * bundle definitions (product types), and various API functions to manage
7
 * products and interact with them through forms and autocompletes.
8
 */
9

    
10
/**
11
 * Implements hook_menu().
12
 */
13
function commerce_product_menu() {
14
  $items = array();
15

    
16
  $items['commerce_product/autocomplete'] = array(
17
    'title' => 'commerce_product autocomplete',
18
    'page callback' => 'commerce_product_autocomplete',
19
    'access callback' => TRUE,
20
    'type' => MENU_CALLBACK,
21
  );
22

    
23
  return $items;
24
}
25

    
26
/**
27
 * Implements hook_hook_info().
28
 */
29
function commerce_product_hook_info() {
30
  $hooks = array(
31
    'commerce_product_type_info' => array(
32
      'group' => 'commerce',
33
    ),
34
    'commerce_product_type_info_alter' => array(
35
      'group' => 'commerce',
36
    ),
37
    'commerce_product_type_insert' => array(
38
      'group' => 'commerce',
39
    ),
40
    'commerce_product_type_update' => array(
41
      'group' => 'commerce',
42
    ),
43
    'commerce_product_type_delete' => array(
44
      'group' => 'commerce',
45
    ),
46
    'commerce_product_uri' => array(
47
      'group' => 'commerce',
48
    ),
49
    'commerce_product_view' => array(
50
      'group' => 'commerce',
51
    ),
52
    'commerce_product_presave' => array(
53
      'group' => 'commerce',
54
    ),
55
    'commerce_product_insert' => array(
56
      'group' => 'commerce',
57
    ),
58
    'commerce_product_update' => array(
59
      'group' => 'commerce',
60
    ),
61
    'commerce_product_can_delete' => array(
62
      'group' => 'commerce',
63
    ),
64
    'commerce_product_delete' => array(
65
      'group' => 'commerce',
66
    ),
67
  );
68

    
69
  return $hooks;
70
}
71

    
72
/**
73
 * Implements hook_entity_info().
74
 */
75
function commerce_product_entity_info() {
76
  $return = array(
77
    'commerce_product' => array(
78
      'label' => t('Commerce Product'),
79
      'controller class' => 'CommerceProductEntityController',
80
      'base table' => 'commerce_product',
81
      'revision table' => 'commerce_product_revision',
82
      'fieldable' => TRUE,
83
      'entity keys' => array(
84
        'id' => 'product_id',
85
        'bundle' => 'type',
86
        'label' => 'title',
87
        'revision' => 'revision_id',
88
        'language' => 'language',
89
      ),
90
      'bundle keys' => array(
91
        'bundle' => 'type',
92
      ),
93
      'bundles' => array(),
94
      'load hook' => 'commerce_product_load',
95
      'view modes' => array(
96
        'full' => array(
97
          'label' => t('Admin display'),
98
          'custom settings' => FALSE,
99
        ),
100
      ),
101
      'uri callback' => 'commerce_product_uri',
102
      'metadata controller class' => '',
103
      'token type' => 'commerce-product',
104
      'access callback' => 'commerce_entity_access',
105
      'access arguments' => array(
106
        'user key' => 'uid',
107
        'access tag' => 'commerce_product_access',
108
      ),
109
      'permission labels' => array(
110
        'singular' => t('product'),
111
        'plural' => t('products'),
112
      ),
113

    
114
      // Prevent Redirect alteration of the product form.
115
      'redirect' => FALSE,
116

    
117
      // Add translation support.
118
      'translation' => array(
119
        'locale' => TRUE,
120
        'entity_translation' => array(
121
          'class' => 'EntityTranslationCommerceProductHandler',
122
          'bundle callback' => 'commerce_product_entity_translation_supported_type',
123
          'default settings' => array(
124
            'default_language' => LANGUAGE_NONE,
125
            'hide_language_selector' => FALSE,
126
          ),
127
        ),
128
      ),
129

    
130
      // Add title replacement support for translations.
131
      'field replacement' => array(
132
        'title' => array(
133
          'field' => array(
134
            'type' => 'text',
135
            'cardinality' => 1,
136
            'translatable' => TRUE,
137
          ),
138
          'instance' => array(
139
            'label' => t('Title'),
140
            'required' => TRUE,
141
            'settings' => array(
142
              'text_processing' => 0,
143
            ),
144
            'widget' => array(
145
              'weight' => -5,
146
            ),
147
          ),
148
        ),
149
      ),
150
    ),
151
  );
152

    
153
  foreach (commerce_product_type_get_name() as $type => $name) {
154
    $return['commerce_product']['bundles'][$type] = array(
155
      'label' => $name,
156
    );
157
  }
158

    
159
  return $return;
160
}
161

    
162
/**
163
 * Entity uri callback: gives modules a chance to specify a path for a product.
164
 */
165
function commerce_product_uri($product) {
166
  // Allow modules to specify a path, returning the first one found.
167
  foreach (module_implements('commerce_product_uri') as $module) {
168
    $uri = module_invoke($module, 'commerce_product_uri', $product);
169

    
170
    // If the implementation returned data, use that now.
171
    if (!empty($uri)) {
172
      return $uri;
173
    }
174
  }
175

    
176
  return NULL;
177
}
178

    
179
/**
180
 * Implements hook_file_download_access().
181
 *
182
 * This hook is grants access to files based on a user's access to the entity
183
 * a file is attached to. For example, users with access to a product should be
184
 * allowed to download files attached to that product. Here we do this on a per-
185
 * field basis for files attached to products.
186
 *
187
 * @param $field
188
 *   The field to which the file belongs.
189
 * @param $entity_type
190
 *   The type of $entity; for example, 'node' or 'user' or 'commerce_product'.
191
 * @param $entity
192
 *   The $entity to which $file is referenced.
193
 *
194
 * @return
195
 *   TRUE if access should be allowed by this entity or FALSE if denied. Note
196
 *   that denial may be overridden by another entity controller, making this
197
 *   grant permissive rather than restrictive.
198
 */
199
function commerce_product_file_download_access($field, $entity_type, $entity) {
200
  if ($entity_type == 'commerce_product') {
201
    return field_access('view', $field, $entity_type, $entity);
202
  }
203
}
204

    
205
/**
206
 * Implements hook_field_extra_fields().
207
 */
208
function commerce_product_field_extra_fields() {
209
  $extra = array();
210

    
211
  foreach (commerce_product_types() as $type => $product_type) {
212
    $extra['commerce_product'][$type] = array(
213
      'form' => array(
214
        'sku' => array(
215
          'label' => t('Product SKU'),
216
          'description' => t('Product module SKU form element'),
217
          'weight' => -10,
218
        ),
219
        'title' => array(
220
          'label' => t('Title'),
221
          'description' => t('Product module title form element'),
222
          'weight' => -5,
223
        ),
224
        'status' => array(
225
          'label' => t('Status'),
226
          'description' => t('Product module status form element'),
227
          'weight' => 35,
228
        ),
229
      ),
230
      'display' => array(
231
        'sku' => array(
232
          'label' => t('SKU'),
233
          'description' => t('The human readable identifier of the product'),
234
          'theme' => 'commerce_product_sku',
235
          'weight' => -10,
236
        ),
237
        'title' => array(
238
          'label' => t('Title'),
239
          'description' => t('Full product title'),
240
          'theme' => 'commerce_product_title',
241
          'weight' => -5,
242
        ),
243
        'status' => array(
244
          'label' => t('Status'),
245
          'description' => t('Whether the product is active or disabled'),
246
          'theme' => 'commerce_product_status',
247
          'weight' => 5,
248
        ),
249
      ),
250
    );
251
  }
252

    
253
  return $extra;
254
}
255

    
256
/**
257
 * Implements hook_theme().
258
 */
259
function commerce_product_theme() {
260
  return array(
261
    'commerce_product_sku' => array(
262
      'variables' => array('sku' => NULL, 'label' => NULL, 'product' => NULL),
263
      'path' => drupal_get_path('module', 'commerce_product') . '/theme',
264
      'template' => 'commerce-product-sku',
265
    ),
266
    'commerce_product_title' => array(
267
      'variables' => array('title' => NULL, 'label' => NULL, 'product' => NULL),
268
      'path' => drupal_get_path('module', 'commerce_product') . '/theme',
269
      'template' => 'commerce-product-title',
270
    ),
271
    'commerce_product_status' => array(
272
      'variables' => array('status' => NULL, 'label' => NULL, 'product' => NULL),
273
      'path' => drupal_get_path('module', 'commerce_product') . '/theme',
274
      'template' => 'commerce-product-status',
275
    ),
276
  );
277
}
278

    
279
/**
280
 * Implements hook_views_api().
281
 */
282
function commerce_product_views_api() {
283
  return array(
284
    'api' => 3,
285
    'path' => drupal_get_path('module', 'commerce_product') . '/includes/views',
286
  );
287
}
288

    
289
/**
290
 * Implements hook_permission().
291
 */
292
function commerce_product_permission() {
293
  $permissions = array(
294
    'administer product types' => array(
295
      'title' => t('Administer product types'),
296
      'description' => t('Allows users to configure product types and their fields.'),
297
      'restrict access' => TRUE,
298
    ),
299
  );
300

    
301
  $permissions += commerce_entity_access_permissions('commerce_product');
302

    
303
  return $permissions;
304
}
305

    
306
/**
307
 * Implements hook_enable().
308
 */
309
function commerce_product_enable() {
310
  commerce_product_configure_product_types();
311
}
312

    
313
/**
314
 * Implements hook_modules_enabled().
315
 */
316
function commerce_product_modules_enabled($modules) {
317
  commerce_product_configure_product_fields($modules);
318
}
319

    
320
/**
321
 * Configure the product types defined by enabled modules.
322
 */
323
function commerce_product_configure_product_types() {
324
  foreach (commerce_product_types() as $type => $product_type) {
325
    commerce_product_configure_product_type($type);
326
  }
327
}
328

    
329
/**
330
 * Ensures a base price field is present on a product type bundle.
331
 */
332
function commerce_product_configure_product_type($type) {
333
  commerce_price_create_instance('commerce_price', 'commerce_product', $type, t('Price'), 0, 'calculated_sell_price');
334
}
335

    
336
/**
337
 * Configures the fields on product types provided by other modules.
338
 *
339
 * @param $modules
340
 *   An array of module names whose product type fields should be configured;
341
 *   if left NULL, will default to all modules that implement
342
 *   hook_commerce_product_type_info().
343
 */
344
function commerce_product_configure_product_fields($modules = NULL) {
345
  // If no modules array is passed, recheck the fields for all product types
346
  // defined by enabled modules.
347
  if (empty($modules)) {
348
    $modules = module_implements('commerce_product_type_info');
349
  }
350

    
351
  // Reset the product type cache to get types added by newly enabled modules.
352
  commerce_product_types_reset();
353

    
354
  // Loop through all the enabled modules.
355
  foreach ($modules as $module) {
356
    // If the module implements hook_commerce_product_type_info()...
357
    if (module_hook($module, 'commerce_product_type_info')) {
358
      $product_types = module_invoke($module, 'commerce_product_type_info');
359

    
360
      // Loop through and configure the product types defined by the module.
361
      foreach ($product_types as $type => $product_type) {
362
        commerce_product_configure_product_type($type);
363
      }
364
    }
365
  }
366
}
367

    
368
/**
369
 * Returns an array of product type arrays keyed by type.
370
 */
371
function commerce_product_types() {
372
  // First check the static cache for a product types array.
373
  $product_types = &drupal_static(__FUNCTION__);
374

    
375
  // If it did not exist, fetch the types now.
376
  if (!isset($product_types)) {
377
    $product_types = array();
378

    
379
    // Find product types defined by hook_commerce_product_type_info().
380
    foreach (module_implements('commerce_product_type_info') as $module) {
381
      foreach (module_invoke($module, 'commerce_product_type_info') as $type => $product_type) {
382
        // Set the module each product type is defined by and revision handling
383
        // if they aren't set yet.
384
        $product_type += array(
385
          'module' => $module,
386
          'revision' => 1,
387
        );
388
        $product_types[$type] = $product_type;
389
      }
390
    }
391

    
392
    // Last allow the info to be altered by other modules.
393
    drupal_alter('commerce_product_type_info', $product_types);
394
  }
395

    
396
  return $product_types;
397
}
398

    
399
/**
400
 * Resets the cached list of product types.
401
 */
402
function commerce_product_types_reset() {
403
  $product_types = &drupal_static('commerce_product_types');
404
  $product_types = NULL;
405
  entity_info_cache_clear();
406
}
407

    
408
/**
409
 * Loads a product type.
410
 *
411
 * @param $type
412
 *   The machine-readable name of the product type; accepts normal machine names
413
 *     and URL prepared machine names with underscores replaced by hyphens.
414
 */
415
function commerce_product_type_load($type) {
416
  $type = strtr($type, array('-' => '_'));
417
  $product_types = commerce_product_types();
418
  return !empty($product_types[$type]) ? $product_types[$type] : FALSE;
419
}
420

    
421
/**
422
 * Returns the human readable name of any or all product types.
423
 *
424
 * @param $type
425
 *   Optional parameter specifying the type whose name to return.
426
 *
427
 * @return
428
 *   Either an array of all product type names keyed by the machine name or a
429
 *     string containing the human readable name for the specified type. If a
430
 *     type is specified that does not exist, this function returns FALSE.
431
 */
432
function commerce_product_type_get_name($type = NULL) {
433
  $product_types = commerce_product_types();
434

    
435
  // Return a type name if specified and it exists.
436
  if (!empty($type)) {
437
    if (isset($product_types[$type])) {
438
      return $product_types[$type]['name'];
439
    }
440
    else {
441
      // Return FALSE if it does not exist.
442
      return FALSE;
443
    }
444
  }
445

    
446
  // Otherwise turn the array values into the type name only.
447
  foreach ($product_types as $key => $value) {
448
    $product_types[$key] = $value['name'];
449
  }
450

    
451
  return $product_types;
452
}
453

    
454
/**
455
 * Wraps commerce_product_type_get_name() for the Entity module.
456
 */
457
function commerce_product_type_options_list() {
458
  return commerce_product_type_get_name();
459
}
460

    
461
/**
462
 * Returns a path argument from a product type.
463
 */
464
function commerce_product_type_to_arg($type) {
465
  return strtr($type, '_', '-');
466
}
467

    
468
/**
469
 * Returns an initialized product object.
470
 *
471
 * @param $type
472
 *   The machine-readable type of the product.
473
 *
474
 * @return
475
 *   A product object with all default fields initialized.
476
 */
477
function commerce_product_new($type = '') {
478
  return entity_get_controller('commerce_product')->create(array('type' => $type));
479
}
480

    
481
/**
482
 * Saves a product.
483
 *
484
 * @param $product
485
 *   The full product object to save.
486
 *
487
 * @return
488
 *   SAVED_NEW or SAVED_UPDATED depending on the operation performed.
489
 */
490
function commerce_product_save($product) {
491
  return entity_get_controller('commerce_product')->save($product);
492
}
493

    
494
/**
495
 * Loads a product by ID.
496
 */
497
function commerce_product_load($product_id) {
498
  if (empty($product_id)) {
499
    return FALSE;
500
  }
501

    
502
  $products = commerce_product_load_multiple(array($product_id), array());
503
  return $products ? reset($products) : FALSE;
504
}
505

    
506
/**
507
 * Loads a product by SKU.
508
 */
509
function commerce_product_load_by_sku($sku) {
510
  $products = commerce_product_load_multiple(array(), array('sku' => $sku));
511
  return $products ? reset($products) : FALSE;
512
}
513

    
514
/**
515
 * Loads multiple products by ID or based on a set of matching conditions.
516
 *
517
 * @see entity_load()
518
 *
519
 * @param $product_ids
520
 *   An array of product IDs.
521
 * @param $conditions
522
 *   An array of conditions on the {commerce_product} table in the form
523
 *     'field' => $value.
524
 * @param $reset
525
 *   Whether to reset the internal product loading cache.
526
 *
527
 * @return
528
 *   An array of product objects indexed by product_id.
529
 */
530
function commerce_product_load_multiple($product_ids = array(), $conditions = array(), $reset = FALSE) {
531
  if (empty($product_ids) && empty($conditions)) {
532
    return array();
533
  }
534

    
535
  return entity_load('commerce_product', $product_ids, $conditions, $reset);
536
}
537

    
538
/**
539
 * Determines whether or not the give product can be deleted.
540
 *
541
 * @param $product
542
 *   The product to be checked for deletion.
543
 *
544
 * @return
545
 *   Boolean indicating whether or not the product can be deleted.
546
 */
547
function commerce_product_can_delete($product) {
548
  // Return FALSE if the given product does not have an ID; it need not be
549
  // deleted, which is functionally equivalent to cannot be deleted as far as
550
  // code depending on this function is concerned.
551
  if (empty($product->product_id)) {
552
    return FALSE;
553
  }
554

    
555
  // If any module implementing hook_commerce_product_can_delete() returns FALSE
556
  // the product cannot be deleted. Return TRUE if none return FALSE.
557
  return !in_array(FALSE, module_invoke_all('commerce_product_can_delete', $product));
558
}
559

    
560
/**
561
 * Deletes a product by ID.
562
 *
563
 * @param $product_id
564
 *   The ID of the product to delete.
565
 *
566
 * @return
567
 *   TRUE on success, FALSE otherwise.
568
 */
569
function commerce_product_delete($product_id) {
570
  return commerce_product_delete_multiple(array($product_id));
571
}
572

    
573
/**
574
 * Deletes multiple products by ID.
575
 *
576
 * @param $product_ids
577
 *   An array of product IDs to delete.
578
 *
579
 * @return
580
 *   TRUE on success, FALSE otherwise.
581
 */
582
function commerce_product_delete_multiple($product_ids) {
583
  return entity_get_controller('commerce_product')->delete($product_ids);
584
}
585

    
586
/**
587
 * Checks product access for various operations.
588
 *
589
 * @param $op
590
 *   The operation being performed. One of 'view', 'update', 'create' or
591
 *   'delete'.
592
 * @param $product
593
 *   Optionally a product to check access for or for the create operation the
594
 *   product type. If nothing is given access permissions for all products are returned.
595
 * @param $account
596
 *   The user to check for. Leave it to NULL to check for the current user.
597
 */
598
function commerce_product_access($op, $product = NULL, $account = NULL) {
599
  return commerce_entity_access($op, $product, $account, 'commerce_product');
600
}
601

    
602
/**
603
 * Implements hook_query_TAG_alter().
604
 */
605
function commerce_product_query_commerce_product_access_alter(QueryAlterableInterface $query) {
606
  return commerce_entity_access_query_alter($query, 'commerce_product');
607
}
608

    
609
/**
610
 * Performs token replacement on a SKU for valid tokens only.
611
 *
612
 * TODO: This function currently limits acceptable Tokens to Product ID and type
613
 * with no ability to use Tokens for the Fields attached to the product. That
614
 * might be fine for a core Token replacement, but we should at least open the
615
 * $valid_tokens array up to other modules to enable various Tokens for use.
616
 *
617
 * @param $sku
618
 *   The raw SKU string including any tokens as entered.
619
 * @param $product
620
 *   A product object used to perform token replacement on the SKU.
621
 *
622
 * @return
623
 *   The SKU with tokens replaced or else FALSE if it included invalid tokens.
624
 */
625
function commerce_product_replace_sku_tokens($sku, $product) {
626
  // Build an array of valid SKU tokens.
627
  $valid_tokens = array('product-id', 'type');
628

    
629
  // Ensure that only valid tokens were used.
630
  $invalid_tokens = FALSE;
631

    
632
  foreach (token_scan($sku) as $type => $token) {
633
    if ($type !== 'product') {
634
      $invalid_tokens = TRUE;
635
    }
636
    else {
637
      foreach (array_keys($token) as $value) {
638
        if (!in_array($value, $valid_tokens)) {
639
          $invalid_tokens = TRUE;
640
        }
641
      }
642
    }
643
  }
644

    
645
  // Register the error if an invalid token was detected.
646
  if ($invalid_tokens) {
647
    return FALSE;
648
  }
649

    
650
  return $sku;
651
}
652

    
653
/**
654
 * Checks to see if a given SKU already exists for another product.
655
 *
656
 * @param $sku
657
 *   The string to match against existing SKUs.
658
 * @param $product_id
659
 *   The ID of the product the SKU is for; an empty value represents the SKU is
660
 *     meant for a new product.
661
 *
662
 * @return
663
 *   TRUE or FALSE indicating whether or not the SKU exists for another product.
664
 */
665
function commerce_product_validate_sku_unique($sku, $product_id) {
666
  // Look for an ID of a product matching the supplied SKU.
667
  if ($match_id = db_query('SELECT product_id FROM {commerce_product} WHERE sku = :sku', array(':sku' => $sku))->fetchField()) {
668
    // If this SKU is supposed to be for a new product or a product other than
669
    // the one that matched...
670
    if (empty($product_id) || $match_id != $product_id) {
671
      return FALSE;
672
    }
673
  }
674

    
675
  return TRUE;
676
}
677

    
678
/**
679
 * Checks to ensure a given SKU does not contain invalid characters.
680
 *
681
 * @param $sku
682
 *   The string to validate as a SKU.
683
 *
684
 * @return
685
 *   TRUE or FALSE indicating whether or not the SKU is valid.
686
 */
687
function commerce_product_validate_sku($sku) {
688
  // Do not allow commas in a SKU.
689
  if (strpos($sku, ',')) {
690
    return FALSE;
691
  }
692

    
693
  return TRUE;
694
}
695

    
696
/**
697
 * Callback for getting product properties.
698
 * @see commerce_product_entity_property_info()
699
 */
700
function commerce_product_get_properties($product, array $options, $name) {
701
  switch ($name) {
702
    case 'creator':
703
      return $product->uid;
704
    case 'edit_url':
705
      return url('admin/commerce/products/' . $product->product_id . '/edit', $options);
706
  }
707
}
708

    
709
/**
710
 * Callback for setting product properties.
711
 * @see commerce_product_entity_property_info()
712
 */
713
function commerce_product_set_properties($product, $name, $value) {
714
  if ($name == 'creator') {
715
    $product->uid = $value;
716
  }
717
}
718

    
719
/**
720
 * Returns output for product autocompletes.
721
 *
722
 * The values returned will be keyed by SKU and appear as such in the textfield,
723
 * even though the preview in the autocomplete list shows "SKU: Title".
724
 */
725
function commerce_product_autocomplete($entity_type, $field_name, $bundle, $string = '') {
726
  $field = field_info_field($field_name);
727
  $instance = field_info_instance($entity_type, $field_name, $bundle);
728

    
729
  $matches = array();
730

    
731
  // Extract the SKU string from the URL while preserving support for SKUs that
732
  // contain slashes. Whereas we previously relied on the $string parameter, its
733
  // value was being truncated when any SKUs in the autocomplete widget had a /.
734
  // @see http://drupal.org/node/1962144
735
  $args = explode('/', request_path());
736

    
737
  if (count($args) <= 5) {
738
    drupal_json_output($matches);
739
    return;
740
  }
741

    
742
  // Trim off leading arguments from other modules such as Locale.
743
  $offset = array_search('commerce_product', $args);
744

    
745
  array_splice($args, 0, 5 + $offset);
746
  $string = implode('/', $args);
747

    
748
  // The user enters a comma-separated list of tags. We only autocomplete the last tag.
749
  $tags_typed = drupal_explode_tags($string);
750
  $tag_last = drupal_strtolower(array_pop($tags_typed));
751

    
752
  if (!empty($tag_last)) {
753
    $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
754

    
755
    // Determine the type of autocomplete match to use when searching for products.
756
    $match = isset($field['widget']['autocomplete_match']) ? $field['widget']['autocomplete_match'] : 'contains';
757

    
758
    // Get an array of matching products.
759
    $products = commerce_product_match_products($field, $instance, $tag_last, $match, array(), 10);
760

    
761
    // Loop through the products and convert them into autocomplete output.
762
    foreach ($products as $product_id => $data) {
763
      $matches[$prefix . $data['sku']] = $data['rendered'];
764
    }
765
  }
766

    
767
  drupal_json_output($matches);
768
}
769

    
770
/**
771
 * Fetches an array of all products matching the given parameters.
772
 *
773
 * This info is used in various places (allowed values, autocomplete results,
774
 * input validation...). Some of them only need the product_ids, others
775
 * product_id + titles, others yet product_id + titles + rendered row (for
776
 * display in widgets).
777
 *
778
 * The array we return contains all the potentially needed information,
779
 * and lets calling functions use the parts they actually need.
780
 *
781
 * @param $field
782
 *   The field description.
783
 * @param $string
784
 *   Optional string to filter SKUs and titles on (used by autocomplete).
785
 * @param $match
786
 *   Operator to match filtered SKUs and titles against, can be any of:
787
 *   'contains', 'equals', 'starts_with'
788
 * @param $ids
789
 *   Optional product ids to lookup (used when $string and $match arguments are
790
 *   not given).
791
 * @param $limit
792
 *   If non-zero, limit the size of the result set.
793
 * @param $access_tag
794
 *   Boolean indicating whether or not an access control tag should be added to
795
 *   the query to find matching product data. Defaults to FALSE.
796
 *
797
 * @return
798
 *   An array of valid products in the form:
799
 *   array(
800
 *     product_id => array(
801
 *       'product_sku' => The product SKU,
802
 *       'title' => The product title,
803
 *       'rendered' => The text to display in widgets (can be HTML)
804
 *     ),
805
 *     ...
806
 *   )
807
 */
808
function commerce_product_match_products($field, $instance = NULL, $string = '', $match = 'contains', $ids = array(), $limit = NULL, $access_tag = FALSE) {
809
  $results = &drupal_static(__FUNCTION__, array());
810

    
811
  // Create unique id for static cache.
812
  $cid = implode(':', array(
813
    $field['field_name'],
814
    $match,
815
    ($string !== '' ? $string : implode('-', $ids)),
816
    $limit,
817
  ));
818

    
819
  if (!isset($results[$cid])) {
820
    $matches = _commerce_product_match_products_standard($instance, $string, $match, $ids, $limit, $access_tag);
821

    
822
    // Store the results.
823
    $results[$cid] = !empty($matches) ? $matches : array();
824
  }
825

    
826
  return $results[$cid];
827
}
828

    
829
/**
830
 * Helper function for commerce_product_match_products().
831
 *
832
 * Returns an array of products matching the specific parameters.
833
 */
834
function _commerce_product_match_products_standard($instance, $string = '', $match = 'contains', $ids = array(), $limit = NULL, $access_tag = FALSE) {
835
  $query = new EntityFieldQuery;
836

    
837
  $query->entityCondition('entity_type', 'commerce_product');
838

    
839
  // Add the access control tag if specified.
840
  if ($access_tag) {
841
    $query->addTag('commerce_product_access');
842
  }
843

    
844
  // Add a global query tag so anyone can alter this query.
845
  $query->addTag('commerce_product_match');
846

    
847
  // Add a condition to the query to filter by matching product types.
848
  if (!empty($instance['settings']['referenceable_types'])) {
849
    $types = array_diff(array_values($instance['settings']['referenceable_types']), array(0, NULL));
850

    
851
    // Only filter by type if some types have been specified.
852
    if (!empty($types)) {
853
      $query->propertyCondition('type', $types, 'IN');
854
    }
855
  }
856

    
857
  if ($string !== '') {
858
    // EntityFieldQuery cannot do OR clauses, so we use hook_query_TAG_alter.
859
    $query->addTag('commerce_sku_or_title_match');
860

    
861
    $sku_title_meta = new stdClass();
862
    $sku_title_meta->properties = array(
863
      'sku',
864
      'title',
865
    );
866
    $sku_title_meta->string = $string;
867
    $sku_title_meta->match = $match;
868

    
869
    $query->addMetaData('commerce_sku_or_title_match', $sku_title_meta);
870
  }
871
  elseif ($ids) {
872
    // Otherwise add a product_id specific condition if specified.
873
    $query->propertyCondition('product_id', $ids, 'IN');
874
  }
875

    
876
  // Order the results by SKU, title, and then product type.
877
  $query
878
    ->propertyOrderBy('sku')
879
    ->propertyOrderBy('title')
880
    ->propertyOrderBy('type');
881

    
882
  // Add a limit if specified.
883
  if ($limit) {
884
    $query->range(0, $limit);
885
  }
886

    
887
  $entities = $query->execute();
888

    
889
  $matches = array();
890

    
891
  if (isset($entities['commerce_product'])) {
892
    $pids = array_keys($entities['commerce_product']);
893

    
894
    // EntityFieldQuery doesn't return sku and title, so we have to load again.
895
    $products = commerce_product_load_multiple($pids);
896
    foreach ($products AS $product) {
897
      $matches[$product->product_id] = array(
898
        'sku' => $product->sku,
899
        'type' => $product->type,
900
        'title' => $product->title,
901
        'rendered' => t('@sku: @title', array('@sku' => $product->sku, '@title' => $product->title)),
902
      );
903
    }
904
  }
905

    
906
  return $matches;
907
}
908

    
909
/**
910
 * Implements hook_query_TAG_alter.
911
 *
912
 * EntityFieldQuery used in _commerce_product_match_products_standard() does not
913
 * allow OR clauses. Alter the SQL query to string match on sku OR title.
914
 *
915
 * @param QueryAlterableInterface $query
916
 */
917
function commerce_product_query_commerce_sku_or_title_match_alter(QueryAlterableInterface $query) {
918
  $string = $query->alterMetaData['commerce_sku_or_title_match']->string;
919
  $match = $query->alterMetaData['commerce_sku_or_title_match']->match;
920

    
921
  if (isset($string, $match)) {
922
    // Build a where clause matching on either the SKU or title.
923
    switch ($match) {
924
      case 'contains':
925
        $or = db_or()->condition('sku', '%' . $string . '%', 'LIKE')
926
          ->condition('title', '%' . $string . '%', 'LIKE');
927
        break;
928

    
929
      case 'starts_with':
930
        $or = db_or()->condition('sku', $string . '%', 'LIKE')
931
          ->condition('title', $string . '%', 'LIKE');
932
        break;
933

    
934
      case 'equals':
935
      default:
936
        $or = db_or()->condition('sku', $string, '=')
937
          ->condition('title', $string, '=');
938
        break;
939
    }
940

    
941
    $query->condition($or);
942
  }
943
}
944

    
945
/**
946
 * Access callback: determines access to a product's translation tab.
947
 */
948
function commerce_product_entity_translation_tab_access($product) {
949
  return entity_translation_tab_access('commerce_product', $product);
950
}
951

    
952
/**
953
 * Returns whether the given product type has support for translations.
954
 *
955
 * @param $type
956
 *   The machine-name of the product type to check for translation support.
957
 *
958
 * @return
959
 *   TRUE or FALSE indicating translation support for the requested type.
960
 */
961
function commerce_product_entity_translation_supported_type($type) {
962
  return variable_get('language_product_type_' . $type, 0) == ENTITY_TRANSLATION_ENABLED;
963
}
964

    
965
/**
966
 * Sanitizes the product SKU before output.
967
 */
968
function template_preprocess_commerce_product_sku(&$variables) {
969
  $variables['sku'] = check_plain($variables['sku']);
970
}
971

    
972
/**
973
 * Sanitizes the product title before output.
974
 */
975
function template_preprocess_commerce_product_title(&$variables) {
976
  $variables['title'] = check_plain($variables['title']);
977
}
978

    
979
/**
980
 * Returns the options list for the product status property.
981
 */
982
function commerce_product_status_options_list() {
983
  return array(
984
    0 => t('Disabled'),
985
    1 => t('Active'),
986
  );
987
}
988

    
989
/**
990
 * Converts the product status integer to a string before output.
991
 */
992
function template_preprocess_commerce_product_status(&$variables) {
993
  $variables['status'] = empty($variables['status']) ? t('Disabled') : t('Active');
994
}