184 lines
6.9 KiB
PHP
184 lines
6.9 KiB
PHP
<?php
|
||
/**
|
||
* 前端:为混装产品渲染自定义的“添加到购物车”表单,并处理验证和加入购物车的逻辑。
|
||
*/
|
||
defined('ABSPATH') || exit;
|
||
|
||
class Yoone_Product_Bundles_Frontend {
|
||
|
||
private static $_instance = null;
|
||
|
||
public static function instance() {
|
||
if (is_null(self::$_instance)) {
|
||
self::$_instance = new self();
|
||
}
|
||
return self::$_instance;
|
||
}
|
||
|
||
private function __construct() {
|
||
$this->setup_hooks();
|
||
}
|
||
|
||
/**
|
||
* 设置所有与前端相关的钩子。
|
||
*/
|
||
public function setup_hooks() {
|
||
// 处理混装产品的自定义“添加到购物车”流程
|
||
add_filter('woocommerce_add_to_cart_validation', array($this, 'process_bundle_add_to_cart'), 10, 3);
|
||
|
||
// 当混装容器被移除时,移除其子项目
|
||
add_action('woocommerce_cart_item_removed', array($this, 'remove_bundle_children'), 10, 2);
|
||
|
||
// 隐藏购物车中子项目的“移除”链接
|
||
add_filter('woocommerce_cart_item_remove_link', array($this, 'hide_child_remove_link'), 10, 2);
|
||
|
||
// 在购物车中,显示子项目所属的混装产品
|
||
add_filter('woocommerce_get_item_data', array($this, 'display_child_bundle_link'), 10, 2);
|
||
|
||
// 为混装产品页面添加 body class,以便于样式化
|
||
add_filter('body_class', array($this, 'filter_body_class'));
|
||
|
||
// 用我们的自定义表单替换默认的“添加到购物车”按钮
|
||
add_action('woocommerce_single_product_summary', array($this, 'remove_default_add_to_cart_for_bundle'), 29);
|
||
add_action('woocommerce_single_product_summary', array($this, 'render_bundle_add_to_cart_form'), 30);
|
||
}
|
||
|
||
/**
|
||
* Hijack the add-to-cart process for bundle products.
|
||
* Validates the submission, then adds the container and all child products to the cart.
|
||
*
|
||
* @return bool False to prevent the default add-to-cart action.
|
||
*/
|
||
public function process_bundle_add_to_cart($passed, $product_id, $quantity) {
|
||
$product = wc_get_product($product_id);
|
||
if (!$product || $product->get_type() !== Yoone_Product_Bundles::TYPE) {
|
||
return $passed; // Not our product, pass through.
|
||
}
|
||
|
||
// 1. Validation
|
||
$config = Yoone_Product_Bundles::get_bundle_config($product);
|
||
$min_qty = max(1, absint($config['min_qty']));
|
||
$components = !empty($_POST['yoone_bundle_components']) ? (array) $_POST['yoone_bundle_components'] : array();
|
||
|
||
$total_qty = 0;
|
||
$clean_components = array();
|
||
foreach ($components as $comp_id => $qty) {
|
||
$qty = absint($qty);
|
||
if ($qty > 0) {
|
||
$total_qty += $qty;
|
||
$clean_components[absint($comp_id)] = $qty;
|
||
}
|
||
}
|
||
|
||
if ($total_qty < $min_qty) {
|
||
wc_add_notice(sprintf(__('You need to select at least %d items to add this bundle to your cart.', 'yoone-product-bundles'), $min_qty), 'error');
|
||
return false;
|
||
}
|
||
|
||
// 2. Add items to cart
|
||
try {
|
||
$bundle_container_id = uniqid('bundle_');
|
||
|
||
// Add the main bundle product (as a 0-price container)
|
||
WC()->cart->add_to_cart($product_id, 1, 0, array(), array('yoone_bundle_container_id' => $bundle_container_id));
|
||
|
||
// Add child components
|
||
foreach ($clean_components as $comp_id => $qty) {
|
||
WC()->cart->add_to_cart($comp_id, $qty, 0, array(), array(
|
||
'yoone_bundle_parent_id' => $bundle_container_id,
|
||
'yoone_bundle_parent_product_id' => $product_id
|
||
));
|
||
}
|
||
|
||
// Set success message and redirect
|
||
wc_add_to_cart_message(array($product_id => 1), true);
|
||
add_filter('woocommerce_add_to_cart_redirect', function() { return wc_get_cart_url(); });
|
||
|
||
} catch (Exception $e) {
|
||
wc_add_notice($e->getMessage(), 'error');
|
||
return false;
|
||
}
|
||
|
||
// Prevent default add-to-cart for the main product
|
||
return false;
|
||
}
|
||
|
||
/**
|
||
* When a cart item is removed, check if it's a bundle container.
|
||
* If so, find and remove all its child items.
|
||
*/
|
||
public function remove_bundle_children($removed_cart_item_key, $cart) {
|
||
$removed_item = $cart->get_removed_cart_items()[$removed_cart_item_key];
|
||
|
||
if (isset($removed_item['yoone_bundle_container_id'])) {
|
||
$bundle_container_id = $removed_item['yoone_bundle_container_id'];
|
||
|
||
foreach ($cart->get_cart() as $cart_item_key => $cart_item) {
|
||
if (isset($cart_item['yoone_bundle_parent_id']) && $cart_item['yoone_bundle_parent_id'] === $bundle_container_id) {
|
||
$cart->remove_cart_item($cart_item_key);
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Hide the remove link for child items of a bundle.
|
||
*/
|
||
public function hide_child_remove_link($link, $cart_item_key) {
|
||
$cart_item = WC()->cart->get_cart_item($cart_item_key);
|
||
if (isset($cart_item['yoone_bundle_parent_id'])) {
|
||
return '';
|
||
}
|
||
return $link;
|
||
}
|
||
|
||
/**
|
||
* For child items, add a meta line in the cart to link back to the parent bundle.
|
||
*/
|
||
public function display_child_bundle_link($item_data, $cart_item) {
|
||
if (isset($cart_item['yoone_bundle_parent_product_id'])) {
|
||
$parent_product = wc_get_product($cart_item['yoone_bundle_parent_product_id']);
|
||
if ($parent_product) {
|
||
$item_data[] = array(
|
||
'key' => __('Part of', 'yoone-product-bundles'),
|
||
'value' => $parent_product->get_name(),
|
||
);
|
||
}
|
||
}
|
||
return $item_data;
|
||
}
|
||
|
||
/**
|
||
* Remove the default "Add to Cart" button for bundle products only.
|
||
*/
|
||
public function remove_default_add_to_cart_for_bundle() {
|
||
global $product;
|
||
if ($product && $product->get_type() === Yoone_Product_Bundles::TYPE) {
|
||
remove_action('woocommerce_single_product_summary', 'woocommerce_template_single_add_to_cart', 30);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Render the custom "Add to Cart" form for bundle products.
|
||
*/
|
||
public function render_bundle_add_to_cart_form() {
|
||
global $product;
|
||
if ($product && $product->get_type() === Yoone_Product_Bundles::TYPE) {
|
||
wc_get_template('global/yoone-bundle-form.php', array(), '', YOONE_PB_PATH . 'templates/');
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Add a body class for bundle product pages.
|
||
*/
|
||
public function filter_body_class($classes) {
|
||
global $post;
|
||
if (is_singular('product') && $post) {
|
||
$product = wc_get_product($post->ID);
|
||
if ($product && $product->get_type() === Yoone_Product_Bundles::TYPE) {
|
||
$classes[] = 'yoone-bundle-product';
|
||
}
|
||
}
|
||
return $classes;
|
||
}
|
||
} |