diff --git a/admin/admin.php b/admin/admin.php
index 7356d16..63fd14e 100644
--- a/admin/admin.php
+++ b/admin/admin.php
@@ -231,6 +231,11 @@ add_action('admin_enqueue_scripts', function($hook) {
]);
}
+ // Rule builder scripts
+ if (strpos($hook, 'sodino-add-rule') !== false || strpos($hook, 'sodino_page_sodino-add-rule') !== false) {
+ wp_enqueue_script('sodino-rule-admin', plugin_dir_url(__FILE__) . 'js/rule-admin.js', [], SODINO_VERSION, true);
+ }
+
// Banner specific scripts
if (strpos($hook, 'sodino-add-banner') !== false || strpos($hook, 'sodino_page_sodino-add-banner') !== false) {
wp_enqueue_media();
diff --git a/admin/class-rules-list-table.php b/admin/class-rules-list-table.php
index 2f89f0a..ff40c4e 100644
--- a/admin/class-rules-list-table.php
+++ b/admin/class-rules-list-table.php
@@ -25,8 +25,9 @@ class Sodino_Rules_List_Table extends WP_List_Table {
return [
'cb' => '',
'name' => __('عنوان قانون', 'sodino'),
- 'condition_type' => __('شرط', 'sodino'),
- 'action_value' => __('عملیات', 'sodino'),
+ 'conditions' => __('شرطها', 'sodino'),
+ 'actions_summary'=> __('عملیاتها', 'sodino'),
+ 'usage' => __('استفاده', 'sodino'),
'enabled' => __('وضعیت', 'sodino'),
'actions' => __('عملیات', 'sodino'),
];
@@ -68,31 +69,64 @@ class Sodino_Rules_List_Table extends WP_List_Table {
return $title;
}
- public function column_condition_type($item) {
- $labels = [
+ private function conditionLabels() {
+ return [
'user_type' => __('نوع کاربر', 'sodino'),
'product_category' => __('دستهبندی محصول', 'sodino'),
+ 'exclude_product_category' => __('بهجز دستهبندی', 'sodino'),
+ 'product_tag' => __('برچسب محصول', 'sodino'),
'product_ids' => __('محصولات خاص', 'sodino'),
+ 'exclude_product_ids' => __('بهجز محصولات', 'sodino'),
'cart_total_min' => __('حداقل مبلغ سبد', 'sodino'),
'cart_total_max' => __('حداکثر مبلغ سبد', 'sodino'),
'cart_item_count_min' => __('حداقل تعداد سبد', 'sodino'),
'cart_item_count_max' => __('حداکثر تعداد سبد', 'sodino'),
+ 'cart_contains_product' => __('سبد شامل محصول', 'sodino'),
+ 'cart_contains_category' => __('سبد شامل دستهبندی', 'sodino'),
+ 'customer_order_count_min' => __('حداقل سفارش مشتری', 'sodino'),
+ 'customer_order_count_max' => __('حداکثر سفارش مشتری', 'sodino'),
+ 'day_of_week' => __('روز هفته', 'sodino'),
];
-
- $type = $labels[$item->condition_type] ?? $item->condition_type;
- return esc_html(sprintf('%s: %s', $type, $item->condition_value));
}
- public function column_action_value($item) {
+ public function column_conditions($item) {
+ $labels = [
+ ];
+ $labels = $this->conditionLabels();
+ $parts = [];
+ foreach ((array) $item->conditions as $condition) {
+ $type = $condition['type'] ?? '';
+ $value = $condition['value'] ?? '';
+ $parts[] = esc_html(sprintf('%s: %s', $labels[$type] ?? $type, is_array($value) ? implode(',', $value) : $value));
+ }
+ return $parts ? implode('
', $parts) : esc_html__('بدون شرط', 'sodino');
+ }
+
+ public function column_actions_summary($item) {
$labels = [
'discount_percent' => __('درصد تخفیف', 'sodino'),
'discount_fixed' => __('تخفیف ثابت', 'sodino'),
'set_price' => __('قیمت ثابت', 'sodino'),
+ 'increase_percent' => __('افزایش درصدی', 'sodino'),
+ 'increase_fixed' => __('افزایش ثابت', 'sodino'),
'free_shipping' => __('ارسال رایگان', 'sodino'),
];
- $type = $labels[$item->action_type] ?? $item->action_type;
- return esc_html(sprintf('%s: %s', $type, $item->action_value));
+ $parts = [];
+ foreach ((array) $item->actions as $action) {
+ $type = $action['type'] ?? '';
+ $value = $action['value'] ?? 0;
+ $parts[] = esc_html(sprintf('%s: %s', $labels[$type] ?? $type, $type === 'free_shipping' ? '-' : $value));
+ }
+ return $parts ? implode('
', $parts) : esc_html__('بدون عملیات', 'sodino');
+ }
+
+ public function column_usage($item) {
+ $limit = (int) $item->usage_limit;
+ if ($limit <= 0) {
+ return esc_html(sprintf('%s / %s', number_format_i18n($item->usage_count), __('نامحدود', 'sodino')));
+ }
+ return esc_html(sprintf('%s / %s', number_format_i18n($item->usage_count), number_format_i18n($limit)));
}
public function column_enabled($item) {
@@ -102,8 +136,9 @@ class Sodino_Rules_List_Table extends WP_List_Table {
public function column_default($item, $column_name) {
switch ($column_name) {
case 'name':
- case 'condition_type':
- case 'action_value':
+ case 'conditions':
+ case 'actions_summary':
+ case 'usage':
case 'enabled':
case 'actions':
return '';
diff --git a/admin/js/rule-admin.js b/admin/js/rule-admin.js
new file mode 100644
index 0000000..22fe2d3
--- /dev/null
+++ b/admin/js/rule-admin.js
@@ -0,0 +1,37 @@
+(function () {
+ function nextIndex(container) {
+ return container.querySelectorAll('[data-row]').length;
+ }
+
+ function addRow(target) {
+ const container = document.getElementById(target === 'actions' ? 'sodino-actions' : 'sodino-conditions');
+ const template = document.getElementById(target === 'actions' ? 'sodino-action-template' : 'sodino-condition-template');
+ if (!container || !template) {
+ return;
+ }
+
+ const index = nextIndex(container);
+ const wrapper = document.createElement('div');
+ wrapper.innerHTML = template.innerHTML.replaceAll('__INDEX__', String(index)).trim();
+ container.appendChild(wrapper.firstElementChild);
+ }
+
+ document.addEventListener('click', function (event) {
+ const addButton = event.target.closest('.sodino-add-row');
+ if (addButton) {
+ event.preventDefault();
+ addRow(addButton.dataset.target);
+ return;
+ }
+
+ const removeButton = event.target.closest('.sodino-remove-row');
+ if (removeButton) {
+ event.preventDefault();
+ const row = removeButton.closest('[data-row]');
+ const container = row ? row.parentElement : null;
+ if (container && container.querySelectorAll('[data-row]').length > 1) {
+ row.remove();
+ }
+ }
+ });
+})();
diff --git a/admin/views/partials/rule-action-row.php b/admin/views/partials/rule-action-row.php
new file mode 100644
index 0000000..b8d8f34
--- /dev/null
+++ b/admin/views/partials/rule-action-row.php
@@ -0,0 +1,33 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/views/partials/rule-condition-row.php b/admin/views/partials/rule-condition-row.php
new file mode 100644
index 0000000..92193ee
--- /dev/null
+++ b/admin/views/partials/rule-condition-row.php
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/admin/views/rule-form.php b/admin/views/rule-form.php
index f362d78..e1671ed 100644
--- a/admin/views/rule-form.php
+++ b/admin/views/rule-form.php
@@ -5,20 +5,28 @@ if (!defined('ABSPATH')) {
}
$current_page = sanitize_text_field($_GET['page'] ?? 'sodino-add-rule');
-$form_condition_type = function_exists('sodino_old_input') ? sodino_old_input('condition_type', $rule->condition_type) : $rule->condition_type;
-$form_action_type = function_exists('sodino_old_input') ? sodino_old_input('action_type', $rule->action_type) : $rule->action_type;
+$conditions = function_exists('sodino_old_input') ? sodino_old_input('conditions', $rule->conditions) : $rule->conditions;
+$actions = function_exists('sodino_old_input') ? sodino_old_input('actions', $rule->actions) : $rule->actions;
+$conditions = is_array($conditions) && $conditions ? array_values($conditions) : [['type' => 'user_type', 'operator' => 'is', 'value' => 'new']];
+$actions = is_array($actions) && $actions ? array_values($actions) : [['type' => 'discount_percent', 'value' => 10]];
+$product_categories = get_terms(['taxonomy' => 'product_cat', 'hide_empty' => false]);
+$product_tags = get_terms(['taxonomy' => 'product_tag', 'hide_empty' => false]);
+$weekdays = [
+ '1' => __('دوشنبه', 'sodino'),
+ '2' => __('سهشنبه', 'sodino'),
+ '3' => __('چهارشنبه', 'sodino'),
+ '4' => __('پنجشنبه', 'sodino'),
+ '5' => __('جمعه', 'sodino'),
+ '6' => __('شنبه', 'sodino'),
+ '7' => __('یکشنبه', 'sodino'),
+];
?>
-
@@ -26,151 +34,118 @@ $form_action_type = function_exists('sodino_old_input') ? sodino_old_input('acti
-
-
+
-
-
-
-
+
+
-
id ? __('ویرایش قانون', 'sodino') : __('افزودن قانون جدید', 'sodino'); ?>
-
+
id ? __('ویرایش قانون پیشرفته', 'sodino') : __('افزودن قانون پیشرفته', 'sodino'); ?>
+
-
-
+
-
-
+
+
+ 'user_type', 'operator' => 'is', 'value' => 'new']; include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-condition-row.php'; ?>
+
+
+
+ 'discount_percent', 'value' => 10]; include SODINO_PLUGIN_DIR . 'admin/views/partials/rule-action-row.php'; ?>
+
diff --git a/app/Controllers/AdminController.php b/app/Controllers/AdminController.php
index 1e6492e..8e07341 100644
--- a/app/Controllers/AdminController.php
+++ b/app/Controllers/AdminController.php
@@ -22,8 +22,25 @@ class AdminController {
private $allowedBannerDeviceTargets = ['all', 'desktop', 'mobile'];
private $allowedUpsellTriggerTypes = ['product', 'category', 'cart_total'];
private $allowedUpsellDiscountTypes = ['percentage', 'fixed', 'none'];
- private $allowedRuleConditionTypes = ['user_type', 'product_category', 'product_ids', 'cart_total_min', 'cart_total_max', 'cart_item_count_min', 'cart_item_count_max'];
- private $allowedRuleActionTypes = ['discount_percent', 'discount_fixed', 'set_price', 'free_shipping'];
+ private $allowedRuleConditionTypes = [
+ 'user_type',
+ 'product_category',
+ 'exclude_product_category',
+ 'product_tag',
+ 'product_ids',
+ 'exclude_product_ids',
+ 'cart_total_min',
+ 'cart_total_max',
+ 'cart_item_count_min',
+ 'cart_item_count_max',
+ 'cart_contains_product',
+ 'cart_contains_category',
+ 'customer_order_count_min',
+ 'customer_order_count_max',
+ 'day_of_week',
+ ];
+ private $allowedRuleConditionOperators = ['is', 'is_not', 'in', 'not_in'];
+ private $allowedRuleActionTypes = ['discount_percent', 'discount_fixed', 'set_price', 'increase_percent', 'increase_fixed', 'free_shipping'];
private $allowedStrategies = ['priority', 'highest_discount', 'first_valid'];
public function __construct(RuleRepository $ruleRepository, UpsellRepository $upsellRepository, BannerRepository $bannerRepository) {
@@ -667,6 +684,72 @@ class AdminController {
return $deleted === false ? 0 : (int) $deleted;
}
+ private function sanitizeRuleConditions($rawConditions) {
+ $conditions = [];
+ $rawConditions = is_array($rawConditions) ? wp_unslash($rawConditions) : [];
+
+ foreach ($rawConditions as $condition) {
+ if (!is_array($condition)) {
+ continue;
+ }
+
+ $type = sanitize_key($condition['type'] ?? '');
+ if (!in_array($type, $this->allowedRuleConditionTypes, true)) {
+ continue;
+ }
+
+ $operator = sanitize_key($condition['operator'] ?? 'is');
+ if (!in_array($operator, $this->allowedRuleConditionOperators, true)) {
+ $operator = 'is';
+ }
+
+ $value = sanitize_text_field($condition['value'] ?? '');
+ if ($value === '') {
+ continue;
+ }
+
+ $conditions[] = [
+ 'type' => $type,
+ 'operator' => $operator,
+ 'value' => $value,
+ ];
+ }
+
+ return $conditions;
+ }
+
+ private function sanitizeRuleActions($rawActions) {
+ $actions = [];
+ $rawActions = is_array($rawActions) ? wp_unslash($rawActions) : [];
+
+ foreach ($rawActions as $action) {
+ if (!is_array($action)) {
+ continue;
+ }
+
+ $type = sanitize_key($action['type'] ?? '');
+ if (!in_array($type, $this->allowedRuleActionTypes, true)) {
+ continue;
+ }
+
+ $value = max(0, floatval($action['value'] ?? 0));
+ if (in_array($type, ['discount_percent', 'increase_percent'], true)) {
+ $value = min(100, $value);
+ }
+
+ if ($type !== 'free_shipping' && $value <= 0) {
+ continue;
+ }
+
+ $actions[] = [
+ 'type' => $type,
+ 'value' => $value,
+ ];
+ }
+
+ return $actions;
+ }
+
private function getSettingsDefaults() {
return [
'plugin_enabled' => 1,
@@ -764,41 +847,30 @@ class AdminController {
$name = sanitize_text_field($_POST['name'] ?? '');
$this->requireValue($name, __('عنوان قانون الزامی است.', 'sodino'), 'sodino-add-rule');
- $conditionType = sanitize_key($_POST['condition_type'] ?? 'user_type');
- if (!in_array($conditionType, $this->allowedRuleConditionTypes, true)) {
- $conditionType = 'user_type';
+ $conditions = $this->sanitizeRuleConditions($_POST['conditions'] ?? []);
+ if (empty($conditions)) {
+ $this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('حداقل یک شرط معتبر برای قانون لازم است.', 'sodino'), 'error');
}
- $conditionValue = sanitize_text_field($_POST['condition_value'] ?? '');
- $this->requireValue($conditionValue, __('مقدار شرط قانون الزامی است.', 'sodino'), 'sodino-add-rule');
-
- $actionType = sanitize_key($_POST['action_type'] ?? 'discount_percent');
- if (!in_array($actionType, $this->allowedRuleActionTypes, true)) {
- $actionType = 'discount_percent';
+ $actions = $this->sanitizeRuleActions($_POST['actions'] ?? []);
+ if (empty($actions)) {
+ $this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('حداقل یک عملیات معتبر برای قانون لازم است.', 'sodino'), 'error');
}
- $actionValue = sanitize_text_field($_POST['action_value'] ?? '0');
- if ($actionType !== 'free_shipping' && floatval($actionValue) <= 0) {
- $this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('مقدار عملیات باید بزرگتر از صفر باشد.', 'sodino'), 'error');
+ $startDate = $this->normalizeDatetime($_POST['start_date'] ?? '');
+ $endDate = $this->normalizeDatetime($_POST['end_date'] ?? '');
+ if ($startDate && $endDate && strtotime($endDate) < strtotime($startDate)) {
+ $this->redirectWithNotice($this->getBackUrl('sodino-add-rule'), __('تاریخ پایان قانون نباید قبل از تاریخ شروع باشد.', 'sodino'), 'error');
}
$rule->name = $name;
$rule->priority = max(1, intval($_POST['priority'] ?? 10));
$rule->usage_limit = max(0, intval($_POST['usage_limit'] ?? 0));
$rule->user_roles = array_map('sanitize_text_field', (array) ($_POST['user_roles'] ?? []));
-
- $rule->conditions = [
- [
- 'type' => $conditionType,
- 'value' => $conditionValue,
- ],
- ];
- $rule->actions = [
- [
- 'type' => $actionType,
- 'value' => $actionValue,
- ],
- ];
+ $rule->start_date = $startDate;
+ $rule->end_date = $endDate;
+ $rule->conditions = $conditions;
+ $rule->actions = $actions;
$rule->syncLegacyFields();
$rule->enabled = isset($_POST['enabled']) ? 1 : 0;
diff --git a/app/Services/PricingService.php b/app/Services/PricingService.php
index d49f0e3..16147d5 100644
--- a/app/Services/PricingService.php
+++ b/app/Services/PricingService.php
@@ -51,9 +51,7 @@ class PricingService {
$oldPrice = $price;
$price = $this->applyRuleActions($rule, $price);
- if ($price < $oldPrice) {
- $this->trackDiscountOnce($product, $oldPrice, $price, $rule->id);
- }
+ $this->trackDiscountOnce($product, $oldPrice, $price, $rule->id);
}
$price = $this->enforceLimits($originalPrice, $price);
@@ -62,20 +60,16 @@ class PricingService {
}
private function getApplicableRules($product) {
- $cache_key = 'applicable_rules_' . ($product ? $product->get_id() : 'all');
-
- return $this->cache->remember($cache_key, function() use ($product) {
- $rules = $this->ruleRepository->getEnabled();
- $applicable = [];
+ $rules = $this->ruleRepository->getEnabled();
+ $applicable = [];
- foreach ($rules as $rule) {
- if ($this->ruleMatches($rule, $product)) {
- $applicable[] = $rule;
- }
+ foreach ($rules as $rule) {
+ if ($this->ruleMatches($rule, $product)) {
+ $applicable[] = $rule;
}
+ }
- return $applicable;
- }, 300, 'pricing');
+ return $applicable;
}
private function chooseRule(array $rules, $price) {
@@ -139,7 +133,7 @@ class PricingService {
switch ($type) {
case 'user_type':
- return $this->getUserType() === $value;
+ return $this->compareAnyValue($this->getUserTypeAliases(), $value, $condition['operator'] ?? 'is');
case 'cart_total_min':
return $this->getCartTotal() >= floatval($value);
case 'cart_total_max':
@@ -150,8 +144,24 @@ class PricingService {
return $this->getCartItemCount() <= intval($value);
case 'product_category':
return $this->productHasCategory($product, (array) $value);
+ case 'exclude_product_category':
+ return !$this->productHasCategory($product, (array) $value);
+ case 'product_tag':
+ return $this->productHasTerm($product, (array) $value, 'product_tag');
case 'product_ids':
return $this->productIsInIds($product, (array) $value);
+ case 'exclude_product_ids':
+ return !$this->productIsInIds($product, (array) $value);
+ case 'cart_contains_product':
+ return $this->cartContainsProduct((array) $value);
+ case 'cart_contains_category':
+ return $this->cartContainsCategory((array) $value);
+ case 'customer_order_count_min':
+ return $this->getCustomerOrderCount() >= intval($value);
+ case 'customer_order_count_max':
+ return $this->getCustomerOrderCount() <= intval($value);
+ case 'day_of_week':
+ return in_array((string) current_time('N'), array_map('strval', $this->normalizeIdList($value)), true);
default:
return true;
}
@@ -168,6 +178,23 @@ class PricingService {
return $order_count > 0 ? 'returning' : 'new';
}
+ private function getUserTypeAliases() {
+ $type = $this->getUserType();
+ $aliases = [$type];
+ if (is_user_logged_in()) {
+ $aliases[] = 'logged_in';
+ }
+ return $aliases;
+ }
+
+ private function getCustomerOrderCount() {
+ if (!is_user_logged_in()) {
+ return 0;
+ }
+
+ return (int) wc_get_customer_order_count(get_current_user_id());
+ }
+
private function userHasAllowedRole($roles) {
if (!is_user_logged_in()) {
return false;
@@ -197,16 +224,20 @@ class PricingService {
}
private function productHasCategory($product, $categories) {
- if (!$product || empty($categories)) {
+ return $this->productHasTerm($product, $categories, 'product_cat');
+ }
+
+ private function productHasTerm($product, $terms, $taxonomy) {
+ if (!$product || empty($terms)) {
return false;
}
- $product_cats = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'ids']);
- if (is_wp_error($product_cats)) {
+ $product_terms = wp_get_post_terms($product->get_id(), $taxonomy, ['fields' => 'ids']);
+ if (is_wp_error($product_terms)) {
return false;
}
- return (bool) array_intersect($product_cats, $this->normalizeIdList($categories));
+ return (bool) array_intersect($product_terms, $this->normalizeIdList($terms));
}
private function productIsInIds($product, $ids) {
@@ -214,7 +245,43 @@ class PricingService {
return false;
}
- return in_array($product->get_id(), $this->normalizeIdList($ids), true);
+ $ids = $this->normalizeIdList($ids);
+ $productIds = [(int) $product->get_id()];
+ if ($product->is_type('variation') && method_exists($product, 'get_parent_id')) {
+ $productIds[] = (int) $product->get_parent_id();
+ }
+
+ return (bool) array_intersect($productIds, $ids);
+ }
+
+ private function cartContainsProduct($ids) {
+ if (!function_exists('WC') || !WC()->cart) {
+ return false;
+ }
+
+ $ids = $this->normalizeIdList($ids);
+ foreach (WC()->cart->get_cart() as $cartItem) {
+ if (in_array((int) $cartItem['product_id'], $ids, true) || in_array((int) $cartItem['variation_id'], $ids, true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ private function cartContainsCategory($categories) {
+ if (!function_exists('WC') || !WC()->cart) {
+ return false;
+ }
+
+ foreach (WC()->cart->get_cart() as $cartItem) {
+ $product = wc_get_product($cartItem['product_id']);
+ if ($this->productHasCategory($product, $categories)) {
+ return true;
+ }
+ }
+
+ return false;
}
private function normalizeIdList($value) {
@@ -249,7 +316,7 @@ class PricingService {
if ($value <= 0) {
return $price;
}
- return $price * (1 - $value / 100);
+ return $price * (1 - min(100, $value) / 100);
case 'discount_fixed':
if ($value <= 0) {
return $price;
@@ -257,6 +324,10 @@ class PricingService {
return $price - $value;
case 'set_price':
return $value > 0 ? $value : $price;
+ case 'increase_percent':
+ return $value > 0 ? $price * (1 + min(100, $value) / 100) : $price;
+ case 'increase_fixed':
+ return $value > 0 ? $price + $value : $price;
case 'free_shipping':
return $price;
default:
@@ -290,11 +361,78 @@ class PricingService {
return 0;
}
+ public function applyFreeShippingRates($rates) {
+ $rules = $this->getApplicableRules(null);
+ $hasFreeShippingRule = false;
+
+ foreach ($rules as $rule) {
+ foreach ($rule->actions as $action) {
+ if (($action['type'] ?? '') === 'free_shipping') {
+ $hasFreeShippingRule = true;
+ break 2;
+ }
+ }
+ }
+
+ if (!$hasFreeShippingRule) {
+ return $rates;
+ }
+
+ foreach ($rates as $rate) {
+ $rate->cost = 0;
+ if (!empty($rate->taxes) && is_array($rate->taxes)) {
+ foreach ($rate->taxes as $taxId => $tax) {
+ $rate->taxes[$taxId] = 0;
+ }
+ }
+ }
+
+ return $rates;
+ }
+
+ private function compareValue($actual, $expected, $operator) {
+ $expectedValues = array_map('trim', explode(',', (string) $expected));
+ $actual = (string) $actual;
+
+ switch ($operator) {
+ case 'is_not':
+ return $actual !== (string) $expected;
+ case 'in':
+ return in_array($actual, $expectedValues, true);
+ case 'not_in':
+ return !in_array($actual, $expectedValues, true);
+ case 'is':
+ default:
+ return $actual === (string) $expected;
+ }
+ }
+
+ private function compareAnyValue(array $actualValues, $expected, $operator) {
+ $expectedValues = array_map('trim', explode(',', (string) $expected));
+
+ if (in_array($operator, ['is_not', 'not_in'], true)) {
+ foreach ($actualValues as $actual) {
+ if (in_array((string) $actual, $expectedValues, true)) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ foreach ($actualValues as $actual) {
+ if (in_array((string) $actual, $expectedValues, true)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
private function trackDiscountOnce($product, $oldPrice, $price, $ruleId) {
$productId = $product ? $product->get_id() : 0;
$key = implode(':', [$productId, (int) $ruleId, round($oldPrice, 4), round($price, 4)]);
- if (isset($this->trackedApplications[$key])) {
+ if ($price >= $oldPrice || isset($this->trackedApplications[$key])) {
return;
}
diff --git a/public/hooks/pricing-hooks.php b/public/hooks/pricing-hooks.php
index a3bdb67..c50e9df 100644
--- a/public/hooks/pricing-hooks.php
+++ b/public/hooks/pricing-hooks.php
@@ -21,8 +21,14 @@ add_filter('woocommerce_product_get_price', 'sodino_apply_dynamic_pricing', 10,
add_filter('woocommerce_product_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
add_filter('woocommerce_product_variation_get_price', 'sodino_apply_dynamic_pricing', 10, 2);
add_filter('woocommerce_product_variation_get_sale_price', 'sodino_apply_dynamic_pricing', 10, 2);
+add_filter('woocommerce_package_rates', 'sodino_apply_free_shipping_rules', 20, 1);
function sodino_apply_dynamic_pricing($price, $product) {
global $sodino_pricing_service;
return $sodino_pricing_service->applyDynamicPricing($price, $product);
}
+
+function sodino_apply_free_shipping_rules($rates) {
+ global $sodino_pricing_service;
+ return $sodino_pricing_service->applyFreeShippingRates($rates);
+}