| Server IP : 43.141.49.92 / Your IP : 113.219.202.141 Web Server : Apache System : Linux VM-8-5-opencloudos 6.6.34-9.oc9.x86_64 #1 SMP PREEMPT_DYNAMIC Wed Jun 19 19:35:45 CST 2024 x86_64 User : www ( 1000) PHP Version : 8.1.27 Disable Function : passthru,exec,system,putenv,chroot,chgrp,chown,shell_exec,popen,proc_open,pcntl_exec,ini_alter,ini_restore,dl,openlog,syslog,readlink,symlink,popepassthru,pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,imap_open,apache_setenv MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /www/wwwroot/www.ucppt.com/wp-content/plugins/wpcom-member-pro/includes/ |
Upload File : |
<?php
namespace WPCOM\Member;
use WPCOM\Themer\Plugin\Utils;
defined('ABSPATH') || exit;
class Cards{
protected static $products_table = 'wpcom_card_products';
protected static $items_table = 'wpcom_card_items';
private static $slug = 'wpcom-cards';
private static $base_url;
function __construct() {
self::$base_url = admin_url('admin.php?page=' . self::$slug);
$this->register_actions();
$this->register_filters();
add_shortcode('wpcom-cards', [$this, 'shortcode']);
}
// 注册 action 钩子
private function register_actions() {
add_action('admin_menu', [$this, 'admin_menu']);
add_action('wpcom_themer_maybe_updated', function(){ self::init_database(); });
add_action('wp_ajax_wpcom_get_card_product_data', [$this, 'get_card_product_data']);
add_action('wp_ajax_nopriv_wpcom_get_card_product_data', [$this, 'get_card_product_data']);
add_action('wpcom_card_payment_complete', [$this, 'payment_complete'], 10, 2);
add_action('wpcom_order_item_detail_card', [$this, 'show_in_order'], 10, 2);
}
// 注册 filter 钩子
private function register_filters() {
add_filter('wpcom_member_card_product_metas', [$this, 'card_product_metas']);
add_filter('wpcom_member_card_item_metas', [$this, 'card_item_metas'], 10, 2);
add_filter('wpcom_init_card_product_options', [$this, 'card_product_options'], 10, 2);
add_filter('wpcom_init_card_item_options', [$this, 'card_item_options'], 10, 2);
add_filter('parent_file', [$this, 'highlight_parent_menu']);
add_filter('submenu_file', [$this, 'highlight_submenu']);
add_filter('wpcom_card_stock_check', [$this, 'stock_check'], 10, 2);
}
// 后台菜单高亮处理
public function highlight_parent_menu($parent_file) {
global $plugin_page;
if ($plugin_page === 'wpcom-cards-items') {
global $_wp_real_parent_file, $_wp_menu_nopriv;
$_wp_real_parent_file[$plugin_page] = 'wpcom-member-pro';
$_wp_menu_nopriv[$plugin_page] = 'wpcom-member-pro';
return 'wpcom-member-pro';
}
return $parent_file;
}
public function highlight_submenu($submenu_file) {
global $pagenow;
$screen = get_current_screen();
if ($pagenow == 'admin.php' && $screen->base == 'admin_page_wpcom-cards-items') {
return 'wpcom-cards';
}
return $submenu_file;
}
function admin_menu(){
add_submenu_page('wpcom-member-pro', '卡密管理', '卡密管理', 'manage_options', self::$slug, [$this, 'cards'], 10);
add_submenu_page(self::$slug, '管理卡密', '管理卡密', 'manage_options', 'wpcom-cards-items', [$this, 'card_items'], 20);
}
function cards(){
$action = isset($_GET['action']) ? $_GET['action'] : '';
if ($action === 'add' || $action === 'edit') {
// 加载添加卡密表单页面
$this->edit_card_product_page();
return;
}
if (!class_exists('Card_Products')) {
require_once WPCOM_MP_DIR . '/includes/card-products.php';
}
$list_table = new Card_Products();
$list_table->prepare_items();
echo '<div class="wrap">';
echo '<h1 class="wp-heading-inline">卡密管理</h1>';
echo ' <a href="' . self::$base_url . '&action=add" class="page-title-action">添加卡密</a>';
echo '<form method="get">';
echo '<input type="hidden" name="page" value="wpcom-cards" />';
$list_table->views();
$list_table->search_box('搜索卡密', 'card_product');
$list_table->display();
echo '</form>';
echo '</div>';
}
function card_items() {
$product_id = isset($_GET['product_id']) ? intval($_GET['product_id']) : 0;
$action = isset($_GET['action']) ? $_GET['action'] : '';
$item_id = isset($_GET['id']) ? intval($_GET['id']) : 0;
if (!$product_id) {
echo '<div class="notice notice-error"><p>参数错误,未指定产品ID。</p></div>';
return;
}
if ($action === 'add' || $action === 'edit') {
$this->edit_card_item_page($product_id, $item_id);
return;
}
if ($action === 'import') {
$this->import_card_item_page($product_id);
return;
}
if (isset($_GET['status']) && $_GET['status'] === 'success') {
$count = isset($_GET['count']) ? intval($_GET['count']) : 0;
$repeat = isset($_GET['repeat']) ? intval($_GET['repeat']) : 0;
$msg = ["导入完成"];
if ($count) $msg[] = "添加成功 {$count} 条";
if ($repeat) $msg[] = "跳过 {$repeat} 条重复";
echo '<div class="notice notice-success"><p>' . implode(',', $msg) . '。</p></div>';
}
// 这里可以加载 Card_Items 类,显示卡密列表
if (!class_exists('Card_Items')) {
require_once WPCOM_MP_DIR . '/includes/card-items.php';
}
$list_table = new Card_Items($product_id);
$list_table->prepare_items();
echo '<div class="wrap">';
echo '<h1 class="wp-heading-inline">管理卡密</h1>';
echo ' <a href="' . self::$base_url . '" class="page-title-action"><span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回</a>';
echo ' <a href="' . admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id . '&action=add') . '" class="page-title-action">添加卡密</a>';
echo ' <a href="' . admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id . '&action=import') . '" class="page-title-action">导入卡密</a>';
echo '<form method="get">';
echo '<input type="hidden" name="page" value="wpcom-cards-items" />';
echo '<input type="hidden" name="product_id" value="' . $product_id . '" />';
$list_table->views();
$list_table->search_box('搜索卡密', 'card_item');
$list_table->display();
echo '</form>';
echo '</div>';
}
function edit_card_product_page() {
$product_id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : 0;
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
Utils::panel_script();
$error = $this->save_card();
?>
<div class="wrap">
<h1 class="wp-heading-inline">
<?php echo $product_id ? '编辑' : '添加';?>卡密
<a class="page-title-action" href="<?php echo self::$base_url; ?>"><span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回</a>
</h1>
<?php if ($error === false || (isset($_GET['status']) && $_GET['status'] === 'success')) { ?>
<div id="message" class="notice notice-success">
<p><strong>卡密信息已更新。</strong></p>
<p>
<a href="<?php echo self::$base_url; ?>" style="text-decoration: none;">
<span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回“卡密管理”页面
</a>
</p>
</div>
<?php } else if ($error) { ?>
<div id="message" class="notice notice-error">
<p><strong>卡密信息更新失败。</strong></p>
<p><?php echo $error; ?></p>
</div>
<?php } ?>
<form name="edit-card" id="edittag" method="post" action="" class="card-edit validate">
<input type="hidden" name="action" value="<?php echo $product_id ? 'edit' : 'new';?>" />
<input type="hidden" name="product_id" value="<?php echo $product_id; ?>" />
<?php wp_nonce_field('card-edit', 'card-edit-nonce'); ?>
<table class="form-table" role="presentation">
<tr id="wpcom-plugin-panel" class="wpcom-term-wrap">
<td colspan="2">
<term-panel :ready="ready" />
</td>
</tr>
</table>
<input type="submit" class="button button-primary" value="提交" />
</form>
<div style="display: none;"><?php wp_editor('EDITOR', 'WPCOM-EDITOR', Utils::editor_settings(array('textarea_name' => 'EDITOR-NAME'))); ?></div>
<script>
if (typeof _plugins_options === 'undefined') _plugins_options = [];
_plugins_options.push(<?php echo $this->get_metas(); ?>);
if (window.location.search.indexOf('status=success') > -1) {
var url = new URL(window.location.href);
url.searchParams.delete('status');
window.history.replaceState({}, document.title, url.toString());
}
</script>
</div>
<?php }
function edit_card_item_page($product_id, $item_id) {
$item_id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : 0;
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
Utils::panel_script();
$base_url = admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id);
$error = $this->save_card_item($product_id, $item_id);
$is_edit = $item_id ? true : false;
?>
<div class="wrap">
<h1 class="wp-heading-inline">
<?php echo $item_id ? '编辑' : '添加';?>卡密
<a class="page-title-action" href="<?php echo $base_url; ?>"><span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回</a>
</h1>
<?php if ($error === false || (isset($_GET['status']) && $_GET['status'] === 'success')) { ?>
<div id="message" class="notice notice-success">
<p><strong>卡密已更新。</strong></p>
<p>
<a href="<?php echo $base_url; ?>" style="text-decoration: none;">
<span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回“管理卡密”页面
</a>
</p>
</div>
<?php } else if ($error) { ?>
<div id="message" class="notice notice-error">
<p><strong>卡密更新失败。</strong></p>
<p><?php echo $error; ?></p>
</div>
<?php } ?>
<form name="edit-card" id="edittag" method="post" action="" class="card-edit validate">
<input type="hidden" name="action" value="<?php echo $item_id ? 'edit' : 'new';?>" />
<input type="hidden" name="product_id" value="<?php echo $product_id; ?>" />
<input type="hidden" name="item_id" value="<?php echo $item_id; ?>" />
<?php wp_nonce_field('card_item-edit', 'card_item-edit-nonce'); ?>
<table class="form-table" role="presentation">
<tr id="wpcom-plugin-panel" class="wpcom-term-wrap">
<td colspan="2">
<term-panel :ready="ready" />
</td>
</tr>
</table>
<input type="submit" class="button button-primary" value="提交" />
</form>
<div style="display: none;"><?php wp_editor('EDITOR', 'WPCOM-EDITOR', Utils::editor_settings(array('textarea_name' => 'EDITOR-NAME'))); ?></div>
<script>
if (typeof _plugins_options === 'undefined') _plugins_options = [];
_plugins_options.push(<?php echo $this->get_metas('card_item', $is_edit); ?>);
if (window.location.search.indexOf('status=success') > -1) {
var url = new URL(window.location.href);
url.searchParams.delete('status');
window.history.replaceState({}, document.title, url.toString());
}
</script>
</div>
<?php }
function import_card_item_page($product_id) {
$error = '';
$success = '';
$expire_at = isset($_POST['expire_at']) ? sanitize_text_field($_POST['expire_at']) : '';
if (current_user_can('manage_options') && isset($_FILES['import_file']) && isset($_POST['card_item-import-nonce']) && wp_verify_nonce($_POST['card_item-import-nonce'], 'card_item-import')) {
$file = $_FILES['import_file'];
if ($file['error'] === UPLOAD_ERR_OK) {
$ext = strtolower(pathinfo($file['name'], PATHINFO_EXTENSION));
$content = file_get_contents($file['tmp_name']);
if ($ext === 'csv') {
$codes = array_map('trim', explode("\n", $content));
} else {
$codes = preg_split('/\r\n|\r|\n/', $content);
}
$count = 0;
$repeat = 0;
if (count($codes) > 0) {
global $wpdb;
$table = $wpdb->prefix . self::$items_table;
foreach ($codes as $code) {
$code = trim($code);
if ($code === '') continue;
if ($this->is_code_exists($code, $product_id)) {
$repeat++;
continue;
}
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$wpdb->insert($table, [
'product_id' => $product_id,
'code' => $code,
'status' => 0,
'expire_at' => Utils::input_time($expire_at),
'created_at' => current_time('mysql')
]);
$count++;
}
// 更新库存
self::update_stock($product_id);
// 导入成功后跳转到列表页并提示
$url = admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id . '&status=success&count=' . $count . '&repeat=' . $repeat);
echo '<script>window.location.href="' . $url . '";</script>';
exit;
} else {
$error = '导入失败,未识别到有效卡密内容。';
}
} else {
$error = '文件上传失败,请重试。';
}
}
$return_url = admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id);
?>
<div class="wrap">
<h1 class="wp-heading-inline">导入卡密
<a class="page-title-action" href="<?php echo esc_attr($return_url); ?>"><span class="dashicons dashicons-arrow-left-alt" style="line-height: inherit;width:1em;height:1em;font-size:inherit;"></span> 返回</a>
</h1>
<?php if ($error): ?>
<div class="notice notice-error"><p><?php echo esc_html($error); ?></p></div>
<?php elseif ($success): ?>
<div class="notice notice-success"><p><?php echo esc_html($success); ?></p></div>
<?php endif; ?>
<form method="post" enctype="multipart/form-data">
<?php wp_nonce_field('card_item-import', 'card_item-import-nonce'); ?>
<table class="form-table" role="presentation">
<tr>
<th scope="row"><label for="import_file">选择文件</label></th>
<td>
<input type="file" name="import_file" id="import_file" accept=".txt,.csv" required>
<p class="description">支持 txt 或 csv 格式文件,每行一个卡密。<br>温馨提示:为确保正常导入建议控制单次导入数量,避免服务器处理超时。</p>
</td>
</tr>
<tr>
<th scope="row"><label for="expire_at">过期时间</label></th>
<td>
<input type="datetime-local" name="expire_at" id="expire_at" value="<?php echo esc_attr($expire_at); ?>">
<p class="description">可选,为本次导入的所有卡密设置统一过期时间,不填则不过期。</p>
</td>
</tr>
</table>
<p class="submit"><input type="submit" class="button-primary" value="导入"></p>
</form>
</div>
<?php
}
function get_metas($type = 'card_product', $is_edit = false){
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$res = [ 'type' => 'taxonomy', 'tax' => $type, 'options' => [] ];
$the_id = isset($_GET['id']) ? sanitize_text_field($_GET['id']) : 0;
$plugin = $GLOBALS['wpcom_mp']->info;
$res['filters'] = apply_filters('wpcom_member_' . $type . '_metas', [], $is_edit);
$res['ver'] = $plugin['ver'];
$res['plugin-id'] = $plugin['plugin_id'];
$res['plugin-slug'] = $plugin['slug'];
$res['framework_ver'] = Utils::framework_version($plugin['slug']);
$res = apply_filters('wpcom_init_' . $type . '_options', $res, $the_id);
return wp_json_encode($res);
}
function card_product_options($res, $id){
$res = $res ?: [];
$res['options'] = $res['options'] ?: [];
if ($id && $product = $this->get_card_product($id)) {
$res['options'] = [
'title' => $product->title,
'price' => $product->price,
'pay_type' => $product->price_type,
'status' => $product->status,
'expire_at' => $product->expire_at,
'description' => $product->description
];
}
if(isset($_POST) && isset($_POST['wpcom_price'])){
$data = map_deep(stripslashes_deep($_POST), 'sanitize_text_field');
if(isset($data['wpcom_title'])){
$res['options']['title'] = $data['wpcom_title'];
}
if(isset($data['wpcom_price'])){
$res['options']['price'] = $data['wpcom_price'];
}
if(isset($data['wpcom_pay_type'])){
$res['options']['pay_type'] = (int) $data['wpcom_pay_type'];
}
if(isset($data['wpcom_status'])){
$res['options']['status'] = $data['wpcom_status'];
}
if(isset($data['wpcom_expire_at'])){
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$res['options']['expire_at'] = Utils::input_time($data['wpcom_expire_at']);
}
if(isset($data['wpcom_description'])){
$res['options']['description'] = sanitize_textarea_field($_POST['wpcom_description']);
}
}
if(isset($res['options']['price']) && $res['options']['price'] !== '' && $res['options']['pay_type'] == 1){
$res['options']['price'] = intval($res['options']['price']);
}
return $res;
}
function card_item_options($res, $id){
$res = $res ?: [];
$res['options'] = $res['options'] ?: [];
if ($id && $item = $this->get_card_item($id)) {
$res['options'] = [
'code' => $item->code,
'status' => $item->status,
'order_id' => $item->order_id,
'expire_at' => $item->expire_at
];
}
if(isset($_POST) && isset($_POST['wpcom_code'])){
$data = map_deep(stripslashes_deep($_POST), 'sanitize_text_field');
if(isset($data['wpcom_code'])){
$res['options']['code'] = $data['wpcom_code'];
}
if(isset($data['wpcom_status'])){
$res['options']['status'] = $data['wpcom_status'];
}
if(isset($data['wpcom_order_id'])){
$res['options']['order_id'] = $data['wpcom_order_id'];
}
if(isset($data['wpcom_expire_at'])){
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$res['options']['expire_at'] = Utils::input_time($data['wpcom_expire_at']);
}
}
return $res;
}
function card_product_metas($metas){
$options = $GLOBALS['wpmx_options'];
$is_points = isset($options['points_on']) && $options['points_on'] == '1';
$metas['card_product'] = [
[
'n' => 'title',
'l' => '卡密名称',
'd' => '卡密产品的标题'
],
];
if($is_points){
$metas['card_product'] = array_merge($metas['card_product'], [
[
'n' => 'pay_type',
'l' => '积分购买',
't' => 't',
's' => 0,
'd' => '开启后将使用积分购买'
],
[
'n' => 'price',
'l' => '价格',
'd' => '卡密单价,单位:元,如果使用积分购买则为单位为:积分;如果是积分购买,则价格需为整数'
]
]);
}else{
$metas['card_product'] = array_merge($metas['card_product'], [
[
'n' => 'price',
'l' => '价格',
'd' => '卡密单价,单位:元'
]
]);
}
$metas['card_product'] = array_merge($metas['card_product'], [
[
'n' => 'status',
'l' => '状态',
't' => 'r',
's' => 0,
'o' => [
0 => '在售',
1 => '下架'
],
'd' => '卡密产品的销售状态'
],
[
'n' => 'expire_at',
'l' => '过期时间',
't' => 'dp',
'time' => 1,
'd' => '可选,卡密的统一过期时间,不设置则不过期'
],
[
'n' => 'description',
'l' => '使用说明',
't' => 'ta',
'rows' => 3,
'd' => '卡密使用说明,支持多行,购买后在订单详情显示'
]
]);
return $metas;
}
function card_item_metas($metas, $is_edit = false){
$metas['card_item'] = [
[
'n' => 'expire_at',
'l' => '过期时间',
't' => 'dp',
'time' => 1,
'd' => '可选,卡密的过期时间'
]
];
if($is_edit){
$metas['card_item'] = array_merge([
[
'n' => 'code',
'l' => '卡密',
'd' => '卡密内容'
],
[
'n' => 'status',
'l' => '状态',
't' => 'r',
's' => 0,
'o' => [
0 => '未售出',
1 => '已售出',
2 => '作废'
],
'd' => '卡密的状态'
],
[
'n' => 'order_id',
'l' => '关联订单',
'f' => 'status:1',
'd' => '如果卡密已售出,则可以填写关联的<b>订单ID</b>或者<b>订单号</b>'
]
], $metas['card_item']);
}else{
$metas['card_item'] = array_merge([
[
'n' => 'code',
'l' => '卡密',
't' => 'ta',
'rows' => 8,
'd' => '每行一个卡密,可批量添加'
]
], $metas['card_item']);
}
return $metas;
}
function save_card(){
global $wpdb;
if (current_user_can('manage_options') && isset($_POST['product_id']) && $_POST['action'] && isset($_POST['wpcom_title'])) {
$data = map_deep(stripslashes_deep($_POST), 'sanitize_text_field');
// nonce
if (!wp_verify_nonce($data['card-edit-nonce'], 'card-edit')) {
return '表单校验失败,请刷新页面重试';
}
// 数据检查
if (empty($data['wpcom_title'])) {
return '标题不能为空';
}
if (!isset($data['wpcom_price']) || $data['wpcom_price'] === '') {
return '价格不能为空';
}
if (!is_numeric($data['wpcom_price']) || floatval($data['wpcom_price']) < 0) {
return '价格填写不正确';
}
// 判断是否使用积分
$price_type = 0;
$options = $GLOBALS['wpmx_options'];
$is_points = isset($options['points_on']) && $options['points_on'] == '1';
if ($is_points && isset($data['wpcom_pay_type']) && $data['wpcom_pay_type'] == '1') {
$price_type = 1;
// 积分取整
$data['wpcom_price'] = intval($data['wpcom_price']);
}
$table = $wpdb->prefix . self::$products_table;
$product_id = intval($data['product_id']);
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$_data = [
'title' => $data['wpcom_title'],
'price' => $data['wpcom_price'],
'price_type' => $price_type,
'status' => $data['wpcom_status'],
'expire_at' => Utils::input_time($data['wpcom_expire_at']),
'description' => isset($_POST['wpcom_description']) ? sanitize_textarea_field($_POST['wpcom_description']) : '',
];
if($product_id && $this->get_card_product($product_id)){
$result = $wpdb->update($table, $_data, ['ID' => $product_id]);
if($result === false) return '更新失败';
}else if($product_id === 0){
$result = $wpdb->insert($table, $_data);
$product_id = $wpdb->insert_id;
if(!$product_id) return '添加失败';
}
if($data['action'] === 'new'){
$url = self::$base_url . '&action=edit&id='.$product_id.'&status=success';
echo '<script>window.location.href="' . $url . '";</script>';
exit;
}
return false;
}
}
function save_card_item($product_id = 0, $item_id = 0){
global $wpdb;
$table = $wpdb->prefix . self::$items_table;
if (current_user_can('manage_options') && isset($_POST['card_item-edit-nonce'])) {
$data = map_deep(stripslashes_deep($_POST), 'sanitize_text_field');
$is_edit = $item_id ? true : false;
// nonce 校验
if (!wp_verify_nonce($data['card_item-edit-nonce'], 'card_item-edit')) {
return '表单校验失败,请刷新页面重试';
}
if (empty($data['wpcom_code'])) {
return '卡密不能为空';
}
$status = isset($data['wpcom_status']) ? intval($data['wpcom_status']) : 0;
$order_id_input = isset($data['wpcom_order_id']) ? trim($data['wpcom_order_id']) : '';
require_once WPCOM_ADMIN_PATH . 'includes/class-utils.php';
$expire_at = !empty($data['wpcom_expire_at']) ? Utils::input_time($data['wpcom_expire_at']) : null;
// 订单号兼容处理,仅在已售出时处理
$order_id = null;
if ($status == 1 && $order_id_input !== '') {
if (is_numeric($order_id_input) && strlen($order_id_input) < 16) {
// 直接当作订单ID
$order_id = intval($order_id_input);
// 验证订单是否存在
$order = Order::get_order($order_id);
if (!$order) {
return '关联订单不存在,请检查订单ID是否正确';
}
} else {
// 订单号,查找订单ID
$order = Order::get_order_by_number($order_id_input);
if ($order && isset($order->ID)) {
$order_id = $order->ID;
} else {
return '关联订单不存在,请检查订单号是否正确';
}
}
}
if ($is_edit) {
// 查询卡密明细
$item = $this->get_card_item($item_id);
if(!$item) return '卡密不存在或已被删除';
$code = trim($data['wpcom_code']);
if ($this->is_code_exists($code, $product_id, $item_id)) {
return '卡密内容已存在,请勿重复添加';
}
// 售出时间处理
$sold_at = $item && $status == 1 && $item->status != $status ? current_time('mysql') : ($item ? $item->sold_at : null);
$wpdb->update($table, [
'code' => $code,
'status' => $status,
'order_id' => $order_id,
'expire_at' => $expire_at,
'sold_at' => $sold_at
], ['ID' => $item_id]);
// 更新库存
self::update_stock($product_id);
$url = admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id . '&action=edit&id=' . $item_id . '&status=success');
echo '<script>window.location.href="' . $url . '";</script>';
exit;
} else {
// 批量添加
$lines = preg_split('/\r\n|\r|\n/', sanitize_textarea_field($_POST['wpcom_code']));
$count = 0;
$repeat = 0;
foreach ($lines as $code) {
$code = trim($code);
if ($code === '') continue;
if ($this->is_code_exists($code, $product_id)) {
$repeat++;
continue;
}
$wpdb->insert($table, [
'product_id' => $product_id,
'code' => $code,
'status' => $status,
'order_id' => $order_id,
'expire_at' => $expire_at,
'created_at' => current_time('mysql')
]);
$count++;
}
// 更新库存
self::update_stock($product_id);
// 跳转到列表页并提示
$url = admin_url('admin.php?page=wpcom-cards-items&product_id=' . $product_id . '&status=success&count=' . $count . '&repeat=' . $repeat);
echo '<script>window.location.href="' . $url . '";</script>';
exit;
}
}
return '';
}
function get_card_product($id){
global $wpdb;
$table = $wpdb->prefix . self::$products_table;
if(!$id) return null;
$product = $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE ID = %d", $id));
if ($product && isset($product->expire_at) && $product->expire_at && strtotime($product->expire_at) < time()) {
// 视为下架
$product->status = 1;
$wpdb->update(
$table,
['status' => 1],
['ID' => $id]
);
}
return $product;
}
function get_card_product_data(){
$res = ['result' => 0];
if (WPCOM_MP_can_setup_order()) {
$product_id = isset($_POST['product_id']) ? sanitize_text_field($_POST['product_id']) : '';
$post_id = isset($_POST['post_id']) ? sanitize_text_field($_POST['post_id']) : '';
$email = isset($_POST['email']) ? sanitize_email($_POST['email']) : '';
$has_email = $email && get_current_user_id() == 0;
if($has_email && !is_email($email)){
$res = ['result' => -11, 'msg' => '请输入正确的邮箱地址'];
wp_send_json($res);
}
if($product_id) {
$product = $this->get_card_product($product_id);
if($product && isset($product->status) && $product->status == 1){
$res = ['result' => -11, 'msg' => '卡密已下架,无法购买'];
wp_send_json($res);
}
}
if(isset($product) && $product->ID){
$points = $product->price_type == 1;
$items = [
[
'title' => $product->title,
'price' => number_format($product->price, $points ? 0 : 2, '.', ''),
'url' => get_permalink($post_id),
'number' => 1,
'set_number' => 1,
'type' => 'card',
'type_id' => $product_id,
'data' => [
'post_id' => $post_id
]
]
];
if(!$this->stock_check(true, $items[0])){
$res = ['result' => -10, 'msg' => '库存不足,无法购买'];
}else{
if($has_email) $items[0]['data']['email'] = $email;
$res['total'] = number_format($product->price, $points ? 0 : 2, '.', '');
$res['items'] = $items;
if($points) $res['points'] = $points ? 1 : 0;
$res = Order::setup_order_data($res);
}
}else{
$res['result'] = -2;
$res['msg'] = '产品信息获取失败';
}
}else{ // 未登录
$res['result'] = -1;
}
wp_send_json($res);
}
function stock_check($check, $item){
global $wpdb;
$product_id = $item['type_id'];
$number = 1;
if(isset($item['update_number']) && $item['update_number']){
$number = $item['update_number'];
}else if(isset($item['number']) && $item['number']){
$number = $item['number'];
}else if(isset($item['data']) && $item['data']){
$data = json_decode($item['data'], true);
$number = $data['quantity'] ?? $number;
}
// 更新已过期卡密状态为作废
$items_table = $wpdb->prefix . self::$items_table;
$now = current_time('mysql');
$wpdb->query($wpdb->prepare(
"UPDATE $items_table SET status = 2 WHERE product_id = %d AND status = 0 AND expire_at IS NOT NULL AND expire_at < %s",
$product_id, $now
));
// 检查库存
$product = $this->get_card_product($product_id);
if($product && $product->stock < $number){
return false;
}
return $check;
}
function payment_complete($item, $order){
global $wpdb;
$product_id = $item->type_id;
$number = 1;
if (isset($item->data)) {
// data 可能是 JSON 字符串,需要先解码
$data = is_array($item->data) ? $item->data : json_decode($item->data, true);
if (is_array($data) && isset($data['quantity'])) {
$number = intval($data['quantity']);
}
}
$items_table = $wpdb->prefix . self::$items_table;
// 1. 选取未售出卡密
$now = current_time('mysql');
$cards = $wpdb->get_results($wpdb->prepare(
"SELECT * FROM $items_table WHERE product_id = %d AND status = 0 AND (expire_at IS NULL OR expire_at >= %s) LIMIT %d",
$product_id, $now, $number
));
if(!$cards || count($cards) < $number){
// 库存不足,建议做异常处理
return;
}
$card_data = [];
foreach($cards as $card){
$card_data[] = [
'code' => $card->code,
'expire_at' => $card->expire_at,
// 可加更多字段
];
// 标记为已售出
$wpdb->update($items_table, [
'status' => 1,
'order_id' => $order->ID,
'sold_at' => current_time('mysql')
], ['ID' => $card->ID]);
}
// 2. 写入订单data字段
$order_item_table = $wpdb->prefix . 'wpcom_order_items';
// 读取原有 data 字段
$old_data = $wpdb->get_var($wpdb->prepare(
"SELECT data FROM $order_item_table WHERE ID = %d", $item->ID
));
$old_data_arr = [];
if ($old_data) {
$old_data_arr = is_array($old_data) ? $old_data : json_decode($old_data, true);
if (!is_array($old_data_arr)) $old_data_arr = [];
}
// 合并新卡密信息
$old_data_arr['cards'] = $card_data;
$old_data_arr['desc'] = $this->get_card_product($product_id)->description ?? '';
// 写回
$wpdb->update($order_item_table, [
'data' => wp_json_encode($old_data_arr, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE)
], ['ID' => $item->ID]);
// 3. 重新统计库存
self::update_stock($product_id);
// 4. 邮件通知
if($order->user){
$user = get_user_by('ID', $order->user);
if($user && $user->user_email && !wpcom_is_empty_mail($user->user_email)) {
$email = $user->user_email;
}
}else if(isset($old_data_arr['email']) && is_email($old_data_arr['email'])){
$email = $old_data_arr['email'];
}
if(isset($email) && $email) $this->send_card_mail($email, $card_data, $old_data_arr['desc']);
}
function send_card_mail($email, $card_data, $desc){
if(empty($email) || empty($card_data)) return;
if ( is_multisite() ) {
$site_name = get_network()->site_name;
} else {
$site_name = wp_specialchars_decode( get_option( 'blogname' ), ENT_QUOTES );
}
$title = '卡密购买成功';
$content = '亲爱的用户:你好!<br>';
$content .= '感谢您的购买,以下是您的卡密信息:<br>';
foreach($card_data as $c){
$content .= '<br>卡密:' . esc_html($c['code']) . '<br>';
if(!empty($c['expire_at']) && $c['expire_at'] !== '0000-00-00 00:00:00') $content .= '过期时间:' . esc_html($c['expire_at']) . '<br>';
}
if($desc) $content .= '<br>使用说明:<br>' . nl2br(esc_html($desc));
$headers = array('Content-Type: text/html; charset=UTF-8');
wp_mail( $email, wp_specialchars_decode( '【' . $site_name . '】' . $title ), $content, $headers );
}
public function show_in_order($item, $order) {
if ($item->type_id && $item->data && $data = json_decode($item->data)) { ?>
<ul class="member-account-order">
<li class="member-account-order-item">
<div class="member-account-order-label">购买卡密:</div>
<div class="member-account-order-value">
<?php
if(isset($data->post_id) && $data->post_id){
echo '<a href="' . esc_url(get_permalink($data->post_id)) . '" target="_blank">' . $item->title . '</a>';
}else {
echo $item->title;
} ?>
</div>
</li>
<li class="member-account-order-item">
<div class="member-account-order-label">卡密信息:</div>
<div class="member-account-order-value">
<?php
$content = '';
if(isset($data->cards) && $data->cards){
foreach($data->cards as $card){
$content .= '<div class="card-items-code">卡密:' . esc_html($card->code) . '</div>';
if(!empty($card->expire_at) && $card->expire_at !== '0000-00-00 00:00:00') $content .= '<div class="card-items-expire">过期时间:' . esc_html($card->expire_at) . '</div>';
}
}
if($content) { ?>
<div class="member-account-card-items">
<?php echo $content; ?>
</div>
<?php } ?>
</div>
</li>
<li class="member-account-order-item">
<div class="member-account-order-label">使用说明:</div>
<div class="member-account-order-value">
<?php echo isset($data->desc) && $data->desc ? wp_kses_post(wpautop($data->desc)) : '/';?>
</div>
</li>
</ul>
<?php }
}
function get_card_item($id) {
global $wpdb;
$table = $wpdb->prefix . self::$items_table;
if(!$id) return null;
return $wpdb->get_row($wpdb->prepare("SELECT * FROM $table WHERE ID = %d", $id));
}
public function shortcode($atts) {
global $wpcom_member;
$atts = shortcode_atts([
'id' => ''
], $atts, 'wpcom-cards');
$ids = array_filter(array_map('intval', preg_split('/[,,]/u', $atts['id'])));
if (!$ids) return '<div>未指定卡密产品ID</div>';
$attr = [
'ids' => $ids,
'products' => []
];
foreach ($ids as $id) {
$product = $this->get_card_product($id);
if (!$product) continue;
$attr['products'][] = $product;
}
$output = $wpcom_member->load_template('cards', $attr);
return '<div class="wp-block-wpcom-cards">' . $output . '</div>';
}
private function is_code_exists($code, $product_id, $exclude_id = 0) {
global $wpdb;
$table = $wpdb->prefix . self::$items_table;
$sql = "SELECT COUNT(*) FROM $table WHERE code = %s AND product_id = %d";
$params = [$code, $product_id];
if ($exclude_id) {
$sql .= " AND ID != %d";
$params[] = $exclude_id;
}
return $wpdb->get_var($wpdb->prepare($sql, ...$params)) > 0;
}
public static function update_stock($product_id) {
global $wpdb;
$products_table = $wpdb->prefix . self::$products_table;
$items_table = $wpdb->prefix . self::$items_table;
// 更新过期卡密作废
$now = current_time('mysql');
$wpdb->query($wpdb->prepare(
"UPDATE $items_table SET status = 2 WHERE product_id = %d AND status = 0 AND expire_at IS NOT NULL AND expire_at < %s",
$product_id, $now
));
// 统计未售出卡密数量
$stock = (int) $wpdb->get_var(
$wpdb->prepare(
"SELECT COUNT(*) FROM $items_table WHERE product_id = %d AND status = 0",
$product_id
)
);
// 更新产品表的 stock 字段
$wpdb->update(
$products_table,
['stock' => $stock],
['ID' => $product_id]
);
}
private static function init_database(){
global $wpdb;
$products_table = $wpdb->prefix . self::$products_table;
$items_table = $wpdb->prefix . self::$items_table;
if( $wpdb->get_var("SHOW TABLES LIKE '$products_table'") !== $products_table ){
$charset_collate = $wpdb->get_charset_collate();
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
$sql = "CREATE TABLE $products_table (
ID BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
title VARCHAR(255) NOT NULL,
price DECIMAL(10,2) NOT NULL DEFAULT 0.00,
price_type TINYINT(1) NOT NULL DEFAULT 0,
stock INT UNSIGNED NOT NULL DEFAULT 0,
description TEXT NULL,
status TINYINT(1) NOT NULL DEFAULT 1,
expire_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (ID)
) $charset_collate;";
dbDelta($sql);
}
if( $wpdb->get_var("SHOW TABLES LIKE '$items_table'") !== $items_table ){
$charset_collate = $wpdb->get_charset_collate();
require_once( ABSPATH . 'wp-admin/includes/upgrade.php' );
$sql = "CREATE TABLE $items_table (
ID BIGINT(20) UNSIGNED NOT NULL AUTO_INCREMENT,
product_id BIGINT(20) UNSIGNED NOT NULL,
code TEXT NOT NULL,
status TINYINT(1) NOT NULL DEFAULT 0,
order_id BIGINT(20) UNSIGNED DEFAULT NULL,
sold_at DATETIME DEFAULT NULL,
remark TEXT NULL,
expire_at DATETIME DEFAULT NULL,
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (ID),
KEY product_id (product_id),
KEY status (status)
) $charset_collate;";
dbDelta($sql);
}
}
}
new Cards();