Projet

Général

Profil

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

root / htmltest / sites / all / modules / commerce / commerce.module @ a5572547

1 85ad3d82 Assos Assos
<?php
2
3
/**
4
 * @file
5
 * Defines features and functions common to the Commerce modules.
6
 */
7
8
9
// Define our own rounding constants since we can't depend on PHP 5.3.
10
define('COMMERCE_ROUND_NONE', 0);
11
define('COMMERCE_ROUND_HALF_UP', 1);
12
define('COMMERCE_ROUND_HALF_DOWN', 2);
13
define('COMMERCE_ROUND_HALF_EVEN', 3);
14
define('COMMERCE_ROUND_HALF_ODD', 4);
15
16
/**
17
 * Implements hook_permission().
18
 */
19
function commerce_permission() {
20
  $permissions = array(
21
    'configure store' => array(
22
      'title' => t('Configure store settings'),
23
      'description' => t('Allows users to update store currency and contact settings.'),
24
      'restrict access' => TRUE,
25
    ),
26
  );
27
28
  return $permissions;
29
}
30
31
/**
32
 * Implements hook_hook_info().
33
 */
34
function commerce_hook_info() {
35
  $hooks = array(
36
    'commerce_currency_info' => array(
37
      'group' => 'commerce',
38
    ),
39
    'commerce_currency_info_alter' => array(
40
      'group' => 'commerce',
41
    ),
42
    'commerce_entity_access' => array(
43
      'group' => 'commerce',
44
    ),
45
    'commerce_entity_access_condition_alter' => array(
46
      'group' => 'commerce',
47
    ),
48
    'commerce_entity_create_alter' => array(
49
      'group' => 'commerce',
50
    ),
51
  );
52
53
  return $hooks;
54
}
55
56
/**
57
 * Implements hook_system_info_alter().
58
 *
59
 * Drupal's Field module doesn't allow field type providing modules to be
60
 * disabled while fields and instances of those types exist in the system.
61
 * See http://drupal.org/node/943772 for further explanation.
62
 *
63
 * That approach doesn't work for Commerce, creating circular dependencies
64
 * and making uninstall impossible. This function removes the requirement,
65
 * allowing Commerce to implement its own workaround.
66
 *
67
 * @see commerce_delete_field()
68
 */
69
function commerce_system_info_alter(&$info, $file, $type) {
70
  $modules = array(
71
    'commerce_product_reference',
72
    'commerce_price',
73
    'commerce_customer',
74
    'commerce_line_item',
75
  );
76
77
  if ($type == 'module' && in_array($file->name, $modules)) {
78
    unset($info['required']);
79
    unset($info['explanation']);
80
  }
81
}
82
83
/**
84
 * Finds all fields of a particular field type.
85
 *
86
 * @param $field_type
87
 *   The type of field to search for.
88
 * @param $entity_type
89
 *   Optional entity type to restrict the search to.
90
 *
91
 * @return
92
 *   An array of the matching fields keyed by the field name.
93
 */
94
function commerce_info_fields($field_type, $entity_type = NULL) {
95
  $fields = array();
96
97
  // Loop through the fields looking for any fields of the specified type.
98
  foreach (field_info_fields() as $field_name => $field) {
99
    if ($field['type'] == $field_type) {
100
      // Add this field to the return array if no entity type was specified or
101
      // if the specified type exists in the field's bundles array.
102
      if (empty($entity_type) || in_array($entity_type, array_keys($field['bundles']))) {
103
        $fields[$field_name] = $field;
104
      }
105
    }
106
  }
107
108
  return $fields;
109
}
110
111
/**
112
 * Deletes a reference to another entity from an entity with a reference field.
113
 *
114
 * @param $entity
115
 *   The entity that contains the reference field.
116
 * @param $field_name
117
 *   The name of the entity reference field.
118
 * @param $col_name
119
 *   The name of the column in the field's schema containing the referenced
120
 *   entity's ID.
121
 * @param $ref_entity_id
122
 *   The ID of the entity to delete from the reference field.
123
 */
124
function commerce_entity_reference_delete($entity, $field_name, $col_name, $ref_entity_id) {
125
  // Exit now if the entity does not have the expected field.
126
  if (empty($entity->{$field_name})) {
127
    return;
128
  }
129
130
  // Loop over each of the field's items in every language.
131
  foreach ($entity->{$field_name} as $langcode => $items) {
132
    $rekey = FALSE;
133
    foreach ($items as $delta => $item) {
134
      // If the item references the specified entity...
135
      if (!empty($item[$col_name]) && $item[$col_name] == $ref_entity_id) {
136
        // Unset the reference.
137
        unset($entity->{$field_name}[$langcode][$delta]);
138
        $rekey = TRUE;
139
      }
140
    }
141
142
    if ($rekey) {
143
      // Rekey the field items if necessary.
144
      $entity->{$field_name}[$langcode] = array_values($entity->{$field_name}[$langcode]);
145
146
      // If the reference field is empty, wipe its data altogether.
147
      if (count($entity->{$field_name}[$langcode]) == 0) {
148
        unset($entity->{$field_name}[$langcode]);
149
      }
150
    }
151
  }
152
}
153
154
/**
155
 * Attempts to directly activate a field that was disabled due to its module
156
 * being disabled.
157
 *
158
 * The normal API function for updating fields, field_update_field(), will not
159
 * work on disabled fields. As a workaround, this function directly updates the
160
 * database, but it is up to the caller to clear the cache.
161
 *
162
 * @param $field_name
163
 *   The name of the field to activate.
164
 *
165
 * @return
166
 *   Boolean indicating whether or not the field was activated.
167
 */
168
function commerce_activate_field($field_name) {
169
  // Set it to active via a query because field_update_field() does
170
  // not work on inactive fields.
171
  $updated = db_update('field_config')
172
    ->fields(array('active' => 1))
173
    ->condition('field_name', $field_name, '=')
174
    ->condition('deleted', 0, '=')
175
    ->execute();
176
177
  return !empty($updated) ? TRUE : FALSE;
178
}
179
180
/**
181
 * Enables and deletes fields of the specified type.
182
 *
183
 * @param $type
184
 *   The type of fields to enable and delete.
185
 *
186
 * @see commerce_delete_field()
187
 */
188
function commerce_delete_fields($type) {
189
  // Read the fields for any active or inactive field of the specified type.
190
  foreach (field_read_fields(array('type' => $type), array('include_inactive' => TRUE)) as $field_name => $field) {
191
    commerce_delete_field($field_name);
192
  }
193
}
194
195
/**
196
 * Enables and deletes the specified field.
197
 *
198
 * The normal API function for deleting fields, field_delete_field(), will not
199
 * work on disabled fields. As a workaround, this function first activates the
200
 * fields of the specified type and then deletes them.
201
 *
202
 * @param $field_name
203
 *   The name of the field to enable and delete.
204
 */
205
function commerce_delete_field($field_name) {
206
  // In case the field is inactive, first activate it and clear the field cache.
207
  if (commerce_activate_field($field_name)) {
208
    field_cache_clear();
209
  }
210
211
  // Delete the field.
212
  field_delete_field($field_name);
213
}
214
215
/**
216
 * Deletes any field instance attached to entities of the specified type,
217
 * regardless of whether or not the field is active.
218
 *
219
 * @param $entity_type
220
 *   The type of entity whose fields should be deleted.
221
 * @param $bundle
222
 *   Optionally limit instance deletion to a specific bundle of the specified
223
 *   entity type.
224
 */
225
function commerce_delete_instances($entity_type, $bundle = NULL) {
226
  // Prepare a parameters array to load the specified instances.
227
  $params = array(
228
    'entity_type' => $entity_type,
229
  );
230
231
  if (!empty($bundle)) {
232
    $params['bundle'] = $bundle;
233
    // Delete this bundle's field bundle settings.
234
    variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle);
235
  }
236
  else {
237
    // Delete all field bundle settings for this entity type.
238
    db_delete('variable')
239
      ->condition('name', db_like('field_bundle_settings_' . $entity_type . '__') . '%', 'LIKE')
240
      ->execute();
241
  }
242
243
  // Read and delete the matching field instances.
244
  foreach (field_read_instances($params, array('include_inactive' => TRUE)) as $instance) {
245
    commerce_delete_instance($instance);
246
  }
247
}
248
249
/**
250
 * Deletes the specified instance and handles field cleanup manually in case the
251
 * instance is of a disabled field.
252
 *
253
 * @param $instance
254
 *   The field instance info array to be deleted.
255
 */
256
function commerce_delete_instance($instance) {
257
  // First activate the instance's field if necessary.
258
  $field_name = $instance['field_name'];
259
  $activated = commerce_activate_field($field_name);
260
261
  // Clear the field cache if we just activated the field.
262
  if ($activated) {
263
    field_cache_clear();
264
  }
265
266
  // Then delete the instance.
267
  field_delete_instance($instance, FALSE);
268
269
  // Now check to see if there are any other instances of the field left.
270
  $field = field_info_field($field_name);
271
272
  if (count($field['bundles']) == 0) {
273
    field_delete_field($field_name);
274
  }
275
  elseif ($activated) {
276
    // If there are remaining instances but the field was originally disabled,
277
    // disabled it again now.
278
    $field['active'] = 0;
279
    field_update_field($field);
280
  }
281
}
282
283
/**
284
 * Makes any required form elements in a form unrequired.
285
 *
286
 * @param $form
287
 *   The form array to search for required elements.
288
 */
289
function commerce_unrequire_form_elements(&$form) {
290
  array_walk_recursive($form, '_commerce_unrequire_element');
291
}
292
293
/**
294
 * array_walk_recursive callback: makes an individual element unrequired.
295
 *
296
 * @param &$value
297
 *   The value of the form array being walked.
298
 * @param $key
299
 *   The key of the form array corresponding to the present value.
300
 */
301
function _commerce_unrequire_element(&$value, $key) {
302
  if ($key === '#required') {
303
    $value = FALSE;
304
  }
305
}
306
307
/**
308
 * Returns the callback for a form ID as defined by hook_forms().
309
 *
310
 * @param $form_id
311
 *   The form ID to find the callback for.
312
 * @return
313
 *   A string containing the form's callback function name.
314
 *
315
 * @see drupal_retrieve_form()
316
 * @see hook_forms()
317
 */
318
function commerce_form_callback($form_id, $form_state) {
319
  // If a function named after the $form_id does not exist, look for its
320
  // definition in hook_forms().
321
  if (!function_exists($form_id)) {
322
    $forms = &drupal_static(__FUNCTION__);
323
324
    // In cases where many form_ids need to share a central builder function,
325
    // such as the product editing form, modules can implement hook_forms(). It
326
    // maps one or more form_ids to the correct constructor functions.
327
    if (!isset($forms) || !isset($forms[$form_id])) {
328
      $forms = module_invoke_all('forms', $form_id, $form_state['build_info']['args']);
329
    }
330
331
    if (isset($forms[$form_id]['callback'])) {
332
      return $forms[$form_id]['callback'];
333
    }
334
  }
335
336
  return $form_id;
337
}
338
339
/**
340
 * Renders a View for display in some other element.
341
 *
342
 * @param $view_key
343
 *   The ID of the View to embed.
344
 * @param $display_id
345
 *   The ID of the display of the View that will actually be rendered.
346
 * @param $arguments
347
 *   An array of arguments to pass to the View.
348
 * @param $override_url
349
 *   A url that overrides the url of the current view.
350
 *
351
 * @return
352
 *   The rendered output of the chosen View display.
353
 */
354
function commerce_embed_view($view_id, $display_id, $arguments, $override_url = '') {
355
  // Load the specified View.
356
  $view = views_get_view($view_id);
357
  $view->set_display($display_id);
358
359
  // Set the specific arguments passed in.
360
  $view->set_arguments($arguments);
361
362
  // Override the view url, if an override was provided.
363
  if (!empty($override_url)) {
364
    $view->override_url = $override_url;
365
  }
366
367
  // Prepare and execute the View query.
368
  $view->pre_execute();
369
  $view->execute();
370
371
  // Return the rendered View.
372
  return $view->render();
373
}
374
375
/**
376
 * Returns the e-mail address from which to send commerce related e-mails.
377
 *
378
 * Currently this is just using the site's e-mail address, but this may be
379
 * updated to use a specific e-mail address when we add a settings form for the
380
 * store's physical address and contact information.
381
 */
382
function commerce_email_from() {
383
  return variable_get('site_mail', ini_get('sendmail_from'));
384
}
385
386
/**
387
 * Translate a data structure using i18n_string, if available.
388
 *
389
 * @param $type
390
 *   The i18n object type.
391
 * @param $object
392
 *   The object or array to translate.
393
 * @param $options
394
 *   An array of options passed along to i18n.
395
 *
396
 * @return
397
 *   The translated data structure if i18_string is available, the original
398
 *   otherwise.
399
 *
400
 * @see i18n_string_object_translate()
401
 */
402
function commerce_i18n_object($type, $object, $options = array()) {
403
  // Clone the object, to ensure the original remains untouched.
404
  if (is_object($object)) {
405
    $object = clone $object;
406
  }
407
408
  if (module_exists('i18n_string')) {
409
    return i18n_string_object_translate($type, $object, $options);
410
  }
411
  else {
412
    return $object;
413
  }
414
}
415
416
/**
417
 * Implements hook_i18n_string_info().
418
 */
419
function commerce_i18n_string_info() {
420
  $groups['commerce'] = array(
421
    'title' => t('Drupal Commerce'),
422
    'format' => TRUE,
423
  );
424
  return $groups;
425
}
426
427
/**
428
 * Translate a string using i18n_string, if available.
429
 *
430
 * @param $name
431
 *   Textgroup and context glued with ':'.
432
 * @param $default
433
 *   String in default language. Default language may or may not be English.
434
 * @param $options
435
 *   An associative array of additional options, with the following keys:
436
 *   - langcode: the language code to translate to a language other than what is
437
 *     used to display the page; defaults to the current language
438
 *   - filter: filtering callback to apply to the translated string only
439
 *   - format: input format to apply to the translated string only
440
 *   - callback: callback to apply to the result (both to the translated or
441
 *     untranslated string)
442
 *   - update: whether to update source table; defaults to FALSE
443
 *   - translate: whether to return a translation; defaults to TRUE
444
 *
445
 * @return
446
 *   The translated string if i18n_string is available, the original otherwise.
447
 *
448
 * @see i18n_string()
449
 */
450
function commerce_i18n_string($name, $default, $options = array()) {
451
  if (module_exists('i18n_string')) {
452
    $result = i18n_string($name, $default, $options);
453
  }
454
  else {
455
    $result = $default;
456
    $options += array(
457
      'format' => NULL,
458
      'sanitize' => FALSE,
459
    );
460
    if ($options['sanitize']) {
461
      $result = check_markup($result, $options['format']);
462
    }
463
  }
464
465
  return $result;
466
}
467
468
/**
469
 * Returns the currency code of the site's default currency.
470
 */
471
function commerce_default_currency() {
472
  return variable_get('commerce_default_currency', 'USD');
473
}
474
475
/**
476
 * Returns a single currency array.
477
 *
478
 * @param $currency_code
479
 *   The code of the currency to return or NULL to return the default currency.
480
 *
481
 * @return
482
 *   The specified currency array or FALSE if it does not exist.
483
 */
484
function commerce_currency_load($currency_code = NULL) {
485
  $currencies = commerce_currencies();
486
487
  // Check to see if we should return the default currency.
488
  if (empty($currency_code)) {
489
    $currency_code = commerce_default_currency();
490
  }
491
492
  return isset($currencies[$currency_code]) ? $currencies[$currency_code] : FALSE;
493
}
494
495
/**
496
 * Returns an array of all available currencies.
497
 *
498
 * @param $enabled
499
 *   Boolean indicating whether or not to return only enabled currencies.
500
 * @param $reset
501
 *   Boolean indicating whether or not the cache should be reset before currency
502
 *     data is loaded and returned.
503
 *
504
 * @return
505
 *   An array of altered currency arrays keyed by the currency code.
506
 */
507
function commerce_currencies($enabled = FALSE, $reset = FALSE) {
508
  global $language;
509
  $currencies = &drupal_static(__FUNCTION__);
510
511
  // If there is no static cache for currencies yet or a reset was specified...
512
  if (!isset($currencies) || $reset) {
513
    // First attempt to load currency data from the cache if we simply didn't
514
    // have it statically cached and a reset hasn't been specified.
515
    if (!$reset && $currencies_cached = cache_get('commerce_currencies:' . $language->language)) {
516
      $currencies['all'] = $currencies_cached->data;
517
    }
518
    else {
519
      // Otherwise we'll load currency definitions afresh from enabled modules.
520
      // Begin by establishing some default values for currencies.
521
      $defaults = array(
522
        'symbol' => '',
523
        'minor_unit' => '',
524
        'decimals' => 2,
525
        'rounding_step' => 0,
526
        'thousands_separator' => ',',
527
        'decimal_separator' => '.',
528
        'symbol_placement' => 'hidden',
529
        'symbol_spacer' => ' ',
530
        'code_placement' => 'after',
531
        'code_spacer' => ' ',
532
        'format_callback' => '',
533
        'conversion_callback' => '',
534
        'conversion_rate' => 1,
535
      );
536
537
      // Include the currency file and invoke the currency info hook.
538
      module_load_include('inc', 'commerce', 'includes/commerce.currency');
539
      $currencies['all'] = module_invoke_all('commerce_currency_info');
540
      drupal_alter('commerce_currency_info', $currencies['all']);
541
542
      // Add default values if they don't exist.
543
      foreach ($currencies['all'] as $currency_code => $currency) {
544
        $currencies['all'][$currency_code] = array_merge($defaults, $currency);
545
      }
546
547
      // Sort the currencies
548
      ksort($currencies['all']);
549
550
      cache_set('commerce_currencies:' . $language->language, $currencies['all']);
551
    }
552
553
    // Form an array of enabled currencies based on the variable set by the
554
    // checkboxes element on the currency settings form.
555
    $enabled_currencies = array_diff(array_values(variable_get('commerce_enabled_currencies', array('USD' => 'USD'))), array(0));
556
    $currencies['enabled'] = array_intersect_key($currencies['all'], drupal_map_assoc($enabled_currencies));
557
  }
558
559
  return $enabled ? $currencies['enabled'] : $currencies['all'];
560
}
561
562
/**
563
 * Returns an associative array of the specified currency codes.
564
 *
565
 * @param $enabled
566
 *   Boolean indicating whether or not to include only enabled currencies.
567
 */
568
function commerce_currency_get_code($enabled = FALSE) {
569
  return drupal_map_assoc(array_keys(commerce_currencies($enabled)));
570
}
571
572
/**
573
 * Wraps commerce_currency_get_code() for use by the Entity module.
574
 */
575
function commerce_currency_code_options_list() {
576
  return commerce_currency_get_code(TRUE);
577
}
578
579
/**
580
 * Returns the symbol of any or all currencies.
581
 *
582
 * @param $code
583
 *   Optional parameter specifying the code of the currency whose symbol to return.
584
 *
585
 * @return
586
 *   Either an array of all currency symbols keyed by the currency code or a
587
 *     string containing the symbol for the specified currency. If a currency is
588
 *     specified that does not exist, this function returns FALSE.
589
 */
590
function commerce_currency_get_symbol($currency_code = NULL) {
591
  $currencies = commerce_currencies();
592
593
  // Return a specific currency symbol if specified.
594
  if (!empty($currency_code)) {
595
    if (isset($currencies[$currency_code])) {
596
      return $currencies[$currency_code]['symbol'];
597
    }
598
    else {
599
      return FALSE;
600
    }
601
  }
602
603
  // Otherwise turn the array values into the type name only.
604
  foreach ($currencies as $currency_code => $currency) {
605
    $currencies[$currency_code] = $currency['symbol'];
606
  }
607
608
  return $currencies;
609
}
610
611
/**
612
 * Formats a price for a particular currency.
613
 *
614
 * @param $amount
615
 *   A numeric price amount value.
616
 * @param $currency_code
617
 *   The three character code of the currency.
618
 * @param $object
619
 *   When present, the object to which the price is attached.
620
 * @param $convert
621
 *   Boolean indicating whether or not the amount needs to be converted to a
622
 *   decimal price amount when formatting.
623
 *
624
 * @return
625
 *   A fully formatted currency.
626
 */
627
function commerce_currency_format($amount, $currency_code, $object = NULL, $convert = TRUE) {
628
  // First load the currency array.
629
  $currency = commerce_currency_load($currency_code);
630
631
  // Then convert the price amount to the currency's major unit decimal value.
632
  if ($convert == TRUE) {
633
    $amount = commerce_currency_amount_to_decimal($amount, $currency_code);
634
  }
635
636
  // Invoke the custom format callback if specified.
637
  if (!empty($currency['format_callback'])) {
638
    return $currency['format_callback']($amount, $currency, $object);
639
  }
640
641
  // Format the price as a number.
642
  $price = number_format(commerce_currency_round(abs($amount), $currency), $currency['decimals'], $currency['decimal_separator'], $currency['thousands_separator']);
643
644
  // Establish the replacement values to format this price for its currency.
645
  $replacements = array(
646
    '@code_before' => $currency['code_placement'] == 'before' ? $currency['code'] : '',
647
    '@symbol_before' => $currency['symbol_placement'] == 'before' ? $currency['symbol'] : '',
648
    '@price' => $price,
649
    '@symbol_after' => $currency['symbol_placement'] == 'after' ? $currency['symbol'] : '',
650
    '@code_after' => $currency['code_placement'] == 'after' ? $currency['code'] : '',
651
    '@negative' => $amount < 0 ? '-' : '',
652
    '@symbol_spacer' => $currency['symbol_spacer'],
653
    '@code_spacer' => $currency['code_spacer'],
654
  );
655
656
  return trim(t('@code_before@code_spacer@negative@symbol_before@price@symbol_spacer@symbol_after@code_spacer@code_after', $replacements));
657
}
658
659
/**
660
 * Rounds a price amount for the specified currency.
661
 *
662
 * Rounding of the minor unit with a currency specific step size. For example,
663
 * Swiss Francs are rounded using a step size of 0.05. This means a price of
664
 * 10.93 is converted to 10.95.
665
 *
666
 * @param $amount
667
 *   The numeric amount value of the price to be rounded.
668
 * @param $currency
669
 *   The currency array containing the rounding information pertinent to this
670
 *     price. Specifically, this function looks for the 'rounding_step' property
671
 *     for the step size to round to, supporting '0.05' and '0.02'. If the value
672
 *     is 0, this function performs normal rounding to the nearest supported
673
 *     decimal value.
674
 *
675
 * @return
676
 *   The rounded numeric amount value for the price.
677
 */
678
function commerce_currency_round($amount, $currency) {
679
  if (!$currency['rounding_step']) {
680
    return round($amount, $currency['decimals']);
681
  }
682
683
  $modifier = 1 / $currency['rounding_step'];
684
685
  return round($amount * $modifier) / $modifier;
686
}
687
688
/**
689
 * Converts a price amount from a currency to the target currency based on the
690
 *   current currency conversion rates.
691
 *
692
 * The Commerce module establishes a default conversion rate for every currency
693
 * as 1, so without any additional information there will be a 1:1 conversion
694
 * from one currency to the next. Other modules can provide UI based or web
695
 * service based alterations to the conversion rate of the defined currencies as
696
 * long as every rate is calculated relative to a single base currency. It does
697
 * not matter which currency is the base currency as long as the same one is
698
 * used for every rate calculation.
699
 *
700
 * To convert an amount from one currency to another, we simply take the amount
701
 * value and multiply it by the current currency's conversion rate divided by
702
 * the target currency's conversion rate.
703
 *
704
 * @param $amount
705
 *   The numeric amount value of the price to be rounded.
706
 * @param $currency_code
707
 *   The currency code for the current currency of the price.
708
 * @param $target_currency_code
709
 *   The currency code for the target currency of the price.
710
 *
711
 * @return
712
 *   The numeric amount value converted to its equivalent in the target currency.
713
 */
714
function commerce_currency_convert($amount, $currency_code, $target_currency_code) {
715
  $currency = commerce_currency_load($currency_code);
716
717
  // Invoke the custom conversion callback if specified.
718
  if (!empty($currency['conversion_callback'])) {
719
    return $currency['conversion_callback']($amount, $currency_code, $target_currency_code);
720
  }
721
722
  $target_currency = commerce_currency_load($target_currency_code);
723
724
  // First multiply the amount to accommodate differences in decimals between
725
  // the source and target currencies.
726
  $exponent = $target_currency['decimals'] - $currency['decimals'];
727
  $amount *= pow(10, $exponent);
728
729
  return $amount * ($currency['conversion_rate'] / $target_currency['conversion_rate']);
730
}
731
732
/**
733
 * Converts a price amount to an integer value for storage in the database.
734
 *
735
 * @param $decimal
736
 *   The decimal amount to convert to a price amount.
737
 * @param $currency_code
738
 *   The currency code of the price whose decimals value will be used to
739
 *     multiply by the proper factor when converting the decimal amount.
740
 * @param $round
741
 *   Whether or not the return value should be rounded and cast to an integer;
742
 *     defaults to TRUE as necessary for standard price amount column storage.
743
 *
744
 * @return
745
 *   The appropriate price amount based on the currency's decimals value.
746
 */
747
function commerce_currency_decimal_to_amount($decimal, $currency_code, $round = TRUE) {
748
  static $factors;
749
750
  // If the divisor for this currency hasn't been calculated yet...
751
  if (empty($factors[$currency_code])) {
752
    // Load the currency and calculate its factor as a power of 10.
753
    $currency = commerce_currency_load($currency_code);
754
    $factors[$currency_code] = pow(10, $currency['decimals']);
755
  }
756
757
  // Ensure the amount has the proper number of decimal places for the currency.
758
  if ($round) {
759
    $decimal = commerce_currency_round($decimal, commerce_currency_load($currency_code));
760
    return (int) round($decimal * $factors[$currency_code]);
761
  }
762
  else {
763
    return $decimal * $factors[$currency_code];
764
  }
765
}
766
767
/**
768
 * Converts a price amount to a decimal value based on the currency.
769
 *
770
 * @param $amount
771
 *   The price amount to convert to a decimal value.
772
 * @param $currency_code
773
 *   The currency code of the price whose decimals value will be used to
774
 *     divide by the proper divisor when converting the amount.
775
 *
776
 * @return
777
 *   The decimal amount depending on the number of places the currency uses.
778
 */
779
function commerce_currency_amount_to_decimal($amount, $currency_code) {
780
  static $divisors;
781
782
  // If the divisor for this currency hasn't been calculated yet...
783
  if (empty($divisors[$currency_code])) {
784
    // Load the currency and calculate its divisor as a power of 10.
785
    $currency = commerce_currency_load($currency_code);
786
    $divisors[$currency_code] = pow(10, $currency['decimals']);
787
  }
788
789
  return $amount / $divisors[$currency_code];
790
}
791
792
/**
793
 * Returns an associative array of month names keyed by numeric representation.
794
 */
795
function commerce_months() {
796
  return array(
797
    '01' => t('January'),
798
    '02' => t('February'),
799
    '03' => t('March'),
800
    '04' => t('April'),
801
    '05' => t('May'),
802
    '06' => t('June'),
803
    '07' => t('July'),
804
    '08' => t('August'),
805
    '09' => t('September'),
806
    '10' => t('October'),
807
    '11' => t('November'),
808
    '12' => t('December'),
809
  );
810
}
811
812
/**
813
 * Returns an array of numerical comparison operators for use in Rules.
814
 */
815
function commerce_numeric_comparison_operator_options_list() {
816
  return drupal_map_assoc(array('<', '<=', '=', '>=', '>'));
817
}
818
819
/**
820
 * Returns an options list of round modes.
821
 */
822
function commerce_round_mode_options_list() {
823
  return array(
824
    COMMERCE_ROUND_NONE => t('Do not round at all'),
825
    COMMERCE_ROUND_HALF_UP => t('Round the half up'),
826
    COMMERCE_ROUND_HALF_DOWN => t('Round the half down'),
827
    COMMERCE_ROUND_HALF_EVEN => t('Round the half to the nearest even number'),
828
    COMMERCE_ROUND_HALF_ODD => t('Round the half to the nearest odd number'),
829
  );
830
}
831
832
/**
833
 * Rounds a number using the specified rounding mode.
834
 *
835
 * @param $round_mode
836
 *   The round mode specifying which direction to round the number.
837
 * @param $number
838
 *   The number to round.
839
 *
840
 * @return
841
 *   The number rounded based on the specified mode.
842
 *
843
 * @see commerce_round_mode_options_list()
844
 */
845
function commerce_round($round_mode, $number) {
846
  // Remember if this is a negative or positive number and make it positive.
847
  $negative = $number < 0;
848
  $number = abs($number);
849
850
  // Store the decimal value of the number.
851
  $decimal = $number - floor($number);
852
853
  // No need to round if there is no decimal value.
854
  if ($decimal == 0) {
855
    return $negative ? -$number : $number;
856
  }
857
858
  // Round it now according to the specified round mode.
859
  switch ($round_mode) {
860
    // PHP's round() function defaults to rounding the half up.
861
    case COMMERCE_ROUND_HALF_UP:
862
      $number = round($number);
863
      break;
864
865
    // PHP < 5.3.0 does not support rounding the half down, so we compare the
866
    // decimal value and use floor() / ceil() directly.
867
    case COMMERCE_ROUND_HALF_DOWN:
868
      if ($decimal <= .5) {
869
        $number = floor($number);
870
      }
871
      else {
872
        $number = ceil($number);
873
      }
874
      break;
875
876
    // PHP < 5.3.0 does not support rounding to the nearest even number, so we
877
    // determine it ourselves if the decimal is .5.
878
    case COMMERCE_ROUND_HALF_EVEN:
879
      if ($decimal == .5) {
880
        if (floor($number) % 2 == 0) {
881
          $number = floor($number);
882
        }
883
        else {
884
          $number = ceil($number);
885
        }
886
      }
887
      else {
888
        $number = round($number);
889
      }
890
      break;
891
892
    // PHP < 5.3.0 does not support rounding to the nearest odd number, so we
893
    // determine it ourselves if the decimal is .5.
894
    case COMMERCE_ROUND_HALF_ODD:
895
      if ($decimal == .5) {
896
        if (floor($number) % 2 == 0) {
897
          $number = ceil($number);
898
        }
899
        else {
900
          $number = floor($number);
901
        }
902
      }
903
      else {
904
        $number = round($number);
905
      }
906
      break;
907
908
    case COMMERCE_ROUND_NONE:
909
    default:
910
      break;
911
  }
912
913
  // Return the number preserving the initial negative / positive value.
914
  return $negative ? -$number : $number;
915
}
916
917
/**
918
 * Adds child elements to a SimpleXML element using the data provided.
919
 *
920
 * @param $simplexml_element
921
 *   The SimpleXML element that will be populated with children from the child
922
 *   data array. This should already be initialized with its container element.
923
 * @param $child_data
924
 *   The array of data. It can be of any depth, but it only provides support for
925
 *   child elements and their values - not element attributes. If an element can
926
 *   have multiple child elements with the same name, you cannot depend on a
927
 *   simple associative array because of key collision. You must instead include
928
 *   each child element as a value array in a numerically indexed array.
929
 */
930
function commerce_simplexml_add_children(&$simplexml_element, $child_data) {
931
  // Loop over all the child data...
932
  foreach ($child_data as $key => $value) {
933
    // If the current child is itself a container...
934
    if (is_array($value)) {
935
      // If it has a non-numeric key...
936
      if (!is_numeric($key)) {
937
        // Add a child element to the current element with the key as the name.
938
        $child_element = $simplexml_element->addChild("$key");
939
940
        // Add the value of this element to the child element.
941
        commerce_simplexml_add_children($child_element, $value);
942
      }
943
      else {
944
        // Otherwise assume we have multiple child elements of the same name and
945
        // pass through to add the child data from the current value array to
946
        // current element.
947
        commerce_simplexml_add_children($simplexml_element, $value);
948
      }
949
    }
950
    else {
951
      // Otherwise add the child element with its simple value.
952
      $simplexml_element->addChild("$key", "$value");
953
    }
954
  }
955
}
956
957
/**
958
 * Generic access control for Drupal Commerce entities.
959
 *
960
 * @param $op
961
 *   The operation being performed. One of 'view', 'update', 'create' or
962
 *   'delete'.
963
 * @param $entity
964
 *   Optionally an entity to check access for. If no entity is given, it will be
965
 *   determined whether access is allowed for all entities of the given type.
966
 * @param $account
967
 *   The user to check for. Leave it to NULL to check for the global user.
968
 * @param $entity_type
969
 *   The entity type of the entity to check for.
970
 *
971
 * @see entity_access()
972
 */
973
function commerce_entity_access($op, $entity, $account, $entity_type) {
974
  global $user;
975
  $account = isset($account) ? $account : $user;
976
977
  $entity_info = entity_get_info($entity_type);
978
979
  if ($op == 'view') {
980
    if (isset($entity)) {
981
      // When trying to figure out access to an entity, query the base table using
982
      // our access control tag.
983
      if (!empty($entity_info['access arguments']['access tag']) && module_implements('query_' . $entity_info['access arguments']['access tag'] . '_alter')) {
984
        $query = db_select($entity_info['base table']);
985
        $query->addExpression('1');
986
        return (bool) $query
987
          ->addTag($entity_info['access arguments']['access tag'])
988
          ->addMetaData('account', $account)
989
          ->condition($entity_info['entity keys']['id'], $entity->{$entity_info['entity keys']['id']})
990
          ->range(0, 1)
991
          ->execute()
992
          ->fetchField();
993
      }
994
      else {
995
        return TRUE;
996
      }
997
    }
998
    else {
999
      return user_access('view any ' . $entity_type . ' entity', $account);
1000
    }
1001
  }
1002
  else {
1003
    // First grant access to the entity for the specified operation if no other
1004
    // module denies it and at least one other module says to grant access.
1005
    $access_results = module_invoke_all('commerce_entity_access', $op, $entity, $account, $entity_type);
1006
1007
    if (in_array(FALSE, $access_results, TRUE)) {
1008
      return FALSE;
1009
    }
1010
    elseif (in_array(TRUE, $access_results, TRUE)) {
1011
      return TRUE;
1012
    }
1013
1014
    // Grant generic administrator level access.
1015
    if (user_access('administer ' . $entity_type . ' entities', $account)) {
1016
      return TRUE;
1017
    }
1018
1019
    // Grant access based on entity type and bundle specific permissions with
1020
    // special handling for the create operation since the entity passed in will
1021
    // be initialized without ownership.
1022
    if ($op == 'create') {
1023
      // Assuming an entity was passed in and we know its bundle key, perform
1024
      // the entity type and bundle-level access checks.
1025
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
1026
        return user_access('create ' . $entity_type . ' entities', $account) || user_access('create ' . $entity_type . ' entities of bundle ' . $entity->{$entity_info['entity keys']['bundle']}, $account);
1027
      }
1028
      else {
1029
        // Otherwise perform an entity type-level access check.
1030
        return user_access('create ' . $entity_type . ' entities', $account);
1031
      }
1032
    }
1033
    else {
1034
      // Next perform checks for the edit and delete operations. Begin by
1035
      // extracting the bundle name from the entity if available.
1036
      $bundle_name = '';
1037
1038
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
1039
        $bundle_name = $entity->{$entity_info['entity keys']['bundle']};
1040
      }
1041
1042
      // For the edit and delete operations, first perform the entity type and
1043
      // bundle-level access check for any entity.
1044
      if (user_access('edit any ' . $entity_type . ' entity', $account) ||
1045
        user_access('edit any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {
1046
        return TRUE;
1047
      }
1048
1049
      // Then check an authenticated user's access to edit his own entities.
1050
      if ($account->uid && !empty($entity_info['access arguments']['user key']) && isset($entity->{$entity_info['access arguments']['user key']}) && $entity->{$entity_info['access arguments']['user key']} == $account->uid) {
1051
        if (user_access('edit own ' . $entity_type . ' entities', $account) ||
1052
          user_access('edit own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {
1053
          return TRUE;
1054
        }
1055
      }
1056
    }
1057
  }
1058
1059
  return FALSE;
1060
}
1061
1062
/**
1063
 * Return permission names for a given entity type.
1064
 */
1065
function commerce_entity_access_permissions($entity_type) {
1066
  $entity_info = entity_get_info($entity_type);
1067
  $labels = $entity_info['permission labels'];
1068
1069
  $permissions = array();
1070
1071
  // General 'administer' permission.
1072
  $permissions['administer ' . $entity_type . ' entities'] = array(
1073
    'title' => t('Administer @entity_type', array('@entity_type' => $labels['plural'])),
1074
    'description' => t('Allows users to perform any action on @entity_type.', array('@entity_type' => $labels['plural'])),
1075
    'restrict access' => TRUE,
1076
  );
1077
1078
  // Generic create and edit permissions.
1079
  $permissions['create ' . $entity_type . ' entities'] = array(
1080
    'title' => t('Create @entity_type of any type', array('@entity_type' => $labels['plural'])),
1081
  );
1082
  if (!empty($entity_info['access arguments']['user key'])) {
1083
    $permissions['edit own ' . $entity_type . ' entities'] = array(
1084
      'title' => t('Edit own @entity_type of any type', array('@entity_type' => $labels['plural'])),
1085
    );
1086
  }
1087
  $permissions['edit any ' . $entity_type . ' entity'] = array(
1088
    'title' => t('Edit any @entity_type of any type', array('@entity_type' => $labels['singular'])),
1089
    'restrict access' => TRUE,
1090
  );
1091
  if (!empty($entity_info['access arguments']['user key'])) {
1092
    $permissions['view own ' . $entity_type . ' entities'] = array(
1093
      'title' => t('View own @entity_type of any type', array('@entity_type' => $labels['plural'])),
1094
    );
1095
  }
1096
  $permissions['view any ' . $entity_type . ' entity'] = array(
1097
    'title' => t('View any @entity_type of any type', array('@entity_type' => $labels['singular'])),
1098
    'restrict access' => TRUE,
1099
  );
1100
1101
  // Per-bundle create and edit permissions.
1102
  if (!empty($entity_info['entity keys']['bundle'])) {
1103
    foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
1104
      $permissions['create ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1105
        'title' => t('Create %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1106
      );
1107
      if (!empty($entity_info['access arguments']['user key'])) {
1108
        $permissions['edit own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1109
          'title' => t('Edit own %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1110
        );
1111
      }
1112
      $permissions['edit any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
1113
        'title' => t('Edit any %bundle @entity_type', array('@entity_type' => $labels['singular'], '%bundle' => $bundle_info['label'])),
1114
        'restrict access' => TRUE,
1115
      );
1116
      if (!empty($entity_info['access arguments']['user key'])) {
1117
        $permissions['view own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1118
          'title' => t('View own %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1119
        );
1120
      }
1121
      $permissions['view any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
1122
        'title' => t('View any %bundle @entity_type', array('@entity_type' => $labels['singular'], '%bundle' => $bundle_info['label'])),
1123
        'restrict access' => TRUE,
1124
      );
1125
    }
1126
  }
1127
1128
  return $permissions;
1129
}
1130
1131
/**
1132
 * Generic implementation of hook_query_alter() for Drupal Commerce entities.
1133
 */
1134
function commerce_entity_access_query_alter($query, $entity_type, $base_table = NULL, $account = NULL) {
1135
  global $user;
1136
1137
  // Read the account from the query if available or default to the current user.
1138
  if (!isset($account) && !$account = $query->getMetaData('account')) {
1139
    $account = $user;
1140
  }
1141
1142
  // Read the base table from the query if available or default to the first
1143
  // table in the query's tables array.
1144
  if (!isset($base_table) && !$base_table = $query->getMetaData('base_table')) {
1145
    // Assume that the base table is the first table if not set. It will result
1146
    // in an invalid query if the first table is not the table we expect,
1147
    // forcing the caller to actually properly pass a base table in that case.
1148
    $tables = &$query->getTables();
1149
    reset($tables);
1150
    $base_table = key($tables);
1151
  }
1152
1153
  // Do not apply any conditions for users with administrative view permissions.
1154
  if (user_access('administer ' . $entity_type . ' entities', $account)
1155
    || user_access('view any ' . $entity_type . ' entity', $account)) {
1156
    return;
1157
  }
1158
1159
  // Get the entity type info array for the current access check and prepare a
1160
  // conditions object.
1161
  $entity_info = entity_get_info($entity_type);
1162
  $conditions = db_or();
1163
1164
  // Perform bundle specific permission checks for the specified entity type.
1165
  $really_restricted = FALSE;
1166
1167
  // Loop over every possible bundle for the given entity type.
1168
  foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
1169
    // If the user has access to view entities of the current bundle...
1170
    if (user_access('view any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {
1171
      // Add a condition granting access if the entity specified by the view
1172
      // query is of the same bundle.
1173
      $conditions->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name);
1174
    }
1175
    elseif ($account->uid && !empty($entity_info['access arguments']['user key']) && user_access('view own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {
1176
      // Otherwise if an authenticated user has access to view his own entities
1177
      // of the current bundle and the given entity type has a user ownership key...
1178
      $really_restricted = TRUE;
1179
1180
      // Add an AND condition group that grants access if the entity specified
1181
      // by the view query matches the same bundle and belongs to the user.
1182
      $conditions->condition(db_and()
1183
        ->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name)
1184
        ->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid)
1185
      );
1186
    }
1187
    else {
1188
      $really_restricted = TRUE;
1189
    }
1190
  }
1191
1192
  // No further conditions need to be added to the query if we determined above
1193
  // that the user has an administrative view permission for any entity of the
1194
  // type and bundles represented by the query.
1195
  if (!$really_restricted) {
1196
    return;
1197
  }
1198
1199
  // If the given entity type has a user ownership key...
1200
  if (!empty($entity_info['access arguments']['user key'])) {
1201
    // Perform 'view own' access control for the entity in the query if the user
1202
    // is authenticated.
1203
    if ($account->uid && user_access('view own ' . $entity_type . ' entities', $account)) {
1204
      $conditions->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid);
1205
    }
1206
  }
1207
1208
  // Prepare an array of condition alter hooks to invoke and an array of context
1209
  // data for the current query.
1210
  $hooks = array(
1211
    'commerce_entity_access_condition_' . $entity_type,
1212
    'commerce_entity_access_condition'
1213
  );
1214
1215
  $context = array(
1216
    'account' => $account,
1217
    'entity_type' => $entity_type,
1218
    'base_table' => $base_table
1219
  );
1220
1221
  // Allow other modules to add conditions to the array as necessary.
1222
  drupal_alter($hooks, $conditions, $context);
1223
1224
  // If we have more than one condition based on the entity access permissions
1225
  // and any hook implementations...
1226
  if (count($conditions)) {
1227
    // Add the conditions to the query.
1228
    $query->condition($conditions);
1229
  }
1230
  else {
1231
    // Otherwise, since we don't have any possible conditions to match against,
1232
    // we falsify this query. View checks are access grants, not access denials.
1233
    $query->where('1 = 0');
1234
  }
1235
}
1236
1237
/**
1238
 * Ensures an options list limit value is either empty or a positive integer.
1239
 */
1240
function commerce_options_list_limit_validate($element, &$form_state, $form) {
1241
  if (!empty($element['#value']) && (!is_numeric($element['#value']) || $element['#value'] < 1)) {
1242
    form_error($element, t('Use a positive number for the options list limit or else leave it blank for no limit.'));
1243
  }
1244
}