Skip to content

Commit 65a6166

Browse files
committed
Merge branch 'release/4.3.0' into main
2 parents 64f9d46 + 6c7ca13 commit 65a6166

File tree

147 files changed

+6424
-1439
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

147 files changed

+6424
-1439
lines changed

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ on:
55
branches:
66
- develop
77
- v3
8-
- '4.2'
8+
- '4.3'
99
pull_request:
1010
permissions:
1111
contents: read

CHANGELOG.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,41 @@
11
# Release Notes for Craft Commerce
22

3+
## 4.3.0 - 2023-09-13
4+
5+
- Sales and discounts now support using related entries in their matching item conditions. ([#3134](https://github.com/craftcms/commerce/issues/3134), [#2717](https://github.com/craftcms/commerce/issues/2717))
6+
- It’s now possible to query products by shipping category and tax category. ([#3219](https://github.com/craftcms/commerce/issues/3219))
7+
- Guest customers registering during checkout now have their addresses saved to their account. ([#3203](https://github.com/craftcms/commerce/pull/3203))
8+
- Product conditions can now have “Product Type”, “Variant SKU”, “Variant Has Unlimited Stock”, “Variant Price”, and “Variant Stock” rules. ([#3209](https://github.com/craftcms/commerce/issues/3209))
9+
- Improved the performance of discount recalculation.
10+
- Improved the performance of the `commerce/upgrade` command. ([#3208](https://github.com/craftcms/commerce/pull/3208))
11+
- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206))
12+
- The `commerce/cart/update-cart` action now accepts `firstName` and `lastName` address parameters. ([#3015](https://github.com/craftcms/commerce/issues/3015))
13+
- Added `craft\commerce\controllers\OrdersController::EVENT_MODIFY_PURCHASABLES_TABLE_QUERY`. ([#3198](https://github.com/craftcms/commerce/pull/3198))
14+
- Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138))
15+
- Added `craft\commerce\elements\db\ProductQuery::$shippingCategoryId`.
16+
- Added `craft\commerce\elements\db\ProductQuery::$taxCategoryId`.
17+
- Added `craft\commerce\elements\db\ProductQuery::shippingCategory()`.
18+
- Added `craft\commerce\elements\db\ProductQuery::shippingCategoryId()`.
19+
- Added `craft\commerce\elements\db\ProductQuery::taxCategory()`.
20+
- Added `craft\commerce\elements\db\ProductQuery::taxCategoryId()`.
21+
- Added `craft\commerce\models\Discount::hasBillingAddressCondition()`.
22+
- Added `craft\commerce\models\Discount::hasCustomerCondition()`.
23+
- Added `craft\commerce\models\Discount::hasOrderCondition()`.
24+
- Added `craft\commerce\models\Discount::hasShippingAddressCondition()`.
25+
- Deprecated payment source creation via the `commerce/subscriptions/subscribe` action.
26+
- Deprecated `craft\commerce\elements\Order::setEmail()`. `Order::setCustomer()` should be used instead.
27+
- Removed the `htmx` option from the`commerce/example-templates` command.
28+
- Removed the `color` option from the`commerce/example-templates` command.
29+
- Added `craft\commerce\events\ModifyPurchasablesTableQueryEvent`. ([#3198](https://github.com/craftcms/commerce/pull/3198))
30+
- Fixed a bug where products/variants could be saved with a minimum quantity that was set higher than the maximum quantity. ([#3234](https://github.com/craftcms/commerce/issues/3234))
31+
- Fixed a bug where `craft\commerce\elements\Order::hasMatchingAddresses()` could incorrectly return `false`. ([#3183](https://github.com/craftcms/commerce/issues/3183))
32+
- Fixed a bug where changing a user’s email could cause additional user elements to be created. ([#3138](https://github.com/craftcms/commerce/issues/3138))
33+
- Fixed a bug where related sales were displaying when creating a new product.
34+
- Fixed a bug where Commerce wasn’t invoking `craft\services\Elements::EVENT_AUTHORIZE_*` event handlers.
35+
- Fixed a bug where discounts’ per user usage counters weren’t getting migrated properly when upgrading to Commerce 4.
36+
- Fixed a bug where address changes weren’t being synced to carts that were using them. ([#3178](https://github.com/craftcms/commerce/issues/3178))
37+
- Fixed an XSS vulnerability.
38+
339
## 4.2.11 - 2023-06-05
440

541
- Fixed a bug where “Send Email” option text wasn’t getting translated. ([#3172](https://github.com/craftcms/commerce/issues/3172))

example-templates/dist/shop/_private/address/fields.twig

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
{#
33
Outputs address form fields for editing an address.
44
#}
5+
{% set showPrimaryCheckboxes = showPrimaryCheckboxes is defined ? showPrimaryCheckboxes : false %}
56
{% set addressFieldLayout = craft.app.getAddresses().getLayout() %}
67
{% set addressCustomFields = addressFieldLayout.getCustomFields()|filter(f => className(f) == 'craft\\fields\\PlainText') %}
78
{# @var address \craft\elements\Address #}
@@ -193,9 +194,34 @@ Outputs address form fields for editing an address.
193194
{% endfor %}
194195
</div>
195196
{% endif %}
197+
198+
{% if showPrimaryCheckboxes %}
199+
<hr class="my-2">
200+
<div class="my-2">
201+
{{ input('text', 'isPrimaryBilling', address.isPrimarybilling ? 1 : 0) }}
202+
<label>{{ input('checkbox', 'isPrimaryBillingBox', 1, { checked: address.isPrimaryBilling, 'data-primary-input': 'isPrimaryBilling' }) }} {{ 'Use as the primary billing address'|t('commerce') }}</label>
203+
</div>
204+
<div class="my-2">
205+
{{ input('text', 'isPrimaryShipping', address.isPrimaryShipping ? 1 : 0) }}
206+
<label>{{ input('checkbox', 'isPrimaryShippingBox', 1, { checked: address.isPrimaryShipping, 'data-primary-input': 'isPrimaryShipping' }) }} {{ 'Use as the primary shipping address'|t('commerce') }}</label>
207+
</div>
208+
{% endif %}
196209
</div>
197210

198211
{% js %}
212+
{% if showPrimaryCheckboxes %}
213+
document.querySelectorAll('input[type=checkbox][data-primary-input]').forEach(el => {
214+
el.addEventListener('change', ev => {
215+
let primaryInput = document.querySelector(`input[name="${ev.target.dataset.primaryInput}"]`);
216+
if (ev.target.checked) {
217+
primaryInput.value = 1;
218+
} else {
219+
primaryInput.value = 0;
220+
}
221+
});
222+
});
223+
{% endif %}
224+
199225
document.querySelector('select#{{ 'countryCode'|namespaceInputId(addressName) }}').addEventListener('change', ev => {
200226
const countryCode = ev.target.value;
201227
const stateSelect = document.querySelector('select#{{ 'administrativeArea'|namespaceInputId(addressName) }}');

example-templates/dist/shop/_private/address/fieldset.twig

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati
99
@var title string
1010
@var currentUser \craft\elements\User
1111
#}
12+
{% set sourceAttribute = 'source' ~ (name|slice(0, 1)|capitalize) ~ (name|slice(1) ~ 'Id') %}
1213
<div class="js-address-fieldset {{ classes }}">
1314
<h2 class="text-lg font-bold mb-4">
1415
{{- title -}}
@@ -19,22 +20,24 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati
1920
selectable: true,
2021
primaryBillingAddressId: cart and cart.customer ? cart.customer.primaryBillingAddressId : null,
2122
primaryShippingAddressId: cart and cart.customer ? cart.customer.primaryShippingAddressId : null,
23+
showAdd: true,
2224
}) }}
2325

24-
<div class="js-address-select">
26+
<div class="js-address-select" data-model-name="{{ name }}">
2527
{% if attribute(cart, name ~ 'Id') %}
2628
{% set addressHasErrors = attribute(cart, name) and attribute(cart, name).hasErrors() %}
2729
{% else %}
2830
{% set addressHasErrors = false %}
2931
{% endif %}
3032

31-
{% if currentUser %}
33+
{# Show the custom toggle if there is a custom address on the order #}
34+
{% if currentUser and cart and attribute(cart, name ~ 'Id') and not attribute(cart, sourceAttribute) %}
3235
<div class="js-radio">
3336
<label>
3437
{{ input('radio', name ~ 'Id', '', {
35-
checked: not addresses|length or addressHasErrors
38+
checked: true
3639
}) }}
37-
{{ 'New {title}'|t({ title: title }) }}
40+
{{ 'Custom {title}'|t({ title: title }) }}
3841
</label>
3942
</div>
4043
{% endif %}

example-templates/dist/shop/_private/address/list.twig

Lines changed: 62 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -2,72 +2,74 @@
22
{% set selectable = selectable ?? false %}
33
{% set primaryBillingAddressId = primaryBillingAddressId ?? null %}
44
{% set primaryShippingAddressId = primaryShippingAddressId ?? null %}
5-
{% set cardWidth = cardWidth ?? 'md:w-1/2' %}
65
{% set showDelete = showDelete ?? false %}
6+
{% set showAdd = showAdd ?? false %}
7+
{% set addUrl = '/shop/customer/addresses/edit?redirect=' ~ craft.app.request.fullPath %}
78

8-
{% if addresses and currentUser %}
9-
<div class="md:flex md:flex-wrap md:-mx-2 pb-4">
10-
{% for address in addresses %}
11-
{% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %}
12-
<div class="pb-2 my-4 md:px-2 {{ cardWidth }} md:my-0">
13-
{% tag selectable ? 'label' : 'div' with {
14-
class: 'block relative address-select js-address-select border-blue-300 border-b-2 px-6 py-4 rounded-md shadow-md hover:shadow-lg',
15-
data: {
16-
'address-id': address.id,
17-
},
18-
} %}
19-
<span class="js-radio flex py-2">
20-
{% if selectable %}
21-
{{ input('radio', name ~ 'Id', address.id, {
22-
checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)),
23-
}) }}
24-
{% endif %}
25-
<span class="-mt-2 ml-3 mb-2">
26-
{{ address|address }}
27-
</span>
28-
</span>
29-
<span class="block mb-1">
30-
<a href="{{ url(editUrl) }}" class="cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white">
31-
{{- 'Edit'|t -}}
32-
</a>
33-
{% if showDelete and not selectable %}
34-
<form method="post" action="" class="js-address-delete inline-block">
35-
{{ csrfInput() }}
36-
{{ actionInput('users/delete-address') }}
37-
{{ redirectInput('shop/customer/addresses') }}
38-
{{ hiddenInput('addressId', address.id) }}
39-
{{ tag('button', {
40-
type: 'submit',
41-
class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white',
42-
text: 'Delete'|t
43-
}) }}
44-
</form>
45-
{% endif %}
46-
</span>
47-
48-
{% if primaryBillingAddressId == address.id or primaryShippingAddressId == address.id %}
49-
<span class="absolute top-4 right-4">
50-
{% if primaryBillingAddressId == address.id %}
51-
<span title="{{ 'Primary billing address'|t }}">💳</span>
9+
{% if currentUser %}
10+
{% if addresses|length %}
11+
<div class="my-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 xl:grid-cols-3 gap-4">
12+
{% for address in addresses %}
13+
{% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %}
14+
<div class="block border border-gray-200 bg-white rounded-lg shadow-sm hover:shadow-md p-4 w-full">
15+
{% tag selectable ? 'label' : 'div' with {
16+
class: 'block relative address-select js-address-select',
17+
data: {
18+
'address-id': address.id,
19+
},
20+
} %}
21+
<span class="js-radio flex py-2">
22+
{% if selectable %}
23+
{{ input('radio', name ~ 'Id', address.id, {
24+
data: {
25+
'model-name': name,
26+
},
27+
checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)),
28+
}) }}
5229
{% endif %}
53-
{% if primaryShippingAddressId == address.id %}
54-
<span title="{{ 'Primary shipping address'|t }}">📦</span>
30+
<span class="-mt-2 mb-2 {% if selectable %}ml-4{% endif %}">
31+
{{ address|address }}
32+
</span>
33+
</span>
34+
<span class="block mb-1">
35+
<a href="{{ url(editUrl) }}" class="cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white">
36+
{{- 'Edit'|t -}}
37+
</a>
38+
{% if showDelete and not selectable %}
39+
<form method="post" action="" class="js-address-delete inline-block">
40+
{{ csrfInput() }}
41+
{{ actionInput('users/delete-address') }}
42+
{{ redirectInput('shop/customer/addresses') }}
43+
{{ hiddenInput('addressId', address.id) }}
44+
{{ tag('button', {
45+
type: 'submit',
46+
class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white',
47+
text: 'Delete'|t
48+
}) }}
49+
</form>
5550
{% endif %}
5651
</span>
57-
{% endif %}
58-
{% endtag %}
59-
</div>
60-
{% endfor %}
61-
</div>
52+
{% endtag %}
53+
</div>
54+
{% endfor %}
55+
{% if showAdd %}
56+
<a href="{{ addUrl }}" class="block rounded-lg hover:shadow-md w-full">
57+
<div class="flex items-center justify-center h-full border border-gray-200 bg-white rounded-lg shadow-sm p-4 w-full group hover:shadow-md">
58+
<span class="group-hover:underline">Add Address</span>
59+
</div>
60+
</a>
61+
{% endif %}
62+
</div>
63+
{% endif %}
6264
{% endif %}
6365

6466
{% js %}
65-
const addressDeletes = document.querySelectorAll('.js-address-delete');
66-
for (let i = 0; i < addressDeletes.length; i++) {
67-
addressDeletes[i].addEventListener('submit', ev => {
68-
if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) {
69-
ev.preventDefault();
67+
const addressDeletes = document.querySelectorAll('.js-address-delete');
68+
for (let i = 0; i < addressDeletes.length; i++) {
69+
addressDeletes[i].addEventListener('submit', ev => {
70+
if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) {
71+
ev.preventDefault();
72+
}
73+
});
7074
}
71-
});
72-
}
73-
{% endjs %}
75+
{% endjs %}

example-templates/dist/shop/_private/images/placeholder.svg

Lines changed: 2 additions & 0 deletions
Loading

example-templates/dist/shop/_private/images/shopping-cart.svg

Lines changed: 0 additions & 3 deletions
This file was deleted.

example-templates/dist/shop/_private/layouts/includes/footer.twig

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,9 +64,6 @@ Outputs the global site footer.
6464
version: craft.commerce.version,
6565
})|raw }}
6666
</p>
67-
<p>
68-
{{ 'Use these templates as a starting point for learning and customizing—you’ll want to customize them for your site.'|t }}
69-
</p>
7067
</div>
7168
<div class="md:w-1/2 md:pl-6">
7269
<h5 class="text-gray-600 pb-2 uppercase text-sm tracking-wide pt-3">
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<div class="bg-gray-900">
2+
<div class="container mx-auto p-6 md:flex justify-content-between align-content-center">
3+
<h1 class="text-3xl">
4+
<a href="{{ siteUrl('/shop') }}" class="text-white">
5+
{{- siteName ~ ' Shop' -}}
6+
</a>
7+
</h1>
8+
{% if craft.app.sites.getAllSites()|length > 1 %}
9+
<div class="ml-auto">
10+
<select name="site" id="js-site-selector" class="mt-1 block w-full rounded-md border-gray-300 py-2 pl-3 pr-10 text-base focus:border-indigo-500 focus:outline-none focus:ring-indigo-500 sm:text-sm">
11+
{% for site in craft.app.sites.getAllSites() %}
12+
<option value="{{ siteUrl(craft.app.request.absoluteUrl|replace(currentSite.getBaseUrl(), ''), null, null, site.id) }}" {% if site.handle == currentSite.handle %}selected{% endif %}>{{ site.name }}</option>
13+
{% endfor %}
14+
</select>
15+
</div>
16+
{% endif %}
17+
</div>
18+
</div>

example-templates/dist/shop/_private/layouts/includes/nav-checkout.twig

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,10 @@ Outputs the checkout progress navigation using the request path and included `ch
2323
label: 'Payment Method',
2424
url: 'shop/checkout/payment-method'
2525
},
26+
{
27+
label: 'Options',
28+
url: 'shop/checkout/options'
29+
},
2630
{
2731
label: 'Payment',
2832
url: 'shop/checkout/payment'

0 commit comments

Comments
 (0)