Projet

Général

Profil

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

root / drupal7 / sites / all / modules / commerce / commerce.module @ 651307cd

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
  $currency_code = variable_get('commerce_default_currency', 'USD');
474
  drupal_alter('commerce_default_currency', $currency_code);
475
  return $currency_code;
476
}
477

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

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

    
495
  return isset($currencies[$currency_code]) ? $currencies[$currency_code] : FALSE;
496
}
497

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

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

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

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

    
550
      // Sort the currencies
551
      ksort($currencies['all']);
552

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

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

    
562
  return $enabled ? $currencies['enabled'] : $currencies['all'];
563
}
564

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

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

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

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

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

    
611
  return $currencies;
612
}
613

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

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

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

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

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

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

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

    
686
  $modifier = 1 / $currency['rounding_step'];
687

    
688
  return round($amount * $modifier) / $modifier;
689
}
690

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

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

    
725
  $target_currency = commerce_currency_load($target_currency_code);
726

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

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

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

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

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

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

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

    
792
  return $amount / $divisors[$currency_code];
793
}
794

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

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

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

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

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

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

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

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

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

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

    
911
    case COMMERCE_ROUND_NONE:
912
    default:
913
      break;
914
  }
915

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

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

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

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

    
980
  $entity_info = entity_get_info($entity_type);
981

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

    
1011
    if (in_array(FALSE, $access_results, TRUE)) {
1012
      return FALSE;
1013
    }
1014
    elseif (in_array(TRUE, $access_results, TRUE)) {
1015
      return TRUE;
1016
    }
1017

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

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

    
1042
      if (isset($entity) && !empty($entity_info['entity keys']['bundle'])) {
1043
        $bundle_name = $entity->{$entity_info['entity keys']['bundle']};
1044
      }
1045

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

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

    
1063
  return FALSE;
1064
}
1065

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

    
1073
  $permissions = array();
1074

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

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

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

    
1132
  return $permissions;
1133
}
1134

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

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

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

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

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

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

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

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

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

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

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

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

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

    
1243
  $context = array(
1244
    'account' => $account,
1245
    'entity' => $query->getMetaData('entity'),
1246
    'entity_type' => $entity_type,
1247
    'base_table' => $base_table
1248
  );
1249

    
1250
  // Allow other modules to add conditions to the array as necessary.
1251
  drupal_alter($hooks, $conditions, $context);
1252

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

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

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

    
1291
  // Unshift the core preprocess function and the Commerce specific function so
1292
  // they appear at the beginning of the array in the desired order.
1293
  array_unshift($functions, 'template_preprocess', 'commerce_preprocess_entity');
1294

    
1295
  // Update the function list in the theme registry.
1296
  $theme_registry['entity']['preprocess functions'] = array_values($functions);
1297
}
1298

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

    
1313
  $variables['title'] = check_plain(entity_label($entity_type, $entity));
1314

    
1315
  $uri = entity_uri($entity_type, $entity);
1316
  $variables['url'] = $uri && isset($uri['path']) ? url($uri['path'], $uri['options']) : FALSE;
1317

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

    
1327
  // Helpful $content variable for templates.
1328
  $variables['content'] = array();
1329
  foreach (element_children($variables['elements']) as $key) {
1330
    $variables['content'][$key] = $variables['elements'][$key];
1331
  }
1332

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

    
1339
  // Gather css classes.
1340
  $variables['classes_array'][] = drupal_html_class('entity-' . $entity_type);
1341
  $variables['classes_array'][] = drupal_html_class($entity_type . '-' . $bundle);
1342

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

    
1349
  // Add suggestions.
1350
  $variables['theme_hook_suggestions'][] = $entity_type;
1351
  $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle;
1352
  $variables['theme_hook_suggestions'][] = $entity_type . '__' . $bundle . '__' . $variables['view_mode'];
1353
  if ($id = entity_id($entity_type, $entity)) {
1354
    $variables['theme_hook_suggestions'][] = $entity_type . '__' . $id;
1355
  }
1356
}