0) { self::$role_pricing_suspended--; } } public static function role_pricing_suppressed() { return self::$role_pricing_suspended > 0 || self::is_back_office_request(); } public static function is_back_office_request() { if (null !== self::$back_office_memo) { return self::$back_office_memo; } $is = false; $final = true; if (is_admin() && !wp_doing_ajax()) { $is = true; } elseif (wp_doing_ajax()) { $referer = function_exists('wp_get_raw_referer') ? (string) wp_get_raw_referer() : ''; $path = '' !== $referer ? (string) wp_parse_url($referer, PHP_URL_PATH) : ''; $is = '' !== $path && false !== strpos($path, '/wp-admin/'); } elseif (defined('REST_REQUEST') && REST_REQUEST) { $route = ''; if (isset($GLOBALS['wp']) && is_object($GLOBALS['wp']) && isset($GLOBALS['wp']->query_vars['rest_route'])) { $route = (string) $GLOBALS['wp']->query_vars['rest_route']; } $is = (bool) preg_match('#^/(wc/v\d+|wc-admin|wc-analytics)(/|$)#', $route); } elseif (!did_action('parse_request')) { $final = false; } $is = (bool) apply_filters('aieo_role_pricing_back_office_request', $is); if ($final) { self::$back_office_memo = $is; } return $is; } public function get_effective_price($product_id, $user_id = null, $base = null) { $product_id = (int) $product_id; if ($product_id <= 0) { return $base !== null ? (float) $base : 0.0; } if ($user_id === null) { $user_id = get_current_user_id(); } $user_id = (int) $user_id; $ctx_key = AIEO_DMM_Cache::user_context_key($user_id) . ':' . $product_id; $memo = AIEO_DMM_Cache::get_l1('prices', $ctx_key); if ($memo !== null) { return (float) $memo; } if ($base === null) { $base = (float) $this->lookup_base_price($product_id); } else { $base = (float) $base; } $price = $this->resolve_price($product_id, $user_id, $base); AIEO_DMM_Cache::put_l1('prices', $ctx_key, $price); return $price; } public function apply_recipe_only($base_after_role, $product_id, $user_id = null) { $product_id = (int) $product_id; if ($product_id <= 0 || !is_numeric($base_after_role)) { return is_numeric($base_after_role) ? (float) $base_after_role : 0.0; } $base_after_role = (float) $base_after_role; if ($base_after_role <= 0) { return $base_after_role; } if ($user_id === null) { $user_id = get_current_user_id(); } $user_id = (int) $user_id; $ctx_key = 'recipe_only:' . $product_id . ':' . AIEO_DMM_Cache::user_context_key($user_id) . ':' . number_format($base_after_role, 4, '.', ''); $memo = AIEO_DMM_Cache::get_l1('prices', $ctx_key); if ($memo !== null) { return (float) $memo; } $settings = AIEO_DMM_Settings::get_all(); $recipe = isset($settings['recipe']) ? (array) $settings['recipe'] : array(); $mode = isset($settings['pricing_mode']) ? (string) $settings['pricing_mode'] : 'reactive'; $force_reactive = !empty($GLOBALS['aieo_dmm_force_reactive']); if (!$force_reactive && $mode === 'precomputed' && class_exists('AIEO_DMM_Product_FP') && class_exists('AIEO_DMM_User_FP')) { $price = $this->recipe_only_precomputed($base_after_role, $product_id, $user_id, $recipe); } else { $price = $this->recipe_only_reactive($base_after_role, $product_id, $user_id, $recipe); } if ($price < 0) { $price = 0.0; } AIEO_DMM_Cache::put_l1('prices', $ctx_key, (float) $price); return (float) $price; } private function recipe_only_precomputed($base_after_role, $product_id, $user_id, array $recipe) { $fp = AIEO_DMM_Product_FP::get($product_id); $ufp = AIEO_DMM_User_FP::get($user_id); if (!$fp) { return $this->recipe_only_reactive($base_after_role, $product_id, $user_id, $recipe); } $price = (float) $base_after_role; $role_adjusted = $price; $brand_pct = (float) ($fp['effective_combined_pct'] ?? 0); if ($brand_pct > 0 && !empty($recipe['brand_discount']) && $recipe['brand_discount'] !== 'off') { switch ($recipe['brand_discount']) { case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break; case 'replace_role': $price = (float) ($fp['base_regular'] ?? $base_after_role) * (1.0 - ($brand_pct / 100.0)); break; } } $sched_pct = (float) ($fp['active_schedule_pct'] ?? 0); if ($sched_pct > 0) { $price = $price * (1.0 - ($sched_pct / 100.0)); } if ($user_id > 0 && is_array($ufp) && !empty($recipe['loyalty_class']) && $recipe['loyalty_class'] !== 'off') { $loyal_pct = (float) ($ufp['loyalty_extra_pct_sum'] ?? 0) + (float) ($ufp['personal_extra_pct'] ?? 0); $seq_expires = isset($ufp['sequence_expires_at']) ? (string) $ufp['sequence_expires_at'] : ''; if (!empty($ufp['sequence_pct']) && $seq_expires !== '' && strtotime($seq_expires) > time()) { $loyal_pct += (float) $ufp['sequence_pct']; } if ($loyal_pct > 0) { switch ($recipe['loyalty_class']) { case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break; case 'replace_all': $price = (float) ($fp['base_regular'] ?? $base_after_role) * (1.0 - ($loyal_pct / 100.0)); break; } } if (!empty($ufp['brand_overrides']) && is_array($ufp['brand_overrides'])) { $brand_ids = isset($fp['brand_term_ids']) ? (array) $fp['brand_term_ids'] : array(); foreach ($brand_ids as $bid) { if (isset($ufp['brand_overrides'][$bid])) { $bo = (float) $ufp['brand_overrides'][$bid]; if ($bo > 0) { $price = $price * (1.0 - ($bo / 100.0)); } } } } } return $price; } private function recipe_only_reactive($base_after_role, $product_id, $user_id, array $recipe) { $price = (float) $base_after_role; $role_adjusted = $price; $brand_pct = $this->lookup_brand_discount_percent($product_id); if ($brand_pct > 0 && !empty($recipe['brand_discount']) && $recipe['brand_discount'] !== 'off') { $base = (float) $this->lookup_base_price($product_id); switch ($recipe['brand_discount']) { case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break; case 'replace_role': $price = $base * (1.0 - ($brand_pct / 100.0)); break; } } $sched_pct = $this->lookup_schedule_percent($product_id); if ($sched_pct > 0) { $price = $price * (1.0 - ($sched_pct / 100.0)); } if ($user_id > 0 && !empty($recipe['loyalty_class']) && $recipe['loyalty_class'] !== 'off') { $loyal_pct = $this->get_loyalty_extra_percent($user_id); if ($loyal_pct > 0) { $base = (float) $this->lookup_base_price($product_id); switch ($recipe['loyalty_class']) { case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break; case 'replace_all': $price = $base * (1.0 - ($loyal_pct / 100.0)); break; } } } return $price; } public function is_excluded_from_free_shipping($product_id) { $product_id = (int) $product_id; if ($product_id <= 0) { return false; } $mode = class_exists('AIEO_DMM_Settings') ? AIEO_DMM_Settings::get('pricing_mode', 'reactive') : 'reactive'; if ($mode === 'precomputed' && class_exists('AIEO_DMM_Product_FP')) { $fp = AIEO_DMM_Product_FP::get($product_id); if (is_array($fp) && array_key_exists('nfs_excluded', $fp)) { return !empty($fp['nfs_excluded']); } } $set = $this->get_no_free_shipping_set(); if (isset($set[$product_id])) { return true; } $parent = (int) wp_get_post_parent_id($product_id); if ($parent > 0 && isset($set[$parent])) { return true; } return false; } public function get_loyalty_class_ids_for_user($user_id) { $user_id = (int) $user_id; if ($user_id <= 0) { return array(); } if (!isset($this->loyalty_cache[$user_id])) { $this->loyalty_cache[$user_id] = $this->load_loyalty_for_user($user_id); } return array_keys($this->loyalty_cache[$user_id]); } public function get_loyalty_extra_percent($user_id) { $user_id = (int) $user_id; if ($user_id <= 0) { return 0.0; } if (!isset($this->loyalty_cache[$user_id])) { $this->loyalty_cache[$user_id] = $this->load_loyalty_for_user($user_id); } return (float) array_sum($this->loyalty_cache[$user_id]); } public function preload_role_prices(array $product_ids) { global $wpdb; $ids = array_filter(array_map('intval', $product_ids)); $ids = array_unique($ids); $ids = array_diff($ids, array_keys($this->role_price_cache)); if (empty($ids)) { return; } $table = $wpdb->prefix . 'aieo_dmm_role_prices'; if (!$this->table_has_rows_column($table)) { return; } $placeholders = implode(',', array_fill(0, count($ids), '%d')); $sql = "SELECT * FROM {$table} WHERE product_id IN ({$placeholders}) OR parent_id IN ({$placeholders})"; $args = array_merge(array_values($ids), array_values($ids)); $rows = $wpdb->get_results($wpdb->prepare($sql, $args), ARRAY_A); if (!is_array($rows)) { return; } foreach ($rows as $row) { $this->role_price_cache[(int) $row['product_id']] = $this->row_to_role_prices($row); } foreach ($ids as $id) { $this->role_price_families_loaded[$id] = true; $this->role_price_ids_covered[$id] = true; if (!isset($this->role_price_cache[$id])) { $this->role_price_cache[$id] = array(); } } } private function row_to_role_prices(array $row) { $prices = array(); foreach ($row as $col => $val) { if (strpos($col, 'role_') !== 0 || $col === 'role_prices') { continue; } if ($val === null || $val === '') { continue; } $price = (float) $val; if ($price > 0) { $prices[substr($col, 5)] = $price; } } return $prices; } private function warm_role_prices_for($product_id) { $product_id = (int) $product_id; if ($product_id <= 0 || array_key_exists($product_id, $this->role_price_cache)) { return; } if (isset($this->role_price_ids_covered[$product_id])) { $this->role_price_cache[$product_id] = array(); return; } $parent = (int) wp_get_post_parent_id($product_id); $root = $parent > 0 ? $parent : $product_id; if (isset($this->role_price_families_loaded[$root])) { $this->load_role_prices_by_ids(array($product_id)); } else { $this->load_role_price_family($root, array($product_id)); } if (isset($this->role_price_ids_covered[$product_id]) && !array_key_exists($product_id, $this->role_price_cache)) { $this->role_price_cache[$product_id] = array(); } } private function load_role_price_family($root, array $also_ids = array()) { global $wpdb; $root = (int) $root; $table = $wpdb->prefix . 'aieo_dmm_role_prices'; if (!$this->table_has_rows_column($table)) { return; } $ids = array_values(array_unique(array_filter(array_map('intval', array_merge(array($root), $also_ids))))); $ph = implode(',', array_fill(0, count($ids), '%d')); $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$table} WHERE product_id IN ({$ph}) OR parent_id = %d", array_merge($ids, array($root)) ), ARRAY_A); if ('' !== $wpdb->last_error) { return; } $this->role_price_families_loaded[$root] = true; foreach ($ids as $id) { $this->role_price_ids_covered[$id] = true; } foreach ((array) $rows as $row) { $this->role_price_cache[(int) $row['product_id']] = $this->row_to_role_prices($row); } if (!array_key_exists($root, $this->role_price_cache)) { $this->role_price_cache[$root] = array(); } } private function load_role_prices_by_ids(array $ids) { global $wpdb; $ids = array_values(array_unique(array_filter(array_map('intval', $ids)))); if (empty($ids)) { return; } $table = $wpdb->prefix . 'aieo_dmm_role_prices'; if (!$this->table_has_rows_column($table)) { return; } $ph = implode(',', array_fill(0, count($ids), '%d')); $rows = $wpdb->get_results($wpdb->prepare( "SELECT * FROM {$table} WHERE product_id IN ({$ph})", $ids ), ARRAY_A); if ('' !== $wpdb->last_error) { return; } foreach ($ids as $id) { $this->role_price_ids_covered[$id] = true; } foreach ((array) $rows as $row) { $this->role_price_cache[(int) $row['product_id']] = $this->row_to_role_prices($row); } } public function warm_role_price_family($parent_id, array $child_ids = array()) { $parent_id = (int) $parent_id; if ($parent_id <= 0) { return; } $pending = array(); foreach ($child_ids as $cid) { $cid = (int) $cid; if ($cid > 0 && !isset($this->role_price_ids_covered[$cid]) && !array_key_exists($cid, $this->role_price_cache)) { $pending[] = $cid; } } if (isset($this->role_price_families_loaded[$parent_id]) && empty($pending)) { return; } $this->load_role_price_family($parent_id, $pending); if (!isset($this->role_price_families_loaded[$parent_id])) { return; } foreach ($pending as $cid) { if (!array_key_exists($cid, $this->role_price_cache)) { $this->role_price_cache[$cid] = array(); } } } public function get_cached_role_prices($product_id) { $product_id = (int) $product_id; if ($product_id <= 0) { return array(); } $this->warm_role_prices_for($product_id); return array_key_exists($product_id, $this->role_price_cache) ? $this->role_price_cache[$product_id] : null; } private function resolve_price($product_id, $user_id, $base) { $settings = AIEO_DMM_Settings::get_all(); $recipe = $settings['recipe']; $mode = isset($settings['pricing_mode']) ? (string) $settings['pricing_mode'] : 'reactive'; $force_reactive = !empty($GLOBALS['aieo_dmm_force_reactive']); if (!$force_reactive && $mode === 'precomputed' && class_exists('AIEO_DMM_Product_FP') && class_exists('AIEO_DMM_User_FP')) { return $this->resolve_price_precomputed($product_id, $user_id, $base, $recipe); } $price = $base; if ($recipe['role_pricing'] === 'active') { $role_price = $this->lookup_role_price($product_id, $user_id, $settings['enabled_roles']); if ($role_price !== null && $role_price > 0) { $price = $role_price; } } $role_adjusted = $price; $brand_percent = $this->lookup_brand_discount_percent($product_id); if ($brand_percent > 0 && $recipe['brand_discount'] !== 'off') { switch ($recipe['brand_discount']) { case 'stack_multiplicative': $price = $price * (1.0 - ($brand_percent / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($brand_percent / 100.0)); break; case 'replace_role': $price = $base * (1.0 - ($brand_percent / 100.0)); break; } } $sched_percent = $this->lookup_schedule_percent($product_id); if ($sched_percent > 0) { $price = $price * (1.0 - ($sched_percent / 100.0)); } if ($user_id > 0 && $recipe['loyalty_class'] !== 'off') { $loyal_percent = $this->get_loyalty_extra_percent($user_id); if ($loyal_percent > 0) { switch ($recipe['loyalty_class']) { case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_percent / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($loyal_percent / 100.0)); break; case 'replace_all': $price = $base * (1.0 - ($loyal_percent / 100.0)); break; } } } if ($price < 0) { $price = 0.0; } return (float) $price; } private function resolve_price_precomputed($product_id, $user_id, $base, $recipe) { $fp = AIEO_DMM_Product_FP::get($product_id); $ufp = AIEO_DMM_User_FP::get($user_id); if (!$fp) { return $this->resolve_price_reactive_fallback($product_id, $user_id, $base, $recipe); } $price = $base; if ($recipe['role_pricing'] === 'active' && is_array($ufp) && !empty($ufp['enabled_role_column'])) { $this->warm_role_prices_for($product_id); $map = isset($this->role_price_cache[$product_id]) ? $this->role_price_cache[$product_id] : array(); $col = $ufp['enabled_role_column']; if (isset($map[$col]) && $map[$col] > 0) { $price = (float) $map[$col]; } } $role_adjusted = $price; $brand_pct = (float) ($fp['effective_combined_pct'] ?? 0); if ($brand_pct > 0 && $recipe['brand_discount'] !== 'off') { switch ($recipe['brand_discount']) { case 'stack_multiplicative': $price = $price * (1.0 - ($brand_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($brand_pct / 100.0)); break; case 'replace_role': $price = $base * (1.0 - ($brand_pct / 100.0)); break; } } $sched_pct = (float) ($fp['active_schedule_pct'] ?? 0); if ($sched_pct > 0) { $price = $price * (1.0 - ($sched_pct / 100.0)); } if ($user_id > 0 && is_array($ufp) && $recipe['loyalty_class'] !== 'off') { $loyal_pct = (float) ($ufp['loyalty_extra_pct_sum'] ?? 0) + (float) ($ufp['personal_extra_pct'] ?? 0); $seq_expires = isset($ufp['sequence_expires_at']) ? (string) $ufp['sequence_expires_at'] : ''; if (!empty($ufp['sequence_pct']) && $seq_expires !== '' && strtotime($seq_expires) > time()) { $loyal_pct += (float) $ufp['sequence_pct']; } if ($loyal_pct > 0) { switch ($recipe['loyalty_class']) { case 'stack_multiplicative': $price = $price * (1.0 - ($loyal_pct / 100.0)); break; case 'stack_additive': $price = $price - ($role_adjusted * ($loyal_pct / 100.0)); break; case 'replace_all': $price = $base * (1.0 - ($loyal_pct / 100.0)); break; } } if (!empty($ufp['brand_overrides']) && is_array($ufp['brand_overrides'])) { $brand_ids = isset($fp['brand_term_ids']) ? (array) $fp['brand_term_ids'] : array(); foreach ($brand_ids as $bid) { if (isset($ufp['brand_overrides'][$bid])) { $bo = (float) $ufp['brand_overrides'][$bid]; if ($bo > 0) { $price = $price * (1.0 - ($bo / 100.0)); } } } } } if ($price < 0) { $price = 0.0; } return (float) $price; } private function resolve_price_reactive_fallback($product_id, $user_id, $base, $recipe) { $old = $GLOBALS['aieo_dmm_force_reactive'] ?? false; $GLOBALS['aieo_dmm_force_reactive'] = true; try { return $this->resolve_price($product_id, $user_id, $base); } finally { $GLOBALS['aieo_dmm_force_reactive'] = $old; } } private function lookup_base_price($product_id) { if (!function_exists('wc_get_product')) { return 0.0; } $product = wc_get_product($product_id); if (!$product) { return 0.0; } $price = $product->get_regular_price('edit'); return is_numeric($price) ? (float) $price : 0.0; } public function get_role_price($product_id, $user_id = null) { $product_id = (int) $product_id; if ($product_id <= 0) { return null; } if ($user_id === null) { if (self::role_pricing_suppressed()) { return null; } $user_id = get_current_user_id(); } $enabled_roles = AIEO_DMM_Settings::get('enabled_roles', array()); if (!is_array($enabled_roles) || empty($enabled_roles)) { return null; } return $this->lookup_role_price($product_id, (int) $user_id, $enabled_roles); } private function lookup_role_price($product_id, $user_id, array $enabled_roles) { if (empty($enabled_roles)) { return null; } $user_roles = array(); if ($user_id > 0) { $user = get_userdata($user_id); if ($user && !empty($user->roles)) { $user_roles = array_values(array_intersect($user->roles, $enabled_roles)); } } if (empty($user_roles)) { return null; } $this->warm_role_prices_for($product_id); $map = isset($this->role_price_cache[$product_id]) ? $this->role_price_cache[$product_id] : array(); if (empty($map)) { return null; } $best = null; foreach ($user_roles as $role) { $col = $this->sanitize_role_column_name($role); if (isset($map[$col]) && $map[$col] > 0) { if ($best === null || $map[$col] < $best) { $best = (float) $map[$col]; } } } return $best; } private function sanitize_role_column_name($role) { $clean = strtolower(preg_replace('/[^a-z0-9_]/', '_', $role)); if ($clean === '') { return 'r'; } if (preg_match('/^[0-9]/', $clean)) { $clean = 'r_' . $clean; } return $clean; } private function lookup_brand_discount_percent($product_id) { $rules = $this->get_brand_discount_rules(); if (empty($rules)) { return 0.0; } $brand_terms = $this->get_product_brand_term_ids($product_id); $cat_terms = $this->get_product_category_term_ids($product_id); $need_terms = taxonomy_exists('pa_need') ? wp_get_object_terms($product_id, 'pa_need', array('fields' => 'ids')) : array(); if (is_wp_error($need_terms)) { $need_terms = array(); } $need_terms = array_map('intval', (array) $need_terms); $self_ids = array((int) $product_id); $parent_pid = (int) wp_get_post_parent_id($product_id); if ($parent_pid > 0) { $self_ids[] = $parent_pid; } $max = 0.0; foreach ($rules as $rule) { if ((float) (isset($rule['min_subtotal']) ? $rule['min_subtotal'] : 0) > 0) { continue; } if (!empty($rule['product_ids'])) { continue; } if (!empty($rule['excluded_brand_ids']) && !empty($brand_terms) && array_intersect($brand_terms, $rule['excluded_brand_ids'])) { continue; } if (!empty($rule['excluded_category_ids']) && !empty($cat_terms) && array_intersect($cat_terms, $rule['excluded_category_ids'])) { continue; } if (!empty($rule['excluded_need_ids']) && !empty($need_terms) && array_intersect($need_terms, $rule['excluded_need_ids'])) { continue; } if (!empty($rule['excluded_product_ids']) && array_intersect($self_ids, $rule['excluded_product_ids'])) { continue; } $rule_need_ids = isset($rule['need_term_ids']) && is_array($rule['need_term_ids']) ? $rule['need_term_ids'] : array(); $has_brand_filter = !empty($rule['brand_term_id']); $has_cat_filter = !empty($rule['category_term_ids']); $has_need_filter = !empty($rule_need_ids); if ($rule['operator'] === 'OR') { $match = ($has_brand_filter && in_array((int) $rule['brand_term_id'], $brand_terms, true)) || ($has_cat_filter && !empty($cat_terms) && array_intersect($rule['category_term_ids'], $cat_terms)) || ($has_need_filter && !empty($need_terms) && array_intersect($rule_need_ids, $need_terms)); if (!$has_brand_filter && !$has_cat_filter && !$has_need_filter) { $match = false; } } else { $brand_match = !$has_brand_filter || (!empty($brand_terms) && in_array((int) $rule['brand_term_id'], $brand_terms, true)); $cat_match = !$has_cat_filter || (!empty($cat_terms) && array_intersect($rule['category_term_ids'], $cat_terms)); $need_match = !$has_need_filter || (!empty($need_terms) && array_intersect($rule_need_ids, $need_terms)); $match = $brand_match && $cat_match && $need_match; } if ($match && $rule['percent'] > $max) { $max = (float) $rule['percent']; } } return $max; } private function get_product_brand_term_ids($product_id) { $taxonomy = class_exists('AIEO_DMM_Brand_Discounts') ? AIEO_DMM_Brand_Discounts::get_brand_taxonomy() : 'product_brand'; if (!$taxonomy) { $taxonomy = 'product_brand'; } $terms = wp_get_object_terms($product_id, $taxonomy, array('fields' => 'ids')); if (is_wp_error($terms) || !is_array($terms) || empty($terms)) { $parent = (int) wp_get_post_parent_id($product_id); if ($parent > 0) { $terms = wp_get_object_terms($parent, $taxonomy, array('fields' => 'ids')); } } return is_array($terms) ? array_map('intval', $terms) : array(); } private function get_product_category_term_ids($product_id) { $terms = wp_get_object_terms($product_id, 'product_cat', array('fields' => 'ids')); if (is_wp_error($terms) || !is_array($terms) || empty($terms)) { $parent = (int) wp_get_post_parent_id($product_id); if ($parent > 0) { $terms = wp_get_object_terms($parent, 'product_cat', array('fields' => 'ids')); } } return is_array($terms) ? array_map('intval', $terms) : array(); } private function get_brand_discount_rules() { if ($this->brand_discount_snapshot !== null) { return $this->brand_discount_snapshot; } $this->brand_discount_snapshot = AIEO_DMM_Cache::remember( 'brands', 'active_rules', array($this, 'load_brand_discount_rules'), AIEO_DMM_Cache::TTL_SHORT ); return $this->brand_discount_snapshot; } public function load_brand_discount_rules() { global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_brand_discounts'; if (!$this->table_exists($table)) { return array(); } $cols = $wpdb->get_col("SHOW COLUMNS FROM {$table}"); $cols = is_array($cols) ? $cols : array(); $select_cols = array('brand_term_id', 'category_term_ids', 'rule_operator', 'percent'); foreach (array('need_term_ids', 'min_subtotal', 'min_subtotal_scope', 'application_scope', 'excluded_brand_ids', 'excluded_category_ids', 'excluded_need_ids', 'product_ids', 'excluded_product_ids') as $opt_col) { if (in_array($opt_col, $cols, true)) { $select_cols[] = $opt_col; } } $select = 'SELECT ' . implode(', ', $select_cols); $today = current_time('Y-m-d'); $rows = $wpdb->get_results($wpdb->prepare( "{$select} FROM {$table} WHERE enabled = 1 AND (date_from IS NULL OR date_from <= %s) AND (date_to IS NULL OR date_to >= %s)", $today, $today ), ARRAY_A); $decode_ids = static function ($val) { if (empty($val)) { return array(); } $d = json_decode((string) $val, true); return is_array($d) ? array_values(array_filter(array_map('intval', $d))) : array(); }; $out = array(); if (is_array($rows)) { foreach ($rows as $row) { $out[] = array( 'brand_term_id' => (int) $row['brand_term_id'], 'category_term_ids' => $decode_ids(isset($row['category_term_ids']) ? $row['category_term_ids'] : ''), 'need_term_ids' => $decode_ids(isset($row['need_term_ids']) ? $row['need_term_ids'] : ''), 'operator' => (isset($row['rule_operator']) && strtoupper($row['rule_operator']) === 'OR') ? 'OR' : 'AND', 'percent' => (float) $row['percent'], 'min_subtotal' => isset($row['min_subtotal']) ? (float) $row['min_subtotal'] : 0.0, 'min_subtotal_scope' => isset($row['min_subtotal_scope']) ? (string) $row['min_subtotal_scope'] : 'targeted', 'application_scope' => isset($row['application_scope']) ? (string) $row['application_scope'] : 'matching', 'excluded_brand_ids' => $decode_ids(isset($row['excluded_brand_ids']) ? $row['excluded_brand_ids'] : ''), 'excluded_category_ids' => $decode_ids(isset($row['excluded_category_ids']) ? $row['excluded_category_ids'] : ''), 'excluded_need_ids' => $decode_ids(isset($row['excluded_need_ids']) ? $row['excluded_need_ids'] : ''), 'product_ids' => $decode_ids(isset($row['product_ids']) ? $row['product_ids'] : ''), 'excluded_product_ids' => $decode_ids(isset($row['excluded_product_ids']) ? $row['excluded_product_ids'] : ''), ); } } return $out; } public function load_brand_discount_snapshot() { $rules = $this->load_brand_discount_rules(); $out = array(); foreach ($rules as $r) { $tid = (int) $r['brand_term_id']; if ($tid > 0 && $r['percent'] > 0 && (!isset($out[$tid]) || $r['percent'] > $out[$tid])) { $out[$tid] = (float) $r['percent']; } } return $out; } private function lookup_schedule_percent($product_id) { $snap = $this->get_discount_schedule_snapshot(); return isset($snap[$product_id]) ? (float) $snap[$product_id] : 0.0; } private function get_discount_schedule_snapshot() { if ($this->discount_schedule_snapshot !== null) { return $this->discount_schedule_snapshot; } $this->discount_schedule_snapshot = AIEO_DMM_Cache::remember( 'prices', 'schedule_snapshot', array($this, 'load_discount_schedule_snapshot'), AIEO_DMM_Cache::TTL_SHORT ); return $this->discount_schedule_snapshot; } public function load_discount_schedule_snapshot() { global $wpdb; $table = $wpdb->prefix . 'aieo_dmm_discount_schedules'; if (!$this->table_exists($table)) { return array(); } $today = current_time('Y-m-d'); $rows = $wpdb->get_results($wpdb->prepare( "SELECT product_id, percent FROM {$table} WHERE active = 1 AND (date_from IS NULL OR date_from <= %s) AND (date_to IS NULL OR date_to >= %s)", $today, $today ), ARRAY_A); $out = array(); if (is_array($rows)) { foreach ($rows as $row) { $pid = (int) $row['product_id']; $pct = (float) $row['percent']; if ($pid > 0 && $pct > 0 && (!isset($out[$pid]) || $pct > $out[$pid])) { $out[$pid] = $pct; } } } return $out; } private function get_no_free_shipping_set() { if ($this->no_free_shipping_set !== null) { return $this->no_free_shipping_set; } $cached = wp_cache_get('om_no_free_shipping_ids', 'om_shipping', false, $found); if ($found && is_array($cached)) { $this->no_free_shipping_set = $cached; return $cached; } $out = $this->load_no_free_shipping_set(); wp_cache_set('om_no_free_shipping_ids', $out, 'om_shipping', AIEO_DMM_Cache::TTL_MEDIUM); $this->no_free_shipping_set = $out; return $out; } public function load_no_free_shipping_set() { global $wpdb; $out = array(); $candidates = array( $wpdb->prefix . 'aieo_dmm_no_free_shipping', $wpdb->prefix . 'om_no_free_shipping', ); foreach ($candidates as $table) { if (!$this->table_exists($table)) { continue; } $ids = $wpdb->get_col("SELECT product_id FROM {$table}"); foreach ((array) $ids as $pid) { $out[(int) $pid] = true; } } return $out; } private function load_loyalty_for_user($user_id) { global $wpdb; $assign = $wpdb->prefix . 'aieo_dmm_loyalty_assignments'; $defs = $wpdb->prefix . 'aieo_dmm_loyalty_classes'; if (!$this->table_exists($assign) || !$this->table_exists($defs)) { return array(); } $key = 'user:' . $user_id; return AIEO_DMM_Cache::remember('loyalty', $key, function () use ($wpdb, $assign, $defs, $user_id) { $rows = $wpdb->get_results($wpdb->prepare( "SELECT d.id, d.extra_percent FROM {$assign} a INNER JOIN {$defs} d ON d.id = a.class_id WHERE a.user_id = %d AND d.enabled = 1", $user_id ), ARRAY_A); $out = array(); if (is_array($rows)) { foreach ($rows as $row) { $out[(int) $row['id']] = (float) $row['extra_percent']; } } return $out; }, AIEO_DMM_Cache::TTL_MEDIUM); } private function table_exists($table) { global $wpdb; static $cache = array(); if (isset($cache[$table])) { return $cache[$table]; } $found = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s", $table )); $cache[$table] = ((int) $found) > 0; return $cache[$table]; } private function table_has_rows_column($table) { global $wpdb; static $cache = array(); if (isset($cache[$table])) { return $cache[$table]; } if (!$this->table_exists($table)) { return $cache[$table] = false; } $col = $wpdb->get_var($wpdb->prepare( "SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = %s AND COLUMN_NAME = 'product_id'", $table )); return $cache[$table] = ((int) $col) > 0; } public function register_preload_hooks() { add_action('woocommerce_before_shop_loop', array($this, 'preload_archive'), 1); add_action('woocommerce_before_main_content', array($this, 'preload_archive'), 1); add_filter('the_posts', array($this, 'preload_queried_products'), 10, 2); } public function preload_queried_products($posts, $query = null) { if (is_admin() || !is_array($posts) || count($posts) < 2) { return $posts; } if (!$this->viewer_has_priced_role()) { return $posts; } $ids = array(); foreach ($posts as $post) { if (isset($post->ID, $post->post_type) && 'product' === $post->post_type) { $ids[] = (int) $post->ID; } } if (count($ids) > 1) { $this->preload_role_prices_with_children($ids); } return $posts; } private function viewer_has_priced_role() { if (self::role_pricing_suppressed()) { return false; } if (null !== $this->viewer_priced_role) { return $this->viewer_priced_role; } $enabled = AIEO_DMM_Settings::get('enabled_roles', array()); if (!is_array($enabled) || empty($enabled) || !is_user_logged_in()) { return $this->viewer_priced_role = false; } $user = wp_get_current_user(); $roles = ($user && !empty($user->roles)) ? (array) $user->roles : array(); return $this->viewer_priced_role = !empty(array_intersect($roles, $enabled)); } private function preload_role_prices_with_children(array $parent_ids) { global $wpdb; $parent_ids = array_values(array_unique(array_filter(array_map('intval', $parent_ids)))); $parent_ids = array_values(array_diff($parent_ids, array_keys($this->role_price_children_expanded))); if (empty($parent_ids)) { return; } foreach ($parent_ids as $pid) { $this->role_price_children_expanded[$pid] = true; } $ph = implode(',', array_fill(0, count($parent_ids), '%d')); $children = $wpdb->get_col($wpdb->prepare( "SELECT ID FROM {$wpdb->posts} WHERE post_parent IN ({$ph}) AND post_type = 'product_variation'", $parent_ids )); $ids = $parent_ids; if ('' === $wpdb->last_error) { foreach ((array) $children as $cid) { $ids[] = (int) $cid; } } foreach (array_chunk($ids, self::ROLE_PRICE_PRELOAD_CHUNK) as $chunk) { $this->preload_role_prices($chunk); } } public function preload_archive() { if (is_admin() || !$this->viewer_has_priced_role()) { return; } global $wp_query; if (!$wp_query || empty($wp_query->posts)) { return; } $ids = array(); foreach ($wp_query->posts as $post) { if (isset($post->ID)) { $ids[] = (int) $post->ID; } } if (!empty($ids)) { $this->preload_role_prices_with_children($ids); } } }