const META_MIN_QTY = '_yoone_bundle_min_quantity'; // int // 选择模式:include(仅包含列表)、exclude(排除列表,其余全部允许)、all(全部 simple 产品) const META_SELECT_MODE = '_yoone_bundle_select_mode'; // 'include' | 'exclude' | 'all' // 旧版:仅支持分类(product_cat)。 const META_CATEGORIES = '_yoone_bundle_categories'; // array product_cat term_ids(兼容旧数据) // 新版:支持按 taxonomy(product_cat 或 product_tag)进行分组显示 const META_GROUP_TAXONOMY = '_yoone_bundle_group_taxonomy'; // 'product_cat' | 'product_tag' const META_GROUP_TERMS = '_yoone_bundle_group_terms'; // array 选中的术语ID(来自所选taxonomy) protected static $instance = null; public static function instance() { if (null === self::$instance) { self::$instance = new self(); } return self::$instance; } private function __construct() { // Add "Mix and Match" to the "Product Type" dropdown in the admin. add_filter('product_type_selector', array($this, 'register_product_type_in_selector')); // Map the type to our class name, so WooCommerce instantiates the correct product object. add_filter('woocommerce_product_class', array($this, 'map_product_class'), 10, 2); } /** * Add "Mix and Match" to the "Product Type" dropdown in the admin. */ public function register_product_type_in_selector($types) { $types[self::TYPE] = __('Mix and Match (Yoone Bundle)', 'yoone-product-bundles'); return $types; } /** * Map the type to our class name, so WooCommerce instantiates the correct product object. */ public function map_product_class($classname, $product_type) { if ($product_type === self::TYPE) { $classname = 'WC_Product_Yoone_Bundle'; } return $classname; } /** * Read and normalize the bundle configuration. * @param int|WC_Product $product * @return array{allowed_products:int[],min_qty:int,categories:int[]} */ public static function get_bundle_config($product) { $product = is_numeric($product) ? wc_get_product($product) : $product; if (! $product) return array('allowed_products' => array(), 'min_qty' => 0, 'group_taxonomy' => 'product_cat', 'group_terms' => array()); $pid = $product->get_id(); $allowed = get_post_meta($pid, self::META_ALLOWED_PRODUCTS, true); $min = absint(get_post_meta($pid, self::META_MIN_QTY, true)); $select_mode = get_post_meta($pid, self::META_SELECT_MODE, true); $select_mode = in_array($select_mode, array('include','exclude','all'), true) ? $select_mode : 'include'; // 读取新的分组设置 $group_tax = get_post_meta($pid, self::META_GROUP_TAXONOMY, true); $group_terms = get_post_meta($pid, self::META_GROUP_TERMS, true); $group_tax = in_array($group_tax, array('product_cat','product_tag'), true) ? $group_tax : 'product_cat'; // Keep only simple products (to avoid issues if variations or other types were selected in the backend) $allowed = is_array($allowed) ? array_values(array_map('absint', $allowed)) : array(); if (! empty($allowed)) { $simple_only = array(); foreach ($allowed as $aid) { $p = wc_get_product($aid); if ($p && $p->is_type('simple')) { $simple_only[] = $aid; } } $allowed = $simple_only; } // 根据选择模式计算最终 allowed 列表 if ($select_mode === 'all' || $select_mode === 'exclude') { // 获取所有 simple 产品ID(包含任何目录可见性) $all_ids = wc_get_products(array( 'type' => array('simple'), 'status' => array('publish'), 'catalog_visibility' => 'any', 'limit' => -1, 'return' => 'ids', )); $all_ids = is_array($all_ids) ? array_values(array_map('absint', $all_ids)) : array(); if ($select_mode === 'all') { $allowed = $all_ids; // 全部 simple 产品 } else { // exclude 模式:从全部 simple 产品中排除配置列表 $allowed = array_values(array_diff($all_ids, $allowed)); } } // include 模式:保持 allowed 原样 // 兼容旧版:如果未配置新术语,但存在旧的分类设置,则沿用分类 if (empty($group_terms)) { $cats = get_post_meta($pid, self::META_CATEGORIES, true); $group_terms = is_array($cats) ? array_values(array_map('absint', $cats)) : array(); $group_tax = 'product_cat'; } else { $group_terms = is_array($group_terms) ? array_values(array_map('absint', $group_terms)) : array(); } return array( 'allowed_products' => $allowed, 'min_qty' => max(0, $min), 'select_mode' => $select_mode, 'group_taxonomy' => $group_tax, 'group_terms' => $group_terms, ); } }