diff --git a/commerce.info b/commerce.info index a9282bf..ae27ac4 100644 --- a/commerce.info +++ b/commerce.info @@ -11,3 +11,7 @@ files[] = tests/commerce_base.test ; Central Entity Controller. files[] = includes/commerce.controller.inc + +; Views related includes. +files[] = includes/views/commerce_views_query.inc +files[] = includes/views/handlers/commerce_views_handler_field_field.inc diff --git a/commerce.module b/commerce.module index 1bf6226..3adb2b0 100755 --- a/commerce.module +++ b/commerce.module @@ -374,6 +374,16 @@ function commerce_embed_view($view_id, $display_id, $arguments, $override_url = } /** + * Implements hook_views_api(). + */ +function commerce_views_api() { + return array( + 'api' => 3, + 'path' => drupal_get_path('module', 'commerce') . '/includes/views', + ); +} + +/** * Returns the e-mail address from which to send commerce related e-mails. * * Currently this is just using the site's e-mail address, but this may be diff --git a/includes/commerce.controller.inc b/includes/commerce.controller.inc index 1cdd605..b29ddb9 100644 --- a/includes/commerce.controller.inc +++ b/includes/commerce.controller.inc @@ -7,20 +7,193 @@ * A full fork of Entity API's controller, with support for revisions. */ -class DrupalCommerceEntityController extends DrupalDefaultEntityController implements EntityAPIControllerInterface { +/** + * Interface for the default Drupal Commerce entity controller. + */ +interface DrupalCommerceEntityControllerInterface extends EntityAPIControllerInterface { + + /** + * Determines whether the provided entity is locked. + * + * @param object $entity + * The entity to check. + * + * @return bool + * True if the entity is locked, false otherwise. + */ + public function isLocked($entity); + + /** + * Determines whether the provided entity is cached. + * + * @param object $entity + * The entity to check. + * + * @return bool + * True if the entity is cached, false otherwise. + */ + public function isCached($entity); + + /** + * Request that locking be skipped. + * + * Actual skipping of locking may or may not be possible. + * + * @param bool $skip_locking + * The boolean indicating whether locking should be skipped. + */ + public function requestSkipLocking($skip_locking); + + /** + * Releases locking on all entities, use with caution. + */ + public function releaseLocking(); + + /** + * Releases the lock on an entity. + * + * @param object $entity + * The entity to release the lock for. + */ + public function releaseLock($entity); + +} + +/** + * Default implementation of DrupalCommerceEntityControllerInterface. + * + * Provides base entity controller for Drupal Commerce entities. + */ +class DrupalCommerceEntityController extends DrupalDefaultEntityController implements DrupalCommerceEntityControllerInterface { /** * Stores our transaction object, necessary for pessimistic locking to work. + * + * @var DatabaseTransaction */ protected $controllerTransaction = NULL; /** - * Stores the ids of locked entities, necessary for knowing when to release a - * lock by committing the transaction. + * Stores the ids of locked entities. + * + * Necessary for knowing when to release lock by committing the transaction. + * + * @var array */ protected $lockedEntities = array(); /** + * Stores the flag for if a condition has been passed for requesting locking. + * + * By default, locking is always requested unless specifically set to false. + * + * @var bool + */ + protected $requestLocking = TRUE; + + /** + * Stores whether a request for skipping locking has been set. + * + * If locking has been requested as well it will take preference and the + * entity load will default to locking. + * + * @var bool + */ + protected $requestSkipLocking = FALSE; + + /** + * Implements DrupalCommerceEntityControllerInterface::isLocked(). + */ + public function isLocked($entity) { + return $this->controllerTransaction && isset($this->lockedEntities[$entity->{$this->idKey}]); + } + + /** + * Implements DrupalCommerceEntityControllerInterface::isCached(). + */ + public function isCached($entity) { + return isset($this->entityCache[$entity->{$this->idKey}]); + } + + /** + * Implements DrupalCommerceEntityControllerInterface::requestSkipLocking(). + */ + public function requestSkipLocking($skip_locking = TRUE) { + $this->requestSkipLocking = $skip_locking; + + return $this; + } + + /** + * Implements DrupalCommerceEntityControllerInterface::releaseLock(). + */ + public function releaseLock($entity) { + // Maintain the list of locked entities and release the lock if possible. + unset($this->lockedEntities[$entity->{$this->idKey}]); + $this->releaseLocks(); + } + + /** + * Determines whether the current entity type requires locking. + * + * @return bool + * True if locking is required, false otherwise. + */ + protected function requireLocking() { + $enabled = isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic'; + $not_skipped = empty($this->requestSkipLocking); + + return $enabled && $not_skipped && $this->requestLocking; + } + + /** + * Checks the list of tracked locked entities, and if it's empty, commits + * the transaction in order to remove the acquired locks. + * + * The transaction is not necessarily committed immediately. Drupal will + * commit it as soon as possible given the state of the transaction stack. + */ + protected function releaseLocks() { + if (empty($this->lockedEntities)) { + $this->controllerTransaction = NULL; + } + } + + /** + * Removes all locking for the controller, mostly used for testing + * if you want to stop locking and run something else, or you want to cancel locking + * on all entities at once, although be cautious with this. + */ + public function releaseLocking() { + $this->controllerTransaction = NULL; + $this->lockedEntities = array(); + } + + /** + * Overrides DrupalDefaultEntityController::load(). + * + * Accepts a condition of locking request, may not necessarily take effect + * as other options can override locking. If anything conflicts, locking always + * take precedence over not locking. + */ + public function load($ids = array(), $conditions = array()) { + // Lock by default if the caller didn't indicate a preference. + $conditions += array('_lock' => TRUE); + $this->requestLocking = $conditions['_lock']; + unset($conditions['_lock']); + + // If locking has been required, then bypass the internal cache for any + // entities that are not already locked. + if ($this->requireLocking()) { + foreach (array_diff_key(array_flip($ids), $this->lockedEntities) as $id => $value) { + unset($this->entityCache[$id]); + } + } + + return parent::load($ids, $conditions); + } + + /** * Override of DrupalDefaultEntityController::buildQuery(). * * Handle pessimistic locking. @@ -28,7 +201,7 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple protected function buildQuery($ids, $conditions = array(), $revision_id = FALSE) { $query = parent::buildQuery($ids, $conditions, $revision_id); - if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') { + if ($this->requireLocking()) { // In pessimistic locking mode, we issue the load query with a FOR UPDATE // clause. This will block all other load queries to the loaded objects // but requires us to start a transaction. @@ -48,41 +221,6 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple return $query; } - public function resetCache(array $ids = NULL) { - parent::resetCache($ids); - - // Maintain the list of locked entities, so that the releaseLock() method - // can know when it's time to commit the transaction. - if (!empty($this->lockedEntities)) { - if (isset($ids)) { - foreach ($ids as $id) { - unset($this->lockedEntities[$id]); - } - } - else { - $this->lockedEntities = array(); - } - } - - // Try to release the lock, if possible. - $this->releaseLock(); - } - - /** - * Checks the list of tracked locked entities, and if it's empty, commits - * the transaction in order to remove the acquired locks. - * - * The transaction is not necessarily committed immediately. Drupal will - * commit it as soon as possible given the state of the transaction stack. - */ - protected function releaseLock() { - if (isset($this->entityInfo['locking mode']) && $this->entityInfo['locking mode'] == 'pessimistic') { - if (empty($this->lockedEntities)) { - unset($this->controllerTransaction); - } - } - } - /** * (Internal use) Invokes a hook on behalf of the entity. * @@ -141,6 +279,12 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple // Reset the cache as soon as the changes have been applied. $this->resetCache($ids); + // Maintain the list of locked entities and release the lock if possible. + foreach ($ids as $id) { + unset($this->lockedEntities[$id]); + } + $this->releaseLocks(); + foreach ($entities as $id => $entity) { $this->invoke('delete', $entity); } @@ -239,7 +383,7 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple // Maintain the list of locked entities and release the lock if possible. unset($this->lockedEntities[$entity->{$this->idKey}]); - $this->releaseLock(); + $this->releaseLock($entity); $this->invoke($op, $entity); diff --git a/includes/views/commerce.views.inc b/includes/views/commerce.views.inc new file mode 100644 index 0000000..fcbcb7e --- /dev/null +++ b/includes/views/commerce.views.inc @@ -0,0 +1,19 @@ + array( + 'commerce_views_query' => array( + 'title' => t('Commerce Query'), + 'help' => t('Query will be generated and run for Commerce related views.'), + 'handler' => 'CommerceViewsQuery', + ), + ), + ); +} diff --git a/includes/views/commerce_views_query.inc b/includes/views/commerce_views_query.inc new file mode 100644 index 0000000..450cace --- /dev/null +++ b/includes/views/commerce_views_query.inc @@ -0,0 +1,115 @@ + TRUE, + 'translatable' => FALSE, + 'bool' => TRUE, + ); + + return $options; + } + + /** + * Add settings for the ui. + * @return array + */ + function options_form(&$form, &$form_state) { + parent::options_form($form, $form_state); + + $form['commerce_lock_items'] = array( + '#title' => t('Lock Commerce items'), + '#description' => t('When loading entities, those will be locked for further processing. Disable locking to avoid lock-timeouts or deadlocks in queries.'), + '#type' => 'checkbox', + '#default_value' => !empty($this->options['commerce_lock_items']), + ); + + return $form; + } + + /** + * Returns the entity objects for the given query results. + * + * Mimics the behaviour of views_plugin_query, but allows admins to define + * if the loaded orders should be locked or not. + * + */ + function get_result_entities($results, $relationship = NULL) { + // The following code is basically a copy-and-paste from the default + // views_plugin_query_default::get_result_entities() code. The difference is + // that here we handle the "_lock" condition when loading entity instances. + $base_table = $this->base_table; + $base_table_alias = $base_table; + + if (!empty($relationship)) { + foreach ($this->view->relationship as $current) { + if ($current->alias == $relationship) { + $base_table = $current->definition['base']; + $base_table_alias = $relationship; + break; + } + } + } + $table_data = views_fetch_data($base_table); + + // Bail out if the table has not specified the entity-type. + if (!isset($table_data['table']['entity type'])) { + return FALSE; + } + $entity_type = $table_data['table']['entity type']; + $info = entity_get_info($entity_type); + $is_revision = !empty($table_data['table']['revision']); + $id_alias = $this->get_field_alias($base_table_alias, $info['entity keys'][$is_revision ? 'revision' : 'id']); + + // Assemble the ids of the entities to load. + $ids = array(); + foreach ($results as $key => $result) { + if (isset($result->$id_alias)) { + $ids[$key] = $result->$id_alias; + } + } + + if (!$is_revision) { + // Creating the entity_load conditions given the locking strategy. + $conditions = array('_lock' => !empty($this->options['commerce_lock_items'])); + + + $entities = entity_load($entity_type, $ids, $conditions); + // Re-key the array by row-index. + $result = array(); + foreach ($ids as $key => $id) { + $result[$key] = isset($entities[$id]) ? $entities[$id] : FALSE; + } + } + else { + // There's no way in core to load revisions in bulk. + $result = array(); + foreach ($ids as $key => $id) { + if (module_exists('entity')) { + $result[$key] = entity_revision_load($entity_type, $id); + } + else { + // Otherwise this isn't supported. + watchdog('views', 'Attempt to load a revision on an unsupported entity type @entity_type.', array('@entity_type' => $entity_type), WATCHDOG_WARNING); + } + } + } + + return array($entity_type, $result); + } +} diff --git a/includes/views/handlers/commerce_views_handler_field_field.inc b/includes/views/handlers/commerce_views_handler_field_field.inc new file mode 100644 index 0000000..12b015f --- /dev/null +++ b/includes/views/handlers/commerce_views_handler_field_field.inc @@ -0,0 +1,85 @@ +query instanceof CommerceViewsQuery) { + // For non-locking Order loading, we need to pass the "_lock" condition. + $conditions = array('_lock' => !empty($this->query->options['commerce_lock_items'])); + } + + // The following code is basically a copy-and-paste from the default + // views_handler_field_field::post_execute() code. The difference is that + // here we handle the "_lock" condition when loading Order instances. + if (!empty($values)) { + // Divide the entity ids by entity type, so they can be loaded in bulk. + $entities_by_type = array(); + $revisions_by_type = array(); + foreach ($values as $key => $object) { + if (isset($this->aliases['entity_type']) && isset($object->{$this->aliases['entity_type']}) && isset($object->{$this->field_alias}) && !isset($values[$key]->_field_data[$this->field_alias])) { + $entity_type = $object->{$this->aliases['entity_type']}; + if (empty($this->definition['is revision'])) { + $entity_id = $object->{$this->field_alias}; + $entities_by_type[$entity_type][$key] = $entity_id; + } + else { + $revision_id = $object->{$this->field_alias}; + $entity_id = $object->{$this->aliases['entity_id']}; + $entities_by_type[$entity_type][$key] = array($entity_id, $revision_id); + } + } + } + + // Load the entities. + foreach ($entities_by_type as $entity_type => $entity_ids) { + $entity_info = entity_get_info($entity_type); + if (empty($this->definition['is revision'])) { + $entities = entity_load($entity_type, $entity_ids, $conditions); + $keys = $entity_ids; + } + else { + // Revisions can't be loaded multiple, so we have to load them + // one by one. + $entities = array(); + $keys = array(); + foreach ($entity_ids as $key => $combined) { + list($entity_id, $revision_id) = $combined; + // Adding the revision as a condition to the previous ones. + $conditions[$entity_info['entity keys']['revision']] = $revision_id; + $entity = entity_load($entity_type, array($entity_id), $conditions); + if ($entity) { + $entities[$revision_id] = array_shift($entity); + $keys[$key] = $revision_id; + } + } + } + + foreach ($keys as $key => $entity_id) { + // If this is a revision, load the revision instead. + if (isset($entities[$entity_id])) { + $values[$key]->_field_data[$this->field_alias] = array( + 'entity_type' => $entity_type, + 'entity' => $entities[$entity_id], + ); + } + } + } + + // Now, transfer the data back into the resultset so it can be easily used. + foreach ($values as $row_id => &$value) { + $value->{'field_' . $this->options['id']} = $this->set_items($value, $row_id); + } + } + } +} diff --git a/modules/cart/commerce_cart.module b/modules/cart/commerce_cart.module index 002b263..22073a6 100644 --- a/modules/cart/commerce_cart.module +++ b/modules/cart/commerce_cart.module @@ -74,7 +74,7 @@ function commerce_cart_menu_item_title() { $title = t('Shopping cart'); // If the user actually has a cart order... - if ($order = commerce_cart_order_load($user->uid)) { + if ($order = commerce_cart_order_load($user->uid, FALSE)) { // Count the number of product line items on the order. $wrapper = entity_metadata_wrapper('commerce_order', $order); $quantity = commerce_line_items_quantity($wrapper->commerce_line_items, commerce_product_line_item_types()); @@ -545,7 +545,8 @@ function commerce_cart_commerce_product_calculate_sell_price_line_item_alter($li // and avoid any untraceable recursive loops. // @see http://drupal.org/node/1268472 if (empty($line_item->order_id)) { - $order = commerce_cart_order_load($user->uid); + // Since we do not need to save the order, load it without locking. + $order = commerce_cart_order_load($user->uid, FALSE); if ($order) { $line_item->order_id = $order->order_id; @@ -605,22 +606,22 @@ function commerce_cart_user_login(&$edit, $account) { function commerce_cart_user_update(&$edit, $account, $category) { // If the e-mail address was changed... if (!empty($edit['original']->mail) && $account->mail != $edit['original']->mail) { - // Load the user's shopping cart orders. + // Load the user's shopping cart orders that have the old email address. $query = new EntityFieldQuery(); $query ->entityCondition('entity_type', 'commerce_order', '=') ->propertyCondition('uid', $account->uid, '=') + ->propertyCondition('mail', $account->mail, '<>') ->propertyCondition('status', array_keys(commerce_order_statuses(array('cart' => TRUE))), 'IN'); $result = $query->execute(); if (!empty($result['commerce_order'])) { foreach (commerce_order_load_multiple(array_keys($result['commerce_order'])) as $order) { - if ($order->mail != $account->mail) { - $order->mail = $account->mail; - commerce_order_save($order); - } + // Updating the order eMail with the new account's one. + $order->mail = $account->mail; + commerce_order_save($order); } } } @@ -655,7 +656,7 @@ function commerce_cart_block_view($delta) { $content = theme('commerce_cart_empty_block'); // First check to ensure there are products in the shopping cart. - if ($order = commerce_cart_order_load($user->uid)) { + if ($order = commerce_cart_order_load($user->uid, FALSE)) { $wrapper = entity_metadata_wrapper('commerce_order', $order); // If there are one or more products in the cart... @@ -800,17 +801,19 @@ function theme_commerce_cart_empty_page() { * @param $uid * The uid of the customer whose cart to load. If left 0, attempts to load * an anonymous order from the session. + * @param bool $lock + * Define if the loaded Order should be locked when loading it. * * @return * The fully loaded shopping cart order or FALSE if nonexistent. */ -function commerce_cart_order_load($uid = 0) { +function commerce_cart_order_load($uid = 0, $lock = TRUE) { // Retrieve the order ID for the specified user's current shopping cart. $order_id = commerce_cart_order_id($uid); // If a valid cart order ID exists for the user, return it now. if (!empty($order_id)) { - return commerce_order_load($order_id); + return commerce_order_load($order_id, $lock); } return FALSE; @@ -1429,7 +1432,7 @@ function commerce_cart_product_add_by_id($product_id, $quantity = 1, $combine = global $user; // If the specified product exists... - if ($product = commerce_product_load($product_id)) { + if ($product = commerce_product_load($product_id, FALSE)) { // Create a new product line item for it. $line_item = commerce_product_line_item_new($product, $quantity); @@ -2286,7 +2289,7 @@ function commerce_cart_add_to_cart_form_attributes_refresh($form, $form_state) { // Then render and return the various product fields that might need to be // updated on the page. if (!empty($form_state['context'])) { - $product = commerce_product_load($form_state['default_product_id']); + $product = commerce_product_load($form_state['default_product_id'], FALSE); $form_state['default_product'] = $product; $product->display_context = $form_state['context']; @@ -2393,7 +2396,7 @@ function commerce_cart_add_to_cart_views_form_refresh($form, $form_state) { */ function commerce_cart_add_to_cart_form_submit($form, &$form_state) { $product_id = $form_state['values']['product_id']; - $product = commerce_product_load($product_id); + $product = commerce_product_load($product_id, FALSE); // If the line item passed to the function is new... if (empty($form_state['line_item']->line_item_id)) { diff --git a/modules/cart/includes/commerce_cart.pages.inc b/modules/cart/includes/commerce_cart.pages.inc index 207f6bd..562d1c3 100644 --- a/modules/cart/includes/commerce_cart.pages.inc +++ b/modules/cart/includes/commerce_cart.pages.inc @@ -13,7 +13,7 @@ function commerce_cart_checkout_router() { global $user; // Load the shopping cart order. - if ($order = commerce_cart_order_load($user->uid)) { + if ($order = commerce_cart_order_load($user->uid, FALSE)) { $wrapper = entity_metadata_wrapper('commerce_order', $order); } @@ -38,7 +38,7 @@ function commerce_cart_view() { $content = theme('commerce_cart_empty_page'); // First check to make sure we have a valid order. - if ($order = commerce_cart_order_load($user->uid)) { + if ($order = commerce_cart_order_load($user->uid, FALSE)) { $wrapper = entity_metadata_wrapper('commerce_order', $order); // Only show the cart form if we found product line items. diff --git a/modules/cart/tests/commerce_cart.test b/modules/cart/tests/commerce_cart.test index da87246..220c45d 100644 --- a/modules/cart/tests/commerce_cart.test +++ b/modules/cart/tests/commerce_cart.test @@ -509,10 +509,8 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); // Get the order just created. - $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE); + $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart', '_lock' => FALSE), TRUE); $order_anonymous = reset($orders); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); // Access to the cart and check if the product is in it. $this->drupalGet($this->getCommerceUrl('cart')); @@ -530,10 +528,9 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase $this->drupalPost('user', array('name' => $this->store_customer->name, 'pass' => $this->store_customer->pass_raw), t('Log in')); // Get the order for user just logged in. - $orders = commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, 'status' => 'cart'), TRUE); + $orders = commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, 'status' => 'cart', '_lock' => FALSE), TRUE); $order_authenticated = reset($orders); // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); // Access to the cart and check if the product is in it. $this->drupalGet($this->getCommerceUrl('cart')); @@ -546,3 +543,43 @@ class CommerceCartTestCaseAnonymousToAuthenticated extends CommerceCartTestCase } } + +class CommerceCartOrderLockingTest extends CommerceCartTestCase { + /** + * Product that is being added to the cart. + */ + protected $product; + + /** + * Product display. + */ + protected $product_node; + + + /** + * Implementation of getInfo(). + */ + public static function getInfo() { + return array( + 'name' => 'Shopping cart order locking test', + 'description' => 'Test that the cart refresh does not remove the order lock.', + 'group' => 'Drupal Commerce', + ); + } + + /** + * Implementation of setUp(). + */ + function setUp() { + parent::setUpHelper('ui'); + } + + public function testCartRefreshOrderLock() { + $created_order = $this->createDummyOrder(); + $this->assertFalse(commerce_order_is_locked($created_order), t('The generated and saved order is not locked.')); + $order = commerce_order_load($created_order->order_id); + $this->assertTrue(commerce_order_is_locked($order), t('The loaded order is locked.')); + commerce_cart_order_refresh($order); + $this->assertTrue(commerce_order_is_locked($order), t('After a "Cart refresh" the order is still lock.')); + } +} diff --git a/modules/checkout/commerce_checkout.module b/modules/checkout/commerce_checkout.module index f0fdd87..84d4717 100644 --- a/modules/checkout/commerce_checkout.module +++ b/modules/checkout/commerce_checkout.module @@ -15,6 +15,7 @@ function commerce_checkout_menu() { $items['checkout/%commerce_order'] = array( 'title' => 'Checkout', + 'load arguments' => array(FALSE), 'page callback' => 'commerce_checkout_router', 'page arguments' => array(1), 'access arguments' => array('access checkout'), @@ -23,6 +24,7 @@ function commerce_checkout_menu() { ); $items['checkout/%commerce_order/%commerce_checkout_page'] = array( 'title' => 'Checkout', + 'load arguments' => array(FALSE), 'page callback' => 'commerce_checkout_router', 'page arguments' => array(1, 2), 'access arguments' => array('access checkout'), diff --git a/modules/checkout/includes/commerce_checkout.pages.inc b/modules/checkout/includes/commerce_checkout.pages.inc index bbb2a06..14c4052 100644 --- a/modules/checkout/includes/commerce_checkout.pages.inc +++ b/modules/checkout/includes/commerce_checkout.pages.inc @@ -351,9 +351,6 @@ function commerce_checkout_form_validate($form, &$form_state) { function commerce_checkout_form_submit($form, &$form_state) { $checkout_page = $form_state['checkout_page']; - // Load a fresh copy of the order stored in the form. - $order = commerce_order_load($form_state['order']->order_id); - // If we are going to redirect with checkout pane messages stored in the form // state, they will not be displayed on a subsequent form build like normal. // Move them out of the form state messages array and into the current @@ -368,6 +365,9 @@ function commerce_checkout_form_submit($form, &$form_state) { if (end($form_state['triggering_element']['#array_parents']) == 'continue') { // If there is another checkout page... if ($checkout_page['next_page']) { + // Load a fresh copy of the order stored in the form. + $order = commerce_order_load($form_state['order']->order_id); + // Update the order status to reflect the next checkout page. $order = commerce_order_status_update($order, 'checkout_' . $checkout_page['next_page'], FALSE, NULL, t('Customer continued to the next checkout page via a submit button.')); diff --git a/modules/checkout/tests/commerce_checkout.test b/modules/checkout/tests/commerce_checkout.test index 56351cc..7fc3797 100644 --- a/modules/checkout/tests/commerce_checkout.test +++ b/modules/checkout/tests/commerce_checkout.test @@ -76,10 +76,8 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase { $this->drupalPost('node/' . $this->product_node->nid, array(), t('Add to cart')); // Get the order for the anonymous user. - $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart'), TRUE); + $orders = commerce_order_load_multiple(array(), array('uid' => $user->uid, 'status' => 'cart', '_lock' => FALSE), TRUE); $this->order = reset($orders); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); } /** @@ -162,9 +160,7 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase { $this->assertText('Example payment', t('Example payment method pane is present')); // Load the order to check the status. - $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); + $order = commerce_order_load_multiple(array($this->order->order_id), array('_lock' => FALSE), TRUE); // At this point we should be in Checkout Review. $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.')); @@ -324,9 +320,7 @@ class CommerceCheckoutTestProcess extends CommerceBaseTestCase { $this->assertText($user_mail, t('Account information is correct')); // Load the order to check the status. - $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); + $order = commerce_order_load_multiple(array($this->order->order_id), array('_lock' => FALSE), TRUE); // At this point we should be in Checkout Review. $this->assertEqual(reset($order)->status, 'checkout_review', t('Order status is \'Checkout Review\' in the review phase.')); diff --git a/modules/line_item/commerce_line_item.module b/modules/line_item/commerce_line_item.module index 7dfe3b9..9a7b015 100644 --- a/modules/line_item/commerce_line_item.module +++ b/modules/line_item/commerce_line_item.module @@ -139,8 +139,8 @@ function commerce_line_item_form_alter(&$form, &$form_state, $form_id) { // Add an additional class to the actions wrapper. $form['actions']['#attributes']['class'][] = 'commerce-line-item-actions'; - // Load the order from the Views argument. - $order = commerce_order_load($view->argument['order_id']->value[0]); + // Load the order from the Views argument, no locking used. + $order = commerce_order_load($view->argument['order_id']->value[0], FALSE); $form_state['order'] = $order; } @@ -604,8 +604,8 @@ function commerce_line_item_save($line_item) { /** * Loads a line item by ID. */ -function commerce_line_item_load($line_item_id) { - $line_items = commerce_line_item_load_multiple(array($line_item_id), array()); +function commerce_line_item_load($line_item_id, $lock = TRUE) { + $line_items = commerce_line_item_load_multiple(array($line_item_id), array('_lock' => $lock)); return $line_items ? reset($line_items) : FALSE; } @@ -711,7 +711,8 @@ function commerce_line_item_access($op, $line_item, $account = NULL) { // determine access to update or delete a given line item through a connection // to an Order. if (!empty($line_item->order_id) && module_exists('commerce_order')) { - $order = commerce_order_load($line_item->order_id); + // Load the current order, no locking required. + $order = commerce_order_load($line_item->order_id, FALSE); return commerce_order_access($op, $order, $account); } @@ -835,7 +836,8 @@ function commerce_line_item_field_validate($entity_type, $entity, $field, $insta // Prevent performance hog if there are no ids to check. if ($line_item_ids) { - $line_items = commerce_line_item_load_multiple($line_item_ids); + // Load all the given Line Items without acquiring a lock. + $line_items = commerce_line_item_load_multiple($line_item_ids, array('_lock' => FALSE)); foreach ($items as $delta => $item) { if (is_array($item)) { @@ -1010,7 +1012,7 @@ function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $ins // Load the line items for temporary storage in the form array. if(!empty($line_item_ids)) { - $line_items = commerce_line_item_load_multiple($line_item_ids); + $line_items = commerce_line_item_load_multiple($line_item_ids, array('_lock' => FALSE)); } else { $line_items = array(); @@ -1080,8 +1082,8 @@ function commerce_line_item_field_widget_form(&$form, &$form_state, $field, $ins // If the the form has been instructed to add a line item... if (!empty($form_state['line_item_add'])) { - // Load the info object for the selected line item type. - $line_item_type = commerce_line_item_type_load($form_state['line_item_add']); + // Load the info object for the selected line item type, without locking. + $line_item_type = commerce_line_item_type_load($form_state['line_item_add'], array('_lock' => FALSE)); // Store the line item info object in the form array. $element['actions']['line_item_type'] = array( diff --git a/modules/line_item/includes/views/commerce_line_item.views.inc b/modules/line_item/includes/views/commerce_line_item.views.inc index 11c0291..3e31239 100644 --- a/modules/line_item/includes/views/commerce_line_item.views.inc +++ b/modules/line_item/includes/views/commerce_line_item.views.inc @@ -17,6 +17,7 @@ function commerce_line_item_views_data() { 'title' => t('Commerce Line Item'), 'help' => t('A line item referenced by another entity.'), 'access query tag' => 'commerce_line_item_access', + 'query class' => 'commerce_views_query', ); $data['commerce_line_item']['table']['entity type'] = 'commerce_line_item'; diff --git a/modules/line_item/includes/views/commerce_line_item.views_default.inc b/modules/line_item/includes/views/commerce_line_item.views_default.inc index 74c181e..bbde3bc 100644 --- a/modules/line_item/includes/views/commerce_line_item.views_default.inc +++ b/modules/line_item/includes/views/commerce_line_item.views_default.inc @@ -26,8 +26,9 @@ function commerce_line_item_views_default_views() { $handler->display->display_options['use_more_always'] = FALSE; $handler->display->display_options['access']['type'] = 'none'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['pager']['type'] = 'none'; $handler->display->display_options['style_plugin'] = 'table'; diff --git a/modules/order/commerce_order.module b/modules/order/commerce_order.module index 41cd8af..f1c3f6d 100644 --- a/modules/order/commerce_order.module +++ b/modules/order/commerce_order.module @@ -629,7 +629,7 @@ function commerce_order_form_commerce_customer_customer_profile_delete_form_alte if ($entity_type == 'commerce_order') { // Load the referencing order. $order = reset($data); - $order = commerce_order_load($order->order_id); + $order = commerce_order_load($order->order_id, FALSE); // Only exit here if the order is in a non-cart status. if (!in_array($order->status, array_keys(commerce_order_statuses(array('cart' => TRUE))))) { @@ -746,17 +746,27 @@ function commerce_order_save($order) { /** * Loads an order by ID. + * + * @param $order_id + * The order id. + * @param $lock + * Whether to lock the loaded order. */ -function commerce_order_load($order_id) { - $orders = commerce_order_load_multiple(array($order_id), array()); +function commerce_order_load($order_id, $lock = TRUE) { + $orders = commerce_order_load_multiple(array($order_id), array('_lock' => $lock), FALSE); return $orders ? reset($orders) : FALSE; } /** * Loads an order by number. + * + * @param $order_number + * The order number. + * @param $lock + * Whether to lock the loaded order. */ -function commerce_order_load_by_number($order_number) { - $orders = commerce_order_load_multiple(array(), array('order_number' => $order_number)); +function commerce_order_load_by_number($order_number, $lock = TRUE) { + $orders = commerce_order_load_multiple(array(), array('order_number' => $order_number, '_lock' => $lock), FALSE); return $orders ? reset($orders) : FALSE; } @@ -782,6 +792,42 @@ function commerce_order_load_multiple($order_ids = array(), $conditions = array( return entity_load('commerce_order', $order_ids, $conditions, $reset); } + /** + * Determines whether or not the given order object is locked. + * + * @param $order + * A fully loaded order object. + * + * @return + * Boolean indicating whether or not the order object is locked. + */ +function commerce_order_is_locked($order) { + return entity_get_controller('commerce_order')->isLocked($order); +} + +/** + * Determines whether or not the given order object is cached. + * + * @param $order + * A fully loaded order object. + * + * @return + * Boolean indicating whether or not the order object is cached. + */ +function commerce_order_is_cached($order) { + return entity_get_controller('commerce_order')->isCached($order); +} + +/** + * Releases an order's lock. + * + * @param $order + * A fully loaded order object. + */ +function commerce_order_release_lock($order) { + entity_get_controller('commerce_order')->releaseLock($order); +} + /** * Determines whether or not the given order object represents the latest * revision of the order. diff --git a/modules/order/commerce_order_ui.module b/modules/order/commerce_order_ui.module index 6c3a964..1d0a583 100644 --- a/modules/order/commerce_order_ui.module +++ b/modules/order/commerce_order_ui.module @@ -33,6 +33,7 @@ function commerce_order_ui_menu() { ); $items['admin/commerce/orders/%commerce_order'] = array( + 'load arguments' => array(FALSE), 'title callback' => 'commerce_order_ui_order_title', 'title arguments' => array(3), 'page callback' => 'commerce_order_ui_order_view', @@ -84,6 +85,7 @@ function commerce_order_ui_menu() { ); $items['user/%user/orders/%commerce_order'] = array( + 'load arguments' => array(FALSE), 'title callback' => 'commerce_order_ui_order_title', 'title arguments' => array(3), 'page callback' => 'commerce_order_ui_order_view', diff --git a/modules/order/includes/views/commerce_order.views.inc b/modules/order/includes/views/commerce_order.views.inc index e5a8b59..a5e809d 100644 --- a/modules/order/includes/views/commerce_order.views.inc +++ b/modules/order/includes/views/commerce_order.views.inc @@ -17,6 +17,7 @@ function commerce_order_views_data() { 'title' => t('Commerce Order'), 'help' => t('Order placed in the store.'), 'access query tag' => 'commerce_order_access', + 'query class' => 'commerce_views_query', ); $data['commerce_order']['table']['entity type'] = 'commerce_order'; diff --git a/modules/order/includes/views/commerce_order_ui.views_default.inc b/modules/order/includes/views/commerce_order_ui.views_default.inc index ac7f698..5328113 100644 --- a/modules/order/includes/views/commerce_order_ui.views_default.inc +++ b/modules/order/includes/views/commerce_order_ui.views_default.inc @@ -28,8 +28,9 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['access']['type'] = 'perm'; $handler->display->display_options['access']['perm'] = 'view any commerce_order entity'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['pager']['type'] = 'full'; $handler->display->display_options['pager']['options']['items_per_page'] = 50; @@ -239,8 +240,9 @@ function commerce_order_ui_views_default_views() { $handler->display->display_options['access']['type'] = 'perm'; $handler->display->display_options['access']['perm'] = 'view own commerce_order entities'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['pager']['type'] = 'full'; $handler->display->display_options['pager']['options']['items_per_page'] = 25; diff --git a/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc b/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc index c0d238f..51c1e37 100644 --- a/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc +++ b/modules/order/includes/views/handlers/commerce_order_handler_area_order_total.inc @@ -32,7 +32,7 @@ class commerce_order_handler_area_order_total extends views_handler_area { // If it is single value... if (count($argument->value) == 1) { // Load the order. - if ($order = commerce_order_load(reset($argument->value))) { + if ($order = commerce_order_load(reset($argument->value), FALSE)) { // Prepare a display settings array. $display = array( diff --git a/modules/order/tests/commerce_order.test b/modules/order/tests/commerce_order.test index 1164634..08babe5 100644 --- a/modules/order/tests/commerce_order.test +++ b/modules/order/tests/commerce_order.test @@ -121,3 +121,89 @@ class CommerceOrderCRUDTestCase extends CommerceBaseTestCase { $this->assertEqual(token_replace('[commerce-order:changed]', array('commerce-order' => $order)), format_date($order->changed, 'medium'), '[commerce-order:changed] was replaced with the changed date.'); } } + +/** + * Test order locking. + */ +class CommerceOrderLockingTestCase extends CommerceBaseTestCase { + + public static function getInfo() { + return array( + 'name' => 'Order locking', + 'description' => 'Test the order locking.', + 'group' => 'Drupal Commerce', + ); + } + + protected function setUp() { + $modules = parent::setUpHelper('api'); + $modules[] = 'commerce_cart'; + parent::setUp($modules); + } + + /** + * Test releasing order locks. + */ + public function testCommerceOrderReleaseLocking() { + /** @var CommerceOrderEntityController $order_controller */ + $order_controller = entity_get_controller('commerce_order'); + $created_order = $this->createDummyOrder(); + + // The order is locked when loaded from commerce_cart_order_load(), however + // it should not be locked because it was then saved. + $this->assertFalse(commerce_order_is_locked($created_order), 'Generated and saved order is unlocked.'); + + // Ensure that loading locked and unlocked orders works. + $order = commerce_order_load($created_order->order_id); + $this->assertTrue(commerce_order_is_locked($order), 'commerce_order_load() returned an locked order from controller.'); + commerce_order_release_lock($order); + $this->assertFalse(commerce_order_is_locked($order), 'commerce_order_load() returned an unlocked order.'); + + $order = commerce_order_load($created_order->order_id, FALSE); + $this->assertFalse(commerce_order_is_locked($order), 'commerce_order_load() returned an unlocked order.'); + + $locked_order = commerce_order_load($created_order->order_id); + $this->assertTrue(commerce_order_is_locked($locked_order), 'commerce_order_load() returned a locked order.'); + + $order_controller->releaseLocking(); + $this->assertFalse(commerce_order_is_locked($locked_order), 'Order is not locked once all locks released.'); + } + + /** + * Test skip locking. + */ + public function testCommerceOrderSkipLocking() { + /** @var CommerceOrderEntityController $order_controller */ + $order_controller = entity_get_controller('commerce_order'); + $created_order = $this->createDummyOrder(); + + // Ensure that skipLocking() works. + $order_controller->requestSkipLocking(); + $unlocked_order = commerce_order_load($created_order->order_id); + $this->assertFalse(commerce_order_is_locked($unlocked_order), 'commerce_order_load() returned an unlocked order.'); + + // Turn off skipLocking(). + $order_controller->requestSkipLocking(FALSE); + // Our previous loaded order is not considered locked. + $this->assertFalse(commerce_order_is_locked($unlocked_order), 'commerce_order_load() returned a unlocked order.'); + // Re-loading the order will lock it. + $unlocked_order = commerce_order_load($created_order->order_id); + $this->assertTrue(commerce_order_is_locked($unlocked_order), 'commerce_order_load() returned a locked order.'); + } + + /** + * Test controller entity caching. + */ + public function testCommerceOrderControllerCaching() { + $created_order = $this->createDummyOrder(); + + $this->assertTrue(commerce_order_is_cached($created_order)); + $created_order->data['test_cached_data_attribute'] = TRUE; + + $unlocked_cached_order = commerce_order_load($created_order->order_id, FALSE); + $this->assertTrue($unlocked_cached_order->data['test_cached_data_attribute'], 'Requesting read-only order loaded from controller entity cache has same data attribute.'); + + $locked_order = commerce_order_load($created_order->order_id); + $this->assertFalse(isset($locked_order->data['test_cached_data_attribute']), 'Loading a locked order reset controller entity cache for specific item.'); + } +} diff --git a/modules/order/tests/commerce_order_ui.test b/modules/order/tests/commerce_order_ui.test index 9a023f7..d5892d6 100644 --- a/modules/order/tests/commerce_order_ui.test +++ b/modules/order/tests/commerce_order_ui.test @@ -60,12 +60,9 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $this->drupalPost(NULL, array('name' => $this->store_customer->name), t('Save order', array(), array('context' => 'a drupal commerce order'))); // Load the order from database for later use. - $orders = commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid)); + $orders = commerce_order_load_multiple(array(), array('uid' => $this->store_customer->uid, '_lock' => FALSE)); $this->order = reset($orders); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); - // Enable an additional currency. $this->enableCurrencies(array('EUR')); } @@ -150,10 +147,7 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $this->drupalPost(NULL, array(), t('Save order', array(), array('context' => 'a drupal commerce order'))); // Reload the order directly from db. - $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE); - - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); + $order = commerce_order_load_multiple(array($this->order->order_id), array('_lock' => FALSE), TRUE); // Check if the product has been added to the order. foreach (entity_metadata_wrapper('commerce_order', reset($order))->commerce_line_items as $delta => $line_item_wrapper) { @@ -186,13 +180,10 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { $this->drupalPost(NULL, array(), t('Save order', array(), array('context' => 'a drupal commerce order'))); // Reload the order directly from db and wrap it to get the line item ids. - $orders = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE); + $orders = commerce_order_load_multiple(array($this->order->order_id), array('_lock' => FALSE), TRUE); $order = reset($orders); $order_wrapper = entity_metadata_wrapper('commerce_order', $order); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); - // Also wrap the product to access easier to its price. $product_wrapper = entity_metadata_wrapper('commerce_product', $this->product); @@ -264,8 +255,6 @@ class CommerceOrderUIAdminTest extends CommerceBaseTestCase { // Check if the links for editing the order are present. $links = menu_contextual_links('commerce-order', 'admin/commerce/orders', array($this->order->order_id)); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); $this->assertRaw((theme('links', array('links' => $links, 'attributes' => array('class' => array('links', 'inline', 'operations'))))), t('Links for orders are present')); $this->drupalGet('admin/commerce/orders/'. $this->order->order_id . '/view'); diff --git a/modules/payment/commerce_payment.module b/modules/payment/commerce_payment.module index 42cd15a..3d79fa0 100644 --- a/modules/payment/commerce_payment.module +++ b/modules/payment/commerce_payment.module @@ -857,8 +857,8 @@ function commerce_payment_transaction_save($transaction) { /** * Loads a payment transaction by ID. */ -function commerce_payment_transaction_load($transaction_id) { - $transactions = commerce_payment_transaction_load_multiple(array($transaction_id), array()); +function commerce_payment_transaction_load($transaction_id, $lock = TRUE) { + $transactions = commerce_payment_transaction_load_multiple(array($transaction_id), array('_lock' => $lock)); return $transactions ? reset($transactions) : FALSE; } @@ -923,7 +923,7 @@ function commerce_payment_transaction_delete_multiple($transaction_ids) { */ function commerce_payment_transaction_access($op, $transaction, $account = NULL) { if (isset($transaction->order_id)) { - $order = commerce_order_load($transaction->order_id); + $order = commerce_order_load($transaction->order_id, FALSE); if (!$order) { return FALSE; } diff --git a/modules/payment/commerce_payment.tokens.inc b/modules/payment/commerce_payment.tokens.inc index 8513019..da3ec12 100644 --- a/modules/payment/commerce_payment.tokens.inc +++ b/modules/payment/commerce_payment.tokens.inc @@ -212,7 +212,7 @@ function commerce_payment_tokens($type, $tokens, array $data = array(), array $o // Default values for the chained tokens handled below. case 'order': if ($transaction->order_id) { - $order = commerce_order_load($transaction->order_id); + $order = commerce_order_load($transaction->order_id, FALSE); $replacements[$original] = $sanitize ? check_plain($order->order_number) : $order->order_number; } break; @@ -239,7 +239,7 @@ function commerce_payment_tokens($type, $tokens, array $data = array(), array $o } if ($order_tokens = token_find_with_prefix($tokens, 'order')) { - $order = commerce_order_load($transaction->order_id); + $order = commerce_order_load($transaction->order_id, FALSE); $replacements += token_generate('commerce-order', $order_tokens, array('commerce-order' => $order), $options); } diff --git a/modules/payment/commerce_payment_ui.module b/modules/payment/commerce_payment_ui.module index bd728ae..f562686 100644 --- a/modules/payment/commerce_payment_ui.module +++ b/modules/payment/commerce_payment_ui.module @@ -144,7 +144,7 @@ function commerce_payment_ui_payment_transaction_uri($transaction) { // Only return a value if the transaction references an order and the user has // permission to view the payment transaction. - if ($order = commerce_order_load($transaction->order_id)) { + if ($order = commerce_order_load($transaction->order_id, FALSE)) { if (commerce_payment_transaction_access('view', $transaction)) { return array( 'path' => 'admin/commerce/orders/' . $order->order_id . '/payment/' . $transaction->transaction_id . '/view', diff --git a/modules/payment/includes/commerce_payment_transaction.controller.inc b/modules/payment/includes/commerce_payment_transaction.controller.inc index a646770..b3774e5 100644 --- a/modules/payment/includes/commerce_payment_transaction.controller.inc +++ b/modules/payment/includes/commerce_payment_transaction.controller.inc @@ -160,7 +160,7 @@ class CommercePaymentTransactionEntityController extends DrupalCommerceEntityCon */ public function buildContent($transaction, $view_mode = 'administrator', $langcode = NULL, $content = array()) { // Load the order this transaction is attached to. - $order = commerce_order_load($transaction->order_id); + $order = commerce_order_load($transaction->order_id, FALSE); // Add the default fields inherent to the transaction entity. if (!empty($transaction->instance_id) && $payment_method = commerce_payment_method_instance_load($transaction->instance_id)) { diff --git a/modules/payment/includes/views/commerce_payment.views.inc b/modules/payment/includes/views/commerce_payment.views.inc index 0fd5ce0..9a1f81c 100644 --- a/modules/payment/includes/views/commerce_payment.views.inc +++ b/modules/payment/includes/views/commerce_payment.views.inc @@ -17,6 +17,7 @@ function commerce_payment_views_data() { 'title' => t('Commerce Payment Transaction'), 'help' => t('The receipt of a payment transaction.'), 'access query tag' => 'commerce_payment_transaction_access', + 'query class' => 'commerce_views_query', ); $data['commerce_payment_transaction']['table']['entity type'] = 'commerce_payment_transaction'; diff --git a/modules/payment/includes/views/commerce_payment_ui.views_default.inc b/modules/payment/includes/views/commerce_payment_ui.views_default.inc index 61c8bfe..d82b23f 100644 --- a/modules/payment/includes/views/commerce_payment_ui.views_default.inc +++ b/modules/payment/includes/views/commerce_payment_ui.views_default.inc @@ -27,8 +27,8 @@ function commerce_payment_ui_views_default_views() { $handler->display->display_options['access']['type'] = 'perm'; $handler->display->display_options['access']['perm'] = 'administer payments'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; - $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['pager']['type'] = 'none'; $handler->display->display_options['pager']['options']['offset'] = '0'; diff --git a/modules/payment/includes/views/handlers/commerce_payment_handler_field_balance.inc b/modules/payment/includes/views/handlers/commerce_payment_handler_field_balance.inc index 890867e..ca4a20a 100644 --- a/modules/payment/includes/views/handlers/commerce_payment_handler_field_balance.inc +++ b/modules/payment/includes/views/handlers/commerce_payment_handler_field_balance.inc @@ -44,7 +44,7 @@ class commerce_payment_handler_field_balance extends views_handler_field { $order_id = $this->get_value($values, 'order_id'); // Only render this field if we find a valid order. - if (!empty($order_id) && $order = commerce_order_load($order_id)) { + if (!empty($order_id) && $order = commerce_order_load($order_id, FALSE)) { $balance = commerce_payment_order_balance($order); // Output according to the format selected as with price fields. diff --git a/modules/payment/includes/views/handlers/commerce_payment_handler_field_payment_transaction_link_delete.inc b/modules/payment/includes/views/handlers/commerce_payment_handler_field_payment_transaction_link_delete.inc index 368127d..4ef31b8 100644 --- a/modules/payment/includes/views/handlers/commerce_payment_handler_field_payment_transaction_link_delete.inc +++ b/modules/payment/includes/views/handlers/commerce_payment_handler_field_payment_transaction_link_delete.inc @@ -9,7 +9,6 @@ class commerce_payment_handler_field_payment_transaction_link_delete extends com // Ensure the user has access to delete this payment transaction. $transaction_id = $this->get_value($values, 'transaction_id'); $order_id = $this->get_value($values, 'order_id'); - $order = commerce_order_load($order_id); $transaction = commerce_payment_transaction_load($transaction_id); if (commerce_payment_transaction_access('delete', $transaction)) { diff --git a/modules/payment/tests/commerce_payment_ui.test b/modules/payment/tests/commerce_payment_ui.test index b59e657..f62134c 100644 --- a/modules/payment/tests/commerce_payment_ui.test +++ b/modules/payment/tests/commerce_payment_ui.test @@ -143,9 +143,7 @@ class CommercePaymentUITest extends CommerceBaseTestCase { $this->drupalPost(NULL, $post_data, t('Save')); // Reload the order. - $order = commerce_order_load_multiple(array($this->order->order_id), array(), TRUE); - // Reset the cache as we don't want to keep the lock. - entity_get_controller('commerce_order')->resetCache(); + $order = commerce_order_load_multiple(array($this->order->order_id), array('_lock' => FALSE), TRUE); // Check order balance, it should be half of total now. $new_balance = commerce_payment_order_balance(reset($order)); diff --git a/modules/price/includes/views/handlers/commerce_price_handler_field_commerce_price.inc b/modules/price/includes/views/handlers/commerce_price_handler_field_commerce_price.inc index 919b655..e47b522 100644 --- a/modules/price/includes/views/handlers/commerce_price_handler_field_commerce_price.inc +++ b/modules/price/includes/views/handlers/commerce_price_handler_field_commerce_price.inc @@ -4,7 +4,7 @@ * An extension of the default Views field handler that supports aggregating * price fields. */ -class commerce_price_handler_field_commerce_price extends views_handler_field_field { +class commerce_price_handler_field_commerce_price extends commerce_views_handler_field_field { function get_value($values, $field = NULL) { // If this field has aggregation enabled... if (!empty($this->group_fields)) { diff --git a/modules/product/commerce_product.module b/modules/product/commerce_product.module index 76d1c56..34cc41c 100644 --- a/modules/product/commerce_product.module +++ b/modules/product/commerce_product.module @@ -499,12 +499,11 @@ function commerce_product_save($product) { /** * Loads a product by ID. */ -function commerce_product_load($product_id) { +function commerce_product_load($product_id, $lock = TRUE) { if (empty($product_id)) { return FALSE; } - - $products = commerce_product_load_multiple(array($product_id), array()); + $products = commerce_product_load_multiple(array($product_id), array('_lock' => $lock), FALSE); return $products ? reset($products) : FALSE; } @@ -516,10 +515,10 @@ function commerce_product_load($product_id) { * @param bool $reset * Boolean to reset static cache. */ -function commerce_product_load_by_sku($sku, $reset = FALSE) { +function commerce_product_load_by_sku($sku, $reset = FALSE, $lock = TRUE) { $cache = &drupal_static(__FUNCTION__, array()); if (!isset($cache[$sku]) || $reset) { - $products = commerce_product_load_multiple(array(), array('sku' => $sku)); + $products = commerce_product_load_multiple(array(), array('sku' => $sku, '_lock' => $lock)); $cache[$sku] = $products ? reset($products) : FALSE; } diff --git a/modules/product/commerce_product_ui.module b/modules/product/commerce_product_ui.module index 036bae2..251946e 100644 --- a/modules/product/commerce_product_ui.module +++ b/modules/product/commerce_product_ui.module @@ -231,7 +231,7 @@ function commerce_product_ui_help($path, $arg) { return (!empty($product_type['help']) ? '
' . filter_xss_admin($product_type['help']) . '
' : ''); } elseif ($arg[1] == 'commerce' && $arg[2] == 'products' && is_numeric($arg[3])) { - $product = commerce_product_load($arg[3]); + $product = commerce_product_load($arg[3], FALSE); $product_type = commerce_product_type_load($product->type); return (!empty($product_type['help']) ? '' . filter_xss_admin($product_type['help']) . '
' : ''); } diff --git a/modules/product/includes/views/commerce_product.views.inc b/modules/product/includes/views/commerce_product.views.inc index 0231701..4a92270 100644 --- a/modules/product/includes/views/commerce_product.views.inc +++ b/modules/product/includes/views/commerce_product.views.inc @@ -17,6 +17,7 @@ function commerce_product_views_data() { 'title' => t('Commerce Product'), 'help' => t('Products from the store.'), 'access query tag' => 'commerce_product_access', + 'query class' => 'commerce_views_query', ); $data['commerce_product']['table']['entity type'] = 'commerce_product'; diff --git a/modules/product/includes/views/commerce_product_ui.views_default.inc b/modules/product/includes/views/commerce_product_ui.views_default.inc index 680dff1..a75aad4 100644 --- a/modules/product/includes/views/commerce_product_ui.views_default.inc +++ b/modules/product/includes/views/commerce_product_ui.views_default.inc @@ -28,8 +28,9 @@ function commerce_product_ui_views_default_views() { $handler->display->display_options['access']['type'] = 'perm'; $handler->display->display_options['access']['perm'] = 'administer commerce_product entities'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['exposed_form']['options']['reset_button'] = TRUE; $handler->display->display_options['pager']['type'] = 'full'; @@ -197,8 +198,9 @@ function commerce_product_ui_views_default_views() { $handler->display->display_options['access']['type'] = 'perm'; $handler->display->display_options['access']['perm'] = 'administer commerce_product entities'; $handler->display->display_options['cache']['type'] = 'none'; - $handler->display->display_options['query']['type'] = 'views_query'; + $handler->display->display_options['query']['type'] = 'commerce_views_query'; $handler->display->display_options['query']['options']['query_comment'] = FALSE; + $handler->display->display_options['query']['options']['commerce_lock_items'] = FALSE; $handler->display->display_options['exposed_form']['type'] = 'basic'; $handler->display->display_options['pager']['type'] = 'full'; $handler->display->display_options['pager']['options']['items_per_page'] = '50'; diff --git a/modules/product_pricing/commerce_product_pricing.module b/modules/product_pricing/commerce_product_pricing.module index 19a6f46..4ea5021 100644 --- a/modules/product_pricing/commerce_product_pricing.module +++ b/modules/product_pricing/commerce_product_pricing.module @@ -280,7 +280,8 @@ function commerce_product_pre_calculate_sell_prices($from = NULL, $count = 1) { } while ($product_id = $query->fetchField()) { - $product = commerce_product_load($product_id); + // Load the product, no locking is required since we do not have to save it. + $product = commerce_product_load($product_id, FALSE); // If the product is valid for pre-calculation... if (commerce_product_valid_pre_calculation_product($product)) { diff --git a/modules/product_reference/commerce_product_reference.module b/modules/product_reference/commerce_product_reference.module index 59f4d0d..b7119b7 100644 --- a/modules/product_reference/commerce_product_reference.module +++ b/modules/product_reference/commerce_product_reference.module @@ -760,7 +760,7 @@ function commerce_product_reference_field_formatter_prepare_view($entity_type, $ } if ($product_ids) { - $products = entity_load('commerce_product', $product_ids); + $products = commerce_product_load_multiple($product_ids, array('_lock' => FALSE)); } else { $products = array();