yoone-wc-product-bundles/includes/class-yoone-product-bundles...

132 lines
6.5 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* Core: Registers the product type, constants, and utility methods.
*/
defined('ABSPATH') || exit;
class Yoone_Product_Bundles {
const TYPE = 'yoone_bundle';
// Post meta keys for configuration
const META_ALLOWED_PRODUCTS = '_yoone_bundle_allowed_products'; // array<int>
const META_MIN_QTY = '_yoone_bundle_min_quantity'; // int
// 是否允许在购物车中编辑混装(移除子项、调整数量等)
const META_EDIT_IN_CART = '_yoone_bundle_edit_in_cart'; // 'yes' | 'no'
// Bundle 折扣设置
const META_DISCOUNT_TYPE = '_yoone_bundle_discount_type'; // 'none' | 'percent' | 'fixed'
const META_DISCOUNT_AMOUNT = '_yoone_bundle_discount_amount'; // float
// 选择模式include仅包含列表、exclude排除列表其余全部允许、all全部 simple 产品)
const META_SELECT_MODE = '_yoone_bundle_select_mode'; // 'include' | 'exclude' | 'all'
// 旧版仅支持分类product_cat
const META_CATEGORIES = '_yoone_bundle_categories'; // array<int> product_cat term_ids兼容旧数据
// 新版:支持按 taxonomyproduct_cat 或 product_tag进行分组显示
const META_GROUP_TAXONOMY = '_yoone_bundle_group_taxonomy'; // 'product_cat' | 'product_tag'
const META_GROUP_TERMS = '_yoone_bundle_group_terms'; // array<int> 选中的术语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] = __('Product Bundle (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));
$edit_in_cart = get_post_meta($pid, self::META_EDIT_IN_CART, true);
$edit_in_cart = ($edit_in_cart === 'yes') ? 'yes' : 'no';
$discount_type = get_post_meta($pid, self::META_DISCOUNT_TYPE, true);
$discount_type = in_array($discount_type, array('none','percent','fixed'), true) ? $discount_type : 'none';
$discount_amount = floatval(get_post_meta($pid, self::META_DISCOUNT_AMOUNT, true));
if ($discount_amount < 0) $discount_amount = 0.0;
$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),
'edit_in_cart' => $edit_in_cart,
'discount_type' => $discount_type,
'discount_amount' => $discount_amount,
'select_mode' => $select_mode,
'group_taxonomy' => $group_tax,
'group_terms' => $group_terms,
);
}
}