601 行
20 KiB
PHP
601 行
20 KiB
PHP
<?php
|
||
if (!defined('IN_APP')) {
|
||
http_response_code(403);
|
||
exit('Forbidden');
|
||
}
|
||
function h($str)
|
||
{
|
||
return htmlspecialchars((string) $str, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
|
||
}
|
||
function money($n)
|
||
{
|
||
return '¥' . number_format((float) $n, 2);
|
||
}
|
||
function redirect($url)
|
||
{
|
||
if (is_string($url)
|
||
&& (strpos($url, '
|
||
|| preg_match('
|
||
$url = 'index.php';
|
||
}
|
||
header('Location: ' . $url);
|
||
exit;
|
||
}
|
||
function getClientIp()
|
||
{
|
||
$candidates = [];
|
||
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
|
||
$candidates = array_merge($candidates, explode(',', $_SERVER['HTTP_X_FORWARDED_FOR']));
|
||
}
|
||
foreach (['HTTP_X_REAL_IP', 'HTTP_CLIENT_IP', 'REMOTE_ADDR'] as $k) {
|
||
if (!empty($_SERVER[$k])) {
|
||
$candidates[] = $_SERVER[$k];
|
||
}
|
||
}
|
||
foreach ($candidates as $ip) {
|
||
$ip = trim($ip);
|
||
if ($ip !== '' && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE | FILTER_FLAG_NO_RES_RANGE)) {
|
||
return $ip;
|
||
}
|
||
}
|
||
return trim((string) ($_SERVER['REMOTE_ADDR'] ?? '0.0.0.0'));
|
||
}
|
||
function fnwHttpPost($url, $body, $timeout = 10)
|
||
{
|
||
$url = (string) $url;
|
||
if (!preg_match('
|
||
return null;
|
||
}
|
||
if (function_exists('curl_init')) {
|
||
$ch = curl_init($url);
|
||
curl_setopt_array($ch, [
|
||
CURLOPT_POST => true,
|
||
CURLOPT_RETURNTRANSFER => true,
|
||
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Accept: application/json'],
|
||
CURLOPT_POSTFIELDS => $body,
|
||
CURLOPT_TIMEOUT => $timeout,
|
||
CURLOPT_CONNECTTIMEOUT => $timeout,
|
||
CURLOPT_SSL_VERIFYPEER => true,
|
||
CURLOPT_SSL_VERIFYHOST => 2,
|
||
]);
|
||
$r = curl_exec($ch);
|
||
curl_close($ch);
|
||
return $r === false ? null : (string) $r;
|
||
}
|
||
if (function_exists('stream_context_create')) {
|
||
$ctx = stream_context_create(['http' => [
|
||
'method' => 'POST',
|
||
'header' => "Content-Type: application/json\r\nAccept: application/json\r\n",
|
||
'content' => $body,
|
||
'timeout' => $timeout,
|
||
]]);
|
||
$r = @file_get_contents($url, false, $ctx);
|
||
return $r === false ? null : (string) $r;
|
||
}
|
||
return null;
|
||
}
|
||
function regCountToday($ip)
|
||
{
|
||
try {
|
||
$stmt = db()->prepare('SELECT COUNT(*) FROM ' . tn('users') . ' WHERE reg_ip = ? AND DATE(created_at) = CURDATE()');
|
||
$stmt->execute([$ip]);
|
||
return (int) $stmt->fetchColumn();
|
||
} catch (Throwable $e) {
|
||
return 0;
|
||
}
|
||
}
|
||
function siteBaseUrl()
|
||
{
|
||
$scheme = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || ($_SERVER['SERVER_PORT'] ?? '') == '443' ? 'https' : 'http';
|
||
$host = $_SERVER['HTTP_HOST'] ?? 'localhost';
|
||
$dir = dirname($_SERVER['SCRIPT_NAME'] ?? '/index.php');
|
||
$dir = ($dir === '/' || $dir === '\\') ? '' : rtrim($dir, '/');
|
||
return $scheme . ':
|
||
}
|
||
function avatarText($name)
|
||
{
|
||
$name = trim((string) $name);
|
||
if ($name === '') {
|
||
return '?';
|
||
}
|
||
return mb_strtoupper(mb_substr($name, 0, 1, 'UTF-8'), 'UTF-8');
|
||
}
|
||
function sendVerifyMail($userId)
|
||
{
|
||
$stmt = db()->prepare('SELECT id, username, nickname, email FROM ' . tn('users') . ' WHERE id = ?');
|
||
$stmt->execute([$userId]);
|
||
$user = $stmt->fetch();
|
||
if (!$user || $user['email'] === '') {
|
||
return ['ok' => false, 'error' => '该账号没有可用邮箱', 'log' => []];
|
||
}
|
||
$token = bin2hex(random_bytes(24));
|
||
db()->prepare('UPDATE ' . tn('users') . ' SET email_token = ? WHERE id = ?')
|
||
->execute([$token, $user['id']]);
|
||
$url = siteBaseUrl() . '/verify.php?token=' . $token;
|
||
$name = $user['nickname'] ?: $user['username'];
|
||
$subject = '[' . SITE_NAME . '] 请验证你的邮箱';
|
||
$body = '<div style="font-family:Segoe UI,Helvetica,Arial,sans-serif;max-width:480px;margin:0 auto;padding:24px;'
|
||
. 'border:1px solid
|
||
. '<h2 style="color:
|
||
. '<p style="color:
|
||
. '<p style="color:
|
||
. '<p style="margin:18px 0;"><a href="' . h($url) . '" style="background:
|
||
. 'border-radius:999px;text-decoration:none;display:inline-block;font-weight:600;">验证邮箱</a></p>'
|
||
. '<p style="color:
|
||
. '<hr style="border:none;border-top:1px solid
|
||
. '<p style="color:
|
||
. '</div>';
|
||
return fnwSendMail($user['email'], $subject, $body);
|
||
}
|
||
function currentUser($forceRefresh = false)
|
||
{
|
||
if (empty($_SESSION['user_id'])) {
|
||
return null;
|
||
}
|
||
static $cache = null;
|
||
if ($cache !== null && !$forceRefresh) {
|
||
return $cache;
|
||
}
|
||
$cache = null;
|
||
$pdo = db();
|
||
$stmt = $pdo->prepare('SELECT id, username, email, nickname, verified, is_admin, status, points, invite_code, invited_by, avatar FROM ' . tn('users') . ' WHERE id = ?');
|
||
$stmt->execute([$_SESSION['user_id']]);
|
||
$row = $stmt->fetch();
|
||
if (!$row || (int) $row['status'] !== 1) {
|
||
unset($_SESSION['user_id']);
|
||
$cache = null;
|
||
return null;
|
||
}
|
||
$cache = $row;
|
||
return $cache;
|
||
}
|
||
function refreshCurrentUser()
|
||
{
|
||
currentUser(true);
|
||
}
|
||
function isLoggedIn()
|
||
{
|
||
return currentUser() !== null;
|
||
}
|
||
function currentAdmin()
|
||
{
|
||
if (empty($_SESSION['admin_id'])) {
|
||
return null;
|
||
}
|
||
static $cache = null;
|
||
if ($cache !== null) {
|
||
return $cache;
|
||
}
|
||
$pdo = db();
|
||
$stmt = $pdo->prepare('SELECT id, username, email, status FROM ' . tn('users') . ' WHERE id = ? AND is_admin = 1');
|
||
$stmt->execute([$_SESSION['admin_id']]);
|
||
$row = $stmt->fetch();
|
||
if (!$row || (int) $row['status'] !== 1) {
|
||
unset($_SESSION['admin_id']);
|
||
$cache = null;
|
||
return null;
|
||
}
|
||
$cache = $row;
|
||
return $cache;
|
||
}
|
||
function requireLogin($next = '')
|
||
{
|
||
if (!isLoggedIn()) {
|
||
$q = $next ? '?next=' . urlencode($next) : '';
|
||
redirect('login.php' . $q);
|
||
}
|
||
}
|
||
function requireAdmin()
|
||
{
|
||
if (empty($_SESSION['admin_id']) || !currentAdmin()) {
|
||
redirect('login.php');
|
||
}
|
||
}
|
||
function currentStaff()
|
||
{
|
||
if (empty($_SESSION['admin_id'])) {
|
||
return null;
|
||
}
|
||
static $cache = null;
|
||
if ($cache !== null) {
|
||
return $cache;
|
||
}
|
||
$pdo = db();
|
||
$stmt = $pdo->prepare(
|
||
'SELECT id, username, email, status, role, is_admin FROM ' . tn('users') . ' WHERE id = ? AND status = 1 AND role IN (\'admin\', \'cs\')'
|
||
);
|
||
$stmt->execute([$_SESSION['admin_id']]);
|
||
$row = $stmt->fetch();
|
||
if (!$row) {
|
||
unset($_SESSION['admin_id']);
|
||
$cache = null;
|
||
return null;
|
||
}
|
||
$cache = $row;
|
||
return $cache;
|
||
}
|
||
function requireStaff()
|
||
{
|
||
if (empty($_SESSION['admin_id']) || !currentStaff()) {
|
||
redirect('login.php');
|
||
}
|
||
}
|
||
function isAdminStaff()
|
||
{
|
||
$s = currentStaff();
|
||
return $s !== null && (int) $s['is_admin'] === 1;
|
||
}
|
||
function csrfToken()
|
||
{
|
||
if (empty($_SESSION['csrf'])) {
|
||
$_SESSION['csrf'] = bin2hex(random_bytes(32));
|
||
}
|
||
return $_SESSION['csrf'];
|
||
}
|
||
function csrfField()
|
||
{
|
||
return '<input type="hidden" name="csrf" value="' . csrfToken() . '">';
|
||
}
|
||
function verifyCsrf()
|
||
{
|
||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||
$token = $_POST['csrf'] ?? '';
|
||
$sess = $_SESSION['csrf'] ?? '';
|
||
if ($token === '' || $sess === '' || !hash_equals($sess, $token)) {
|
||
http_response_code(403);
|
||
exit('CSRF 校验失败');
|
||
}
|
||
}
|
||
}
|
||
function verifyCsrfToken($token)
|
||
{
|
||
return !empty($token) && !empty($_SESSION['csrf']) && hash_equals((string)$_SESSION['csrf'], (string)$token);
|
||
}
|
||
function currentTheme()
|
||
{
|
||
return (!empty($_COOKIE['fnw_theme']) && $_COOKIE['fnw_theme'] === 'dark') ? 'dark' : 'light';
|
||
}
|
||
function orderStatusLabel($status)
|
||
{
|
||
$map = [
|
||
'pending' => '待处理',
|
||
'paid' => '已受理',
|
||
'shipped' => '已发货',
|
||
'completed' => '已完成',
|
||
'cancelled' => '已取消',
|
||
];
|
||
return $map[$status] ?? $status;
|
||
}
|
||
function getSetting($k, $default = '')
|
||
{
|
||
try {
|
||
$stmt = db()->prepare('SELECT v FROM ' . tn('settings') . ' WHERE k = ?');
|
||
$stmt->execute([$k]);
|
||
$v = $stmt->fetchColumn();
|
||
return $v === false ? $default : $v;
|
||
} catch (Throwable $e) {
|
||
return $default;
|
||
}
|
||
}
|
||
function saveSetting($k, $v)
|
||
{
|
||
db()->prepare(
|
||
'INSERT INTO ' . tn('settings') . ' (k, v) VALUES (?, ?) ON DUPLICATE KEY UPDATE v = ?'
|
||
)->execute([$k, $v, $v]);
|
||
}
|
||
function smtpConfig()
|
||
{
|
||
static $c = null;
|
||
if ($c !== null) {
|
||
return $c;
|
||
}
|
||
$keys = ['smtp_host', 'smtp_port', 'smtp_enc', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_fromname', 'notify_emails'];
|
||
$map = [];
|
||
try {
|
||
$pdo = db();
|
||
$ph = implode(',', array_fill(0, count($keys), '?'));
|
||
$stmt = $pdo->prepare('SELECT k, v FROM ' . tn('settings') . ' WHERE k IN (' . $ph . ')');
|
||
$stmt->execute($keys);
|
||
foreach ($stmt->fetchAll() as $row) {
|
||
$map[$row['k']] = $row['v'];
|
||
}
|
||
} catch (Throwable $e) {
|
||
}
|
||
$c = [];
|
||
foreach ($keys as $k) {
|
||
$c[$k] = $map[$k] ?? '';
|
||
}
|
||
return $c;
|
||
}
|
||
function fnwSendMail($to, $subject, $body)
|
||
{
|
||
$cfg = smtpConfig();
|
||
if (empty($cfg['smtp_host'])) {
|
||
return ['ok' => false, 'error' => 'SMTP 未配置'];
|
||
}
|
||
$rl = mailRateLimit($to);
|
||
if (!$rl['allowed']) {
|
||
return ['ok' => false, 'error' => '发送频率过高,请 ' . ceil($rl['wait'] / 60) . ' 分钟后再试'];
|
||
}
|
||
require_once __DIR__ . '/mailer.php';
|
||
$mailer = new SmtpMailer($cfg['smtp_host'], $cfg['smtp_port'], $cfg['smtp_enc'], $cfg['smtp_user'], $cfg['smtp_pass']);
|
||
$from = $cfg['smtp_from'] ?: $cfg['smtp_user'];
|
||
$ok = $mailer->send($to, $subject, $body, $from, $cfg['smtp_fromname'] ?: SITE_NAME);
|
||
if ($ok) {
|
||
return ['ok' => true, 'log' => $mailer->log];
|
||
}
|
||
return ['ok' => false, 'error' => $mailer->lastError, 'log' => $mailer->log];
|
||
}
|
||
function ticketStatusLabel($status)
|
||
{
|
||
$map = [
|
||
'open' => '待处理',
|
||
'pending' => '处理中',
|
||
'resolved' => '已解决',
|
||
'closed' => '已关闭',
|
||
];
|
||
return $map[$status] ?? $status;
|
||
}
|
||
function ticketPriorityLabel($p)
|
||
{
|
||
$map = [1 => '低', 2 => '普通', 3 => '高'];
|
||
return $map[(int) $p] ?? '普通';
|
||
}
|
||
function periodLabel($days)
|
||
{
|
||
$days = (int) $days;
|
||
if ($days <= 0) {
|
||
return '一次性';
|
||
}
|
||
if ($days % 365 === 0) {
|
||
return ($days / 365) . ' 年';
|
||
}
|
||
if ($days % 30 === 0) {
|
||
return ($days / 30) . ' 个月';
|
||
}
|
||
return $days . ' 天';
|
||
}
|
||
function ticketTypeLabel($t)
|
||
{
|
||
$map = [
|
||
'general' => '普通',
|
||
'renewal' => '续期申请',
|
||
];
|
||
return $map[$t] ?? '普通';
|
||
}
|
||
function expiryText($dt)
|
||
{
|
||
if ($dt === null || $dt === '') {
|
||
return '—';
|
||
}
|
||
return date('Y-m-d H:i', strtotime($dt));
|
||
}
|
||
function isExpired($dt)
|
||
{
|
||
if ($dt === null || $dt === '') {
|
||
return false;
|
||
}
|
||
return strtotime($dt) < time();
|
||
}
|
||
function expiryState($dt)
|
||
{
|
||
if ($dt === null || $dt === '' || $dt === '0000-00-00 00:00:00') {
|
||
return ['has' => false, 'text' => '—', 'days' => null, 'level' => 'none'];
|
||
}
|
||
$ts = strtotime($dt);
|
||
if ($ts === false) {
|
||
return ['has' => false, 'text' => '—', 'days' => null, 'level' => 'none'];
|
||
}
|
||
$now = time();
|
||
$days = ($ts - $now) / 86400;
|
||
$daysLeft = (int) ceil($days);
|
||
if ($ts < $now) {
|
||
return ['has' => true, 'text' => '已到期(' . date('Y-m-d H:i', $ts) . ')', 'days' => $daysLeft, 'level' => 'expired'];
|
||
}
|
||
$level = $daysLeft <= 3 ? 'danger' : ($daysLeft <= 7 ? 'warn' : 'ok');
|
||
return ['has' => true, 'text' => date('Y-m-d H:i', $ts) . '(剩 ' . $daysLeft . ' 天)', 'days' => $daysLeft, 'level' => $level];
|
||
}
|
||
function expiryBadge($dt)
|
||
{
|
||
$s = expiryState($dt);
|
||
if (!$s['has']) {
|
||
return '<span class="exp-badge exp-none">无到期时间</span>';
|
||
}
|
||
$icon = [
|
||
'ok' => 'fa-calendar-check',
|
||
'warn' => 'fa-clock',
|
||
'danger' => 'fa-triangle-exclamation',
|
||
'expired' => 'fa-circle-xmark',
|
||
][$s['level']] ?? 'fa-calendar';
|
||
return '<span class="exp-badge exp-' . $s['level'] . '"><i class="fas ' . $icon . '"></i> ' . h($s['text']) . '</span>';
|
||
}
|
||
function payTypeLabel($t)
|
||
{
|
||
$map = [
|
||
'cash' => '现金(历史)',
|
||
'points' => '积分支付',
|
||
'direct' => '直接下单',
|
||
];
|
||
return $map[$t] ?? '直接下单';
|
||
}
|
||
function getUserPoints($uid)
|
||
{
|
||
try {
|
||
$stmt = db()->prepare('SELECT points FROM ' . tn('users') . ' WHERE id = ?');
|
||
$stmt->execute([(int) $uid]);
|
||
$v = $stmt->fetchColumn();
|
||
return $v === false ? 0 : (int) $v;
|
||
} catch (Throwable $e) {
|
||
return 0;
|
||
}
|
||
}
|
||
function addPoints($uid, $type, $amount, $remark = '')
|
||
{
|
||
$uid = (int) $uid;
|
||
$amount = (int) $amount;
|
||
try {
|
||
$pdo = db();
|
||
$pdo->beginTransaction();
|
||
$stmt = $pdo->prepare('SELECT points FROM ' . tn('users') . ' WHERE id = ? FOR UPDATE');
|
||
$stmt->execute([$uid]);
|
||
$cur = (int) $stmt->fetchColumn();
|
||
$balance = max(0, $cur + $amount);
|
||
$pdo->prepare('UPDATE ' . tn('users') . ' SET points = ? WHERE id = ?')
|
||
->execute([$balance, $uid]);
|
||
$pdo->prepare(
|
||
'INSERT INTO ' . tn('points_log') . ' (user_id, type, amount, balance, remark, created_at) VALUES (?,?,?,?,?,NOW())'
|
||
)->execute([$uid, $type, $amount, $balance, $remark]);
|
||
$pdo->commit();
|
||
return ['ok' => true, 'balance' => $balance, 'error' => ''];
|
||
} catch (Throwable $e) {
|
||
if (isset($pdo) && $pdo->inTransaction()) {
|
||
$pdo->rollBack();
|
||
}
|
||
return ['ok' => false, 'balance' => 0, 'error' => $e->getMessage()];
|
||
}
|
||
}
|
||
function genInviteCode()
|
||
{
|
||
$chars = 'ABCDEFGHJKLMNPQRSTUVWXYZ23456789';
|
||
for ($i = 0; $i < 20; $i++) {
|
||
$code = '';
|
||
for ($j = 0; $j < 8; $j++) {
|
||
$code .= $chars[random_int(0, strlen($chars) - 1)];
|
||
}
|
||
$stmt = db()->prepare('SELECT 1 FROM ' . tn('users') . ' WHERE invite_code = ?');
|
||
$stmt->execute([$code]);
|
||
if (!$stmt->fetch()) {
|
||
return $code;
|
||
}
|
||
}
|
||
return strtoupper(substr(md5(uniqid('', true)), 0, 8));
|
||
}
|
||
function getInviterIdByCode($code)
|
||
{
|
||
$code = trim((string) $code);
|
||
if ($code === '') {
|
||
return 0;
|
||
}
|
||
$stmt = db()->prepare('SELECT id FROM ' . tn('users') . ' WHERE invite_code = ?');
|
||
$stmt->execute([$code]);
|
||
$id = $stmt->fetchColumn();
|
||
return $id === false ? 0 : (int) $id;
|
||
}
|
||
function hasSignedToday($uid)
|
||
{
|
||
try {
|
||
$stmt = db()->prepare('SELECT 1 FROM ' . tn('sign_logs') . ' WHERE user_id = ? AND sign_date = CURDATE()');
|
||
$stmt->execute([(int) $uid]);
|
||
return (bool) $stmt->fetchColumn();
|
||
} catch (Throwable $e) {
|
||
return false;
|
||
}
|
||
}
|
||
function getSiteName() {
|
||
global $_site_name;
|
||
if ($_site_name !== null) {
|
||
return $_site_name;
|
||
}
|
||
try {
|
||
$db = db();
|
||
$stmt = $db->query("SELECT v FROM " . tn('settings') . " WHERE k = 'site_name'");
|
||
$val = $stmt->fetchColumn();
|
||
$_site_name = ($val !== false && $val !== null && $val !== '') ? $val : '自由云商城';
|
||
} catch (Exception $e) {
|
||
$_site_name = '自由云商城';
|
||
}
|
||
return $_site_name;
|
||
}
|
||
function defaultPurchaseAgreement()
|
||
{
|
||
return "1. 您须遵守《中华人民共和国网络安全法》及一切现行法律法规,不得利用所购资源从事任何违法违规活动。\n"
|
||
. "2. 严禁利用本商品提供的服务器、主机、网络等资源搭建违法违规网站或平台。\n"
|
||
. "3. 严禁利用本商品提供的资源搭建 VPN、代理、翻墙等未经许可的跨境网络访问服务。\n"
|
||
. "4. 严禁用于诈骗、赌博、侵权、传播不良信息及其他侵害他人权益的行为。\n"
|
||
. "5. 您须对使用行为承担全部法律责任;一经发现违规,平台有权立即关停相关服务并保留上报权利。";
|
||
}
|
||
function getProductAgreement($product)
|
||
{
|
||
$custom = $product['agreement'] ?? '';
|
||
$custom = is_string($custom) ? trim($custom) : '';
|
||
return $custom !== '' ? $custom : defaultPurchaseAgreement();
|
||
}
|
||
function recordVisit()
|
||
{
|
||
try {
|
||
$ip = getClientIp();
|
||
$uri = isset($_SERVER['REQUEST_URI']) ? substr($_SERVER['REQUEST_URI'], 0, 255) : '';
|
||
$method = isset($_SERVER['REQUEST_METHOD']) ? substr($_SERVER['REQUEST_METHOD'], 0, 8) : 'GET';
|
||
$ua = isset($_SERVER['HTTP_USER_AGENT']) ? substr($_SERVER['HTTP_USER_AGENT'], 0, 255) : '';
|
||
$ref = isset($_SERVER['HTTP_REFERER']) ? substr($_SERVER['HTTP_REFERER'], 0, 255) : '';
|
||
$uid = (int) ($_SESSION['user_id'] ?? 0);
|
||
db()->prepare(
|
||
'INSERT INTO ' . tn('visit_logs') . ' (ip, method, uri, user_id, ua, referer, created_at) VALUES (?,?,?,?,?,?,NOW())'
|
||
)->execute([$ip, $method, $uri, $uid, $ua, $ref]);
|
||
} catch (Throwable $e) {
|
||
}
|
||
}
|
||
function sendResetMail($userId)
|
||
{
|
||
$stmt = db()->prepare('SELECT id, username, nickname, email FROM ' . tn('users') . ' WHERE id = ?');
|
||
$stmt->execute([(int) $userId]);
|
||
$user = $stmt->fetch();
|
||
if (!$user || $user['email'] === '') {
|
||
return ['ok' => false, 'error' => '该账号没有可用邮箱'];
|
||
}
|
||
$token = bin2hex(random_bytes(24));
|
||
$expire = date('Y-m-d H:i:s', strtotime('+30 minutes'));
|
||
db()->prepare('UPDATE ' . tn('users') . ' SET reset_token = ?, reset_expire = ? WHERE id = ?')
|
||
->execute([$token, $expire, $user['id']]);
|
||
$url = siteBaseUrl() . '/reset_password.php?token=' . $token;
|
||
$name = $user['nickname'] ?: $user['username'];
|
||
$subject = '[' . SITE_NAME . '] 重置你的密码';
|
||
$body = '<div style="font-family:Segoe UI,Helvetica,Arial,sans-serif;max-width:480px;margin:0 auto;padding:24px;'
|
||
. 'border:1px solid
|
||
. '<h2 style="color:
|
||
. '<p style="color:
|
||
. '<p style="color:
|
||
. '<p style="margin:18px 0;"><a href="' . h($url) . '" style="background:
|
||
. 'border-radius:999px;text-decoration:none;display:inline-block;font-weight:600;">重置密码</a></p>'
|
||
. '<p style="color:
|
||
. '<hr style="border:none;border-top:1px solid
|
||
. '<p style="color:
|
||
. '</div>';
|
||
return fnwSendMail($user['email'], $subject, $body);
|
||
}
|
||
function getNotifyPrefs($uid)
|
||
{
|
||
$uid = (int) $uid;
|
||
try {
|
||
$stmt = db()->prepare('SELECT * FROM ' . tn('user_notify_prefs') . ' WHERE user_id = ?');
|
||
$stmt->execute([$uid]);
|
||
$row = $stmt->fetch();
|
||
if ($row) return $row;
|
||
} catch (Throwable $e) {
|
||
}
|
||
return [
|
||
'user_id' => $uid,
|
||
'email_enabled' => 1,
|
||
'order_shipped' => 1,
|
||
'expiry_reminder' => 1,
|
||
'promotions' => 0,
|
||
];
|
||
}
|
||
function saveNotifyPrefs($uid, array $data)
|
||
{
|
||
$uid = (int) $uid;
|
||
$bool = function ($v) { return !empty($v) ? 1 : 0; };
|
||
$email_enabled = $bool($data['email_enabled'] ?? 0);
|
||
$order_shipped = $bool($data['order_shipped'] ?? 0);
|
||
$expiry_reminder = $bool($data['expiry_reminder'] ?? 0);
|
||
$promotions = $bool($data['promotions'] ?? 0);
|
||
try {
|
||
db()->prepare(
|
||
'INSERT INTO ' . tn('user_notify_prefs') . ' (user_id, email_enabled, order_shipped, expiry_reminder, promotions, updated_at) '
|
||
. 'VALUES (?,?,?,?,?,NOW()) ON DUPLICATE KEY UPDATE email_enabled=?, order_shipped=?, expiry_reminder=?, promotions=?, updated_at=NOW()'
|
||
)->execute([$uid, $email_enabled, $order_shipped, $expiry_reminder, $promotions,
|
||
$email_enabled, $order_shipped, $expiry_reminder, $promotions]);
|
||
} catch (Throwable $e) {
|
||
}
|
||
}
|