Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions src/base/ShippingMethod.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ abstract class ShippingMethod extends BaseModel implements ShippingMethodInterfa
*/
public ?DateTime $dateUpdated = null;

/**
* @var array<string, ShippingRuleInterface|null>
*/
private array $_matchingRuleByOrderNumber = [];

/**
* @inheritdoc
*/
Expand Down Expand Up @@ -274,11 +279,8 @@ public function matchOrder(Order $order): bool
return false;
}

/** @var ShippingRuleInterface $rule */
foreach ($this->getShippingRules()->all() as $rule) {
if ($rule->matchOrder($order)) {
return true;
}
if ($this->getMatchingShippingRule($order)) {
return true;
}

return false;
Expand All @@ -289,14 +291,26 @@ public function matchOrder(Order $order): bool
*/
public function getMatchingShippingRule(Order $order): ?ShippingRuleInterface
{
if (array_key_exists($order->number, $this->_matchingRuleByOrderNumber)) {
return $this->_matchingRuleByOrderNumber[$order->number];
}

foreach ($this->getShippingRules() as $rule) {
/** @var ShippingRuleInterface $rule */
if ($rule->matchOrder($order)) {
return $rule;
return $this->_matchingRuleByOrderNumber[$order->number] = $rule;
}
}

return null;
return $this->_matchingRuleByOrderNumber[$order->number] = null;
}

/**
* @return void
*/
public function clearMatchingShippingRuleCache(): void
{
$this->_matchingRuleByOrderNumber = [];
}

public function getPriceForOrder(Order $order): float
Expand Down
11 changes: 8 additions & 3 deletions src/elements/Order.php
Original file line number Diff line number Diff line change
Expand Up @@ -2169,7 +2169,11 @@ public function getAvailableShippingMethodOptions(): array

// Get all regular methods and add them to the list, for use only when the order is complete.
if ($this->isCompleted) {
$allShippingMethods = ArrayHelper::index(Plugin::getInstance()->getShippingMethods()->getAllShippingMethods()->all(), fn(ShippingMethodInterface $sm) => $sm->getHandle());
$allShippingMethods = Plugin::getInstance()->getShippingMethods()->getAllShippingMethods()
->keyBy(fn(ShippingMethodInterface $sm) => $sm->getHandle())
->filter(fn(ShippingMethodInterface $sm) => $sm->getIsEnabled())
->all();

$methods = ArrayHelper::merge($allShippingMethods, $methods);
}

Expand All @@ -2191,13 +2195,14 @@ public function getAvailableShippingMethodOptions(): array
}
}

$matchesOrder = ArrayHelper::isIn($method->getHandle(), $matchingMethodHandles);
$option->setOrder($this);
$option->enabled = $method->getIsEnabled();
$option->id = $method->getId();
$option->name = $method->getName();
$option->handle = $method->getHandle();
$option->matchesOrder = ArrayHelper::isIn($method->getHandle(), $matchingMethodHandles);
$option->price = $method->getPriceForOrder($this);
$option->matchesOrder = $matchesOrder;
$option->price = $matchesOrder ? $method->getPriceForOrder($this) : 0;
$option->shippingMethod = $method;
$option->storeId = $storeId;

Expand Down
67 changes: 39 additions & 28 deletions src/models/ShippingRule.php
Original file line number Diff line number Diff line change
Expand Up @@ -117,20 +117,20 @@ class ShippingRule extends Model implements ShippingRuleInterface, HasStoreInter
private ?array $_shippingRuleCategories = null;

/**
* @var ShippingRuleOrderCondition|null
* @var string|array|ShippingRuleOrderCondition|null
* @see setOrderCondition()
* @see getOrderCondition()
* @since 5.0.0
*/
private ?ShippingRuleOrderCondition $_orderCondition = null;
private ShippingRuleOrderCondition|string|array|null $_orderCondition = null;

/**
* @var ShippingRuleCustomerCondition|null
* @var string|array|ShippingRuleCustomerCondition|null
* @see setCustomerCondition()
* @see getCustomerCondition()
* @since 5.4.0
*/
private ?ShippingRuleCustomerCondition $_customerCondition = null;
private ShippingRuleCustomerCondition|string|array|null $_customerCondition = null;

/**
* @throws InvalidConfigException
Expand Down Expand Up @@ -248,17 +248,6 @@ public function setOrderCondition(ShippingRuleOrderCondition|string|array|null $
return;
}

if (is_string($condition)) {
$condition = Json::decodeIfJson($condition);
}

if (!$condition instanceof ShippingRuleOrderCondition) {
$condition['class'] = ShippingRuleOrderCondition::class;
$condition = Craft::$app->getConditions()->createCondition($condition);
/** @var ShippingRuleOrderCondition $condition */
}
$condition->forProjectConfig = false;

$this->_orderCondition = $condition;
}

Expand All @@ -268,12 +257,26 @@ public function setOrderCondition(ShippingRuleOrderCondition|string|array|null $
*/
public function getOrderCondition(): ShippingRuleOrderCondition
{
$condition = $this->_orderCondition ?? new ShippingRuleOrderCondition();
if ($this->_orderCondition instanceof ShippingRuleOrderCondition) {
return $this->_orderCondition;
}

$condition = $this->_orderCondition ?? [];
if (is_string($condition)) {
$condition = Json::decodeIfJson($condition);
}

$condition['class'] = ShippingRuleOrderCondition::class;
$condition = Craft::$app->getConditions()->createCondition($condition);
/** @var ShippingRuleOrderCondition $condition */
$condition->forProjectConfig = false;
$condition->mainTag = 'div';
$condition->name = 'orderCondition';
$condition->storeId = $this->storeId;

return $condition;
$this->_orderCondition = $condition;

return $this->_orderCondition;
}

/**
Expand All @@ -284,31 +287,39 @@ public function getOrderCondition(): ShippingRuleOrderCondition
*/
public function setCustomerCondition(ShippingRuleCustomerCondition|string|array|null $condition): void
{
if (is_string($condition)) {
$condition = Json::decodeIfJson($condition);
}

if (!$condition instanceof ShippingRuleCustomerCondition) {
$condition['class'] = ShippingRuleCustomerCondition::class;
$condition = Craft::$app->getConditions()->createCondition($condition);
/** @var ShippingRuleCustomerCondition $condition */
if (empty($condition)) {
$this->_customerCondition = null;
return;
}
$condition->forProjectConfig = false;

$this->_customerCondition = $condition;
}

/**
* @return ShippingRuleCustomerCondition
* @throws InvalidConfigException
* @since 5.4.0
*/
public function getCustomerCondition(): ShippingRuleCustomerCondition
{
$condition = $this->_customerCondition ?? new ShippingRuleCustomerCondition();
if ($this->_customerCondition instanceof ShippingRuleCustomerCondition) {
return $this->_customerCondition;
}

$condition = $this->_customerCondition ?? [];
if (is_string($condition)) {
$condition = Json::decodeIfJson($condition);
}

$condition['class'] = ShippingRuleCustomerCondition::class;
$condition = Craft::$app->getConditions()->createCondition($condition);
/** @var ShippingRuleCustomerCondition $condition */
$condition->forProjectConfig = false;
$condition->mainTag = 'div';
$condition->name = 'customerCondition';
$this->_customerCondition = $condition;

return $condition;
return $this->_customerCondition;
}

/**
Expand Down
10 changes: 8 additions & 2 deletions src/services/ShippingMethods.php
Original file line number Diff line number Diff line change
Expand Up @@ -138,9 +138,10 @@ public function getMatchingShippingMethods(Order $order): array

/** @var ShippingMethod $method */
foreach ($event->getShippingMethods() as $method) {
$totalPrice = $method->getPriceForOrder($order);

if ($method->getIsEnabled() && $method->matchOrder($order)) {
// Now we know the method matches, let's get the price
$totalPrice = $method->getPriceForOrder($order);

$matchingMethods[$method->getHandle()] = [
'method' => $method,
'price' => $totalPrice, // Store the price so we can sort on it before returning
Expand All @@ -155,6 +156,11 @@ public function getMatchingShippingMethods(Order $order): array
foreach ($matchingMethods as $shippingMethod) {
$method = $shippingMethod['method'];
$shippingMethods[$method->getHandle()] = $method; // Keep the key being the handle of the method for front-end use.

// Clear the matching cache in case things change in the future
if ($method instanceof \craft\commerce\base\ShippingMethod) {
$method->clearMatchingShippingRuleCache();
}
}

// Clear the memoized data so next time we watch to match rules, we get fresh data.
Expand Down
52 changes: 48 additions & 4 deletions src/services/ShippingRuleCategories.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,38 @@
*/
class ShippingRuleCategories extends Component
{
/**
* @var array|null
*/
private ?array $_shippingRuleCategories = null;

/**
* Returns shipping rule category data without instantiating the classes for performances purposes
*
* @return array
*/
public function getAllShippingRuleCategoriesData(): array
{
if ($this->_shippingRuleCategories === null) {
$data = $this->_createShippingRuleCategoriesQuery()->all();

if (!empty($data)) {
$ruleCategories = [];
foreach ($data as $row) {
if (!isset($ruleCategories[$row['shippingRuleId']])) {
$ruleCategories[$row['shippingRuleId']] = [];
}

$ruleCategories[$row['shippingRuleId']][$row['shippingCategoryId']] = $row;
}

$this->_shippingRuleCategories = $ruleCategories;
}
}

return $this->_shippingRuleCategories ?? [];
}

/**
* Returns an array of shipping rules categories per the rule's ID.
*
Expand All @@ -34,15 +66,22 @@ public function getShippingRuleCategoriesByRuleId(int $ruleId): array
{
$rules = [];

$rows = $this->_createShippingRuleCategoriesQuery()
->where(['shippingRuleId' => $ruleId])
->all();
$shippingRuleCategories = $this->getAllShippingRuleCategoriesData();
if (!isset($shippingRuleCategories[$ruleId])) {
return [];
}

foreach ($shippingRuleCategories[$ruleId] as $row) {
if ($row instanceof ShippingRuleCategory) {
continue;
}

foreach ($rows as $row) {
$id = $row['shippingCategoryId'];
$rules[$id] = new ShippingRuleCategory($row);
}

$this->_shippingRuleCategories[$ruleId] = $rules;

return $rules;
}

Expand Down Expand Up @@ -110,6 +149,8 @@ public function createShippingRuleCategory(ShippingRuleCategory $model, bool $ru
// Now that we have a record ID, save it on the model
$model->id = $record->id;

$this->_shippingRuleCategories = null;

return true;
}

Expand All @@ -127,6 +168,9 @@ public function deleteShippingRuleCategoryById(int $id): bool
$record = ShippingRuleCategoryRecord::findOne($id);

if ($record) {
// Clear cache if required
$this->_shippingRuleCategories = null;

return (bool)$record->delete();
}

Expand Down
Loading