ruleRepository = $ruleRepository; $this->trackingService = $trackingService; } public function applyDynamicPricing($price, $product) { $settings = $this->getSettings(); if (empty($settings['plugin_enabled']) || empty($settings['pricing_enabled'])) { return $price; } if (!$price || !is_numeric($price)) { return $price; } $price = $this->normalizePrice($price); if (!$settings['cart_pricing_enabled'] && is_cart()) { return $price; } $originalPrice = $price; $rules = $this->getEnabledRules(); $matchedRules = []; foreach ($rules as $rule) { if ($this->ruleMatches($rule, $product)) { $matchedRules[] = $rule; } } if (empty($matchedRules)) { return $price; } if (!$settings['allow_multiple_rules']) { $chosenRule = $this->chooseRule($matchedRules, $price, $settings['strategy']); $matchedRules = $chosenRule ? [$chosenRule] : []; } foreach ($matchedRules as $rule) { $oldPrice = $price; $price = $this->applyActions($rule, $price); if ($price < $oldPrice) { $this->trackingService->recordDiscountApplied($product, $oldPrice, $price, $rule->id); } } $price = $this->enforceLimits($originalPrice, $price, $settings); return max(0, $price); } private function chooseRule(array $rules, $price, $strategy) { 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 getSettings() { $defaults = [ 'plugin_enabled' => 1, 'pricing_enabled' => 1, 'upsell_enabled' => 1, 'allow_multiple_rules' => 0, 'strategy' => 'priority', 'max_discount_percent' => 100, 'min_product_price' => 0, 'ab_testing_enabled' => 0, 'cart_pricing_enabled' => 1, 'scheduled_campaigns_enabled' => 1, ]; return wp_parse_args(get_option('sodino_settings', []), $defaults); } private function getEnabledRules() { if ($this->rulesCache === null) { $this->rulesCache = $this->ruleRepository->getEnabled(); } return $this->rulesCache; } private function normalizePrice($price) { if ($price === '' || $price === null) { return 0.0; } return floatval($price); } private function ruleMatches($rule, $product = null) { if (!$rule->enabled) { return false; } if ($rule->usage_limit > 0 && $this->trackingService->getRuleUsageCount($rule->id) >= $rule->usage_limit) { return false; } if (!empty($rule->user_roles) && is_array($rule->user_roles)) { if (!$this->userHasAllowedRole($rule->user_roles)) { return false; } } if (!$this->isRuleActive($rule)) { return false; } if (empty($rule->conditions)) { return true; } foreach ($rule->conditions as $condition) { if (!$this->evaluateCondition($condition, $product)) { return false; } } return true; } private function isRuleActive($rule) { $now = current_time('Y-m-d H:i:s'); if (!empty($rule->start_date) && $now < $rule->start_date) { return false; } if (!empty($rule->end_date) && $now > $rule->end_date) { return false; } return true; } private function evaluateCondition($condition, $product = null) { $type = $condition['type'] ?? ''; $value = $condition['value'] ?? null; switch ($type) { case 'user_type': return $this->getUserType() === $value; 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 'product_ids': return $this->productIsInIds($product, (array) $value); 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 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 (!WC()->cart) { return 0; } return floatval(WC()->cart->get_cart_contents_total()); } private function getCartItemCount() { if (!WC()->cart) { return 0; } return WC()->cart->get_cart_contents_count(); } private function productHasCategory($product, $categories) { if (!$product || empty($categories)) { return false; } $product_cats = wp_get_post_terms($product->get_id(), 'product_cat', ['fields' => 'ids']); return (bool) array_intersect($product_cats, $categories); } private function productIsInIds($product, $ids) { if (!$product || empty($ids)) { return false; } return in_array($product->get_id(), $ids, true); } private function applyActions($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 - $value / 100); case 'discount_fixed': if ($value <= 0) { return $price; } return $price - $value; case 'free_shipping': return $price; default: return $price; } } private function enforceLimits($originalPrice, $price, array $settings) { $minPrice = max(0, floatval($settings['min_product_price'])); $price = max($price, $minPrice); $maxDiscountPercent = floatval($settings['max_discount_percent']); if ($maxDiscountPercent > 0 && $maxDiscountPercent < 100) { $limit = $originalPrice * ($maxDiscountPercent / 100); $price = max($originalPrice - $limit, $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; } }