Problem/Motivation

When creating a new product, or updating an existing one, the user is able to enter any numeric price value they wish up to a length of 128 characters.

The existing validation only checks for a numeric value; this leaves a potential issue where a PDO exception is thrown for large input values. For example in a typical MySQL database setup, a price input greater than 21474836 will cause this error:

PDOException: SQLSTATE[22003]: Numeric value out of range: 1264 Out of range value for column 'commerce_price_amount' at row 1: INSERT INTO {field_data_commerce_price} (entity_type, entity_id, revision_id, bundle, delta, language, commerce_price_amount, commerce_price_currency_code, commerce_price_data) VALUES (:db_insert_placeholder_0, :db_insert_placeholder_1, :db_insert_placeholder_2, :db_insert_placeholder_3, :db_insert_placeholder_4, :db_insert_placeholder_5, :db_insert_placeholder_6, :db_insert_placeholder_7, :db_insert_placeholder_8); Array ( [:db_insert_placeholder_0] => commerce_product [:db_insert_placeholder_1] => 1 [:db_insert_placeholder_2] => 28 [:db_insert_placeholder_3] => product [:db_insert_placeholder_4] => 0 [:db_insert_placeholder_5] => und [:db_insert_placeholder_6] => 2222222200 [:db_insert_placeholder_7] => GBP [:db_insert_placeholder_8] => a:1:{s:10:"components";a:0:{}} ) in field_sql_storage_field_storage_write() (line 514 of /var/www/html/sites/lush_content/docroot/modules/field/modules/field_sql_storage/field_sql_storage.module).

Proposed resolution

(Description of the proposed solution, the rationale behind it, and workarounds for people who cannot use the patch.)

// Convert the decimal amount value entered to an integer based
// amount value.
$integer_amount = commerce_currency_decimal_to_amount($element['amount']['#value'], $element['currency_code']['#value']);

// Get the maximum possible integer amount.
$max_possible_integer = _commerce_price_amount_max_int();

// Check if the integer amount exceeds the maximum value allowed
// in the database table.
if ($integer_amount > $max_possible_integer) {
  form_error($element['amount'], t('%title: you have exceeded the maximum price amount. Make sure the price amount is below %max_price.', array('%title' => $element['amount']['#title'], '%max_price' => number_format($max_possible_integer / 100, 2))));
}
else {
  form_set_value($element['amount'], $integer_amount, $form_state);
}

/**
 * Get the maximum allowable integer value for the commerce_price amount.
 */
function _commerce_price_amount_max_int() {
  // Get the field info.
  $commerce_price_field_info = field_info_field('commerce_price');

  // Determine the max value based on if the amount field is signed or not.
  if (isset($commerce_price_field_info['columns']['amount']['unsigned'])) {
    $max_int_value = db_query('SELECT ~0 >> 32 AS max_int_unsigned')->fetchField();
  }
  else {
    $max_int_value = db_query('SELECT ~0 >> 33 AS max_int_signed')->fetchField();
  }

  // Fallback value.
  if (!$max_int_value) {
    $max_int_value = 2147483647;
  }

  return $max_int_value;
}
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

danielmrichards’s picture

See #2

danielmrichards’s picture

Patch for suggested resolution.

boyan.borisov’s picture

Status: Active » Needs review

Status: Needs review » Needs work

The last submitted patch, 2: commerce-price_value_out_of_range_fix-2530004-1.patch, failed testing.

danielmrichards’s picture

Not sure my patch is failing SimpleTest, as it does not affect the areas the test is failing on.

On my local machine with a clean install of commerce 7.x-1.11, the same tests are failing even without my patch applied.

danielmrichards’s picture

  • Added validation for negative out of range values.
  • Tweaked logic for lookup of maximum integer value to store the value in a Drupal variable.
Arne Slabbinck’s picture

I wasn't able to apply the patch cause I have a newer version of commerce installed, so I did it manually, and it did the trick! Thx!

corbacho’s picture

We are using a custom form, exposed to the user, so they can set up the donation amount they wish. Later during the submit we use commerce_cart_product_add
Testing the limits of the field, we found this same error.

Researching further, we found that ALSO is important that the total value that is in the basket can trigger the error.
For example, if the basket has 2147483640, then adding to the basket a value of 10, will trigger the error.

Our work around was this

define('CART_MAX_INT', 2147483647); // in the format of 21,000,000.00


/**
 * Implements hook form_alter
 * Adds a custom validation to the cart form, when user clicks "Secure Checkout".
 */
function MODULE_form_alter(&$form, &$form_state, $form_id) {
  if ($form_id === 'YOUR FORM ID') {
    $form['#validate'][] =  'MODULE_price_validation';
  }
}

/**
 * Validate prices before adding the product/donation to basket.
 */
function MODULE_price_validation(&$form, &$form_state) {
  global $user;

  $donation_to_add_to_cart = (float) $form_state['values']['donation']['amount'];
  if (isset($form_state['values']['donation']['amount'])) {
    $order = commerce_cart_order_load($user->uid);
    if ($order) {
      $wrapper = entity_metadata_wrapper('commerce_order', $order);
      $line_items = $wrapper->commerce_line_items;
      $total = commerce_line_items_total($line_items);
      // $donation_to_add_to_cart comes in a 3.99 format.
      // and the $total amount comes in a 399 format (decimals are part of the integer)
      if($total['amount'] && ((($donation_to_add_to_cart * 100) + (float) $total['amount']) > CART_MAX_INT)){
        form_set_error('donation', t('You have exceeded the maximum amount of donations in your basket already. Please finish the order, and start a new one.'));
      }
    }
    // Individual items can't go over 5,000,000
    if ($donation_to_add_to_cart > 5000000) {
      form_set_error('donation', t('Please, reduce the donation quantity below £5000000.'));
    }
  }
}
reszli’s picture

We needed to allow bigger numbers, so we patched the module to store as BIGINT(20) instead of INT(11).
Note, in order to make this work, we also needed to remove an (int) cast from commerce.module.

mglaman’s picture