336 lines
16 KiB
PHP
336 lines
16 KiB
PHP
<?php
|
||
/**
|
||
* 后台:产品编辑页订阅计划配置面板。
|
||
*/
|
||
defined('ABSPATH') || exit;
|
||
|
||
class Yoone_Subscriptions_Admin {
|
||
protected static $instance = null;
|
||
|
||
public static function instance() {
|
||
if (null === self::$instance) self::$instance = new self();
|
||
return self::$instance;
|
||
}
|
||
|
||
private function __construct() {
|
||
add_filter('woocommerce_product_data_tabs', array($this, 'add_tab'));
|
||
add_action('woocommerce_product_data_panels', array($this, 'render_panel'));
|
||
add_action('woocommerce_admin_process_product_meta', array($this, 'save_meta'));
|
||
|
||
// 在 WooCommerce 菜单下添加“Subscriptions”子菜单
|
||
add_action('admin_menu', array($this, 'register_submenu'));
|
||
}
|
||
|
||
/**
|
||
* 在产品数据区域添加“订阅计划”标签
|
||
*/
|
||
public function add_tab($tabs) {
|
||
$tabs['yoone_subscriptions'] = array(
|
||
'label' => __('订阅计划', 'yoone-subscriptions'),
|
||
'target' => 'yoone_subscriptions_data',
|
||
'class' => array('show_if_simple', 'show_if_variable'),
|
||
'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');
|
||
|
||
echo '<div id="yoone_subscriptions_data" class="panel woocommerce_options_panel hidden">';
|
||
echo '<div class="options_group">';
|
||
|
||
// 启用订阅
|
||
woocommerce_wp_checkbox(array(
|
||
'id' => 'yoone_sub_enabled',
|
||
'label' => __('启用订阅', 'yoone-subscriptions'),
|
||
'description' => __('开启后,前端产品页将显示订阅选项。', 'yoone-subscriptions'),
|
||
'desc_tip' => true,
|
||
'value' => $cfg['enabled'] ? 'yes' : 'no',
|
||
));
|
||
|
||
// 周期(月/年)
|
||
woocommerce_wp_select(array(
|
||
'id' => 'yoone_sub_period',
|
||
'label' => __('订阅周期', 'yoone-subscriptions'),
|
||
'options' => array('month' => __('月', 'yoone-subscriptions'), 'year' => __('年', 'yoone-subscriptions')),
|
||
'value' => $cfg['period'],
|
||
'desc_tip' => true,
|
||
'description' => __('订阅价格以该周期计费。年=12×月。', 'yoone-subscriptions'),
|
||
));
|
||
|
||
// 默认订阅数量
|
||
woocommerce_wp_text_input(array(
|
||
'id' => 'yoone_sub_qty_default',
|
||
'label' => __('默认订阅数量', 'yoone-subscriptions'),
|
||
'type' => 'number',
|
||
'value' => $cfg['qty_default'],
|
||
'custom_attributes' => array('min' => '1', 'step' => '1'),
|
||
'desc_tip' => true,
|
||
'description' => __('用于前端默认数量,可在产品页调整。', 'yoone-subscriptions'),
|
||
));
|
||
|
||
// 最小起订量(仅订阅模式下生效)
|
||
woocommerce_wp_text_input(array(
|
||
'id' => 'yoone_sub_min_qty',
|
||
'label' => __('最小起订量', 'yoone-subscriptions'),
|
||
'type' => 'number',
|
||
'value' => isset($cfg['min_qty']) ? intval($cfg['min_qty']) : 1,
|
||
'custom_attributes' => array('min' => '1', 'step' => '1'),
|
||
'desc_tip' => true,
|
||
'description' => __('仅在订阅购买模式下生效,购物车与订单将进行数量校验与提示。', 'yoone-subscriptions'),
|
||
));
|
||
|
||
// 订阅价格(可选,留空则使用产品常规价)
|
||
woocommerce_wp_text_input(array(
|
||
'id' => 'yoone_sub_price',
|
||
'label' => __('订阅价格(每周期)', 'yoone-subscriptions'),
|
||
'type' => 'text',
|
||
'value' => $cfg['price'] > 0 ? wc_format_decimal($cfg['price'], 2) : '',
|
||
'desc_tip' => true,
|
||
'description' => __('留空表示使用产品常规价。', 'yoone-subscriptions'),
|
||
));
|
||
|
||
// 分级折扣配置(逗号分隔的“数量:折扣%”对)
|
||
woocommerce_wp_text_input(array(
|
||
'id' => 'yoone_sub_tier_rules',
|
||
'label' => __('分级折扣规则', 'yoone-subscriptions'),
|
||
'type' => 'text',
|
||
'placeholder' => '10:5,20:10',
|
||
'value' => isset($cfg['tier_rules']) ? esc_attr($cfg['tier_rules']) : '',
|
||
'desc_tip' => true,
|
||
'description' => __('格式:数量:折扣%,用逗号分隔多级。如“10:5,20:10”表示≥10件打95折,≥20件打9折。留空则不启用。', 'yoone-subscriptions'),
|
||
));
|
||
|
||
// 是否允许一次性购买
|
||
woocommerce_wp_checkbox(array(
|
||
'id' => 'yoone_sub_allow_onetime',
|
||
'label' => __('允许一次性购买', 'yoone-subscriptions'),
|
||
'description' => __('开启后,前端产品页可选择一次性购买或订阅购买。', 'yoone-subscriptions'),
|
||
'desc_tip' => true,
|
||
'value' => $cfg['allow_onetime'] ? 'yes' : 'no',
|
||
));
|
||
|
||
echo '</div></div>';
|
||
}
|
||
|
||
/**
|
||
* 保存订阅配置
|
||
*/
|
||
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_ENABLED, $enabled ? '1' : '');
|
||
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' : '');
|
||
}
|
||
|
||
/**
|
||
* 注册 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 '<div class="notice notice-success is-dismissible"><p>' . esc_html__('状态已更新。', 'yoone-subscriptions') . '</p></div>';
|
||
}
|
||
}
|
||
}
|
||
|
||
// 读取筛选参数
|
||
$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 '<div class="wrap">';
|
||
echo '<h1 class="wp-heading-inline">' . esc_html__('Subscriptions', 'yoone-subscriptions') . '</h1>';
|
||
echo '<hr class="wp-header-end">';
|
||
|
||
// 筛选表单
|
||
echo '<form method="get">';
|
||
echo '<input type="hidden" name="page" value="yoone-subscriptions" />';
|
||
echo '<div class="tablenav top">';
|
||
echo '<div class="alignleft actions">';
|
||
echo '<select name="status">';
|
||
$statuses = array('' => __('全部状态', 'yoone-subscriptions'), 'active' => __('Active', 'yoone-subscriptions'), 'paused' => __('Paused', 'yoone-subscriptions'), 'canceled' => __('Canceled', 'yoone-subscriptions'));
|
||
foreach ($statuses as $key => $label) {
|
||
echo '<option value="' . esc_attr($key) . '"' . selected($status, $key, false) . '>' . esc_html($label) . '</option>';
|
||
}
|
||
echo '</select> ';
|
||
echo '<input type="number" name="product_id" placeholder="' . esc_attr__('产品ID', 'yoone-subscriptions') . '" value="' . esc_attr($product_id) . '" style="width:120px" /> ';
|
||
echo '<input type="text" name="user" placeholder="' . esc_attr__('用户ID或邮箱', 'yoone-subscriptions') . '" value="' . esc_attr($user_query) . '" style="width:200px" /> ';
|
||
submit_button(__('筛选'), 'secondary', null, false);
|
||
echo '</div>';
|
||
echo '</div>';
|
||
echo '</form>';
|
||
|
||
// 列表表格
|
||
echo '<table class="wp-list-table widefat fixed striped">';
|
||
echo '<thead><tr>';
|
||
$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 '<th>' . esc_html($c) . '</th>';
|
||
echo '</tr></thead><tbody>';
|
||
|
||
if (empty($rows)) {
|
||
echo '<tr><td colspan="10">' . esc_html__('暂无订阅记录。', 'yoone-subscriptions') . '</td></tr>';
|
||
} else {
|
||
foreach ($rows as $r) {
|
||
$user = get_user_by('id', intval($r['user_id']));
|
||
$product = wc_get_product(intval($r['product_id']));
|
||
echo '<tr>';
|
||
echo '<td>#' . intval($r['id']) . '</td>';
|
||
echo '<td>' . ($user ? esc_html($user->user_email) . ' (ID ' . intval($user->ID) . ')' : '-') . '</td>';
|
||
echo '<td>' . ($product ? esc_html($product->get_name()) . ' (ID ' . intval($product->get_id()) . ')' : 'ID ' . intval($r['product_id'])) . '</td>';
|
||
echo '<td>' . esc_html($r['period']) . '</td>';
|
||
echo '<td>' . intval($r['qty']) . '</td>';
|
||
echo '<td>' . wc_price(floatval($r['price_per_cycle'])) . '</td>';
|
||
echo '<td>' . esc_html($r['status']) . '</td>';
|
||
echo '<td>' . esc_html($r['start_date']) . '</td>';
|
||
echo '<td>' . esc_html($r['next_renewal_date']) . '</td>';
|
||
echo '<td>';
|
||
echo '<form method="post" style="display:inline-block;margin-right:6px;">' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . '<input type="hidden" name="yoone_sub_id" value="' . intval($r['id']) . '" />';
|
||
if ($r['status'] !== 'paused') {
|
||
echo '<input type="hidden" name="yoone_sub_action" value="pause" />';
|
||
submit_button(__('暂停', 'yoone-subscriptions'), 'small', null, false);
|
||
}
|
||
echo '</form>';
|
||
echo '<form method="post" style="display:inline-block;margin-right:6px;">' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . '<input type="hidden" name="yoone_sub_id" value="' . intval($r['id']) . '" />';
|
||
if ($r['status'] !== 'active') {
|
||
echo '<input type="hidden" name="yoone_sub_action" value="resume" />';
|
||
submit_button(__('恢复', 'yoone-subscriptions'), 'small', null, false);
|
||
}
|
||
echo '</form>';
|
||
echo '<form method="post" style="display:inline-block;">' . wp_nonce_field('yoone_subscriptions_action', '_wpnonce', true, false) . '<input type="hidden" name="yoone_sub_id" value="' . intval($r['id']) . '" />';
|
||
if ($r['status'] !== 'canceled') {
|
||
echo '<input type="hidden" name="yoone_sub_action" value="cancel" />';
|
||
submit_button(__('取消', 'yoone-subscriptions'), 'small', null, false);
|
||
}
|
||
echo '</form>';
|
||
echo '</td>';
|
||
echo '</tr>';
|
||
}
|
||
}
|
||
echo '</tbody></table>';
|
||
|
||
// 分页导航
|
||
if ($total > $per_page) {
|
||
$total_pages = ceil($total / $per_page);
|
||
echo '<div class="tablenav bottom"><div class="tablenav-pages">';
|
||
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 '</div></div>';
|
||
}
|
||
|
||
echo '</div>';
|
||
}
|
||
} |