__('订阅计划', 'yoone-subscriptions'), 'target' => 'yoone_subscriptions_data', // 在 simple、variable 以及 yoone_bundle(混装)产品类型上显示 'class' => array('show_if_simple', 'show_if_variable', 'show_if_yoone_bundle'), 'priority' => 80, ); return $tabs; } /** * 渲染面板内容 */ public function render_panel() { global $post; $product = wc_get_product($post->ID); $cfg = Yoone_Subscriptions::get_config($product); wp_nonce_field('yoone_subscriptions_save', 'yoone_subscriptions_nonce'); // 面板在 simple/variable/yoone_bundle 上显示 echo ''; } /** * 保存订阅配置 */ public function save_meta($post_id) { if (! isset($_POST['yoone_subscriptions_nonce']) || ! wp_verify_nonce($_POST['yoone_subscriptions_nonce'], 'yoone_subscriptions_save')) { return; // 安全验证失败 } if (! current_user_can('edit_post', $post_id)) return; $enabled = isset($_POST['yoone_sub_enabled']) && 'yes' === $_POST['yoone_sub_enabled']; $period = isset($_POST['yoone_sub_period']) ? sanitize_text_field($_POST['yoone_sub_period']) : 'month'; $qty = isset($_POST['yoone_sub_qty_default']) ? absint($_POST['yoone_sub_qty_default']) : 1; $min_qty = isset($_POST['yoone_sub_min_qty']) ? absint($_POST['yoone_sub_min_qty']) : 1; $price = isset($_POST['yoone_sub_price']) ? wc_clean($_POST['yoone_sub_price']) : ''; $tiers = isset($_POST['yoone_sub_tier_rules']) ? sanitize_text_field($_POST['yoone_sub_tier_rules']) : ''; $onetime = isset($_POST['yoone_sub_allow_onetime']) && 'yes' === $_POST['yoone_sub_allow_onetime']; $period = in_array($period, array('month','year'), true) ? $period : 'month'; $qty = max(1, $qty); $price_v = ($price === '' ? '' : wc_format_decimal($price, 2)); // 启用标记延后到保存完订阅计划后,根据是否存在计划自动开启 update_post_meta($post_id, Yoone_Subscriptions::META_PERIOD, $period); update_post_meta($post_id, Yoone_Subscriptions::META_QTY_DEFAULT, $qty); update_post_meta($post_id, Yoone_Subscriptions::META_MIN_QTY, max(1, $min_qty)); if ($price_v === '') { delete_post_meta($post_id, Yoone_Subscriptions::META_PRICE); } else { update_post_meta($post_id, Yoone_Subscriptions::META_PRICE, $price_v); } if ($tiers === '') { delete_post_meta($post_id, Yoone_Subscriptions::META_TIER_RULES); } else { update_post_meta($post_id, Yoone_Subscriptions::META_TIER_RULES, $tiers); } update_post_meta($post_id, Yoone_Subscriptions::META_ALLOW_ONETIME, $onetime ? '1' : ''); // 保存多订阅计划 $plans = array(); if (isset($_POST['yoone_sub_plans']) && is_array($_POST['yoone_sub_plans'])) { $labels = isset($_POST['yoone_sub_plans']['label']) ? (array) $_POST['yoone_sub_plans']['label'] : array(); $periods = isset($_POST['yoone_sub_plans']['period']) ? (array) $_POST['yoone_sub_plans']['period'] : array(); $prices = isset($_POST['yoone_sub_plans']['price']) ? (array) $_POST['yoone_sub_plans']['price'] : array(); $discounts = isset($_POST['yoone_sub_plans']['discount_percent']) ? (array) $_POST['yoone_sub_plans']['discount_percent'] : array(); $count = max(count($labels), count($periods), count($prices), count($discounts)); for ($i=0; $i<$count; $i++) { $label = isset($labels[$i]) ? sanitize_text_field($labels[$i]) : ''; $per = isset($periods[$i]) ? sanitize_text_field($periods[$i]) : 'month'; $per = in_array($per, array('month','year'), true) ? $per : 'month'; $price = isset($prices[$i]) ? wc_clean($prices[$i]) : ''; $price = ($price === '' ? 0.0 : floatval(wc_format_decimal($price, 2))); $disc = isset($discounts[$i]) ? floatval($discounts[$i]) : 0.0; if ($label === '' && $price <= 0 && $disc <= 0) continue; // 全空行跳过 $plans[] = array( 'id' => uniqid('plan_'), 'label' => $label ? $label : __('订阅计划', 'yoone-subscriptions'), 'period' => $per, 'price' => $price, 'discount_percent' => max(0.0, $disc), ); } } update_post_meta($post_id, Yoone_Subscriptions::META_PLANS, $plans); // 如果存在至少一个有效的订阅计划,则自动启用订阅 $auto_enabled = ! empty($plans); update_post_meta($post_id, Yoone_Subscriptions::META_ENABLED, ($enabled || $auto_enabled) ? '1' : ''); } /** * 注册 WooCommerce → Subscriptions 子菜单。 */ public function register_submenu() { // 仅限具有管理 WooCommerce 权限的用户可访问 add_submenu_page( 'woocommerce', __('Subscriptions', 'yoone-subscriptions'), __('Subscriptions', 'yoone-subscriptions'), 'manage_woocommerce', 'yoone-subscriptions', array($this, 'render_subscriptions_page'), 56 // 显示位置(可选) ); } /** * 渲染订阅列表管理页面。 * 支持按用户、产品、状态筛选与分页。 */ public function render_subscriptions_page() { if (! current_user_can('manage_woocommerce')) { wp_die(__('您没有权限查看该页面。', 'yoone-subscriptions')); } // 处理状态更新操作(pause/resume/cancel),带 nonce if (isset($_POST['yoone_sub_action']) && isset($_POST['_wpnonce']) && wp_verify_nonce($_POST['_wpnonce'], 'yoone_subscriptions_action')) { $action = sanitize_key($_POST['yoone_sub_action']); $sub_id = isset($_POST['yoone_sub_id']) ? absint($_POST['yoone_sub_id']) : 0; $allowed = array('pause' => 'paused', 'resume' => 'active', 'cancel' => 'canceled'); if ($sub_id > 0 && isset($allowed[$action])) { if (class_exists('Yoone_Subscriptions_DB')) { Yoone_Subscriptions_DB::update($sub_id, array('status' => $allowed[$action])); echo '

' . esc_html__('状态已更新。', 'yoone-subscriptions') . '

'; } } } // 读取筛选参数 $status = isset($_GET['status']) ? sanitize_key($_GET['status']) : ''; $product_id = isset($_GET['product_id']) ? absint($_GET['product_id']) : 0; $user_query = isset($_GET['user']) ? sanitize_text_field($_GET['user']) : ''; $paged = isset($_GET['paged']) ? max(1, absint($_GET['paged'])) : 1; $per_page = 20; $offset = ($paged - 1) * $per_page; global $wpdb; $table = method_exists('Yoone_Subscriptions_DB', 'table_name') ? Yoone_Subscriptions_DB::table_name() : $wpdb->prefix . 'yoone_subscriptions'; // 构造 WHERE 条件 $where = array('1=1'); $params = array(); if (! empty($status)) { $where[] = 'status = %s'; $params[] = $status; } if ($product_id > 0) { $where[] = 'product_id = %d'; $params[] = $product_id; } $user_clause = ''; if (! empty($user_query)) { // 支持用用户ID或邮箱搜索 if (is_numeric($user_query)) { $where[] = 'user_id = %d'; $params[] = absint($user_query); } else { // 通过邮箱查询用户ID $user = get_user_by('email', $user_query); if ($user) { $where[] = 'user_id = %d'; $params[] = $user->ID; } else { $where[] = '0=1'; } // 无匹配,返回空 } } // 查询总数用于分页 $sql_count = "SELECT COUNT(*) FROM {$table} WHERE " . implode(' AND ', $where); $total = $wpdb->get_var($wpdb->prepare($sql_count, $params)); // 查询当前页数据 $sql = "SELECT * FROM {$table} WHERE " . implode(' AND ', $where) . " ORDER BY id DESC LIMIT %d OFFSET %d"; $params_page = array_merge($params, array($per_page, $offset)); $rows = $wpdb->get_results($wpdb->prepare($sql, $params_page), ARRAY_A); // 渲染页面 echo '
'; echo '

' . esc_html__('Subscriptions', 'yoone-subscriptions') . '

'; echo '
'; // 筛选表单 echo '
'; echo ''; echo '
'; echo '
'; echo ' '; echo ' '; echo ' '; submit_button(__('筛选'), 'secondary', null, false); echo '
'; echo '
'; echo '
'; // 列表表格 echo ''; echo ''; $cols = array( __('ID', 'yoone-subscriptions'), __('用户', 'yoone-subscriptions'), __('产品', 'yoone-subscriptions'), __('周期', 'yoone-subscriptions'), __('数量', 'yoone-subscriptions'), __('每周期金额', 'yoone-subscriptions'), __('状态', 'yoone-subscriptions'), __('开始时间', 'yoone-subscriptions'), __('下次续费', 'yoone-subscriptions'), __('操作', 'yoone-subscriptions'), ); foreach ($cols as $c) echo ''; echo ''; if (empty($rows)) { echo ''; } else { foreach ($rows as $r) { $user = get_user_by('id', intval($r['user_id'])); $product = wc_get_product(intval($r['product_id'])); echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; } } echo '
' . esc_html($c) . '
' . esc_html__('暂无订阅记录。', 'yoone-subscriptions') . '
#' . intval($r['id']) . '' . ($user ? esc_html($user->user_email) . ' (ID ' . intval($user->ID) . ')' : '-') . '' . ($product ? esc_html($product->get_name()) . ' (ID ' . intval($product->get_id()) . ')' : 'ID ' . intval($r['product_id'])) . '' . esc_html($r['period']) . '' . intval($r['qty']) . '' . wc_price(floatval($r['price_per_cycle'])) . '' . esc_html($r['status']) . '' . esc_html($r['start_date']) . '' . esc_html($r['next_renewal_date']) . ''; echo '
' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . ''; if ($r['status'] !== 'paused') { echo ''; submit_button(__('暂停', 'yoone-subscriptions'), 'small', null, false); } echo '
'; echo '
' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . ''; if ($r['status'] !== 'active') { echo ''; submit_button(__('恢复', 'yoone-subscriptions'), 'small', null, false); } echo '
'; echo '
' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . ''; if ($r['status'] !== 'canceled') { echo ''; submit_button(__('取消', 'yoone-subscriptions'), 'small', null, false); } echo '
'; echo '
'; // 分页导航 if ($total > $per_page) { $total_pages = ceil($total / $per_page); echo '
'; echo paginate_links(array( 'base' => add_query_arg(array('paged' => '%#%')), 'format' => '', 'prev_text' => __('« 上一页', 'yoone-subscriptions'), 'next_text' => __('下一页 »', 'yoone-subscriptions'), 'total' => $total_pages, 'current' => $paged, )); echo '
'; } echo '
'; } }