From aa944bf339e5ff53a714867e060521854cd25976 Mon Sep 17 00:00:00 2001 From: soheil khaledabadi Date: Sat, 9 May 2026 21:24:10 +0330 Subject: [PATCH] feat(Rule): add new rules --- admin/admin.php | 5 + admin/class-rules-list-table.php | 59 ++++-- admin/js/rule-admin.js | 37 ++++ admin/views/partials/rule-action-row.php | 33 ++++ admin/views/partials/rule-condition-row.php | 53 +++++ admin/views/rule-form.php | 209 +++++++++----------- app/Controllers/AdminController.php | 126 +++++++++--- app/Services/PricingService.php | 182 ++++++++++++++--- public/hooks/pricing-hooks.php | 6 + 9 files changed, 532 insertions(+), 178 deletions(-) create mode 100644 admin/js/rule-admin.js create mode 100644 admin/views/partials/rule-action-row.php create mode 100644 admin/views/partials/rule-condition-row.php 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'); ?>

+

- - - - +
- -
-
- + + +
+

-
-

-
- -

+
-
- - -

+ + +
+ +
+ + +
+ +
+ +
-
- - roles as $role_key => $role_data) : ?> - + -

+

- -
- - -

-
- - -
- - -

-
- - -
- - -
- - -
- - -

-
- -
+
- -
- +
+
+
+

+

+
+
- -
+ +
+ $condition) : ?> + + +
+ + +
+
+
+

+

+
+ +
+ +
+ $action) : ?> + + +
+
+ +
+ +
+
+ + + + 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); +}