So I'm trying to use the event 'before adding a product to cart' to check (1) the sku of the product being added (2) the quantity of products in the commerce order (3) the sku's already within the commerce order. If the conditions return true then I wan't to stop the 'add to cart' process.

In the actions I've set a 'show a message on the site' so I know the conditions are working correctly. I've also added a 'set data value' where I set quantity to 0. But this does nothing. I'm surprised there isn't a condition where you can raise an exception which bombs out the whole process. Surely on an event like 'before adding a product to cart' you expect to be able to raise a 'no'.

Unfortunately I don't really know anything about rules or how commerce integrates with it otherwise I would try and write something.

Can anyone shed any light on the logic of this? Is this a reasonable request?

Thanks

Issue fork commerce-1658788

Command icon Show commands

Start within a Git clone of the project using the version control instructions.

Or, if you do not have SSH keys set up on git.drupalcode.org:

Comments

rszrama’s picture

Status: Active » Fixed

There isn't a clear cut Rules based way to prevent the addition of a product to the cart, because technically modules that know they want to prevent the add to cart should alter the form and disable the submit button (see the Stock module for an example). However, through Rules you should be able to do one of two things:

  1. In addition to displaying the message, try adding an action to redirect to another page and see if it can take place immediately. If you can do that, then just redirect to the current URL the customer is viewing, and it should effectively prevent the Add to Cart.
  2. Otherwise try setting the unit price of the line item to NULL (i.e. use the Set a data value action on commerce-line-item:commerce-unit-price:amount and leave the amount value textfield empty). This may result in the line item being created but it will be deleted on a subsequent load. If that works, maybe we can alter this function so it prevents the creation in the first place if the unit price is nullified.

Please let me know what works (if either).

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

NecroHill’s picture

Status: Closed (fixed) » Active

these 2 solutions do not work for me neither with "Before adding a product to the cart" event.
1. With redirect I see my message together with the "Product xxxxx added to your cart." message and its true - product being added.
2. I receive "Data selector commerce-line-item:commerce-unit-price:amount for parameter data is invalid." error message when I manually add commerce-line-item:commerce-unit-price:amount into Data selector field and click "Continue".

In my case in conditions I check if "Order contains products of particular product types" and if not I'd like to break adding to cart process but I can't.

rszrama’s picture

Status: Active » Closed (won't fix)

Yeah, you can't use data selectors that don't exist for your event. You'd have to use a different event. But it may just not be possible, hence my first recommendation to just use hook_form_alter().

jegaudin’s picture

I struggled quite a bit with this issue so I thought I'll share what I came out with.
Strangely I got the help of the commerce-stock module which is adding a set of handy events and actions to be used in rules.
In my case, I have 2 product types (A & B) and the user should not be allowed to add a product type B if there is already a product type A in his card.
As rszrama said, I found it more logic to alter the "add to cart form" in order to prevent the user from being able to add the "unwanted" products.
So I created the following custom rule :

  • The commerce-stock add the event : "Check if a product add to cart form should be enabled" which is exactly what we want to do.
  • I added 2 conditions inside an "AND" condition set:
    1. Data comparison (to check if the product page is of a product type B) : Parameter: Data to compare: [commerce-product:type], Data value: Product B
    2. "Order contains products of particular product types" and select your other product type.
  • And then the magic comes from the action : "Advanced configuration of the add to cart form", also added by the commerce-stock module. With the following parameters : Parameter: Hide the Quantity field if it is visible: true, The text to set the action to: not compatible with..., Add a class to the add to cart form: no-eshop, Disable the add to cart?: true, Clear the submit array first: false, Clear the validation array first: false

Of course, another similar rule need to be created if a user should not be able to add product type A to cart if he has already added product type B.

MD3’s picture

Status: Closed (won't fix) » Active

I'm sorry to reopen this, but I think I have found several situations in which hook_form_alter() can be bypassed.

  1. Create two products
  2. Create a basic hook_form_alter() that disables the add to cart button if the cart is not empty.
  3. Open both products in separate tabs
  4. Go to product A tab; Add product A
  5. Go to product B tab; Add product B

Product B can still be added to the cart because it was rendered before the cart had an item in it and there is no rule to stop it from being added to the cart when the user clicks on add to cart.

Further complicating this is that as far as I can tell, commerce_services is not using Form API to create carts.

When I look at the code for commerce_cart_product_add() I do not see a way to modify the $order or $line_item using the rules_invoke_all('commerce_cart_product_add', ... ) hook because $order and $line_item are not passed by reference (Sorry I can not provide line numbers: I get errors when trying to view this file in anything but raw format). (As a quick aside, I do not understand how the commerce services API commerce_services_set_field_values() function works because those items are not being passed by reference either).

My current work-around is to add a custom validation function in my hook_form_commerce_cart_add_to_cart_form_alter() that loads the order and implements the custom logic for my client. For those of you less familiar with Drupal:

/**
  * Implementation of hook_form_FORMID_alter().
  */
function custom_module_form_commerce_cart_add_to_cart_form_alter(&$form, &$form_state) {
  $form['#validate'][] = 'custom_module_cart_add_to_cart_form_validate';
}

/**
  * Custom validation function to implement your business logic.
  */
function custom_module_cart_add_to_cart_form_validate($form, &$form_state) {
  // Custom business logic here:
  if (...) {
    form_set_error('submit', t('This is the error that will be shown to the user.'));
  }
}

In order to make this more robust and fix both issues at the same time, I see two possible strategies moving forward:

  • Allow Rules to deny adding an item to cart
  • Allow a hook in the add to cart process to deny adding an item to cart

Are there any other possible suggestions / ideas? At the end of the day, how is this any different than the Drupal Commerce mantra of products being separate from displays. Business logic should be separate from the displays for the exact same reasons product variations are. If we don't fix this, we're subjecting iPhone / Android / JavaScript / 3rd Party developers to understand and maintain business logic that should be handled by the server and creating holes for customers to bypass our business logic.

anybody’s picture

I also required the functionality several times. Here's a clear request for a new rule :) That about a feature request in commerce issues?

MD3’s picture

@Anybody I guess you're right! This is a feature request. Should we open a new issue?

anybody’s picture

Yes, please!

rszrama’s picture

@MD3 re: your hook_form_alter() problems, the issue is that you just need to add a validate handler to the Add to Cart form. In the core Add to Cart form, it prevents an Add to Cart if the product does not have a valid unit price, so another thing you could do would be to use a Rule that unsets the unit price for the "do not add" condition.

iwant2fly’s picture

Just wanted to say that jegaudin's #5 response worked great. It would be nice if it were a bit more strait forward, but this works for now.

kenorb’s picture

Channel Islander’s picture

@jegaudin Your instruction were perfect. Clear, detailed, and ACCURATE. Thank you.

I have an issue with the Event "Check if a product add to cart form should be enabled (is in stock)" not getting evaluated on one of my Product Types, but am able to work around it by evaluating the test both ways in one Rule, thanks to Conditional Rules (which allows you to put conditions inside the Action of a Rule).

torgospizza’s picture

I was hoping something like this was doable. We sell mostly digital products, and even with a Rule that disables the Add to Cart form when a product is in the cart already, it is still possible for a user to double-click that button before that case, which could result in the addition of multiples of the same product. (Note that we are bypassing the default Product Display system here; I have exposed Product entities' cart forms in a Views block.)

Looking at the Cart API docs, it's obvious that the Commerce developers realized this is a limitation; unfortunately there is no solution. From hook_commerce_cart_product_prepare():

Rules event hook: allows modules to operate prior to adding a product to the cart but does not actually allow you to interrupt the process. Invoking this Rules event / hook does not result in the processing of any return value, so it is not useful for interrupting a cart product add operation outside of a redirect.

So.. what DOES allow you to interrupt the process? Anything? Again, the issue here is in addition to "prevent the Add to Cart button from working" since that is something we already have implemented. I want to introduce a simple Rule *simpler than the one in #12, which seems to be doing way too much) that just says "Before a product is added to the cart, if it meets certain conditions, prevent it from being added."

Am I missing an obvious solution that already exists, or does a new API method need to be introduced that DOES return a value which prevents cart-addition from happening?

rszrama’s picture

Version: 7.x-1.3 » 7.x-1.x-dev

You'd basically just need a new event in commerce_cart_add_to_cart_form_validate(), perhaps called "Validating an Add to Cart form." At that point, if you did a redirect to the current page, it would prevent the Add to Cart submit handler from taking place. I think there might be some "force immediately" toggle or something for the action you'd have to take.

The problem is there isn't an equivalent for a validate callback's boolean response in Rules actions. You don't have a return value out of a normal event invocation, so the terminal behavior would have to be a redirect to the current path (or some other path if you preferred, naturally).

MegaphoneJon made their first commit to this issue’s fork.

megaphonejon’s picture

[I apparently just made a commit via a comment? Someone please remove that.]

I'm a little late to this party, but if anyone else has this issue, here's some code I wrote to remove a line item from an order. Use this with the "After saving a new commerce line item" event. I wish you could prevent it being added on presave but the requisite data isn't available at that time.

function wrltweaks_rules_action_info() {
  return [
    'wrltweaks_remove_line_item_from_order' => [
      'label' => t('Remove item from order'),
      'group' => t('Commerce Line Item'),
      'parameter' => [
        'commerce_line_item' => [
          'type' => 'commerce_line_item',
          'label' => t('Line item'),
        ],
        'commerce_order' => [
          'type' => 'commerce_order',
          'label' => t('Order'),
        ],
      ],
    ],
  ];
}

function wrltweaks_remove_line_item_from_order($lineItem, $order) {
  $lineItemId = $lineItem->line_item_id;
  commerce_cart_order_product_line_item_delete($order, $lineItemId, TRUE);
}