__('Mix and Match', 'yoone-product-bundles'), 'target' => 'yoone_bundle_data', 'class' => array('show_if_yoone_bundle'), // Only show for 'yoone_bundle' type 'priority' => 70, ); return $tabs; } public function render_product_data_panel() { global $post; $product = wc_get_product($post->ID); $config = Yoone_Product_Bundles::get_bundle_config($product); $allowed = $config['allowed_products']; $min_qty = $config['min_qty']; $select_mode = isset($config['select_mode']) ? $config['select_mode'] : 'include'; $group_taxonomy = isset($config['group_taxonomy']) ? $config['group_taxonomy'] : 'product_cat'; $group_terms = isset($config['group_terms']) ? (array) $config['group_terms'] : array(); echo '
'; wp_nonce_field('yoone-bundle-admin-nonce', 'yoone_bundle_admin_nonce_field'); echo '
'; echo '

' . esc_html__('Configure the simple products that can be included in the bundle, the minimum quantity, and the category grouping for the frontend display.', 'yoone-product-bundles') . '

'; // Selection mode woocommerce_wp_select(array( 'id' => 'yoone_bundle_select_mode', 'name' => 'yoone_bundle_select_mode', 'label' => __('Selection Mode', 'yoone-product-bundles'), 'description' => __('Choose how products are selected for the bundle: include list, exclude list, or allow all simple products.', 'yoone-product-bundles'), 'desc_tip' => true, 'options' => array( 'include' => __('Include (only listed products)', 'yoone-product-bundles'), 'exclude' => __('Exclude (allow all except listed)', 'yoone-product-bundles'), 'all' => __('All (allow all simple products)', 'yoone-product-bundles'), ), 'value' => $select_mode, )); // Allowed/Excluded products: use Woo's product search (select2), multiple echo '

'; echo ''; echo ' '; echo ''; // Search only for products, not variations, to avoid errors echo ''; // Dynamic description based on selection mode echo '' . esc_html__('Simple products only. Include: only listed; Exclude: allow all except listed; All: ignore the list.', 'yoone-product-bundles') . ''; echo '

'; // Minimum bundle quantity woocommerce_wp_text_input(array( 'id' => 'yoone_bundle_min_quantity', 'label' => __('Minimum Quantity', 'yoone-product-bundles'), 'type' => 'number', 'desc_tip' => true, 'description' => __('The total quantity of selected items must be greater than or equal to this value to add to the cart.', 'yoone-product-bundles'), 'value' => $min_qty, 'custom_attributes' => array('min' => '0'), )); // Grouping taxonomy (product_cat or product_tag) woocommerce_wp_select(array( 'id' => 'yoone_bundle_group_taxonomy', 'label' => __('Grouping Taxonomy', 'yoone-product-bundles'), 'description' => __('Choose which taxonomy to group products by: category or tag.', 'yoone-product-bundles'), 'desc_tip' => true, 'options' => array( 'product_cat' => __('Category', 'yoone-product-bundles'), 'product_tag' => __('Tag', 'yoone-product-bundles'), ), 'value' => $group_taxonomy, )); // Display terms of selected taxonomy echo '

'; echo ''; echo '' . esc_html__('Group the allowed products by the selected taxonomy terms on the frontend. Only products matching the selected terms will be shown.', 'yoone-product-bundles') . ''; echo '

'; echo '
'; // options_group echo '
'; // panel } public function save_product_meta($post_id) { // 无论当前 product 对象类型为何,只要提交了我们的字段,就进行保存。 // 这可以避免在首次切换产品类型时由于保存顺序问题导致配置未写入。 $has_fields = isset($_POST['yoone_bundle_allowed_products']) || isset($_POST['yoone_bundle_min_quantity']) || isset($_POST['yoone_bundle_group_taxonomy']) || isset($_POST['yoone_bundle_group_terms']) || isset($_POST['yoone_bundle_select_mode']); if (! $has_fields) return; // 保存 allowed products $allowed = isset($_POST['yoone_bundle_allowed_products']) ? (array) $_POST['yoone_bundle_allowed_products'] : array(); $allowed = array_values(array_filter(array_map('absint', $allowed))); // 仅保留 simple 产品ID 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; } update_post_meta($post_id, Yoone_Product_Bundles::META_ALLOWED_PRODUCTS, $allowed); // 保存 min qty $min_qty = isset($_POST['yoone_bundle_min_quantity']) ? absint($_POST['yoone_bundle_min_quantity']) : 0; update_post_meta($post_id, Yoone_Product_Bundles::META_MIN_QTY, max(0, $min_qty)); // 保存 grouping taxonomy $group_taxonomy = isset($_POST['yoone_bundle_group_taxonomy']) ? sanitize_text_field($_POST['yoone_bundle_group_taxonomy']) : 'product_cat'; $group_taxonomy = in_array($group_taxonomy, array('product_cat','product_tag'), true) ? $group_taxonomy : 'product_cat'; update_post_meta($post_id, Yoone_Product_Bundles::META_GROUP_TAXONOMY, $group_taxonomy); // 保存选择的术语 $terms = isset($_POST['yoone_bundle_group_terms']) ? (array) $_POST['yoone_bundle_group_terms'] : array(); $terms = array_values(array_filter(array_map('absint', $terms))); update_post_meta($post_id, Yoone_Product_Bundles::META_GROUP_TERMS, $terms); } /** * AJAX handler: 获取所有已发布的 simple product */ public function ajax_get_all_simple_products() { if (! current_user_can('edit_products')) { wp_send_json_error('permission_denied', 403); } check_ajax_referer('yoone-bundle-admin-nonce', 'security'); $products = wc_get_products(array( 'type' => 'simple', 'status' => 'publish', 'limit' => -1, )); $results = array(); foreach ($products as $product) { $results[] = array( 'id' => $product->get_id(), 'text' => $product->get_formatted_name(), ); } wp_send_json_success($results); } /** * AJAX: 根据选择的 taxonomy 返回术语列表 */ public function ajax_get_taxonomy_terms() { if (! current_user_can('edit_products')) { wp_send_json_error('permission_denied', 403); } check_ajax_referer('yoone-bundle-admin-nonce', 'security'); $taxonomy = isset($_POST['taxonomy']) ? sanitize_text_field($_POST['taxonomy']) : 'product_cat'; if (! in_array($taxonomy, array('product_cat', 'product_tag'), true)) { wp_send_json_error(array('message' => 'invalid_taxonomy')); } $terms = get_terms(array('taxonomy' => $taxonomy, 'hide_empty' => false)); $results = array(); foreach ($terms as $t) { $results[] = array('id' => (int) $t->term_id, 'text' => $t->name); } wp_send_json_success($results); } /** * 后台资源:注入脚本以在切换分组 taxonomy 时动态刷新术语选择 */ public function enqueue_admin_assets($hook) { // 仅在产品编辑页面加载 if ($hook !== 'post.php' && $hook !== 'post-new.php') return; $screen = get_current_screen(); if (! $screen || $screen->post_type !== 'product') return; $handle_js = 'yoone-pb-admin'; wp_register_script($handle_js, plugins_url('assets/js/admin.js', dirname(__FILE__, 3) . '/yoone-product-bundles.php'), array('jquery'), '1.0.0', true); wp_enqueue_script($handle_js); wp_localize_script($handle_js, 'YoonePBAdmin', array( 'ajax_url' => admin_url('admin-ajax.php'), 'security' => wp_create_nonce('yoone-bundle-admin-nonce'), )); // Enqueue CSS for nicer spacing & layout $handle_css = 'yoone-pb-admin-css'; wp_register_style($handle_css, plugins_url('assets/css/admin.css', dirname(__FILE__, 3) . '/yoone-product-bundles.php'), array(), '1.0.1'); wp_enqueue_style($handle_css); } } // 保存选择模式 $select_mode = isset($_POST['yoone_bundle_select_mode']) ? sanitize_text_field($_POST['yoone_bundle_select_mode']) : 'include'; $select_mode = in_array($select_mode, array('include','exclude','all'), true) ? $select_mode : 'include'; update_post_meta($post_id, Yoone_Product_Bundles::META_SELECT_MODE, $select_mode);