Files
FeedLite/thread.php
T

262 lines
11 KiB
PHP
Raw Normal View History

<?php
/**
* 问题详情 + 回复
*/
require __DIR__ . '/lib/bootstrap.php';
$pdo = fb_db();
$id = isset($_GET['id']) ? (int)$_GET['id'] : (isset($_POST['id']) ? (int)$_POST['id'] : 0);
if ($id <= 0) { fb_redirect('index.php'); }
$st = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
$st->execute([$id]);
$thread = $st->fetch();
if (!$thread) {
http_response_code(404);
fb_layout_head('问题不存在');
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
fb_layout_foot();
exit;
}
// 审核(先审后显):未通过审核的问题对访客不可见
if (!fb_thread_public($thread)) {
$justSubmitted = (isset($_GET['submitted']) && (string)$_GET['submitted'] === '1');
if ($justSubmitted) {
fb_layout_head('提交成功', ['robots' => 'noindex,nofollow']);
echo '<div class="empty">'
. '<h2 style="margin:0 0 10px">提交成功,等待审核</h2>'
. '<p>你的反馈已收到(编号 #' . (int)$thread['id'] . '),我们会先确认它是否为应用问题:</p>'
. '<p>· 确认为问题 —— 审核通过后会公开显示,其他人也能查看与回复;<br>'
. '· 确认不是问题 —— 我们会关闭它,并把结论与说明发送到你留下的邮箱。</p>'
. '<p class="muted">审核通常在 1~2 个工作日内完成,请留意邮箱。</p>'
. '<p><a class="btn primary" href="index.php">返回问题列表</a></p>'
. '</div>';
fb_layout_foot();
exit;
}
http_response_code(404);
fb_layout_head('问题不存在');
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
fb_layout_foot();
exit;
}
$statuses = fb_statuses();
$err = '';
$atts = [];
$form = ['name' => '', 'email' => '', 'body' => '', 'captcha' => ''];
/* ---------------- 处理回复提交 ---------------- */
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// 审核中(未公开)的问题不允许回复
if (!fb_thread_public($thread)) {
http_response_code(403);
fb_layout_head('暂不可回复');
echo '<div class="empty"><p>该问题还在审核中,暂时不能回复。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
fb_layout_foot();
exit;
}
$form['name'] = trim(isset($_POST['name']) ? (string)$_POST['name'] : '');
$form['email'] = trim(isset($_POST['email']) ? (string)$_POST['email'] : '');
$form['body'] = trim(isset($_POST['body']) ? (string)$_POST['body'] : '');
$capInput = isset($_POST['captcha']) ? (string)$_POST['captcha'] : '';
$adminPw = isset($_POST['admin_password']) ? (string)$_POST['admin_password'] : '';
$hp = trim(isset($_POST['website']) ? (string)$_POST['website'] : '');
$t0 = isset($_POST['t0']) ? (int)$_POST['t0'] : 0;
list($attOk, $attList, $attErr) = fb_attach_parse(isset($_POST['attachments']) ? $_POST['attachments'] : '');
$isAdmin = ($adminPw !== '' && fb_admin_verify_password($adminPw));
if (!fb_csrf_ok(isset($_POST['csrf']) ? $_POST['csrf'] : '')) {
$err = '会话已过期,请重新提交。';
} elseif ($hp !== '') {
$err = '提交未通过校验,请重试。';
} elseif ($t0 > 0 && (time() - $t0) < max(0, (int)fb_cfg('post_min_seconds', 3))) {
$err = '提交过于频繁,请稍候再试。';
} elseif (!fb_valid_name($form['name']) && !$isAdmin) {
$err = '请填写昵称(1~24 个字符)。';
} elseif (!$isAdmin && !filter_var($form['email'], FILTER_VALIDATE_EMAIL)) {
$err = '请填写有效的邮箱地址(用于接收回复通知)。';
} elseif (!fb_valid_body($form['body'])) {
$err = '回复内容请填写 2~5000 个字符。';
} elseif (!$attOk) {
$err = $attErr;
} elseif (!fb_rate_check('reply', (int)fb_cfg('rate_limit.reply_per_hour', 20))) {
$err = '回复过于频繁,请稍后再试。';
} else {
list($vOk, $vList, $vErr) = fb_attach_verify($attList);
if (!$vOk) {
$err = $vErr;
$atts = $attList;
} elseif (!fb_attach_cap_ok($pdo, $vList)) {
$err = '附件存储空间已满,请减少附件后重试。';
$atts = $vList;
} elseif (!fb_captcha_check($capInput)) {
$err = '人机验证未通过,请重新作答。';
$atts = $vList;
} else {
$now = time();
$replyName = $isAdmin ? '官方' : $form['name'];
$replyMail = $isAdmin ? null : $form['email'];
$ins = $pdo->prepare('INSERT INTO replies(thread_id, body, author_name, author_email, is_admin, created_at, ip_hash) VALUES(?,?,?,?,?,?,?)');
$ins->execute([$id, $form['body'], $replyName, $replyMail, $isAdmin ? 1 : 0, $now, fb_ip_hash()]);
$replyId = (int)$pdo->lastInsertId();
fb_attach_save($pdo, $id, $replyId, $vList);
// 更新主题:回复数 +1、活跃时间;管理员可同时调整状态
$newStatus = null;
if ($isAdmin && isset($_POST['status']) && isset($statuses[(string)$_POST['status']])) {
$newStatus = (string)$_POST['status'];
}
if ($newStatus !== null) {
$upd = $pdo->prepare('UPDATE threads SET reply_count = reply_count + 1, updated_at = ?, status = ? WHERE id = ?');
$upd->execute([$now, $newStatus, $id]);
} else {
$upd = $pdo->prepare('UPDATE threads SET reply_count = reply_count + 1, updated_at = ? WHERE id = ?');
$upd->execute([$now, $id]);
}
fb_rate_hit('reply');
// 发送通知邮件给发帖人
$replyRow = [
'id' => $replyId,
'body' => $form['body'],
'author_name' => $replyName,
'author_email' => $replyMail,
'is_admin' => $isAdmin ? 1 : 0,
];
fb_notify_reply($pdo, $thread, $replyRow, $isAdmin);
fb_redirect('thread.php?id=' . $id . '#r' . $replyId);
}
}
}
/* ---------------- 读取数据 ---------------- */
$st = $pdo->prepare('SELECT id, body, author_name, is_admin, created_at FROM replies WHERE thread_id = ? ORDER BY created_at ASC, id ASC');
$st->execute([$id]);
$replies = $st->fetchAll();
// 重新读取主题(可能在 POST 中更新过状态)
$st = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
$st->execute([$id]);
$thread = $st->fetch();
// 附件(主题级 + 各回复)
$attMap = fb_attach_for_thread($pdo, $id);
fb_layout_head(fb_excerpt($thread['title'], 50));
?>
<div class="crumbs"><a href="index.php">← 返回问题列表</a></div>
<article class="subject">
<div class="item-top">
<span class="badge cat"><?php echo e(fb_category_label($thread['category'])); ?></span>
<span class="badge st st-<?php echo e($thread['status']); ?>"><?php echo e(fb_status_label($thread['status'])); ?></span>
<h1 class="subject-title"><?php echo e($thread['title']); ?></h1>
<span class="item-id" title="问题编号">#<?php echo (int)$id; ?></span>
</div>
<div class="item-meta">
<span class="who"><?php echo e($thread['author_name']); ?></span>
<span class="dot">·</span>
<span><?php echo e(fb_fmt_time($thread['created_at'])); ?></span>
<span class="dot">·</span>
<span><?php echo (int)$thread['reply_count']; ?> 条回复</span>
</div>
<div class="subject-body"><?php echo nl2br(e($thread['body'])); ?></div>
<?php echo fb_attach_render($attMap['main']); ?>
</article>
<section class="replies">
<h2>全部回复(<?php echo count($replies); ?>)</h2>
<?php if (!$replies): ?>
<div class="empty small"><p>还没有回复,欢迎在下方参与讨论。</p></div>
<?php else: ?>
<?php foreach ($replies as $r): ?>
<div class="reply <?php echo $r['is_admin'] ? 'admin' : ''; ?>" id="r<?php echo (int)$r['id']; ?>">
<div class="reply-head">
<span class="who"><?php echo e($r['is_admin'] ? '官方' : $r['author_name']); ?></span>
<?php if ($r['is_admin']): ?><span class="badge official">官方答复</span><?php endif; ?>
<span class="dot">·</span>
<span class="time"><?php echo e(fb_fmt_time($r['created_at'])); ?></span>
</div>
<div class="reply-body"><?php echo nl2br(e($r['body'])); ?></div>
<?php
$rid = (int)$r['id'];
if (!empty($attMap['replies'][$rid])) {
echo fb_attach_render($attMap['replies'][$rid], ['compact' => true]);
}
?>
</div>
<?php endforeach; ?>
<?php endif; ?>
</section>
<section class="reply-form">
<h2>我要回复</h2>
<?php fb_flash_box($err, 'error'); ?>
<form method="post" action="thread.php?id=<?php echo (int)$id; ?>">
<input type="hidden" name="id" value="<?php echo (int)$id; ?>">
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
<input type="hidden" name="t0" value="<?php echo time(); ?>">
<div class="hp-field" aria-hidden="true">
<label>请勿填写此栏<input type="text" name="website" tabindex="-1" autocomplete="off"></label>
</div>
<div class="row2">
<label class="field">
<span>昵称 <b class="req">*</b></span>
<input type="text" name="name" maxlength="24" value="<?php echo e($form['name']); ?>" placeholder="怎么称呼你" required>
</label>
<label class="field" id="emailField">
<span>邮箱 <b class="req">*</b><i class="hint">仅用于接收回复通知,不会公开</i></span>
<input type="email" name="email" maxlength="120" value="<?php echo e($form['email']); ?>" placeholder="you@example.com" required>
</label>
</div>
<label class="field">
<span>回复内容 <b class="req">*</b></span>
<textarea name="body" rows="6" maxlength="5000" placeholder="说说你的看法或补充…" required><?php echo e($form['body']); ?></textarea>
</label>
<?php fb_uploaders($atts); ?>
<div class="captcha">
<label class="field">
<span>人机验证 <b class="req">*</b> <i class="hint">防机器人,随手答一下</i></span>
<div class="captcha-line">
<span class="c-q"><?php echo e(fb_captcha_question()); ?></span>
<input type="text" name="captcha" value="" placeholder="答案" autocomplete="off" required>
<a class="reload" href="captcha.php" data-captcha-refresh>换一题</a>
</div>
</label>
</div>
<label class="field admin-pw">
<span>管理员口令 <i class="hint">选填;填写后将以「官方」身份回复</i></span>
<input type="password" name="admin_password" value="" autocomplete="off" placeholder="管理员专用">
</label>
<div class="field admin-status" hidden>
<span>处理状态 <i class="hint">仅管理员可见</i></span>
<select name="status">
<?php foreach ($statuses as $k => $v): ?>
<option value="<?php echo e($k); ?>" <?php echo $thread['status'] === $k ? 'selected' : ''; ?>><?php echo e($v); ?></option>
<?php endforeach; ?>
</select>
</div>
<div class="form-actions">
<button class="btn primary" type="submit">提交回复</button>
</div>
</form>
</section>
<?php
fb_layout_foot();