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 4612d4d..b29ddb9 100644 --- a/includes/commerce.controller.inc +++ b/includes/commerce.controller.inc @@ -68,6 +68,8 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple /** * Stores our transaction object, necessary for pessimistic locking to work. + * + * @var DatabaseTransaction */ protected $controllerTransaction = NULL; @@ -75,6 +77,8 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple * Stores the ids of locked entities. * * Necessary for knowing when to release lock by committing the transaction. + * + * @var array */ protected $lockedEntities = array(); @@ -82,6 +86,8 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple * 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; @@ -90,6 +96,8 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple * * If locking has been requested as well it will take preference and the * entity load will default to locking. + * + * @var bool */ protected $requestSkipLocking = FALSE; @@ -147,7 +155,7 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple */ protected function releaseLocks() { if (empty($this->lockedEntities)) { - unset($this->controllerTransaction); + $this->controllerTransaction = NULL; } } @@ -157,7 +165,7 @@ class DrupalCommerceEntityController extends DrupalDefaultEntityController imple * on all entities at once, although be cautious with this. */ public function releaseLocking() { - unset($this->controllerTransaction); + $this->controllerTransaction = NULL; $this->lockedEntities = array(); } 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/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 889f407..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))))) { 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/payment/commerce_payment.module b/modules/payment/commerce_payment.module index 42cd15a..f916e26 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; } 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/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/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';