prepare('SELECT * FROM threads WHERE id = ?'); $st->execute([$id]); $thread = $st->fetch(); /* 这条帖子所属的软件 —— 「返回问题列表」要回到它的列表, * 而不是回到首页(首页现在是介绍页)。 */ $backUrl = fb_software_list_url( $thread && !empty($thread['software_id']) ? fb_software((int)$thread['software_id']) : null ); if (!$thread) { http_response_code(404); fb_layout_head('问题不存在'); echo '

该问题不存在或已被删除。

返回问题列表

'; fb_layout_foot(); exit; } /* 审核(先审后显)+ 提交人追踪: * * 可见性三档 —— * ① 已公开:所有人可看 * ② 未公开 + 带对 token:提交人本人可看(这是他的私人进度页) * ③ 未公开 + 没 token:对外一律装作「不存在」,不泄露标题也不泄露状态 * * 🔴 关键:带对 token 时**绝不能**再走「提交成功,等待审核」那个提示页。 * 用户之前就卡在这里 —— 邮件里给了链接让他看进度,点进来却只看到 * 「等待审核」,等于链接白给。带 token 进来就应该看到完整详情。 */ $hasToken = isset($_GET['t']) ? trim((string)$_GET['t']) : ''; $isOwnerView = $hasToken !== '' && fb_track_token_ok($thread, $hasToken); if (!fb_thread_viewable($thread)) { $justSubmitted = (isset($_GET['submitted']) && (string)$_GET['submitted'] === '1'); if ($justSubmitted) { fb_layout_head('提交成功', ['robots' => 'noindex,nofollow']); echo '
' . '

提交成功,等待审核

' . '

您的反馈已收到(编号 #' . (int)$thread['id'] . '),我们会先确认它是否为应用问题:

' . '

· 确认为问题 —— 审核通过后会公开显示,其他人也能查看与回复;
' . '· 确认不是问题 —— 我们会关闭它,并把结论与说明发送到您留下的邮箱。

' . '

审核通常在 1~2 个工作日内完成。' . '我们会发一封邮件给您,里面的链接可以随时查看这条反馈的处理进度。

' . '

返回问题列表

' . '
'; fb_layout_foot(); exit; } http_response_code(404); fb_layout_head('问题不存在'); echo '

该问题不存在或已被删除。

返回问题列表

'; fb_layout_foot(); exit; } $err = ''; $atts = []; $form = ['name' => '', 'email' => '', 'body' => '', 'captcha' => '']; /* ---------------- 处理回复提交 ---------------- */ if ($_SERVER['REQUEST_METHOD'] === 'POST') { /* 未公开的帖子,只有带对 token 的提交人(或管理员)能回复。 * 别人连看都看不到,自然也不该能回。 */ if (!fb_thread_public($thread) && !$isOwnerView) { http_response_code(403); fb_layout_head('暂不可回复'); echo '

该问题还在审核中,暂时不能回复。

返回问题列表

'; 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'] : ''); /* 🔴 带 token(= 提交人本人)时,身份以库里那条帖子为准,**不采信 POST**。 * 前端已经把这两个字段设成 hidden 了,但 hidden 是可以被改的: * 谁拿到链接、改个 email 提交,这条反馈的联系人就变成他的了。 * 身份校验必须在服务端做,不能只靠前端不给输入框。 */ if ($isOwnerView) { $form['name'] = (string)$thread['author_name']; $form['email'] = (string)$thread['author_email']; } $capInput = isset($_POST['captcha']) ? (string)$_POST['captcha'] : ''; $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'] : ''); /* 管理员身份只认「后台登录态」,不再从回复表单收口令。 * 用户 2026-11-03:「管理员口令、处理状态都不要填了, * 用户进来之后就没有这些东西,只保留回复内容 + 人机验证」。 * 状态流转在后台详情页那排按钮里做,不该混在公开的回复框里。 */ $isAdmin = fb_is_admin(); 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、活跃时间。 * 回复框不再承担改状态的职责 —— 状态流转在后台详情页那排按钮里做。 */ $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); /* 回复后跳回去时要带上 token,否则提交人对未公开反馈的访问权会当场丢掉 */ fb_redirect('thread.php?id=' . $id . ($isOwnerView ? '&t=' . rawurlencode($hasToken) : '') . '#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); $thSwForColor = !empty($thread['software_id']) ? fb_software((int)$thread['software_id']) : null; fb_layout_head(fb_excerpt($thread['title'], 50), ['software' => $thSwForColor]); ?>
← 返回问题列表
这是您的私人跟踪链接 这条反馈还没通过审核,其他人看不到。请保留本页链接(或邮件里的链接), 审核结果与后续进展都能在这里查看。

#
· · 条回复

全部回复()

还没有回复,欢迎在下方参与讨论。

官方答复 ·
true]); } ?>

我要回复

回复身份
这是您提交时用的身份。回复会以它显示,通知也会发到这个邮箱。