Projet

Général

Profil

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

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

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
  $return['commerce_product']['bundles'] = array();
154
  foreach (commerce_product_type_get_name() as $type => $name) {
155
    $return['commerce_product']['bundles'][$type] = array(
156
      'label' => $name,
157
    );
158
  }
159

    
160
  return $return;
161
}
162

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

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

    
177
  return NULL;
178
}
179

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

    
206
/**
207
 * Implements hook_field_extra_fields().
208
 */
209
function commerce_product_field_extra_fields() {
210
  $extra = &drupal_static(__FUNCTION__);
211

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

    
256
  return $extra;
257
}
258

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

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

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

    
304
  $permissions += commerce_entity_access_permissions('commerce_product');
305

    
306
  return $permissions;
307
}
308

    
309
/**
310
 * Implements hook_enable().
311
 */
312
function commerce_product_enable() {
313
  commerce_product_configure_product_types();
314
}
315

    
316
/**
317
 * Implements hook_modules_enabled().
318
 */
319
function commerce_product_modules_enabled($modules) {
320
  commerce_product_configure_product_fields($modules);
321
}
322

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

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

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

    
354
  // Reset the product type cache to get types added by newly enabled modules.
355
  commerce_product_types_reset();
356

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

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

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

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

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

    
395
    // Last allow the info to be altered by other modules.
396
    drupal_alter('commerce_product_type_info', $product_types);
397
  }
398

    
399
  return $product_types;
400
}
401

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

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

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

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

    
449
  // Otherwise turn the array values into the type name only.
450
  $product_type_names = array();
451

    
452
  foreach ($product_types as $key => $value) {
453
    $product_type_names[$key] = $value['name'];
454
  }
455

    
456
  return $product_type_names;
457
}
458

    
459
/**
460
 * Wraps commerce_product_type_get_name() for the Entity module.
461
 */
462
function commerce_product_type_options_list() {
463
  return commerce_product_type_get_name();
464
}
465

    
466
/**
467
 * Returns a path argument from a product type.
468
 */
469
function commerce_product_type_to_arg($type) {
470
  return strtr($type, '_', '-');
471
}
472

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

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

    
499
/**
500
 * Loads a product by ID.
501
 */
502
function commerce_product_load($product_id) {
503
  if (empty($product_id)) {
504
    return FALSE;
505
  }
506

    
507
  $products = commerce_product_load_multiple(array($product_id), array());
508
  return $products ? reset($products) : FALSE;
509
}
510

    
511
/**
512
 * Loads a product by SKU.
513
 */
514
function commerce_product_load_by_sku($sku) {
515
  $products = commerce_product_load_multiple(array(), array('sku' => $sku));
516
  return $products ? reset($products) : FALSE;
517
}
518

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

    
540
  return entity_load('commerce_product', $product_ids, $conditions, $reset);
541
}
542

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

    
560
  // If any module implementing hook_commerce_product_can_delete() returns FALSE
561
  // the product cannot be deleted. Return TRUE if none return FALSE.
562
  return !in_array(FALSE, module_invoke_all('commerce_product_can_delete', $product));
563
}
564

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

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

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

    
607
/**
608
 * Implements hook_query_TAG_alter().
609
 */
610
function commerce_product_query_commerce_product_access_alter(QueryAlterableInterface $query) {
611
  return commerce_entity_access_query_alter($query, 'commerce_product');
612
}
613

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

    
634
  // Ensure that only valid tokens were used.
635
  $invalid_tokens = FALSE;
636

    
637
  foreach (token_scan($sku) as $type => $token) {
638
    if ($type !== 'product') {
639
      $invalid_tokens = TRUE;
640
    }
641
    else {
642
      foreach (array_keys($token) as $value) {
643
        if (!in_array($value, $valid_tokens)) {
644
          $invalid_tokens = TRUE;
645
        }
646
      }
647
    }
648
  }
649

    
650
  // Register the error if an invalid token was detected.
651
  if ($invalid_tokens) {
652
    return FALSE;
653
  }
654

    
655
  return $sku;
656
}
657

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

    
680
  return TRUE;
681
}
682

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

    
698
  return TRUE;
699
}
700

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

    
714
/**
715
 * Callback for setting product properties.
716
 * @see commerce_product_entity_property_info()
717
 */
718
function commerce_product_set_properties($product, $name, $value) {
719
  if ($name == 'creator') {
720
    $product->uid = $value;
721
  }
722
}
723

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

    
734
  $matches = array();
735

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

    
742
  if (count($args) <= 5) {
743
    drupal_json_output($matches);
744
    return;
745
  }
746

    
747
  // Trim off leading arguments from other modules such as Locale.
748
  $offset = array_search('commerce_product', $args);
749

    
750
  array_splice($args, 0, 5 + $offset);
751
  $string = implode('/', $args);
752

    
753
  // The user enters a comma-separated list of tags. We only autocomplete the last tag.
754
  $tags_typed = drupal_explode_tags($string);
755
  $tag_last = drupal_strtolower(array_pop($tags_typed));
756

    
757
  if (!empty($tag_last)) {
758
    $prefix = count($tags_typed) ? implode(', ', $tags_typed) . ', ' : '';
759

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

    
763
    // Get an array of matching products.
764
    $products = commerce_product_match_products($field, $instance, $tag_last, $match, array(), 10);
765

    
766
    // Loop through the products and convert them into autocomplete output.
767
    foreach ($products as $product_id => $data) {
768
      $matches[$prefix . $data['sku']] = $data['rendered'];
769
    }
770
  }
771

    
772
  drupal_json_output($matches);
773
}
774

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

    
816
  // Create unique id for static cache.
817
  $cid = implode(':', array(
818
    $field['field_name'],
819
    $match,
820
    ($string !== '' ? $string : implode('-', $ids)),
821
    $limit,
822
  ));
823

    
824
  if (!isset($results[$cid])) {
825
    $matches = _commerce_product_match_products_standard($instance, $string, $match, $ids, $limit, $access_tag);
826

    
827
    // Store the results.
828
    $results[$cid] = !empty($matches) ? $matches : array();
829
  }
830

    
831
  return $results[$cid];
832
}
833

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

    
842
  $query->entityCondition('entity_type', 'commerce_product');
843

    
844
  // Add the access control tag if specified.
845
  if ($access_tag) {
846
    $query->addTag('commerce_product_access');
847
  }
848

    
849
  // Add a global query tag so anyone can alter this query.
850
  $query->addTag('commerce_product_match');
851

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

    
856
    // Only filter by type if some types have been specified.
857
    if (!empty($types)) {
858
      $query->propertyCondition('type', $types, 'IN');
859
    }
860
  }
861

    
862
  if ($string !== '') {
863
    // EntityFieldQuery cannot do OR clauses, so we use hook_query_TAG_alter.
864
    $query->addTag('commerce_sku_or_title_match');
865

    
866
    $sku_title_meta = new stdClass();
867
    $sku_title_meta->properties = array(
868
      'sku',
869
      'title',
870
    );
871
    $sku_title_meta->string = $string;
872
    $sku_title_meta->match = $match;
873

    
874
    $query->addMetaData('commerce_sku_or_title_match', $sku_title_meta);
875
  }
876
  elseif ($ids) {
877
    // Otherwise add a product_id specific condition if specified.
878
    $query->propertyCondition('product_id', $ids, 'IN');
879
  }
880

    
881
  // Order the results by SKU, title, and then product type.
882
  $query
883
    ->propertyOrderBy('sku')
884
    ->propertyOrderBy('title')
885
    ->propertyOrderBy('type');
886

    
887
  // Add a limit if specified.
888
  if ($limit) {
889
    $query->range(0, $limit);
890
  }
891

    
892
  $entities = $query->execute();
893

    
894
  $matches = array();
895

    
896
  if (isset($entities['commerce_product'])) {
897
    $pids = array_keys($entities['commerce_product']);
898

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

    
911
  return $matches;
912
}
913

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

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

    
934
      case 'starts_with':
935
        $or = db_or()->condition('sku', $string . '%', 'LIKE')
936
          ->condition('title', $string . '%', 'LIKE');
937
        break;
938

    
939
      case 'equals':
940
      default:
941
        $or = db_or()->condition('sku', $string, '=')
942
          ->condition('title', $string, '=');
943
        break;
944
    }
945

    
946
    $query->condition($or);
947
  }
948
}
949

    
950
/**
951
 * Access callback: determines access to a product's translation tab.
952
 */
953
function commerce_product_entity_translation_tab_access($product) {
954
  return entity_translation_tab_access('commerce_product', $product);
955
}
956

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

    
970
/**
971
 * Sanitizes the product SKU before output.
972
 */
973
function template_preprocess_commerce_product_sku(&$variables) {
974
  $variables['sku'] = check_plain($variables['sku']);
975
}
976

    
977
/**
978
 * Sanitizes the product title before output.
979
 */
980
function template_preprocess_commerce_product_title(&$variables) {
981
  $variables['title'] = check_plain($variables['title']);
982
}
983

    
984
/**
985
 * Returns the options list for the product status property.
986
 */
987
function commerce_product_status_options_list() {
988
  return array(
989
    0 => t('Disabled'),
990
    1 => t('Active'),
991
  );
992
}
993

    
994
/**
995
 * Converts the product status integer to a string before output.
996
 */
997
function template_preprocess_commerce_product_status(&$variables) {
998
  $variables['status'] = empty($variables['status']) ? t('Disabled') : t('Active');
999
}