Projet

Général

Profil

Paste
Télécharger (46,7 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / commerce / commerce.module @ 87dbc3bf

1
<?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_field_map() as $field_name => $field_stub) {
99
    if ($field_stub['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_stub['bundles']))) {
103
        $field = field_info_field($field_name);
104
        $fields[$field_name] = $field;
105
      }
106
    }
107
  }
108

    
109
  return $fields;
110
}
111

    
112
/**
113
 * Deletes a reference to another entity from an entity with a reference field.
114
 *
115
 * @param $entity
116
 *   The entity that contains the reference field.
117
 * @param $field_name
118
 *   The name of the entity reference field.
119
 * @param $col_name
120
 *   The name of the column in the field's schema containing the referenced
121
 *   entity's ID.
122
 * @param $ref_entity_id
123
 *   The ID of the entity to delete from the reference field.
124
 */
125
function commerce_entity_reference_delete($entity, $field_name, $col_name, $ref_entity_id) {
126
  // Exit now if the entity does not have the expected field.
127
  if (empty($entity->{$field_name})) {
128
    return;
129
  }
130

    
131
  // Loop over each of the field's items in every language.
132
  foreach ($entity->{$field_name} as $langcode => $items) {
133
    $rekey = FALSE;
134
    foreach ($items as $delta => $item) {
135
      // If the item references the specified entity...
136
      if (!empty($item[$col_name]) && $item[$col_name] == $ref_entity_id) {
137
        // Unset the reference.
138
        unset($entity->{$field_name}[$langcode][$delta]);
139
        $rekey = TRUE;
140
      }
141
    }
142

    
143
    if ($rekey) {
144
      // Rekey the field items if necessary.
145
      $entity->{$field_name}[$langcode] = array_values($entity->{$field_name}[$langcode]);
146

    
147
      // If the reference field is empty, wipe its data altogether.
148
      if (count($entity->{$field_name}[$langcode]) == 0) {
149
        unset($entity->{$field_name}[$langcode]);
150
      }
151
    }
152
  }
153
}
154

    
155
/**
156
 * Attempts to directly activate a field that was disabled due to its module
157
 * being disabled.
158
 *
159
 * The normal API function for updating fields, field_update_field(), will not
160
 * work on disabled fields. As a workaround, this function directly updates the
161
 * database, but it is up to the caller to clear the cache.
162
 *
163
 * @param $field_name
164
 *   The name of the field to activate.
165
 *
166
 * @return
167
 *   Boolean indicating whether or not the field was activated.
168
 */
169
function commerce_activate_field($field_name) {
170
  // Set it to active via a query because field_update_field() does
171
  // not work on inactive fields.
172
  $updated = db_update('field_config')
173
    ->fields(array('active' => 1))
174
    ->condition('field_name', $field_name, '=')
175
    ->condition('deleted', 0, '=')
176
    ->execute();
177

    
178
  return !empty($updated) ? TRUE : FALSE;
179
}
180

    
181
/**
182
 * Enables and deletes fields of the specified type.
183
 *
184
 * @param $type
185
 *   The type of fields to enable and delete.
186
 *
187
 * @see commerce_delete_field()
188
 */
189
function commerce_delete_fields($type) {
190
  // Read the fields for any active or inactive field of the specified type.
191
  foreach (field_read_fields(array('type' => $type), array('include_inactive' => TRUE)) as $field_name => $field) {
192
    commerce_delete_field($field_name);
193
  }
194
}
195

    
196
/**
197
 * Enables and deletes the specified field.
198
 *
199
 * The normal API function for deleting fields, field_delete_field(), will not
200
 * work on disabled fields. As a workaround, this function first activates the
201
 * fields of the specified type and then deletes them.
202
 *
203
 * @param $field_name
204
 *   The name of the field to enable and delete.
205
 */
206
function commerce_delete_field($field_name) {
207
  // In case the field is inactive, first activate it and clear the field cache.
208
  if (commerce_activate_field($field_name)) {
209
    field_cache_clear();
210
  }
211

    
212
  // Delete the field.
213
  field_delete_field($field_name);
214
}
215

    
216
/**
217
 * Deletes any field instance attached to entities of the specified type,
218
 * regardless of whether or not the field is active.
219
 *
220
 * @param $entity_type
221
 *   The type of entity whose fields should be deleted.
222
 * @param $bundle
223
 *   Optionally limit instance deletion to a specific bundle of the specified
224
 *   entity type.
225
 */
226
function commerce_delete_instances($entity_type, $bundle = NULL) {
227
  // Prepare a parameters array to load the specified instances.
228
  $params = array(
229
    'entity_type' => $entity_type,
230
  );
231

    
232
  if (!empty($bundle)) {
233
    $params['bundle'] = $bundle;
234
    // Delete this bundle's field bundle settings.
235
    variable_del('field_bundle_settings_' . $entity_type . '__' . $bundle);
236
  }
237
  else {
238
    // Delete all field bundle settings for this entity type.
239
    db_delete('variable')
240
      ->condition('name', db_like('field_bundle_settings_' . $entity_type . '__') . '%', 'LIKE')
241
      ->execute();
242
  }
243

    
244
  // Read and delete the matching field instances.
245
  foreach (field_read_instances($params, array('include_inactive' => TRUE)) as $instance) {
246
    commerce_delete_instance($instance);
247
  }
248
}
249

    
250
/**
251
 * Deletes the specified instance and handles field cleanup manually in case the
252
 * instance is of a disabled field.
253
 *
254
 * @param $instance
255
 *   The field instance info array to be deleted.
256
 */
257
function commerce_delete_instance($instance) {
258
  // First activate the instance's field if necessary.
259
  $field_name = $instance['field_name'];
260
  $activated = commerce_activate_field($field_name);
261

    
262
  // Clear the field cache if we just activated the field.
263
  if ($activated) {
264
    field_cache_clear();
265
  }
266

    
267
  // Then delete the instance.
268
  field_delete_instance($instance, FALSE);
269

    
270
  // Now check to see if there are any other instances of the field left.
271
  $field = field_info_field($field_name);
272

    
273
  if (count($field['bundles']) == 0) {
274
    field_delete_field($field_name);
275
  }
276
  elseif ($activated) {
277
    // If there are remaining instances but the field was originally disabled,
278
    // disabled it again now.
279
    $field['active'] = 0;
280
    field_update_field($field);
281
  }
282
}
283

    
284
/**
285
 * Makes any required form elements in a form unrequired.
286
 *
287
 * @param $form
288
 *   The form array to search for required elements.
289
 */
290
function commerce_unrequire_form_elements(&$form) {
291
  array_walk_recursive($form, '_commerce_unrequire_element');
292
}
293

    
294
/**
295
 * array_walk_recursive callback: makes an individual element unrequired.
296
 *
297
 * @param &$value
298
 *   The value of the form array being walked.
299
 * @param $key
300
 *   The key of the form array corresponding to the present value.
301
 */
302
function _commerce_unrequire_element(&$value, $key) {
303
  if ($key === '#required') {
304
    $value = FALSE;
305
  }
306
}
307

    
308
/**
309
 * Returns the callback for a form ID as defined by hook_forms().
310
 *
311
 * @param $form_id
312
 *   The form ID to find the callback for.
313
 * @return
314
 *   A string containing the form's callback function name.
315
 *
316
 * @see drupal_retrieve_form()
317
 * @see hook_forms()
318
 */
319
function commerce_form_callback($form_id, $form_state) {
320
  // If a function named after the $form_id does not exist, look for its
321
  // definition in hook_forms().
322
  if (!function_exists($form_id)) {
323
    $forms = &drupal_static(__FUNCTION__);
324

    
325
    // In cases where many form_ids need to share a central builder function,
326
    // such as the product editing form, modules can implement hook_forms(). It
327
    // maps one or more form_ids to the correct constructor functions.
328
    if (!isset($forms) || !isset($forms[$form_id])) {
329
      $forms = module_invoke_all('forms', $form_id, $form_state['build_info']['args']);
330
    }
331

    
332
    if (isset($forms[$form_id]['callback'])) {
333
      return $forms[$form_id]['callback'];
334
    }
335
  }
336

    
337
  return $form_id;
338
}
339

    
340
/**
341
 * Renders a View for display in some other element.
342
 *
343
 * @param $view_key
344
 *   The ID of the View to embed.
345
 * @param $display_id
346
 *   The ID of the display of the View that will actually be rendered.
347
 * @param $arguments
348
 *   An array of arguments to pass to the View.
349
 * @param $override_url
350
 *   A url that overrides the url of the current view.
351
 *
352
 * @return
353
 *   The rendered output of the chosen View display.
354
 */
355
function commerce_embed_view($view_id, $display_id, $arguments, $override_url = '') {
356
  // Load the specified View.
357
  $view = views_get_view($view_id);
358
  $view->set_display($display_id);
359

    
360
  // Set the specific arguments passed in.
361
  $view->set_arguments($arguments);
362

    
363
  // Override the view url, if an override was provided.
364
  if (!empty($override_url)) {
365
    $view->override_url = $override_url;
366
  }
367

    
368
  // Prepare and execute the View query.
369
  $view->pre_execute();
370
  $view->execute();
371

    
372
  // Return the rendered View.
373
  return $view->render();
374
}
375

    
376
/**
377
 * Returns the e-mail address from which to send commerce related e-mails.
378
 *
379
 * Currently this is just using the site's e-mail address, but this may be
380
 * updated to use a specific e-mail address when we add a settings form for the
381
 * store's physical address and contact information.
382
 */
383
function commerce_email_from() {
384
  return variable_get('site_mail', ini_get('sendmail_from'));
385
}
386

    
387
/**
388
 * Translate a data structure using i18n_string, if available.
389
 *
390
 * @param $type
391
 *   The i18n object type.
392
 * @param $object
393
 *   The object or array to translate.
394
 * @param $options
395
 *   An array of options passed along to i18n.
396
 *
397
 * @return
398
 *   The translated data structure if i18_string is available, the original
399
 *   otherwise.
400
 *
401
 * @see i18n_string_object_translate()
402
 */
403
function commerce_i18n_object($type, $object, $options = array()) {
404
  // Clone the object, to ensure the original remains untouched.
405
  if (is_object($object)) {
406
    $object = clone $object;
407
  }
408

    
409
  if (module_exists('i18n_string')) {
410
    return i18n_string_object_translate($type, $object, $options);
411
  }
412
  else {
413
    return $object;
414
  }
415
}
416

    
417
/**
418
 * Implements hook_i18n_string_info().
419
 */
420
function commerce_i18n_string_info() {
421
  $groups['commerce'] = array(
422
    'title' => t('Drupal Commerce'),
423
    'format' => TRUE,
424
  );
425
  return $groups;
426
}
427

    
428
/**
429
 * Translate a string using i18n_string, if available.
430
 *
431
 * @param $name
432
 *   Textgroup and context glued with ':'.
433
 * @param $default
434
 *   String in default language. Default language may or may not be English.
435
 * @param $options
436
 *   An associative array of additional options, with the following keys:
437
 *   - langcode: the language code to translate to a language other than what is
438
 *     used to display the page; defaults to the current language
439
 *   - filter: filtering callback to apply to the translated string only
440
 *   - format: input format to apply to the translated string only
441
 *   - callback: callback to apply to the result (both to the translated or
442
 *     untranslated string)
443
 *   - update: whether to update source table; defaults to FALSE
444
 *   - translate: whether to return a translation; defaults to TRUE
445
 *
446
 * @return
447
 *   The translated string if i18n_string is available, the original otherwise.
448
 *
449
 * @see i18n_string()
450
 */
451
function commerce_i18n_string($name, $default, $options = array()) {
452
  if (module_exists('i18n_string')) {
453
    $result = i18n_string($name, $default, $options);
454
  }
455
  else {
456
    $result = $default;
457
    $options += array(
458
      'format' => NULL,
459
      'sanitize' => FALSE,
460
    );
461
    if ($options['sanitize']) {
462
      $result = check_markup($result, $options['format']);
463
    }
464
  }
465

    
466
  return $result;
467
}
468

    
469
/**
470
 * Returns the currency code of the site's default currency.
471
 */
472
function commerce_default_currency() {
473
  return variable_get('commerce_default_currency', 'USD');
474
}
475

    
476
/**
477
 * Returns a single currency array.
478
 *
479
 * @param $currency_code
480
 *   The code of the currency to return or NULL to return the default currency.
481
 *
482
 * @return
483
 *   The specified currency array or FALSE if it does not exist.
484
 */
485
function commerce_currency_load($currency_code = NULL) {
486
  $currencies = commerce_currencies();
487

    
488
  // Check to see if we should return the default currency.
489
  if (empty($currency_code)) {
490
    $currency_code = commerce_default_currency();
491
  }
492

    
493
  return isset($currencies[$currency_code]) ? $currencies[$currency_code] : FALSE;
494
}
495

    
496
/**
497
 * Returns an array of all available currencies.
498
 *
499
 * @param $enabled
500
 *   Boolean indicating whether or not to return only enabled currencies.
501
 * @param $reset
502
 *   Boolean indicating whether or not the cache should be reset before currency
503
 *     data is loaded and returned.
504
 *
505
 * @return
506
 *   An array of altered currency arrays keyed by the currency code.
507
 */
508
function commerce_currencies($enabled = FALSE, $reset = FALSE) {
509
  global $language;
510
  $currencies = &drupal_static(__FUNCTION__);
511

    
512
  // If there is no static cache for currencies yet or a reset was specified...
513
  if (!isset($currencies) || $reset) {
514
    // First attempt to load currency data from the cache if we simply didn't
515
    // have it statically cached and a reset hasn't been specified.
516
    if (!$reset && $currencies_cached = cache_get('commerce_currencies:' . $language->language)) {
517
      $currencies['all'] = $currencies_cached->data;
518
    }
519
    else {
520
      // Otherwise we'll load currency definitions afresh from enabled modules.
521
      // Begin by establishing some default values for currencies.
522
      $defaults = array(
523
        'symbol' => '',
524
        'minor_unit' => '',
525
        'decimals' => 2,
526
        'rounding_step' => 0,
527
        'thousands_separator' => ',',
528
        'decimal_separator' => '.',
529
        'symbol_placement' => 'hidden',
530
        'symbol_spacer' => ' ',
531
        'code_placement' => 'after',
532
        'code_spacer' => ' ',
533
        'format_callback' => '',
534
        'conversion_callback' => '',
535
        'conversion_rate' => 1,
536
      );
537

    
538
      // Include the currency file and invoke the currency info hook.
539
      module_load_include('inc', 'commerce', 'includes/commerce.currency');
540
      $currencies['all'] = module_invoke_all('commerce_currency_info');
541
      drupal_alter('commerce_currency_info', $currencies['all'], $language->language);
542

    
543
      // Add default values if they don't exist.
544
      foreach ($currencies['all'] as $currency_code => $currency) {
545
        $currencies['all'][$currency_code] = array_merge($defaults, $currency);
546
      }
547

    
548
      // Sort the currencies
549
      ksort($currencies['all']);
550

    
551
      cache_set('commerce_currencies:' . $language->language, $currencies['all']);
552
    }
553

    
554
    // Form an array of enabled currencies based on the variable set by the
555
    // checkboxes element on the currency settings form.
556
    $enabled_currencies = array_diff(array_values(variable_get('commerce_enabled_currencies', array('USD' => 'USD'))), array(0));
557
    $currencies['enabled'] = array_intersect_key($currencies['all'], drupal_map_assoc($enabled_currencies));
558
  }
559

    
560
  return $enabled ? $currencies['enabled'] : $currencies['all'];
561
}
562

    
563
/**
564
 * Returns an associative array of the specified currency codes.
565
 *
566
 * @param $enabled
567
 *   Boolean indicating whether or not to include only enabled currencies.
568
 */
569
function commerce_currency_get_code($enabled = FALSE) {
570
  return drupal_map_assoc(array_keys(commerce_currencies($enabled)));
571
}
572

    
573
/**
574
 * Wraps commerce_currency_get_code() for use by the Entity module.
575
 */
576
function commerce_currency_code_options_list() {
577
  return commerce_currency_get_code(TRUE);
578
}
579

    
580
/**
581
 * Returns the symbol of any or all currencies.
582
 *
583
 * @param $code
584
 *   Optional parameter specifying the code of the currency whose symbol to return.
585
 *
586
 * @return
587
 *   Either an array of all currency symbols keyed by the currency code or a
588
 *     string containing the symbol for the specified currency. If a currency is
589
 *     specified that does not exist, this function returns FALSE.
590
 */
591
function commerce_currency_get_symbol($currency_code = NULL) {
592
  $currencies = commerce_currencies();
593

    
594
  // Return a specific currency symbol if specified.
595
  if (!empty($currency_code)) {
596
    if (isset($currencies[$currency_code])) {
597
      return $currencies[$currency_code]['symbol'];
598
    }
599
    else {
600
      return FALSE;
601
    }
602
  }
603

    
604
  // Otherwise turn the array values into the type name only.
605
  foreach ($currencies as $currency_code => $currency) {
606
    $currencies[$currency_code] = $currency['symbol'];
607
  }
608

    
609
  return $currencies;
610
}
611

    
612
/**
613
 * Formats a price for a particular currency.
614
 *
615
 * @param $amount
616
 *   A numeric price amount value.
617
 * @param $currency_code
618
 *   The three character code of the currency.
619
 * @param $object
620
 *   When present, the object to which the price is attached.
621
 * @param $convert
622
 *   Boolean indicating whether or not the amount needs to be converted to a
623
 *   decimal price amount when formatting.
624
 *
625
 * @return
626
 *   A fully formatted currency.
627
 */
628
function commerce_currency_format($amount, $currency_code, $object = NULL, $convert = TRUE) {
629
  // First load the currency array.
630
  $currency = commerce_currency_load($currency_code);
631

    
632
  // Then convert the price amount to the currency's major unit decimal value.
633
  if ($convert == TRUE) {
634
    $amount = commerce_currency_amount_to_decimal($amount, $currency_code);
635
  }
636

    
637
  // Invoke the custom format callback if specified.
638
  if (!empty($currency['format_callback'])) {
639
    return $currency['format_callback']($amount, $currency, $object);
640
  }
641

    
642
  // Format the price as a number.
643
  $price = number_format(commerce_currency_round(abs($amount), $currency), $currency['decimals'], $currency['decimal_separator'], $currency['thousands_separator']);
644

    
645
  // Establish the replacement values to format this price for its currency.
646
  $replacements = array(
647
    '@code_before' => $currency['code_placement'] == 'before' ? $currency['code'] : '',
648
    '@symbol_before' => $currency['symbol_placement'] == 'before' ? $currency['symbol'] : '',
649
    '@price' => $price,
650
    '@symbol_after' => $currency['symbol_placement'] == 'after' ? $currency['symbol'] : '',
651
    '@code_after' => $currency['code_placement'] == 'after' ? $currency['code'] : '',
652
    '@negative' => $amount < 0 ? '-' : '',
653
    '@symbol_spacer' => $currency['symbol_spacer'],
654
    '@code_spacer' => $currency['code_spacer'],
655
  );
656

    
657
  return trim(t('@code_before@code_spacer@negative@symbol_before@price@symbol_spacer@symbol_after@code_spacer@code_after', $replacements));
658
}
659

    
660
/**
661
 * Rounds a price amount for the specified currency.
662
 *
663
 * Rounding of the minor unit with a currency specific step size. For example,
664
 * Swiss Francs are rounded using a step size of 0.05. This means a price of
665
 * 10.93 is converted to 10.95.
666
 *
667
 * @param $amount
668
 *   The numeric amount value of the price to be rounded.
669
 * @param $currency
670
 *   The currency array containing the rounding information pertinent to this
671
 *     price. Specifically, this function looks for the 'rounding_step' property
672
 *     for the step size to round to, supporting '0.05' and '0.02'. If the value
673
 *     is 0, this function performs normal rounding to the nearest supported
674
 *     decimal value.
675
 *
676
 * @return
677
 *   The rounded numeric amount value for the price.
678
 */
679
function commerce_currency_round($amount, $currency) {
680
  if (!$currency['rounding_step']) {
681
    return round($amount, $currency['decimals']);
682
  }
683

    
684
  $modifier = 1 / $currency['rounding_step'];
685

    
686
  return round($amount * $modifier) / $modifier;
687
}
688

    
689
/**
690
 * Converts a price amount from a currency to the target currency based on the
691
 *   current currency conversion rates.
692
 *
693
 * The Commerce module establishes a default conversion rate for every currency
694
 * as 1, so without any additional information there will be a 1:1 conversion
695
 * from one currency to the next. Other modules can provide UI based or web
696
 * service based alterations to the conversion rate of the defined currencies as
697
 * long as every rate is calculated relative to a single base currency. It does
698
 * not matter which currency is the base currency as long as the same one is
699
 * used for every rate calculation.
700
 *
701
 * To convert an amount from one currency to another, we simply take the amount
702
 * value and multiply it by the current currency's conversion rate divided by
703
 * the target currency's conversion rate.
704
 *
705
 * @param $amount
706
 *   The numeric amount value of the price to be rounded.
707
 * @param $currency_code
708
 *   The currency code for the current currency of the price.
709
 * @param $target_currency_code
710
 *   The currency code for the target currency of the price.
711
 *
712
 * @return
713
 *   The numeric amount value converted to its equivalent in the target currency.
714
 */
715
function commerce_currency_convert($amount, $currency_code, $target_currency_code) {
716
  $currency = commerce_currency_load($currency_code);
717

    
718
  // Invoke the custom conversion callback if specified.
719
  if (!empty($currency['conversion_callback'])) {
720
    return $currency['conversion_callback']($amount, $currency_code, $target_currency_code);
721
  }
722

    
723
  $target_currency = commerce_currency_load($target_currency_code);
724

    
725
  // First multiply the amount to accommodate differences in decimals between
726
  // the source and target currencies.
727
  $exponent = $target_currency['decimals'] - $currency['decimals'];
728
  $amount *= pow(10, $exponent);
729

    
730
  return $amount * ($currency['conversion_rate'] / $target_currency['conversion_rate']);
731
}
732

    
733
/**
734
 * Converts a price amount to an integer value for storage in the database.
735
 *
736
 * @param $decimal
737
 *   The decimal amount to convert to a price amount.
738
 * @param $currency_code
739
 *   The currency code of the price whose decimals value will be used to
740
 *     multiply by the proper factor when converting the decimal amount.
741
 * @param $round
742
 *   Whether or not the return value should be rounded and cast to an integer;
743
 *     defaults to TRUE as necessary for standard price amount column storage.
744
 *
745
 * @return
746
 *   The appropriate price amount based on the currency's decimals value.
747
 */
748
function commerce_currency_decimal_to_amount($decimal, $currency_code, $round = TRUE) {
749
  static $factors;
750

    
751
  // If the divisor for this currency hasn't been calculated yet...
752
  if (empty($factors[$currency_code])) {
753
    // Load the currency and calculate its factor as a power of 10.
754
    $currency = commerce_currency_load($currency_code);
755
    $factors[$currency_code] = pow(10, $currency['decimals']);
756
  }
757

    
758
  // Ensure the amount has the proper number of decimal places for the currency.
759
  if ($round) {
760
    $decimal = commerce_currency_round($decimal, commerce_currency_load($currency_code));
761
    return (int) round($decimal * $factors[$currency_code]);
762
  }
763
  else {
764
    return $decimal * $factors[$currency_code];
765
  }
766
}
767

    
768
/**
769
 * Converts a price amount to a decimal value based on the currency.
770
 *
771
 * @param $amount
772
 *   The price amount to convert to a decimal value.
773
 * @param $currency_code
774
 *   The currency code of the price whose decimals value will be used to
775
 *     divide by the proper divisor when converting the amount.
776
 *
777
 * @return
778
 *   The decimal amount depending on the number of places the currency uses.
779
 */
780
function commerce_currency_amount_to_decimal($amount, $currency_code) {
781
  static $divisors;
782

    
783
  // If the divisor for this currency hasn't been calculated yet...
784
  if (empty($divisors[$currency_code])) {
785
    // Load the currency and calculate its divisor as a power of 10.
786
    $currency = commerce_currency_load($currency_code);
787
    $divisors[$currency_code] = pow(10, $currency['decimals']);
788
  }
789

    
790
  return $amount / $divisors[$currency_code];
791
}
792

    
793
/**
794
 * Returns an associative array of month names keyed by numeric representation.
795
 */
796
function commerce_months() {
797
  return array(
798
    '01' => t('January'),
799
    '02' => t('February'),
800
    '03' => t('March'),
801
    '04' => t('April'),
802
    '05' => t('May'),
803
    '06' => t('June'),
804
    '07' => t('July'),
805
    '08' => t('August'),
806
    '09' => t('September'),
807
    '10' => t('October'),
808
    '11' => t('November'),
809
    '12' => t('December'),
810
  );
811
}
812

    
813
/**
814
 * Returns an array of numerical comparison operators for use in Rules.
815
 */
816
function commerce_numeric_comparison_operator_options_list() {
817
  return drupal_map_assoc(array('<', '<=', '=', '>=', '>'));
818
}
819

    
820
/**
821
 * Returns an options list of round modes.
822
 */
823
function commerce_round_mode_options_list() {
824
  return array(
825
    COMMERCE_ROUND_NONE => t('Do not round at all'),
826
    COMMERCE_ROUND_HALF_UP => t('Round the half up'),
827
    COMMERCE_ROUND_HALF_DOWN => t('Round the half down'),
828
    COMMERCE_ROUND_HALF_EVEN => t('Round the half to the nearest even number'),
829
    COMMERCE_ROUND_HALF_ODD => t('Round the half to the nearest odd number'),
830
  );
831
}
832

    
833
/**
834
 * Rounds a number using the specified rounding mode.
835
 *
836
 * @param $round_mode
837
 *   The round mode specifying which direction to round the number.
838
 * @param $number
839
 *   The number to round.
840
 *
841
 * @return
842
 *   The number rounded based on the specified mode.
843
 *
844
 * @see commerce_round_mode_options_list()
845
 */
846
function commerce_round($round_mode, $number) {
847
  // Remember if this is a negative or positive number and make it positive.
848
  $negative = $number < 0;
849
  $number = abs($number);
850

    
851
  // Store the decimal value of the number.
852
  $decimal = $number - floor($number);
853

    
854
  // No need to round if there is no decimal value.
855
  if ($decimal == 0) {
856
    return $negative ? -$number : $number;
857
  }
858

    
859
  // Round it now according to the specified round mode.
860
  switch ($round_mode) {
861
    // PHP's round() function defaults to rounding the half up.
862
    case COMMERCE_ROUND_HALF_UP:
863
      $number = round($number);
864
      break;
865

    
866
    // PHP < 5.3.0 does not support rounding the half down, so we compare the
867
    // decimal value and use floor() / ceil() directly.
868
    case COMMERCE_ROUND_HALF_DOWN:
869
      if ($decimal <= .5) {
870
        $number = floor($number);
871
      }
872
      else {
873
        $number = ceil($number);
874
      }
875
      break;
876

    
877
    // PHP < 5.3.0 does not support rounding to the nearest even number, so we
878
    // determine it ourselves if the decimal is .5.
879
    case COMMERCE_ROUND_HALF_EVEN:
880
      if ($decimal == .5) {
881
        if (floor($number) % 2 == 0) {
882
          $number = floor($number);
883
        }
884
        else {
885
          $number = ceil($number);
886
        }
887
      }
888
      else {
889
        $number = round($number);
890
      }
891
      break;
892

    
893
    // PHP < 5.3.0 does not support rounding to the nearest odd number, so we
894
    // determine it ourselves if the decimal is .5.
895
    case COMMERCE_ROUND_HALF_ODD:
896
      if ($decimal == .5) {
897
        if (floor($number) % 2 == 0) {
898
          $number = ceil($number);
899
        }
900
        else {
901
          $number = floor($number);
902
        }
903
      }
904
      else {
905
        $number = round($number);
906
      }
907
      break;
908

    
909
    case COMMERCE_ROUND_NONE:
910
    default:
911
      break;
912
  }
913

    
914
  // Return the number preserving the initial negative / positive value.
915
  return $negative ? -$number : $number;
916
}
917

    
918
/**
919
 * Adds child elements to a SimpleXML element using the data provided.
920
 *
921
 * @param $simplexml_element
922
 *   The SimpleXML element that will be populated with children from the child
923
 *   data array. This should already be initialized with its container element.
924
 * @param $child_data
925
 *   The array of data. It can be of any depth, but it only provides support for
926
 *   child elements and their values - not element attributes. If an element can
927
 *   have multiple child elements with the same name, you cannot depend on a
928
 *   simple associative array because of key collision. You must instead include
929
 *   each child element as a value array in a numerically indexed array.
930
 */
931
function commerce_simplexml_add_children(&$simplexml_element, $child_data) {
932
  // Loop over all the child data...
933
  foreach ($child_data as $key => $value) {
934
    // If the current child is itself a container...
935
    if (is_array($value)) {
936
      // If it has a non-numeric key...
937
      if (!is_numeric($key)) {
938
        // Add a child element to the current element with the key as the name.
939
        $child_element = $simplexml_element->addChild("$key");
940

    
941
        // Add the value of this element to the child element.
942
        commerce_simplexml_add_children($child_element, $value);
943
      }
944
      else {
945
        // Otherwise assume we have multiple child elements of the same name and
946
        // pass through to add the child data from the current value array to
947
        // current element.
948
        commerce_simplexml_add_children($simplexml_element, $value);
949
      }
950
    }
951
    else {
952
      // Otherwise add the child element with its simple value.
953
      $simplexml_element->addChild("$key", "$value");
954
    }
955
  }
956
}
957

    
958
/**
959
 * Generic access control for Drupal Commerce entities.
960
 *
961
 * @param $op
962
 *   The operation being performed. One of 'view', 'update', 'create' or
963
 *   'delete'.
964
 * @param $entity
965
 *   Optionally an entity to check access for. If no entity is given, it will be
966
 *   determined whether access is allowed for all entities of the given type.
967
 * @param $account
968
 *   The user to check for. Leave it to NULL to check for the global user.
969
 * @param $entity_type
970
 *   The entity type of the entity to check for.
971
 *
972
 * @see entity_access()
973
 */
974
function commerce_entity_access($op, $entity, $account, $entity_type) {
975
  global $user;
976
  $account = isset($account) ? $account : $user;
977

    
978
  $entity_info = entity_get_info($entity_type);
979

    
980
  if ($op == 'view') {
981
    if (isset($entity)) {
982
      // When trying to figure out access to an entity, query the base table using
983
      // our access control tag.
984
      if (!empty($entity_info['access arguments']['access tag']) && module_implements('query_' . $entity_info['access arguments']['access tag'] . '_alter')) {
985
        $query = db_select($entity_info['base table']);
986
        $query->addExpression('1');
987
        return (bool) $query
988
          ->addTag($entity_info['access arguments']['access tag'])
989
          ->addMetaData('account', $account)
990
          ->condition($entity_info['entity keys']['id'], $entity->{$entity_info['entity keys']['id']})
991
          ->range(0, 1)
992
          ->execute()
993
          ->fetchField();
994
      }
995
      else {
996
        return TRUE;
997
      }
998
    }
999
    else {
1000
      return user_access('view any ' . $entity_type . ' entity', $account);
1001
    }
1002
  }
1003
  else {
1004
    // First grant access to the entity for the specified operation if no other
1005
    // module denies it and at least one other module says to grant access.
1006
    $access_results = module_invoke_all('commerce_entity_access', $op, $entity, $account, $entity_type);
1007

    
1008
    if (in_array(FALSE, $access_results, TRUE)) {
1009
      return FALSE;
1010
    }
1011
    elseif (in_array(TRUE, $access_results, TRUE)) {
1012
      return TRUE;
1013
    }
1014

    
1015
    // Grant generic administrator level access.
1016
    if (user_access('administer ' . $entity_type . ' entities', $account)) {
1017
      return TRUE;
1018
    }
1019

    
1020
    // Grant access based on entity type and bundle specific permissions with
1021
    // special handling for the create operation since the entity passed in will
1022
    // be initialized without ownership.
1023
    if ($op == 'create') {
1024
      // Assuming an entity was passed in and we know its bundle key, perform
1025
      // the entity type and bundle-level access checks.
1026
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
1027
        return user_access('create ' . $entity_type . ' entities', $account) || user_access('create ' . $entity_type . ' entities of bundle ' . $entity->{$entity_info['entity keys']['bundle']}, $account);
1028
      }
1029
      else {
1030
        // Otherwise perform an entity type-level access check.
1031
        return user_access('create ' . $entity_type . ' entities', $account);
1032
      }
1033
    }
1034
    else {
1035
      // Next perform checks for the edit and delete operations. Begin by
1036
      // extracting the bundle name from the entity if available.
1037
      $bundle_name = '';
1038

    
1039
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
1040
        $bundle_name = $entity->{$entity_info['entity keys']['bundle']};
1041
      }
1042

    
1043
      // For the edit and delete operations, first perform the entity type and
1044
      // bundle-level access check for any entity.
1045
      if (user_access('edit any ' . $entity_type . ' entity', $account) ||
1046
        user_access('edit any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {
1047
        return TRUE;
1048
      }
1049

    
1050
      // Then check an authenticated user's access to edit his own entities.
1051
      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) {
1052
        if (user_access('edit own ' . $entity_type . ' entities', $account) ||
1053
          user_access('edit own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {
1054
          return TRUE;
1055
        }
1056
      }
1057
    }
1058
  }
1059

    
1060
  return FALSE;
1061
}
1062

    
1063
/**
1064
 * Return permission names for a given entity type.
1065
 */
1066
function commerce_entity_access_permissions($entity_type) {
1067
  $entity_info = entity_get_info($entity_type);
1068
  $labels = $entity_info['permission labels'];
1069

    
1070
  $permissions = array();
1071

    
1072
  // General 'administer' permission.
1073
  $permissions['administer ' . $entity_type . ' entities'] = array(
1074
    'title' => t('Administer @entity_type', array('@entity_type' => $labels['plural'])),
1075
    'description' => t('Allows users to perform any action on @entity_type.', array('@entity_type' => $labels['plural'])),
1076
    'restrict access' => TRUE,
1077
  );
1078

    
1079
  // Generic create and edit permissions.
1080
  $permissions['create ' . $entity_type . ' entities'] = array(
1081
    'title' => t('Create @entity_type of any type', array('@entity_type' => $labels['plural'])),
1082
  );
1083
  if (!empty($entity_info['access arguments']['user key'])) {
1084
    $permissions['edit own ' . $entity_type . ' entities'] = array(
1085
      'title' => t('Edit own @entity_type of any type', array('@entity_type' => $labels['plural'])),
1086
    );
1087
  }
1088
  $permissions['edit any ' . $entity_type . ' entity'] = array(
1089
    'title' => t('Edit any @entity_type of any type', array('@entity_type' => $labels['singular'])),
1090
    'restrict access' => TRUE,
1091
  );
1092
  if (!empty($entity_info['access arguments']['user key'])) {
1093
    $permissions['view own ' . $entity_type . ' entities'] = array(
1094
      'title' => t('View own @entity_type of any type', array('@entity_type' => $labels['plural'])),
1095
    );
1096
  }
1097
  $permissions['view any ' . $entity_type . ' entity'] = array(
1098
    'title' => t('View any @entity_type of any type', array('@entity_type' => $labels['singular'])),
1099
    'restrict access' => TRUE,
1100
  );
1101

    
1102
  // Per-bundle create and edit permissions.
1103
  if (!empty($entity_info['entity keys']['bundle'])) {
1104
    foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
1105
      $permissions['create ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1106
        'title' => t('Create %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1107
      );
1108
      if (!empty($entity_info['access arguments']['user key'])) {
1109
        $permissions['edit own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1110
          'title' => t('Edit own %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1111
        );
1112
      }
1113
      $permissions['edit any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
1114
        'title' => t('Edit any %bundle @entity_type', array('@entity_type' => $labels['singular'], '%bundle' => $bundle_info['label'])),
1115
        'restrict access' => TRUE,
1116
      );
1117
      if (!empty($entity_info['access arguments']['user key'])) {
1118
        $permissions['view own ' . $entity_type . ' entities of bundle ' . $bundle_name] = array(
1119
          'title' => t('View own %bundle @entity_type', array('@entity_type' => $labels['plural'], '%bundle' => $bundle_info['label'])),
1120
        );
1121
      }
1122
      $permissions['view any ' . $entity_type . ' entity of bundle ' . $bundle_name] = array(
1123
        'title' => t('View any %bundle @entity_type', array('@entity_type' => $labels['singular'], '%bundle' => $bundle_info['label'])),
1124
        'restrict access' => TRUE,
1125
      );
1126
    }
1127
  }
1128

    
1129
  return $permissions;
1130
}
1131

    
1132
/**
1133
 * Generic implementation of hook_query_alter() for Drupal Commerce entities.
1134
 */
1135
function commerce_entity_access_query_alter($query, $entity_type, $base_table = NULL, $account = NULL) {
1136
  global $user;
1137

    
1138
  // Read the account from the query if available or default to the current user.
1139
  if (!isset($account) && !$account = $query->getMetaData('account')) {
1140
    $account = $user;
1141
  }
1142

    
1143
  // Do not apply any conditions for users with administrative view permissions.
1144
  if (user_access('administer ' . $entity_type . ' entities', $account)
1145
    || user_access('view any ' . $entity_type . ' entity', $account)) {
1146
    return;
1147
  }
1148

    
1149
  // Get the entity type info array for the current access check and prepare a
1150
  // conditions object.
1151
  $entity_info = entity_get_info($entity_type);
1152

    
1153
  // If a base table wasn't specified, attempt to read it from the query if
1154
  // available, look for a table in the query's tables array that matches the
1155
  // base table of the given entity type, or just default to the first table.
1156
  if (!isset($base_table) && !$base_table = $query->getMetaData('base_table')) {
1157
    // Initialize the base table to the first table in the array. If a table can
1158
    // not be found that matches the entity type's base table, this will result
1159
    // in an invalid query if the first table is not the table we expect,
1160
    // forcing the caller to actually properly pass a base table in that case.
1161
    $tables = $query->getTables();
1162
    reset($tables);
1163
    $base_table = key($tables);
1164

    
1165
    foreach ($tables as $table_info) {
1166
      if (!($table_info instanceof SelectQueryInterface)) {
1167
        // If this table matches the entity type's base table, use its table
1168
        // alias as the base table for the purposes of bundle and ownership
1169
        // access checks.
1170
        if ($table_info['table'] == $entity_info['base table']) {
1171
          $base_table = $table_info['alias'];
1172
        }
1173
      }
1174
    }
1175
  }
1176

    
1177
  // Prepare an OR container for conditions. Conditions will be added that seek
1178
  // to grant access, meaning any particular type of permission check may grant
1179
  // access even if none of the others apply. At the end of this function, if no
1180
  // conditions have been added to the array, a condition will be added that
1181
  // always returns FALSE (1 = 0).
1182
  $conditions = db_or();
1183

    
1184
  // Perform bundle specific permission checks for the specified entity type.
1185
  // In the event that the user has permission to view every bundle of the given
1186
  // entity type, $really_restricted will remain FALSE, indicating that it is
1187
  // safe to exit this function without applying any additional conditions. If
1188
  // the user only had such permission for a subset of the defined bundles,
1189
  // conditions representing those access checks would still be added.
1190
  $really_restricted = FALSE;
1191

    
1192
  // Loop over every possible bundle for the given entity type.
1193
  foreach ($entity_info['bundles'] as $bundle_name => $bundle_info) {
1194
    // If the user has access to view entities of the current bundle...
1195
    if (user_access('view any ' . $entity_type . ' entity of bundle ' . $bundle_name, $account)) {
1196
      // Add a condition granting access if the entity specified by the view
1197
      // query is of the same bundle.
1198
      $conditions->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name);
1199
    }
1200
    elseif ($account->uid && !empty($entity_info['access arguments']['user key']) && user_access('view own ' . $entity_type . ' entities of bundle ' . $bundle_name, $account)) {
1201
      // Otherwise if an authenticated user has access to view his own entities
1202
      // of the current bundle and the given entity type has a user ownership key...
1203
      $really_restricted = TRUE;
1204

    
1205
      // Add an AND condition group that grants access if the entity specified
1206
      // by the view query matches the same bundle and belongs to the user.
1207
      $conditions->condition(db_and()
1208
        ->condition($base_table . '.' . $entity_info['entity keys']['bundle'], $bundle_name)
1209
        ->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid)
1210
      );
1211
    }
1212
    else {
1213
      $really_restricted = TRUE;
1214
    }
1215
  }
1216

    
1217
  // No further conditions need to be added to the query if we determined above
1218
  // that the user has an administrative view permission for any entity of the
1219
  // type and bundles represented by the query.
1220
  if (!$really_restricted) {
1221
    return;
1222
  }
1223

    
1224
  // If the given entity type has a user ownership key...
1225
  if (!empty($entity_info['access arguments']['user key'])) {
1226
    // Perform 'view own' access control for the entity in the query if the user
1227
    // is authenticated.
1228
    if ($account->uid && user_access('view own ' . $entity_type . ' entities', $account)) {
1229
      $conditions->condition($base_table . '.' . $entity_info['access arguments']['user key'], $account->uid);
1230
    }
1231
  }
1232

    
1233
  // Prepare an array of condition alter hooks to invoke and an array of context
1234
  // data for the current query.
1235
  $hooks = array(
1236
    'commerce_entity_access_condition_' . $entity_type,
1237
    'commerce_entity_access_condition'
1238
  );
1239

    
1240
  $context = array(
1241
    'account' => $account,
1242
    'entity_type' => $entity_type,
1243
    'base_table' => $base_table
1244
  );
1245

    
1246
  // Allow other modules to add conditions to the array as necessary.
1247
  drupal_alter($hooks, $conditions, $context);
1248

    
1249
  // If we have more than one condition based on the entity access permissions
1250
  // and any hook implementations...
1251
  if (count($conditions)) {
1252
    // Add the conditions to the query.
1253
    $query->condition($conditions);
1254
  }
1255
  else {
1256
    // Otherwise, since we don't have any possible conditions to match against,
1257
    // we falsify this query. View checks are access grants, not access denials.
1258
    $query->where('1 = 0');
1259
  }
1260
}
1261

    
1262
/**
1263
 * Ensures an options list limit value is either empty or a positive integer.
1264
 */
1265
function commerce_options_list_limit_validate($element, &$form_state, $form) {
1266
  if (!empty($element['#value']) && (!is_numeric($element['#value']) || $element['#value'] < 1)) {
1267
    form_error($element, t('Use a positive number for the options list limit or else leave it blank for no limit.'));
1268
  }
1269
}
1270

    
1271
/**
1272
 * Implements hook_theme_registry_alter().
1273
 *
1274
 * The Entity API theme function template_preprocess_entity() assumes the
1275
 * presence of a path key in an entity URI array, which isn't strictly required
1276
 * by Drupal core itself. Until this is fixed in the Entity API code, we replace
1277
 * that function with a Commerce specific version.
1278
 *
1279
 * @todo Remove when https://drupal.org/node/1934382 is resolved.
1280
 */
1281
function commerce_theme_registry_alter(&$theme_registry) {
1282
  // Copy the preprocess functions array and remove the core preprocess function
1283
  // along with the Entity API's and this module's.
1284
  $functions = drupal_map_assoc($theme_registry['entity']['preprocess functions']);
1285
  unset($functions['template_preprocess'], $functions['template_preprocess_entity'], $functions['commerce_preprocess_entity']);
1286

    
1287
  // Unshift the core preprocess function and the Commerce specific function so
1288
  // they appear at the beginning of the array in the desired order.
1289
  array_unshift($functions, 'template_preprocess', 'commerce_preprocess_entity');
1290

    
1291
  // Update the function list in the theme registry.
1292
  $theme_registry['entity']['preprocess functions'] = array_values($functions);
1293
}
1294

    
1295
/**
1296
 * Processes variables for entity.tpl.php, replacing template_preprocess_entity().
1297
 *
1298
 * @see commerce_theme_registry_alter()
1299
 * @todo Remove when https://drupal.org/node/1934382 is resolved.
1300
 */
1301
function commerce_preprocess_entity(&$variables) {
1302
  $variables['view_mode'] = $variables['elements']['#view_mode'];
1303
  $entity_type = $variables['elements']['#entity_type'];
1304
  $variables['entity_type'] = $entity_type;
1305
  $entity = $variables['elements']['#entity'];
1306
  $variables[$variables['elements']['#entity_type']] = $entity;
1307
  $info = entity_get_info($entity_type);
1308

    
1309
  $variables['title'] = check_plain(entity_label($entity_type, $entity));
1310

    
1311
  $uri = entity_uri($entity_type, $entity);
1312
  $variables['url'] = $uri && isset($uri['path']) ? url($uri['path'], $uri['options']) : FALSE;
1313

    
1314
  if (isset($variables['elements']['#page'])) {
1315
    // If set by the caller, respect the page property.
1316
    $variables['page'] = $variables['elements']['#page'];
1317
  }
1318
  else {
1319
    // Else, try to automatically detect it.
1320
    $variables['page'] = $uri && isset($uri['path']) && $uri['path'] == $_GET['q'];
1321
  }
1322

    
1323
  // Helpful $content variable for templates.
1324
  $variables['content'] = array();
1325
  foreach (element_children($variables['elements']) as $key) {
1326
    $variables['content'][$key] = $variables['elements'][$key];
1327
  }
1328

    
1329
  if (!empty($info['fieldable'])) {
1330
    // Make the field variables available with the appropriate language.
1331
    field_attach_preprocess($entity_type, $entity, $variables['content'], $variables);
1332
  }
1333
  list(, , $bundle) = entity_extract_ids($entity_type, $entity);
1334

    
1335
  // Gather css classes.
1336
  $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type);
1337
  $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle);
1338

    
1339
  // Add RDF type and about URI.
1340
  if (module_exists('rdf')) {
1341
    $variables['attributes_array']['about'] = empty($uri['path']) ? NULL: url($uri['path']);
1342
    $variables['attributes_array']['typeof'] = empty($entity->rdf_mapping['rdftype']) ? NULL : $entity->rdf_mapping['rdftype'];
1343
  }
1344

    
1345
  // Add suggestions.
1346
  $variables['theme_hook_suggestions'][] = $entity_type;
1347
  $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle;
1348
  $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode'];
1349
  if ($id = entity_id($entity_type, $entity)) {
1350
    $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id;
1351
  }
1352
}