Files
freeshop/admin/settings.php
T
2026-08-14 10:55:06 +08:00

300 lines
17 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
require __DIR__ . '/../includes/init.php';
requireStaff();
require '_common.php';
$isAdmin = isAdminStaff();
$admin = currentAdmin();
$msg = '';
$err = '';
// 客服账号仅可查看,禁止任何写操作(直接丢弃写请求键)
if (!$isAdmin && $_SERVER['REQUEST_METHOD'] === 'POST') {
$err = '客服账号仅可查看,如需操作请联系管理员。';
foreach (['savesite','changepw','savesmtp','saveabout','savepoints','savepay_ali','savepay_wx','savefavicon','delfavicon'] as $k) {
unset($_POST[$k]);
}
}
if (isset($_POST['savesite'])) {
verifyCsrf();
$siteName = trim($_POST['site_name'] ?? '');
if ($siteName === '') {
$err = '站点名称不能为空。';
} else {
saveSetting('site_name', $siteName);
$GLOBALS['_site_name'] = null; // 清空全局缓存
$msg = '站点名称已修改。';
}
}
if (isset($_POST['changepw'])) {
verifyCsrf();
$cur = $_POST['cur'] ?? '';
$new = $_POST['new'] ?? '';
$new2 = $_POST['new2'] ?? '';
$stmt = db()->prepare('SELECT password FROM ' . tn('users') . ' WHERE id = ?');
$stmt->execute([$admin['id']]);
$hash = $stmt->fetchColumn();
if (!password_verify($cur, $hash)) {
$err = '当前密码错误。';
} elseif (strlen($new) < 6) {
$err = '新密码至少 6 位。';
} elseif ($new !== $new2) {
$err = '两次输入的新密码不一致。';
} else {
$newHash = password_hash($new, PASSWORD_DEFAULT);
db()->prepare('UPDATE ' . tn('users') . ' SET password = ? WHERE id = ?')
->execute([$newHash, $admin['id']]);
$msg = '管理员密码已修改。';
}
}
if (isset($_POST['savesmtp'])) {
verifyCsrf();
foreach (['smtp_host', 'smtp_port', 'smtp_enc', 'smtp_user', 'smtp_pass', 'smtp_from', 'smtp_fromname', 'notify_emails'] as $k) {
saveSetting($k, trim((string) ($_POST[$k] ?? '')));
}
$msg = 'SMTP 配置已保存!';
}
if (isset($_POST['saveabout'])) {
verifyCsrf();
saveSetting('mall_about', trim((string) ($_POST['mall_about'] ?? '')));
$msg = '商城介绍已保存!';
}
if (isset($_POST['savepoints'])) {
verifyCsrf();
saveSetting('points_enabled', isset($_POST['points_enabled']) ? '1' : '0');
saveSetting('points_sign', max(0, (int) ($_POST['points_sign'] ?? 0)));
saveSetting('points_invite', max(0, (int) ($_POST['points_invite'] ?? 0)));
$msg = '积分设置已保存';
}
if (isset($_POST['savepay_ali'])) {
verifyCsrf();
saveSetting('pay_alipay_enabled', isset($_POST['pay_alipay_enabled']) ? '1' : '0');
saveSetting('pay_alipay_appid', trim((string) ($_POST['pay_alipay_appid'] ?? '')));
saveSetting('pay_alipay_private_key', trim((string) ($_POST['pay_alipay_private_key'] ?? '')));
saveSetting('pay_alipay_public_key', trim((string) ($_POST['pay_alipay_public_key'] ?? '')));
saveSetting('pay_alipay_sandbox', isset($_POST['pay_alipay_sandbox']) ? '1' : '0');
$msg = '支付宝配置已保存!';
}
if (isset($_POST['savepay_wx'])) {
verifyCsrf();
saveSetting('pay_wechat_enabled', isset($_POST['pay_wechat_enabled']) ? '1' : '0');
saveSetting('pay_wechat_mch_id', trim((string) ($_POST['pay_wechat_mch_id'] ?? '')));
saveSetting('pay_wechat_api_key', trim((string) ($_POST['pay_wechat_api_key'] ?? '')));
saveSetting('pay_wechat_appid', trim((string) ($_POST['pay_wechat_appid'] ?? '')));
$msg = '微信支付配置已保存!';
}
if (isset($_POST['savefavicon']) || isset($_POST['delfavicon'])) {
verifyCsrf();
if (!$isAdmin) {
$err = '客服账号仅可查看,如需操作请联系管理员。';
} elseif (isset($_POST['delfavicon'])) {
$old = getSetting('favicon', '');
if ($old && file_exists(__DIR__ . '/../' . $old)) { @unlink(__DIR__ . '/../' . $old); }
saveSetting('favicon', '');
$msg = '已恢复默认图标。';
} else {
if (empty($_FILES['favicon_file']) || $_FILES['favicon_file']['error'] !== UPLOAD_ERR_OK) {
$err = '请选择要上传的 .ico 文件。';
} else {
$f = $_FILES['favicon_file'];
$ext = strtolower(pathinfo($f['name'], PATHINFO_EXTENSION));
if ($ext !== 'ico') {
$err = '仅支持 .ico 格式的图标文件。';
} elseif ($f['size'] > 256 * 1024) {
$err = '文件过大,请控制在 256KB 以内。';
} else {
// 校验 ICO 文件头:00 00 01 00
$head = file_get_contents($f['tmp_name'], false, null, 0, 4);
if ($head !== "\x00\x00\x01\x00") {
$err = '文件内容不是有效的 ICO 图标(文件头校验失败)。';
} else {
$dest = __DIR__ . '/../assets/favicon.ico';
if (!move_uploaded_file($f['tmp_name'], $dest) && !copy($f['tmp_name'], $dest)) {
$err = '保存失败,请检查 assets/ 目录写入权限。';
} else {
saveSetting('favicon', 'assets/favicon.ico');
$msg = '网站图标已更新,刷新浏览器即可看到(若未变请强制刷新清缓存)。';
}
}
}
}
}
}
$testResult = null;
if (isset($_POST['testmail'])) {
verifyCsrf();
$to = trim($_POST['test_to'] ?? '');
if (!filter_var($to, FILTER_VALIDATE_EMAIL)) {
$err = '请填写有效的测试收件邮箱。';
} else {
$cfg = smtpConfig();
$body = '<div style="font-family:sans-serif;max-width:520px;margin:auto">'
. '<h2 style="color:#2563eb">[' . h(SITE_NAME) . '] SMTP 测试邮件</h2>'
. '<p>这是一封由商城后台发送的测试邮件,若你收到了它,说明 SMTP 配置正确。</p>'
. '<p class="muted">发送时间:' . date('Y-m-d H:i:s') . '</p></div>';
$testResult = fnwSendMail($to, '[' . SITE_NAME . '] SMTP 测试邮件', $body);
if ($testResult['ok']) {
$msg = '测试邮件已发送,请查收(含 SMTP 会话日志见下方)。';
} else {
$err = '发送失败:' . ($testResult['error'] ?? '未知错误');
}
}
}
$cfg = smtpConfig();
adminHeader('设置', 'settings');
?>
<h1 class="page-title"><i class="fas fa-cog"></i> 设置</h1>
<?php if ($msg): ?><p class="banner-ok"><i class="fas fa-check-circle"></i> <?= h($msg) ?></p><?php endif; ?>
<?php if ($err): ?><p class="form-err"><?= h($err) ?></p><?php endif; ?>
<div class="panel" style="max-width:560px;">
<h3>修改管理员密码</h3>
<p class="muted">当前管理员:<strong><?= h($admin['username']) ?></strong></p>
<form method="post" action="" class="auth-form">
<?= csrfField() ?>
<label>当前密码<input type="password" name="cur" required></label>
<label>新密码<input type="password" name="new" placeholder="至少 6 位。" required></label>
<label>确认新密码<input type="password" name="new2" required></label>
<button type="submit" name="changepw" class="btn btn-primary">保存修改</button>
</form>
</div>
<div class="panel" style="max-width:680px;">
<h3>商城介绍(前台首页展示)</h3>
<p class="muted">这段文字会显示在商城首页「关于商城」区块,向访客介绍你的商城。留空则不显示该区块。</p>
<form method="post" action="" class="grid-form">
<?= csrfField() ?>
<label class="span2">商城介绍<textarea name="mall_about" rows="4" placeholder="如:本商城是 XX 团队旗下的……"><?= h(getSetting('mall_about', '')) ?></textarea></label>
<div class="form-actions span2">
<button type="submit" name="saveabout" class="btn btn-primary"><i class="fas fa-save"></i> 保存商城介绍</button>
</div>
</form>
</div>
<div class="panel" style="max-width:560px;">
<h3><i class="fas fa-store"></i> 站点名称</h3>
<p class="muted">该名称会显示在浏览器标题、页面顶部、邮件通知等位置。</p>
<form method="post" action="" class="auth-form">
<?= csrfField() ?>
<label>站点名称
<input type="text" name="site_name" value="<?= h(getSetting('site_name', SITE_NAME)) ?>" required>
</label>
<button type="submit" name="savesite" class="btn btn-primary"><i class="fas fa-save"></i> 保存站点名称</button>
</form>
</div>
<div class="panel" style="max-width:560px;">
<h3><i class="fas fa-image"></i> 网站图标(Favicon</h3>
<p class="muted">上传 .ico 格式图标(建议 16×16 或 32×32,最大 256KB),将显示在浏览器标签页。留空则使用默认图标。</p>
<p style="margin:12px 0;">
<?php
$favCur = getSetting('favicon', '');
if ($favCur && file_exists(__DIR__ . '/../' . $favCur)):
?>
<img src="../<?= h($favCur) ?>?v=<?= filemtime(__DIR__ . '/../' . $favCur) ?>" alt="当前图标" style="width:32px;height:32px;border:1px solid var(--border);border-radius:6px;vertical-align:middle;">
<span class="muted"> 当前图标(<?= h($favCur) ?></span>
<?php else: ?>
<span class="muted">当前:默认图标</span>
<?php endif; ?>
</p>
<form method="post" action="" enctype="multipart/form-data" class="auth-form">
<?= csrfField() ?>
<label>选择 .ico 文件
<input type="file" name="favicon_file" accept=".ico,image/vnd.microsoft.icon">
</label>
<div class="form-actions">
<button type="submit" name="savefavicon" class="btn btn-primary"><i class="fas fa-upload"></i> 上传并启用</button>
</div>
</form>
<?php if ($favCur): ?>
<form method="post" action="" class="auth-form" style="margin-top:8px;">
<?= csrfField() ?>
<button type="submit" name="delfavicon" class="btn btn-ghost" onclick="return confirm('确定恢复为默认图标?');"><i class="fas fa-undo"></i> 恢复默认</button>
</form>
<?php endif; ?>
</div>
<div class="panel" style="max-width:680px;">
<h3>积分系统设置</h3>
<p class="muted">开启后,用户可在前台「每日签到」获取积分、通过邀请链接邀请好友获得积分;商品可设置「积分价」,结算时选择「积分支付」。</p>
<form method="post" action="" class="grid-form">
<?= csrfField() ?>
<label class="checkbox"><input type="checkbox" name="points_enabled" <?= getSetting('points_enabled', '1')==='1' ? 'checked' : '' ?>> 启用积分系统</label>
<label>每日签到可得积分<input type="number" min="0" name="points_sign" value="<?= h(getSetting('points_sign', '5')) ?>" placeholder="如 5"></label>
<label>邀请好友成功可得积分<input type="number" min="0" name="points_invite" value="<?= h(getSetting('points_invite', '20')) ?>" placeholder="如 20"></label>
<div class="form-actions span2">
<button type="submit" name="savepoints" class="btn btn-primary"><i class="fas fa-save"></i> 保存积分设置</button>
</div>
</form>
</div>
<div class="panel" style="max-width:680px;">
<h3>SMTP 邮件配置</h3>
<p class="muted">用于:用户提交工单后通知处理人员、处理回复通知用户。常见配置:QQ 邮箱 <code>smtp.qq.com</code> / 端口 <code>465</code> / 加密 <code>SSL</code> / 密码填「授权码」。</p>
<form method="post" action="" class="grid-form">
<?= csrfField() ?>
<label>SMTP 主机<input type="text" name="smtp_host" value="<?= h($cfg['smtp_host']) ?>" placeholder="如 smtp.qq.com"></label>
<label>端口<input type="text" name="smtp_port" value="<?= h($cfg['smtp_port']) ?>" placeholder="如 465"></label>
<label>加密方式
<select name="smtp_enc">
<option value="" <?= $cfg['smtp_enc']==='' ?'selected':'' ?>>无</option>
<option value="ssl" <?= $cfg['smtp_enc']==='ssl' ?'selected':'' ?>>SSL465</option>
<option value="tls" <?= $cfg['smtp_enc']==='tls' ?'selected':'' ?>>STARTTLS587</option>
</select>
</label>
<label>发件人名称<input type="text" name="smtp_fromname" value="<?= h($cfg['smtp_fromname']) ?>" placeholder="如 自由云商城"></label>
<label class="span2">登录账号(邮箱)<input type="text" name="smtp_user" value="<?= h($cfg['smtp_user']) ?>" placeholder="SMTP 登录邮箱"></label>
<label class="span2">登录密码 / 授权码<input type="password" name="smtp_pass" value="<?= h($cfg['smtp_pass']) ?>" placeholder="QQ 邮箱填授权码"></label>
<label class="span2">发件人邮箱<input type="text" name="smtp_from" value="<?= h($cfg['smtp_from']) ?>" placeholder="留空则同登录账号"></label>
<label class="span2">通知收件人(处理人员,逗号分隔)<input type="text" name="notify_emails" value="<?= h($cfg['notify_emails']) ?>" placeholder="如 admin@example.com,ops@example.com"></label>
<div class="form-actions span2">
<button type="submit" name="savesmtp" class="btn btn-primary"><i class="fas fa-save"></i> 保存 SMTP 配置</button>
</div>
</form>
<hr style="margin:22px 0;border:none;border-top:1px solid var(--border);">
<h3>发送测试邮件</h3>
<form method="post" action="" class="grid-form" style="max-width:480px;">
<?= csrfField() ?>
<label class="span2">测试收件邮箱<input type="email" name="test_to" placeholder="输入一个能收信的邮箱" required></label>
<div class="form-actions span2">
<button type="submit" name="testmail" class="btn btn-ghost"><i class="fas fa-paper-plane"></i> 发送测试邮件</button>
</div>
</form>
<?php if ($testResult): ?>
<div class="smtp-log">
<div class="muted" style="margin-bottom:6px;">SMTP 会话日志:</div>
<pre><?= h(implode("\n", $testResult['log'])) ?></pre>
</div>
<?php endif; ?>
</div>
<div class="panel" style="max-width:780px;">
<h3><i class="fab fa-alipay"></i> 支付接口配置</h3>
<p class="muted">配置后用户下单可选择对应支付方式。未启用(或未填密钥)的渠道不会在前台显示。沙箱模式仅支付宝支持,仅用于联调,正式上线请关闭。</p>
<h4 style="margin:18px 0 10px;">支付宝(电脑网站支付 · RSA2</h4>
<form method="post" action="" class="grid-form">
<?= csrfField() ?>
<label class="checkbox"><input type="checkbox" name="pay_alipay_enabled" <?= getSetting('pay_alipay_enabled','0')==='1'?'checked':'' ?>> 启用支付宝支付</label>
<label class="checkbox"><input type="checkbox" name="pay_alipay_sandbox" <?= getSetting('pay_alipay_sandbox','0')==='1'?'checked':'' ?>> 沙箱模式(调试用)</label>
<label class="span2">应用 APPID<input type="text" name="pay_alipay_appid" value="<?= h(getSetting('pay_alipay_appid','')) ?>" placeholder="如 2021000000000000"></label>
<label class="span2">应用私钥(RSA2<textarea name="pay_alipay_private_key" rows="3" placeholder="-----BEGIN PRIVATE KEY----- ..."><?= h(getSetting('pay_alipay_private_key','')) ?></textarea></label>
<label class="span2">支付宝公钥<textarea name="pay_alipay_public_key" rows="3" placeholder="-----BEGIN PUBLIC KEY----- ..."><?= h(getSetting('pay_alipay_public_key','')) ?></textarea></label>
<div class="form-actions span2">
<button type="submit" name="savepay_ali" class="btn btn-primary"><i class="fas fa-save"></i> 保存支付宝配置</button>
</div>
</form>
<hr style="margin:24px 0;border:none;border-top:1px solid var(--border);">
<h4 style="margin:18px 0 10px;">微信支付(Native 扫码支付 · V3</h4>
<form method="post" action="" class="grid-form">
<?= csrfField() ?>
<label class="checkbox"><input type="checkbox" name="pay_wechat_enabled" <?= getSetting('pay_wechat_enabled','0')==='1'?'checked':'' ?>> 启用微信支付</label>
<label class="span2">商户号 MCH ID<input type="text" name="pay_wechat_mch_id" value="<?= h(getSetting('pay_wechat_mch_id','')) ?>" placeholder="如 1900000000"></label>
<label class="span2">API 密钥(Key / APIv3 Key<input type="text" name="pay_wechat_api_key" value="<?= h(getSetting('pay_wechat_api_key','')) ?>" placeholder="32 位密钥"></label>
<label class="span2">APPID(公众号 / 小程序,可选)<input type="text" name="pay_wechat_appid" value="<?= h(getSetting('pay_wechat_appid','')) ?>" placeholder="留空则用商户默认"></label>
<div class="form-actions span2">
<button type="submit" name="savepay_wx" class="btn btn-primary"><i class="fas fa-save"></i> 保存微信支付配置</button>
</div>
</form>
</div>
<?php adminFooter(); ?>