Skip to content

Release Notes

Information

Releases use the following numbering system: {major}.{minor}.{incremental}

  • major: Major refactoring or rewrite - make sure you read and test very carefully!
  • minor: Breaking change in some circumstances, or a new feature. Read carefully and make sure you understand the impact of the change.
  • incremental: A "safe" change / improvement. Should always be safe to upgrade.

  • [BC]: Items marked with [BC] indicate a breaking change that will require updates to your code if you are using that code in your extension.

1.5.0 (unreleased)

  • Cart access is now a row-level ACL on cart ownership instead of a constraint bolted onto CartItem.delete. A cart is reachable if you own it - by contact_id when logged in, by session_id when anonymous - and it is still open; administer CiviCRM data reaches every cart. The rule now covers get and update as well as delete, on Cart, CartItem and CartItemDiscount.
  • This fixes three things the 1.4.0 constraint got wrong, all of which failed by deleting nothing and reporting success: server-side calls passing checkPermissions => FALSE were scoped too (so cron, queue workers and any headless test silently removed nothing); no administrator could act on another contact's cart, because everyone holding "access shopping cart" was scoped to their own; and CartItem.get/update were not scoped at all, so any holder of the permission could read or modify any cart item by id.
  • access shopping cart is now declared implied_by administer CiviCRM data, so a data administrator no longer has to be granted it explicitly. Requires CiviCRM 5.71+, well below the 6.16 minimum.
  • Add an index on Cart.session_id. The row-level ACL matches an anonymous visitor's carts on that column, which was a full table scan of civicrm_cart - a table that grows with every abandoned cart. contact_id needs nothing; its foreign key already indexes it.
  • Security: Cart.submit now refuses a cart the caller cannot reach. It reads the cart with permissions off - submitting has to see the whole cart whoever its items are for - so the row-level ACL never applied, and any holder of "access shopping cart" could submit another contact's open cart by id, creating an Order, Participants and Memberships billed to that contact and consuming their discount codes. Present since carts were introduced, not just in 1.4.0.
  • Security [BC]: a cart item may now only name a contact the user is allowed to act for. Reaching your own cart was not previously a constraint on whose item it was, so any holder of "access shopping cart" could add an item for an arbitrary contact - or re-attribute an existing one - and on submit that contact received a Participant or Membership. The rule is core's own: your own contact, "edit all contacts", a contact ACL, or a permissioned relationship, which is what keeps buying for an employee or family member working. Sites that relied on naming unrelated contacts will need a permissioned relationship, or administer CiviCRM data.
  • Security: Cart.submit no longer accepts a contactID from an untrusted caller unless they may act for that contact. An anonymous cart carries no contact, so the caller had to supply one - and an unchecked id meant an anonymous visitor could raise the Order against any contact they guessed. afformShoppingCartCheckout now records the contact on the cart itself during submission, so anonymous checkout is unaffected.
  • Security: the "View My Cart" table no longer sets "Bypass permissions". That setting disabled permission checks for the table's own query and for its inline editing, and SearchDisplay.inlineEdit writes the row id straight from the request - so any user of the public cart form could re-attribute any cart item in the database by id. The cart_id filter this extension injects did not cover it, applying only to SearchDisplay.run. The row-level ACL now scopes the table instead, which needs no bypass; docs/setup.md previously said the bypass was required, and no longer does.

1.4.0 (2026-08-28)

  • Requires CiviCRM 6.16 (was 6.15).
  • Replace the afform_payments dependency with core's civi_contribute. afform_payments was merged into core and is obsolete from 6.14.
  • Cart.Submit can create a ContributionRecur alongside the Contribution - set contributionRecurValues on the SubmitCartEvent. See docs/cartsubmission.md. [BC] SubmitCartEvent::__construct() takes a fourth $contributionRecurValues argument; update any code constructing the event directly. SubmitCartEvent::trigger() defaults it, so those callers are unaffected.
  • New civi.shoppingcart.addcartitems event, dispatched by CartItems::add() before items are persisted, so a listener can append or modify them. PriceFieldValue.addToCart and Afform submission both route through it. See docs/cartitemadd.md.
  • PriceFieldValue.addToCart adds one cart item per selected price option. A price set attached to several events previously produced one item per event.
  • Submitting an empty cart now fails instead of creating a zero-amount pending Contribution and closing the cart.
  • CartItem.delete is now constrained to the requester's own open cart. A user holding "access shopping cart" could previously delete another user's cart items by id.
  • Add the missing ": " between the event title and the description on Participant cart items.
  • Add a PHPUnit test suite: CIVICRM_UF=UnitTests phpunit9 tests/phpunit.
  • Correct the ContributionRecur docs: once contributionRecurValues is set, frequency_unit is mandatory and financial_type_id must be a numeric id.
  • Fix a broken image path in the discounts docs, and add the "Adding cart items" page to the docs nav.
  • Rewrite README.md, which still described cart submission and payment as unimplemented, and add the standard "Support and Maintenance" section to it and to the docs home page.
  • Coding-standard fixes and missing docblock params throughout, and the array return type on the last getSubscribedEvents() that was missing it - without which the extension is fatal on a Symfony 7 core.

1.3.0 (2026-08-05)

  • Add support for anonymous (not logged in) users. The cart is tracked via a session_id field when there is no logged in contact, instead of contact_id. [BC] CRM_Shoppingcart_BAO_Cart::getCurrentCart() has been renamed to getCurrentOpenCartID() - update any code calling it directly.
  • Cart.Submit API now accepts a contactID param, for submitting an anonymous cart once a contact has been identified/created (eg. via an Afform submit handler).
  • The cart_id filter automatically applied to the "View My Cart" SearchDisplay is now configurable via the new shoppingcart_cart_display_names setting, instead of being hardcoded to View_My_Cart_Table. See docs/setup.md.
  • Fix mapping of related entities (Participant/Event, Membership/MembershipType) so entity_table/entity_id and the Event title are looked up correctly.
  • Don't apply discounts to zero-cost lineitems.
  • Show full error details on the frontend when Cart.Submit fails.

1.2.1 (2026-02-18)

  • Fix setting unit_price/line_total when there is no discount and quantity > 1. This changes CartItem.discounted_amount to CartItem.discounted_line_total and CartItem.discounted_unit_price - you need to update your searches!
  • Increase priority of validate listener (for compatibility with Formbuilder Payments).
  • Improve handling of lineitems.
  • Add CiviDiscount dependency.