ruleRepository = $ruleRepository; $this->trackingService = $trackingService; $this->settings = Settings::getInstance(); $this->cache = Cache::getInstance(); } public function applyDynamicPricing($price, $product) { if (!$this->settings->isPricingEnabled()) { return $price; } if (!$price || !is_numeric($price)) { return $price; } $price = $this->normalizePrice($price); if (!$this->settings->get('cart_pricing_enabled') && is_cart()) { return $price; } $originalPrice = $price; $rules = $this->getApplicableRules($product); if (empty($rules)) { return $price; } if (!$this->settings->get('allow_multiple_rules')) { $chosenRule = $this->chooseRule($rules, $price); $rules = $chosenRule ? [$chosenRule] : []; } foreach ($rules as $rule) { $oldPrice = $price; $price = $this->applyRuleActions($rule, $price); $this->trackDiscountOnce($product, $oldPrice, $price, $rule->id); } $price = $this->enforceLimits($originalPrice, $price); return max(0, $price); } private function getApplicableRules($product) { $rules = $this->ruleRepository->getEnabled(); $applicable = []; foreach ($rules as $rule) { if ($this->ruleMatches($rule, $product)) { $applicable[] = $rule; } } return $applicable; } private function chooseRule(array $rules, $price) { $strategy = $this->settings->get('strategy', 'priority'); if ($strategy === 'highest_discount') { usort($rules, function ($a, $b) use ($price) { return $this->estimateRuleDiscount($b, $price) <=> $this->estimateRuleDiscount($a, $price); }); return $rules[0] ?? null; } if ($strategy === 'first_valid') { return $rules[0] ?? null; } usort($rules, function ($a, $b) { return $b->priority <=> $a->priority; }); return $rules[0] ?? null; } private function normalizePrice($price) { if ($price === '' || $price === null) { return 0.0; } return floatval($price); } private function ruleMatches($rule, $product = null) { if (!$rule->isActive()) { return false; } if ($rule->hasReachedLimit()) { return false; } if (!empty($rule->user_roles) && is_array($rule->user_roles)) { if (!$this->userHasAllowedRole($rule->user_roles)) { return false; } } if (empty($rule->conditions)) { return true; } foreach ($rule->conditions as $condition) { if (!$this->evaluateCondition($condition, $product)) { return false; } } return true; } private function evaluateCondition($condition, $product = null) { $type = $condition['type'] ?? ''; $value = $condition['value'] ?? null; switch ($type) { case 'user_type': return $this->compareAnyValue($this->getUserTypeAliases(), $value, $condition['operator'] ?? 'is'); case 'cart_total_min': return $this->getCartTotal() >= floatval($value); case 'cart_total_max': return $this->getCartTotal() <= floatval($value); case 'cart_item_count_min': return $this->getCartItemCount() >= intval($value); case 'cart_item_count_max': 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; } } private function getUserType() { if (!is_user_logged_in()) { return 'guest'; } $user_id = get_current_user_id(); $order_count = wc_get_customer_order_count($user_id); 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; } $user = wp_get_current_user(); foreach ($roles as $role) { if (in_array($role, $user->roles, true)) { return true; } } return false; } private function getCartTotal() { if (!function_exists('WC') || !WC()->cart) { return 0; } return floatval(WC()->cart->get_subtotal()); } private function getCartItemCount() { if (!function_exists('WC') || !WC()->cart) { return 0; } return intval(WC()->cart->get_cart_contents_count()); } private function productHasCategory($product, $categories) { return $this->productHasTerm($product, $categories, 'product_cat'); } private function productHasTerm($product, $terms, $taxonomy) { if (!$product || empty($terms)) { return false; } $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_terms, $this->normalizeIdList($terms)); } private function productIsInIds($product, $ids) { if (!$product || empty($ids)) { return false; } $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) { $values = is_array($value) ? $value : [$value]; $ids = []; foreach ($values as $item) { foreach (explode(',', (string) $item) as $id) { $id = absint(trim($id)); if ($id > 0) { $ids[] = $id; } } } return array_values(array_unique($ids)); } private function applyRuleActions($rule, $price) { foreach ($rule->actions as $action) { $price = $this->applyAction($action, $price); } return $price; } private function applyAction($action, $price) { $type = $action['type'] ?? ''; $value = isset($action['value']) ? floatval($action['value']) : 0; switch ($type) { case 'discount_percent': if ($value <= 0) { return $price; } return $price * (1 - min(100, $value) / 100); case 'discount_fixed': if ($value <= 0) { return $price; } 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: return $price; } } private function enforceLimits($originalPrice, $price) { $minPrice = max(0, floatval($this->settings->get('min_product_price', 0))); $price = max($price, $minPrice); $maxDiscountPercent = floatval($this->settings->get('max_discount_percent', 100)); if ($maxDiscountPercent > 0 && $maxDiscountPercent < 100) { $maxDiscount = $originalPrice * ($maxDiscountPercent / 100); $minAllowedPrice = $originalPrice - $maxDiscount; $price = max($minAllowedPrice, $price); } return $price; } private function estimateRuleDiscount($rule, $price) { foreach ($rule->actions as $action) { if (($action['type'] ?? '') === 'discount_percent') { return $price * floatval($action['value']) / 100; } if (($action['type'] ?? '') === 'discount_fixed') { return floatval($action['value']); } } 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 ($price >= $oldPrice || isset($this->trackedApplications[$key])) { return; } $this->trackedApplications[$key] = true; $this->trackingService->recordDiscountApplied($product, $oldPrice, $price, $ruleId); $this->ruleRepository->incrementUsage($ruleId); } }