'tiers' => array( $tier(1, 1, 'Buy 1', 0, '', 'Single pack · Regular offer', 'yes'), $tier(2, 2, 'Buy 2', 18, 'Popular', 'Most customers pick this bundle', 'yes'), $tier(3, 3, 'Buy 3', 25, 'Best Value', 'Lowest price per item · Ends soon', 'yes'), $tier(4, 4, 'Buy 4', 30, 'Best Deal', 'Largest bundle savings', 'no'), ), ), ); } private static function yes_no($value) { return $value === 'yes' || $value === true || $value === '1' || $value === 1 ? 'yes' : 'no'; } private static function decimal($value, $min = 0, $max = 999999) { $value = is_numeric($value) ? (float) $value : 0; $value = min((float) $max, max((float) $min, $value)); return rtrim(rtrim(number_format($value, 2, '.', ''), '0'), '.'); } public static function parse_ids($raw) { if (is_array($raw)) { $items = $raw; } else { $items = preg_split('/[\s,]+/', (string) $raw); } $ids = array(); foreach ($items as $item) { $id = absint($item); if ($id > 0) { $ids[] = $id; } } return array_values(array_unique($ids)); } public static function ids_to_string($ids) { $ids = self::parse_ids($ids); return implode(',', $ids); } public static function get_rules() { $rules = get_option(self::OPTION_NAME, null); if (!is_array($rules) || empty($rules)) { $rules = self::default_rules(); } return self::normalize_rules($rules); } public static function normalize_rules($rules) { $normalized = array(); $rules = is_array($rules) ? $rules : array(); foreach ($rules as $index => $rule) { if (!is_array($rule)) { continue; } $scope = sanitize_key($rule['scope'] ?? 'all'); if (!in_array($scope, array('all', 'products', 'categories'), true)) { $scope = 'all'; } $discount_type = sanitize_key($rule['discount_type'] ?? 'percent'); if (!in_array($discount_type, array('percent', 'fixed'), true)) { $discount_type = 'percent'; } $priority = isset($rule['priority']) ? absint($rule['priority']) : self::default_priority_for_scope($scope); $tiers = array(); if (!empty($rule['tiers']) && is_array($rule['tiers'])) { foreach ($rule['tiers'] as $i => $tier) { if (!is_array($tier)) { continue; } $amount_max = $discount_type === 'percent' ? 95 : 999999; $qty = max(1, absint($tier['qty'] ?? ($i + 1))); $amount = self::decimal($tier['amount'] ?? ($tier['discount'] ?? 0), 0, $amount_max); $tiers[] = array( 'enabled' => self::yes_no($tier['enabled'] ?? 'yes'), 'qty' => $qty, 'label' => sanitize_text_field($tier['label'] ?? ('Buy ' . $qty)), 'badge' => sanitize_text_field($tier['badge'] ?? ''), 'amount' => $amount, 'discount' => $amount, 'subtitle' => sanitize_text_field($tier['subtitle'] ?? ''), ); } } usort($tiers, function ($a, $b) { return absint($a['qty']) <=> absint($b['qty']); }); $normalized[] = array( 'id' => sanitize_key($rule['id'] ?? ('rule_' . ($index + 1))), 'title' => sanitize_text_field($rule['title'] ?? ('Discount Rule ' . ($index + 1))), 'enabled' => self::yes_no($rule['enabled'] ?? 'no'), 'scope' => $scope, 'discount_type' => $discount_type, 'priority' => $priority, 'stop_further' => self::yes_no($rule['stop_further'] ?? 'yes'), 'start_date' => sanitize_text_field($rule['start_date'] ?? ''), 'end_date' => sanitize_text_field($rule['end_date'] ?? ''), 'min_spend' => self::decimal($rule['min_spend'] ?? 0, 0, 999999), 'max_spend' => self::decimal($rule['max_spend'] ?? 0, 0, 999999), 'product_ids' => self::parse_ids($rule['product_ids'] ?? array()), 'category_ids' => self::parse_ids($rule['category_ids'] ?? array()), 'exclude_product_ids' => self::parse_ids($rule['exclude_product_ids'] ?? array()), 'exclude_category_ids' => self::parse_ids($rule['exclude_category_ids'] ?? array()), 'tiers' => $tiers, ); } usort($normalized, function ($a, $b) { $priority = absint($b['priority']) <=> absint($a['priority']); if ($priority !== 0) { return $priority; } return self::scope_weight($b['scope']) <=> self::scope_weight($a['scope']); }); return $normalized; } private static function default_priority_for_scope($scope) { if ($scope === 'products') { return 300; } if ($scope === 'categories') { return 200; } return 100; } private static function scope_weight($scope) { if ($scope === 'products') { return 3; } if ($scope === 'categories') { return 2; } return 1; } int', $terms); $all = $ids; foreach ($ids as $term_id) { $ancestors = get_ancestors($term_id, 'product_cat'); if (is_array($ancestors)) { $all = array_merge($all, array_map('absint', $ancestors)); } } return array_values(array_unique(array_filter($all))); } private static function now_timestamp() { return function_exists('current_time') ? current_time('timestamp') : time(); } private static function date_is_active($rule) { $now = self::now_timestamp(); $start = trim((string) ($rule['start_date'] ?? '')); $end = trim((string) ($rule['end_date'] ?? '')); if ($start !== '') { $start_ts = strtotime($start); if ($start_ts && $now < $start_ts) { return false; } } if ($end !== '') { $end_ts = strtotime($end); if ($end_ts && $now > $end_ts) { return false; } } return true; } public static function cart_base_subtotal($cart = null) { if (!$cart && function_exists('WC') && WC()->cart) { $cart = WC()->cart; } if (!is_a($cart, 'WC_Cart')) { return null; } $subtotal = 0; foreach ($cart->get_cart() as $cart_item) { if (empty($cart_item['data']) || !is_a($cart_item['data'], 'WC_Product')) { continue; } $price_product_id = !empty($cart_item['variation_id']) ? absint($cart_item['variation_id']) : absint($cart_item['product_id'] ?? 0); $base_product = $price_product_id && function_exists('wc_get_product') ? wc_get_product($price_product_id) : null; $base_price = $base_product ? (float) $base_product->get_price('edit') : (float) $cart_item['data']->get_price('edit'); $subtotal += $base_price * max(1, absint($cart_item['quantity'] ?? 1)); } return $subtotal; } public static function rule_matches_product($rule, $product_id, $context = array()) { $product_id = self::get_product_parent_id($product_id); if (!$product_id || empty($rule) || !is_array($rule)) { return false; } if (($rule['enabled'] ?? 'no') !== 'yes') { return false; } if (!self::date_is_active($rule)) { return false; } $cart_subtotal = array_key_exists('cart_subtotal', $context) ? $context['cart_subtotal'] : null; if ($cart_subtotal !== null) { $cart_subtotal = (float) $cart_subtotal; $min = (float) ($rule['min_spend'] ?? 0); $max = (float) ($rule['max_spend'] ?? 0); if ($min > 0 && $cart_subtotal < $min) { return false; } if ($max > 0 && $cart_subtotal > $max) { return false; } } $category_ids = self::get_product_category_ids($product_id); $exclude_product_ids = self::parse_ids($rule['exclude_product_ids'] ?? array()); $exclude_category_ids = self::parse_ids($rule['exclude_category_ids'] ?? array()); if (in_array($product_id, $exclude_product_ids, true)) { return false; } if (!empty($exclude_category_ids) && array_intersect($category_ids, $exclude_category_ids)) { return false; } $scope = $rule['scope'] ?? 'all'; if ($scope === 'all') { return true; } if ($scope === 'products') { $product_ids = self::parse_ids($rule['product_ids'] ?? array()); return in_array($product_id, $product_ids, true); } if ($scope === 'categories') { $rule_category_ids = self::parse_ids($rule['category_ids'] ?? array()); return !empty($rule_category_ids) && !empty(array_intersect($category_ids, $rule_category_ids)); } return false; } public static function get_applicable_rule($product_id, $context = array()) { $rules = self::get_rules(); foreach ($rules as $rule) { if (self::rule_matches_product($rule, $product_id, $context)) { return $rule; } } return null; } public static function get_applicable_qty_tier($product_id, $quantity, $context = array()) { $quantity = max(1, absint($quantity)); $rule = self::get_applicable_rule($product_id, $context); if (!$rule || empty($rule['tiers']) || !is_array($rule['tiers'])) { return null; } $matched_tier = null; $matched_qty = 0; $type = $rule['discount_type'] ?? 'percent'; foreach ($rule['tiers'] as $tier) { if (($tier['enabled'] ?? 'no') !== 'yes') { continue; } $tier_qty = max(1, absint($tier['qty'] ?? 1)); $amount = (float) ($tier['amount'] ?? ($tier['discount'] ?? 0)); if ($amount <= 0 || $tier_qty > $quantity || $tier_qty < $matched_qty) { continue; } $matched_qty = $tier_qty; $matched_tier = array( 'rule_id' => $rule['id'] ?? '', 'rule_title' => sanitize_text_field($rule['title'] ?? __('Discount Rule', 'wideoshop')), 'discount_type' => $type, 'qty' => $tier_qty, 'label' => sanitize_text_field($tier['label'] ?? ('Buy ' . $tier_qty)), 'badge' => sanitize_text_field($tier['badge'] ?? ''), 'amount' => $type === 'percent' ? min(95, max(0, $amount)) : max(0, $amount), 'discount' => $type === 'percent' ? min(95, max(0, $amount)) : max(0, $amount), ); } return $matched_tier; } public static function calculate_discounted_price($base_price, $tier) { $base_price = (float) $base_price; if ($base_price <= 0 || empty($tier) || !is_array($tier)) { return $base_price; } $amount = (float) ($tier['amount'] ?? 0); if (($tier['discount_type'] ?? 'percent') === 'fixed') { return max(0, $base_price - $amount); } $amount = min(95, max(0, $amount)); return $base_price * (1 - ($amount / 100)); } public static function format_tier_discount($tier) { if (empty($tier) || !is_array($tier)) { return ''; } $amount = (float) ($tier['amount'] ?? 0); if (($tier['discount_type'] ?? 'percent') === 'fixed') { return function_exists('wc_price') ? wp_strip_all_tags(wc_price($amount)) . ' OFF' : $amount . ' OFF'; } return wc_format_decimal($amount, 0) . '% OFF'; } public static function public_qty_bundle_config_for_product($product_id, $settings = null) { $settings = is_array($settings) ? $settings : get_option('wideoshop_settings', array()); $rule = self::get_applicable_rule($product_id, array('cart_subtotal' => null)); $title = isset($settings['wsv2_qty_title']) ? sanitize_text_field($settings['wsv2_qty_title']) : 'Qty Bundle — Save More'; $default_qty = isset($settings['wsv2_qty_default']) ? max(1, absint($settings['wsv2_qty_default'])) : 1; $sheet_enabled = !isset($settings['wsv2_qty_enabled']) || $settings['wsv2_qty_enabled'] === 'yes'; if (!$sheet_enabled || !$rule || empty($rule['tiers'])) { return array( 'enabled' => false, 'title' => $title, 'defaultQty' => $default_qty, 'tiers' => array(), ); } $tiers = array(); foreach ($rule['tiers'] as $tier) { if (($tier['enabled'] ?? 'no') !== 'yes') { continue; } $amount = (float) ($tier['amount'] ?? 0); $tiers[] = array( 'qty' => max(1, absint($tier['qty'] ?? 1)), 'label' => sanitize_text_field($tier['label'] ?? ('Buy ' . max(1, absint($tier['qty'] ?? 1)))), 'badge' => sanitize_text_field($tier['badge'] ?? ''), 'discount' => $amount, 'discountType' => $rule['discount_type'] ?? 'percent', 'discountText' => ($amount > 0) ? self::format_tier_discount(array('amount' => $amount, 'discount_type' => $rule['discount_type'] ?? 'percent')) : '', 'subtitle' => sanitize_text_field($tier['subtitle'] ?? ''), ); } return array( 'enabled' => !empty($tiers), 'title' => $title, 'defaultQty' => $default_qty, 'ruleId' => $rule['id'] ?? '', 'ruleTitle' => $rule['title'] ?? '', 'tiers' => $tiers, ); } } https://vaseel.com/wp-sitemap-posts-page-1.xmlhttps://vaseel.com/wp-sitemap-posts-product-1.xmlhttps://vaseel.com/wp-sitemap-taxonomies-cms_block_cat-1.xmlhttps://vaseel.com/wp-sitemap-taxonomies-product_cat-1.xmlhttps://vaseel.com/wp-sitemap-taxonomies-product_tag-1.xmlhttps://vaseel.com/wp-sitemap-users-1.xml