Skip to content

Commit

Permalink
Merge branch 'release/4.3.0' into main
Browse files Browse the repository at this point in the history
  • Loading branch information
brandonkelly committed Sep 13, 2023
2 parents 64f9d46 + 6c7ca13 commit 65a6166
Show file tree
Hide file tree
Showing 147 changed files with 6,424 additions and 1,439 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ on:
branches:
- develop
- v3
- '4.2'
- '4.3'
pull_request:
permissions:
contents: read
Expand Down
36 changes: 36 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,41 @@
# Release Notes for Craft Commerce

## 4.3.0 - 2023-09-13

- 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))
- It’s now possible to query products by shipping category and tax category. ([#3219](https://github.com/craftcms/commerce/issues/3219))
- Guest customers registering during checkout now have their addresses saved to their account. ([#3203](https://github.com/craftcms/commerce/pull/3203))
- 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))
- Improved the performance of discount recalculation.
- Improved the performance of the `commerce/upgrade` command. ([#3208](https://github.com/craftcms/commerce/pull/3208))
- Added the `commerce/cart/forget-cart` action. ([#3206](https://github.com/craftcms/commerce/issues/3206))
- The `commerce/cart/update-cart` action now accepts `firstName` and `lastName` address parameters. ([#3015](https://github.com/craftcms/commerce/issues/3015))
- Added `craft\commerce\controllers\OrdersController::EVENT_MODIFY_PURCHASABLES_TABLE_QUERY`. ([#3198](https://github.com/craftcms/commerce/pull/3198))
- Added `craft\commerce\elements\Order::$orderCompletedEmail`. ([#3138](https://github.com/craftcms/commerce/issues/3138))
- Added `craft\commerce\elements\db\ProductQuery::$shippingCategoryId`.
- Added `craft\commerce\elements\db\ProductQuery::$taxCategoryId`.
- Added `craft\commerce\elements\db\ProductQuery::shippingCategory()`.
- Added `craft\commerce\elements\db\ProductQuery::shippingCategoryId()`.
- Added `craft\commerce\elements\db\ProductQuery::taxCategory()`.
- Added `craft\commerce\elements\db\ProductQuery::taxCategoryId()`.
- Added `craft\commerce\models\Discount::hasBillingAddressCondition()`.
- Added `craft\commerce\models\Discount::hasCustomerCondition()`.
- Added `craft\commerce\models\Discount::hasOrderCondition()`.
- Added `craft\commerce\models\Discount::hasShippingAddressCondition()`.
- Deprecated payment source creation via the `commerce/subscriptions/subscribe` action.
- Deprecated `craft\commerce\elements\Order::setEmail()`. `Order::setCustomer()` should be used instead.
- Removed the `htmx` option from the`commerce/example-templates` command.
- Removed the `color` option from the`commerce/example-templates` command.
- Added `craft\commerce\events\ModifyPurchasablesTableQueryEvent`. ([#3198](https://github.com/craftcms/commerce/pull/3198))
- 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))
- Fixed a bug where `craft\commerce\elements\Order::hasMatchingAddresses()` could incorrectly return `false`. ([#3183](https://github.com/craftcms/commerce/issues/3183))
- 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))
- Fixed a bug where related sales were displaying when creating a new product.
- Fixed a bug where Commerce wasn’t invoking `craft\services\Elements::EVENT_AUTHORIZE_*` event handlers.
- Fixed a bug where discounts’ per user usage counters weren’t getting migrated properly when upgrading to Commerce 4.
- Fixed a bug where address changes weren’t being synced to carts that were using them. ([#3178](https://github.com/craftcms/commerce/issues/3178))
- Fixed an XSS vulnerability.

## 4.2.11 - 2023-06-05

- Fixed a bug where “Send Email” option text wasn’t getting translated. ([#3172](https://github.com/craftcms/commerce/issues/3172))
Expand Down
26 changes: 26 additions & 0 deletions example-templates/dist/shop/_private/address/fields.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
{#
Outputs address form fields for editing an address.
#}
{% set showPrimaryCheckboxes = showPrimaryCheckboxes is defined ? showPrimaryCheckboxes : false %}
{% set addressFieldLayout = craft.app.getAddresses().getLayout() %}
{% set addressCustomFields = addressFieldLayout.getCustomFields()|filter(f => className(f) == 'craft\\fields\\PlainText') %}
{# @var address \craft\elements\Address #}
Expand Down Expand Up @@ -193,9 +194,34 @@ Outputs address form fields for editing an address.
{% endfor %}
</div>
{% endif %}

{% if showPrimaryCheckboxes %}
<hr class="my-2">
<div class="my-2">
{{ input('text', 'isPrimaryBilling', address.isPrimarybilling ? 1 : 0) }}
<label>{{ input('checkbox', 'isPrimaryBillingBox', 1, { checked: address.isPrimaryBilling, 'data-primary-input': 'isPrimaryBilling' }) }} {{ 'Use as the primary billing address'|t('commerce') }}</label>
</div>
<div class="my-2">
{{ input('text', 'isPrimaryShipping', address.isPrimaryShipping ? 1 : 0) }}
<label>{{ input('checkbox', 'isPrimaryShippingBox', 1, { checked: address.isPrimaryShipping, 'data-primary-input': 'isPrimaryShipping' }) }} {{ 'Use as the primary shipping address'|t('commerce') }}</label>
</div>
{% endif %}
</div>

{% js %}
{% if showPrimaryCheckboxes %}
document.querySelectorAll('input[type=checkbox][data-primary-input]').forEach(el => {
el.addEventListener('change', ev => {
let primaryInput = document.querySelector(`input[name="${ev.target.dataset.primaryInput}"]`);
if (ev.target.checked) {
primaryInput.value = 1;
} else {
primaryInput.value = 0;
}
});
});
{% endif %}

document.querySelector('select#{{ 'countryCode'|namespaceInputId(addressName) }}').addEventListener('change', ev => {
const countryCode = ev.target.value;
const stateSelect = document.querySelector('select#{{ 'administrativeArea'|namespaceInputId(addressName) }}');
Expand Down
11 changes: 7 additions & 4 deletions example-templates/dist/shop/_private/address/fieldset.twig
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati
@var title string
@var currentUser \craft\elements\User
#}
{% set sourceAttribute = 'source' ~ (name|slice(0, 1)|capitalize) ~ (name|slice(1) ~ 'Id') %}
<div class="js-address-fieldset {{ classes }}">
<h2 class="text-lg font-bold mb-4">
{{- title -}}
Expand All @@ -19,22 +20,24 @@ Outputs a fieldset for selecting one of a user’s available addresses or creati
selectable: true,
primaryBillingAddressId: cart and cart.customer ? cart.customer.primaryBillingAddressId : null,
primaryShippingAddressId: cart and cart.customer ? cart.customer.primaryShippingAddressId : null,
showAdd: true,
}) }}

<div class="js-address-select">
<div class="js-address-select" data-model-name="{{ name }}">
{% if attribute(cart, name ~ 'Id') %}
{% set addressHasErrors = attribute(cart, name) and attribute(cart, name).hasErrors() %}
{% else %}
{% set addressHasErrors = false %}
{% endif %}

{% if currentUser %}
{# Show the custom toggle if there is a custom address on the order #}
{% if currentUser and cart and attribute(cart, name ~ 'Id') and not attribute(cart, sourceAttribute) %}
<div class="js-radio">
<label>
{{ input('radio', name ~ 'Id', '', {
checked: not addresses|length or addressHasErrors
checked: true
}) }}
{{ 'New {title}'|t({ title: title }) }}
{{ 'Custom {title}'|t({ title: title }) }}
</label>
</div>
{% endif %}
Expand Down
122 changes: 62 additions & 60 deletions example-templates/dist/shop/_private/address/list.twig
Original file line number Diff line number Diff line change
Expand Up @@ -2,72 +2,74 @@
{% set selectable = selectable ?? false %}
{% set primaryBillingAddressId = primaryBillingAddressId ?? null %}
{% set primaryShippingAddressId = primaryShippingAddressId ?? null %}
{% set cardWidth = cardWidth ?? 'md:w-1/2' %}
{% set showDelete = showDelete ?? false %}
{% set showAdd = showAdd ?? false %}
{% set addUrl = '/shop/customer/addresses/edit?redirect=' ~ craft.app.request.fullPath %}

{% if addresses and currentUser %}
<div class="md:flex md:flex-wrap md:-mx-2 pb-4">
{% for address in addresses %}
{% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %}
<div class="pb-2 my-4 md:px-2 {{ cardWidth }} md:my-0">
{% tag selectable ? 'label' : 'div' with {
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',
data: {
'address-id': address.id,
},
} %}
<span class="js-radio flex py-2">
{% if selectable %}
{{ input('radio', name ~ 'Id', address.id, {
checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)),
}) }}
{% endif %}
<span class="-mt-2 ml-3 mb-2">
{{ address|address }}
</span>
</span>
<span class="block mb-1">
<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">
{{- 'Edit'|t -}}
</a>
{% if showDelete and not selectable %}
<form method="post" action="" class="js-address-delete inline-block">
{{ csrfInput() }}
{{ actionInput('users/delete-address') }}
{{ redirectInput('shop/customer/addresses') }}
{{ hiddenInput('addressId', address.id) }}
{{ tag('button', {
type: 'submit',
class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white',
text: 'Delete'|t
}) }}
</form>
{% endif %}
</span>

{% if primaryBillingAddressId == address.id or primaryShippingAddressId == address.id %}
<span class="absolute top-4 right-4">
{% if primaryBillingAddressId == address.id %}
<span title="{{ 'Primary billing address'|t }}">💳</span>
{% if currentUser %}
{% if addresses|length %}
<div class="my-6 grid grid-cols-1 sm:grid-cols-2 md:grid-cols-2 xl:grid-cols-3 gap-4">
{% for address in addresses %}
{% set editUrl = '/shop/customer/addresses/edit?addressId=' ~ address.id ~ '&redirect=' ~ craft.app.request.fullPath %}
<div class="block border border-gray-200 bg-white rounded-lg shadow-sm hover:shadow-md p-4 w-full">
{% tag selectable ? 'label' : 'div' with {
class: 'block relative address-select js-address-select',
data: {
'address-id': address.id,
},
} %}
<span class="js-radio flex py-2">
{% if selectable %}
{{ input('radio', name ~ 'Id', address.id, {
data: {
'model-name': name,
},
checked: (attribute(cart, sourceIdName) == address.id) or (not attribute(cart, sourceIdName) and address.id == attribute(_context, primaryIdName)),
}) }}
{% endif %}
{% if primaryShippingAddressId == address.id %}
<span title="{{ 'Primary shipping address'|t }}">📦</span>
<span class="-mt-2 mb-2 {% if selectable %}ml-4{% endif %}">
{{ address|address }}
</span>
</span>
<span class="block mb-1">
<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">
{{- 'Edit'|t -}}
</a>
{% if showDelete and not selectable %}
<form method="post" action="" class="js-address-delete inline-block">
{{ csrfInput() }}
{{ actionInput('users/delete-address') }}
{{ redirectInput('shop/customer/addresses') }}
{{ hiddenInput('addressId', address.id) }}
{{ tag('button', {
type: 'submit',
class: 'cursor-pointer rounded px-2 py-1 text-sm inline-block bg-gray-500 hover:bg-gray-600 text-white hover:text-white',
text: 'Delete'|t
}) }}
</form>
{% endif %}
</span>
{% endif %}
{% endtag %}
</div>
{% endfor %}
</div>
{% endtag %}
</div>
{% endfor %}
{% if showAdd %}
<a href="{{ addUrl }}" class="block rounded-lg hover:shadow-md w-full">
<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">
<span class="group-hover:underline">Add Address</span>
</div>
</a>
{% endif %}
</div>
{% endif %}
{% endif %}

{% js %}
const addressDeletes = document.querySelectorAll('.js-address-delete');
for (let i = 0; i < addressDeletes.length; i++) {
addressDeletes[i].addEventListener('submit', ev => {
if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) {
ev.preventDefault();
const addressDeletes = document.querySelectorAll('.js-address-delete');
for (let i = 0; i < addressDeletes.length; i++) {
addressDeletes[i].addEventListener('submit', ev => {
if (!confirm('{{ 'Are you sure you want to delete this address?'|t }}')) {
ev.preventDefault();
}
});
}
});
}
{% endjs %}
{% endjs %}
2 changes: 2 additions & 0 deletions example-templates/dist/shop/_private/images/placeholder.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 0 additions & 3 deletions example-templates/dist/shop/_private/images/shopping-cart.svg

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ Outputs the global site footer.
version: craft.commerce.version,
})|raw }}
</p>
<p>
{{ 'Use these templates as a starting point for learning and customizing—you’ll want to customize them for your site.'|t }}
</p>
</div>
<div class="md:w-1/2 md:pl-6">
<h5 class="text-gray-600 pb-2 uppercase text-sm tracking-wide pt-3">
Expand Down
18 changes: 18 additions & 0 deletions example-templates/dist/shop/_private/layouts/includes/header.twig
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div class="bg-gray-900">
<div class="container mx-auto p-6 md:flex justify-content-between align-content-center">
<h1 class="text-3xl">
<a href="{{ siteUrl('/shop') }}" class="text-white">
{{- siteName ~ ' Shop' -}}
</a>
</h1>
{% if craft.app.sites.getAllSites()|length > 1 %}
<div class="ml-auto">
<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">
{% for site in craft.app.sites.getAllSites() %}
<option value="{{ siteUrl(craft.app.request.absoluteUrl|replace(currentSite.getBaseUrl(), ''), null, null, site.id) }}" {% if site.handle == currentSite.handle %}selected{% endif %}>{{ site.name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ Outputs the checkout progress navigation using the request path and included `ch
label: 'Payment Method',
url: 'shop/checkout/payment-method'
},
{
label: 'Options',
url: 'shop/checkout/options'
},
{
label: 'Payment',
url: 'shop/checkout/payment'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
<!-- Template: {{ _self }}.twig -->
{#
Outputs the site’s global main navigation based on path and included `pages` array.
@var cart \craft\commerce\elements\Order
@var currentUser \craft\elements\User
#}
{% set pages = [
{
label: 'My Orders',
url: 'shop/customer/orders'
},
{
label: 'My Addresses',
url: 'shop/customer/addresses'
},
{
label: 'My Stored Cards',
url: 'shop/customer/cards'
}
] %}
{% set currentPath = craft.app.request.pathInfo %}
{# Show the nav if we in a customers page #}
{% if currentPath in pages|column('url') %}
{% set activeClasses = 'bg-gray-200' %}
<div class="bg-gray-50">
<div class="container mx-auto justify-between items-center p-6 relative">
<nav class="-ml-4"
role="navigation"
aria-label="Customer">
{% for page in pages %}
<a href="{{ url(page.url) }}"
class="mx-2 px-2 py-1 rounded{% if page.url in currentPath %} {{ activeClasses }}{% endif %}">
{{- page.label|t -}}
</a>
{% endfor %}
</nav>
</div>
</div>
{% endif %}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ Outputs the site’s global main navigation based on path and included `pages` a
] %}
{% set currentPath = craft.app.request.pathInfo %}
{% set activeClasses = 'bg-white' %}
{# Show the nav if we are not in a checkout page #}
{% if 'checkout' not in currentPath %}
<div class="bg-gray-100">
<div class="container mx-auto justify-between items-center p-6 relative">
Expand All @@ -36,8 +37,8 @@ Outputs the site’s global main navigation based on path and included `pages` a
<div class="absolute right-2 top-0">
{% if currentUser %}
<a href="{{ url('shop/customer') }}"
class="mx-2 px-2 py-1 rounded text-sm font-semibold">
{{- currentUser.email -}}
class="relative text-lg cursor-pointer inline-block mx-4 my-5 px-2 py-1 bg-white rounded-lg hover:shadow">
👤&nbsp;My Account
</a>
{% else %}
<a href="{{ url('shop/customer/sign-in') }}"
Expand All @@ -47,12 +48,15 @@ Outputs the site’s global main navigation based on path and included `pages` a
{% endif %}
<a href="{{ url('shop/cart') }}"
class="relative text-lg cursor-pointer inline-block mx-4 my-5 px-2 py-1 bg-white rounded-lg hover:shadow">
{% if cart.totalQty %}
<span class="absolute -mr-3 -mt-3 right-0 top-0 py-1 px-2 rounded-full text-white text-xs bg-blue-800">
{{- cart.totalQty -}}
{% if cart.totalQty %}
{{- cart.totalQty -}}
{% else %}
Empty
{% endif %}
</span>
{% endif %}
<p role="img" aria-label="{{ 'Shopping Cart'|t }}">🛒</p>

<span aria-label="{{ 'View Cart'|t }}">🛒 Cart</span>
</a>
</div>
</nav>
Expand Down
Loading

0 comments on commit 65a6166

Please sign in to comment.