Commerce Commerce 1.x Upgrades v0.11
Most upgrades are automatically applied, but sometimes you may need to update custom templates, or status configuration, to take advantage of new features or improvements. Those changes are discussed here.
Most changes listed in this migration document are optional.
Table of Contents
- Product Images
- Shipping Methods (Shipments)
- Continuing off-site payments with POST redirect
- Automatically select first shipping/payment method
- Configurable email header and footer
- Shipping method pre-selected in cart
- Taxed shipping costs
- Item-specific links
- You have one items in cart
- (0.11.1) Field errors repeated above the address form
- (0.11.1) New AcceptTerms module
- (0.11.3) Fix label in billing address fields
- (0.11.4) Note: get_product now looks at the first valid product
Product Images
Commerce 0.11 saw the release of primary product images. This allows you to add one image to products to show it in the cart/checkout.
For this purpose, the default template frontend/checkout/cart/items.twig
was slightly updated.
- Around line 6, a new empty header was added:
<th id="c-cart-header-image"> </th>
- Around line 16, the colspan wrapping the cart update button was changed from 5 to 6.
- Around line 24, the following column was added to show the image:
<td class="c-cart-item-image">
{% if item.image %}
<img src="{{ item.image }}" alt="{{ item.name }}" onerror="this.style.display = 'none';">
{% endif %}
</td>
If you’re using Resource Products, a new commerce.resourceproduct.image_field
system setting allows you to connect an image TV to the Commerce products table.
If you have a custom product type that does not inherit the fields from comProduct, look at core/c../c../model/commerce/comproduct.class.php
around line 299 for the field definition used in the standard commerce form.
For custom product types that fetch information from a different table/source look at comresourceproduct.class.php
around line 118 for the new getImage()
method you can also override in your product type. Make sure to also update your synchronise method.
Shipping Methods (Shipments)
In v0.11 we also refactored the built-in shipping methods to base calculations on the order shipments, instead of the full order. This applies to both the pricing and availability.
This was announced in 0.8 and is now complete. While it should be backwards compatible, please verify any custom shipping methods still work as expected.
Continuing off-site payments with POST redirect
If the client for some reason finds themselves back on the checkout while the transaction isn’t confirmed/failed/cancelled yet, there was previously a redirect link to continue. This however does not work when the gateway requires a POST-style redirect (e.g. Adyen).
To correct this, the frontend/checkout/pending-transaction.twig
was updated in 0.11 to allow such transactions to continue as well. Around line 7, change the following:
{% if transaction.properties.redirectUrl %}
<a class="c-button" href="{{ transaction.properties.redirectUrl }}">{{ lex('commerce.transaction_pending_to_gateway', { method: method.name }) }}</a>
{% endif %}
to:
{% if transaction.properties.redirectUrl %}
<form method="POST" action="{{ current_url }}" class="commerce-pending-transaction-form commerce-pending-transaction-form-retry">
<input type="hidden" name="retry" value="1">
<button class="c-button" type="submit">{{ lex('commerce.transaction_pending_to_gateway', { method: method.name }) }}</button>
</form>
{% endif %}
For AJAX request handling in the checkout, you’ll need to make sure to look at the response.redirectMethod
and response.redirectData
to also support these redirects.
Automatically select first shipping/payment method
Small but useful tweak in frontend/checkout/shipping-method.twig
to autoselect the first shipping method automatically if none were previously selected.
Around line 43 change:
{% if shipment.method == method.id %}checked="checked"{% endif %}
to:
{% if shipment.method == method.id %}
checked="checked"
{% elseif shipment.method < 1 and loop.first %}
checked="checked"
{% endif %}
Similary in frontend/checkout/payment-method.twig
around line 14 add:
{% if loop.first %}checked="checked"{% endif %}
to the input with name choose_payment_method
. With some new formatting applied, that should now look like:
<input type="radio"
name="choose_payment_method"
class="c-method-radio c-payment-method-radio"
id="payment-method-{{ method.id }}"
value="{{ method.id }}"
{% if loop.first %}checked="checked"{% endif %}
>
Configurable email header and footer
0.11 introduces 2 new settings, commerce.email_header_url
and commerce.email_footer_text
, which automatically adds an image heading and the email footer text from a setting, without having to change the email templates.
If you have a custom emails/wrapper.twig
template, you’ll want to add the following to support the new header image around line 116 (instead of the previously commented out code):
{% if config.email_header and config.email_header|length > 0 %}
<tr>
<td bgcolor="#ffffff" align="center">
<!--[if (gte mso 9)|(IE)]>
<table align="center" border="0" cellspacing="0" cellpadding="0" width="500">
<tr>
<td align="center" valign="top" width="500">
<![endif]-->
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 500px;" class="wrapper">
<tr>
<td align="center" valign="top" style="padding: 15px 0;" class="logo">
<a href="{{ config.site_url }}" target="_blank">
<img alt="{{ config.site_name }}" src="{{ config.email_header }}" width="500" height="120" style="display: block; font-family: Helvetica, Arial, sans-serif; color: #ffffff; font-size: 16px;" border="0">
</a>
</td>
</tr>
</table>
<!--[if (gte mso 9)|(IE)]>
</td>
</tr>
</table>
<![endif]-->
</td>
</tr>
{% endif %}
For the footer text, around line 150 which previously only had {{ config.site_name }}
, add the following:
{% if config.email_footer|length > 0 %}
{{ config.email_footer|raw }}
{% else %}
{{ config.site_name }}
{% endif %}
Shipping method pre-selected in cart
Commerce will now pre-select the first available shipping method for an order, potentially as soon as the cart. To show the shipping method correctly, you may want to update your frontend/checkout/cart/totals.twig
template around line 78.
Previous:
{% if order.shipping != 0 %}
{% set totalIndex = totalIndex + 1 %}
<tr class="{% if totalIndex is odd %}c-cart-total-item{% else %}c-cart-total-item-even{% endif %}">
<th class="c-cart-totals-label c-cart-totals-label-shipping">
{{ lex('commerce.shipping') }}
</th>
<td class="c-cart-totals-shipping">
{{ order.shipping_formatted }}
</td>
</tr>
{% endif %}
New:
{% for shipment in shipments %}
{% if shipment.method.id > 0 %}
{% set totalIndex = totalIndex + 1 %}
<tr class="{% if totalIndex is odd %}c-cart-total-item{% else %}c-cart-total-item-even{% endif %}">
<th class="c-cart-totals-label c-cart-totals-label-shipping c-cart-totals-label-shipment">
{{ shipment.method.name }}
</th>
<td class="c-cart-totals-value c-cart-totals-shipping">
{{ shipment.fee_formatted }}
</td>
</tr>
{% endif %}
{% endfor %}
This functionality looks at the sort order of the shipping methods, so make sure you have them in the right order under Configuration > Shipping methods.
Taxed shipping costs
0.11 now applies tax rules to the shipping costs. Because of this we’re reordered a few elements in frontend/checkout/cart/totals.twig
and frontend/checkout/partial/summary.twig
to make sure the numbers are in the order people would expect.
In frontend/checkout/cart/totals.twig
, the subtotal block was removed from the {% if order.discount != 0 %}
conditional around line 5 and the related {% else %}
around line 32, to be placed ahead of that entire conditional around line 4 instead.
The shipments block around line 78 was moved to after the subtotal block, and before the discount conditional, around line 13.
In frontend/checkout/partial/summary.twig
, the shipments block was moved from around line 84 to around line 65 (before the {% if order.tax != 0 %}
conditional), and an extra check was added to show the fee inclusive or exclusive of VAT, like so:
{% for shipment in shipments %}
{% if shipment.method.id > 0 %}
<tr class="c-cart-summary-totals-row-shipping c-cart-summary-totals-row-shipment">
<th class="c-cart-summary-totals-label c-cart-summary-totals-label-shipping c-cart-summary-totals-label-shipment">
{{ shipment.method.name }}
</th>
<td class="c-cart-summary-totals-value c-cart-summary-totals-shipping">
{% if tax_exclusive %}
{{ shipment.fee_ex_tax_formatted }}
{% else %}
{{ shipment.fee_incl_tax_formatted }}
{% endif %}
</td>
</tr>
{% endif %}
{% endfor %}
As Commerce 0.11 has had quite a few shipping-related template tweaks, it may be worthwhile to grab the default templates and to re-apply any tweaks you made if you get stuck following these instructions.
Item-specific links
When an order item is created, it sets the item’s link
value to the result of product->getLink
. But in frontend/checkout/cart/items.twig
it was still delegating to product->getLink
(item.product.link
) instead of the saved value (item.link
).
0.11 ships with a new ItemData module that can set the link on standard products (which don’t normally have that, as a single product could be used on different resources; vs a resource product type that has the 1-1 connection). This 0.11 module can be used to set the item’s link without the product having an associated link, so this issue finally became obvious.
If you plan to use the item-specific links, edit your theme’s frontend/checkout/cart/items.twig
template to have:
{% if item.link %}
<a href="{{ item.link }}" title="{{ item.name }}">{{ item.name }}</a>
{% else %}
<b>{{ item.name }}</b>
{% endif %}
instead of
{% if item.product.link %}
<a href="{{ item.product.link }}" title="{{ item.name }}">{{ item.name }}</a>
{% else %}
<b>{{ item.name }}</b>
{% endif %}
The default checkout summary doesn’t currently have links, but if you do have that included in your own theme, make sure to also update it there.
You have one items in cart
In rc2 we changed the frontend/checkout/cart.twig template to distinguish between having one or multiple items in the cart, to improve the linguistics of the translations.
Change the header to:
<h2>
{% if order.total_quantity != 1 %}
{{ lex('commerce.cart_header', { 'items': order.total_quantity }) }}
{% else %}
{{ lex('commerce.cart_header_single') }}
{% endif %}
</h2>
Note that not all translations will have been updated when rc2 was released, so it’s possible you’ll see the multiple in your own language, but the single in English.
(0.11.1) Field errors repeated above the address form
The frontend/response-messages.twig
template shows the errors for the checkout, however on the address validation it would also show individual field errors.
In 0.11.1 the template was adjusted to the following to skip field-specific errors with the assumption that they are shown inline with the form:
{% if response.message|length %}
<div class="c-cart-message">
<p>{{ response.message }}</p>
</div>
{% endif %}
{% if response.errors|length > 0 %}
{% for error in response.errors %}
{% if error.field == '' %}
<div class="c-cart-error">
{{ error.message }}
</div>
{% else %}
{#
Field errors are usually handled in the form, but if you want them to be shown at the top, you can use something like this:
<div class="c-cart-error">
{{ lex('commerce.address.' ~ error.field) }}: {{ error.message }}
</div>
#}
{% endif %}
{% endfor %}
{% endif %}
(0.11.1) New AcceptTerms module
A new AcceptTerms module was added in 0.11.1 which, when enabled, requires the following code to be added to the frontend/checkout/payment-method.twig
template. Where is up to you, but usually right before the submit button (line 45) is a good place.
{% if module_accept_terms_enabled %}
<label class="c-accept-terms">
<input type="checkbox" name="accept_terms" value="1" required>
{{ lex('commerce.module.acceptterms.text', {url: module_accept_terms_url}) }}
</label>
{% endif %}
(0.11.3) Fix label in billing address fields
The for
attribute on the VAT Registration billing address field was wrong.
If you have a custom frontend/checkout/partial/billing-address-fields.twig
template, change the following on line 24:
<label for="address-billing-company">{{ lex('commerce.vat_registration') }} <small>{{ lex('commerce.address.optional') }}</small></label>
to:
<label for="address-billing-vat-registration">{{ lex('commerce.vat_registration') }} <small>{{ lex('commerce.address.optional') }}</small></label>
(0.11.4) Note: get_product now looks at the first valid product
Previously the commerce.get_product
snippet only looked at the very first product ID listed. If that product was removed or invalid, it would not return any product at all. Now, the snippet will use all product IDs to find the first option that still exists.
In normal usage, this should not cause any changes in behaviour, but it’s worth double checking your catalog to make sure it will still behave the way you expect it to.