首页改介绍页;列表只服务当前软件;新增UUID追踪让提交人看得到未审核反馈
用户提的两个核心问题:
一、首页不该列全部问题
场景:这套系统是给某个软件配反馈入口、把地址贴进那软件的「意见反馈」里。
访客总是从某个软件过来的,看别的软件的问题对他没意义。
⇒ / 不带 sw 时渲染介绍页(特点介绍 + 「点右上角设置按钮选软件」+ 软件卡片)
带 sw 才是该软件的问题列表,检索/筛选/分类全在本软件范围内
二、提交后未审核,用户点不进去(真实缺陷)
邮件里给了「点击查看您刚才提交的反馈」,但 thread.php 只在 visible=1 时
才给看,未审核的一律 404 / 只看到「等待审核」静态页 —— 链接等于白给。
⇒ threads 加 track_token(32位hex = 128位熵),提交时生成:
- 邮件里所有链接一律带 ?t=<token>(不再是裸 id)
- 带对 token:可看完整详情、可回复,哪怕未公开/已关闭
- 没 token 或 token 错:对外一律 404,不泄露存在性
- 公开列表仍用 id 链接(公开内容谁都能看,用于避免重复提交)
实现细节:
- threads.track_token + 唯一索引;幂等迁移,存量数据自动回填
- fb_track_new_token / fb_track_token_ok(hash_equals 防时序探测)
/ fb_thread_viewable / fb_thread_link(thread, personal)
- thread.php 权限从 fb_thread_public 改为 fb_thread_viewable
- 带 token 看自己未公开的反馈时,顶部给「私人跟踪链接」提示条
- 回复表单与回复后跳转都保留 token,否则回复完就失去访问权
- 提交完成后直接带 token 跳详情页,不再只给静态等待页
- close 类邮件补上「查看这条反馈」按钮(原来完全没链接)
- 顶栏「全部问题」改为「问题列表」且只在选中软件后显示
- 详情/提交页「返回问题列表」按所属软件返回,不再回介绍页
- new.php 无 sw 时不再默认落到第一个软件,改为让用户先选
(默默替选会让反馈进错软件、通知发错人)
验证:首页 0 条问题卡片且未泄露任何标题、三个软件入口齐全;
未公开#4 无 token 404 / 错 token 404 / 正确 token 200 且显示完整详情
(不再卡在「等待审核」)、提示条与表单 token 都在;
已公开#2 无 token 可看;5 类邮件(回执/通过/回复/状态/关闭)链接全带 token;
两套回归各 30+ 项断言全过。
This commit is contained in:
@@ -150,6 +150,75 @@ main.wrap{padding-top:24px; padding-bottom:40px; min-height:60vh}
|
|||||||
/* ---------- 页头 ---------- */
|
/* ---------- 页头 ---------- */
|
||||||
.page-head{display:flex; align-items:flex-start; gap:16px; margin:6px 0 18px}
|
.page-head{display:flex; align-items:flex-start; gap:16px; margin:6px 0 18px}
|
||||||
.page-head .btn{margin-left:auto; flex:none; margin-top:4px}
|
.page-head .btn{margin-left:auto; flex:none; margin-top:4px}
|
||||||
|
|
||||||
|
/* ---------- 首页:轻反馈介绍页 ----------
|
||||||
|
* 用户 2026-11-03:首页不列问题,只做介绍 + 指路。
|
||||||
|
* 因为访客总是从某个软件过来的,他要的是「怎么进自己那个软件的反馈」,
|
||||||
|
* 而不是看别人的问题列表。 */
|
||||||
|
.home{max-width:820px; margin:0 auto; padding:8px 0 40px}
|
||||||
|
.home-hero{text-align:center; padding:26px 0 30px}
|
||||||
|
.home-hero h1{
|
||||||
|
font-size:30px; line-height:1.3; margin:0 0 12px;
|
||||||
|
letter-spacing:-.4px;
|
||||||
|
}
|
||||||
|
.home-lead{
|
||||||
|
font-size:15.5px; line-height:1.75; color:var(--muted);
|
||||||
|
margin:0 auto; max-width:600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.home-feats{
|
||||||
|
display:grid; grid-template-columns:repeat(2, minmax(0,1fr));
|
||||||
|
gap:12px; margin:0 0 34px;
|
||||||
|
}
|
||||||
|
@media (max-width:600px){ .home-feats{grid-template-columns:1fr} }
|
||||||
|
.home-feat{
|
||||||
|
background:var(--card); border:1px solid var(--line);
|
||||||
|
border-radius:12px; padding:16px 18px;
|
||||||
|
}
|
||||||
|
.home-feat-ic{font-size:20px; line-height:1; margin-bottom:8px}
|
||||||
|
.home-feat h3{margin:0 0 6px; font-size:15px; font-weight:700}
|
||||||
|
.home-feat p{margin:0; font-size:13.5px; line-height:1.7; color:var(--muted)}
|
||||||
|
|
||||||
|
.home-enter{
|
||||||
|
background:var(--card); border:1px solid var(--line);
|
||||||
|
border-radius:14px; padding:22px 24px;
|
||||||
|
}
|
||||||
|
.home-enter h2{margin:0 0 10px; font-size:17px}
|
||||||
|
.home-enter-tip{
|
||||||
|
font-size:14px; color:var(--muted); line-height:1.8; margin:0 0 16px;
|
||||||
|
display:flex; align-items:center; flex-wrap:wrap; gap:6px;
|
||||||
|
}
|
||||||
|
.home-enter-tip b{color:var(--fg); font-weight:600}
|
||||||
|
/* 小号三横线示意,让「右上角那个设置按钮」有具体形象可对照 */
|
||||||
|
.home-menu-demo{
|
||||||
|
display:inline-flex; flex-direction:column; justify-content:center; gap:2.5px;
|
||||||
|
width:22px; height:22px; padding:4px; box-sizing:border-box;
|
||||||
|
border:1px solid var(--line-strong); border-radius:6px;
|
||||||
|
background:var(--bg, #f6f8fb);
|
||||||
|
}
|
||||||
|
.home-menu-demo i{display:block; width:100%; height:1.5px; background:var(--fg); border-radius:2px}
|
||||||
|
|
||||||
|
.home-sw-list{display:flex; flex-direction:column; gap:8px}
|
||||||
|
.home-sw{
|
||||||
|
display:flex; align-items:center; gap:12px;
|
||||||
|
padding:11px 14px; border-radius:11px;
|
||||||
|
border:1px solid var(--line); background:var(--card);
|
||||||
|
color:var(--fg);
|
||||||
|
}
|
||||||
|
.home-sw:hover{
|
||||||
|
text-decoration:none; color:var(--fg);
|
||||||
|
border-color:var(--swc); background:var(--bg, #f6f8fb);
|
||||||
|
}
|
||||||
|
.home-sw-ic{
|
||||||
|
width:36px; height:36px; flex:none; border-radius:9px;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
font-size:22px; line-height:1;
|
||||||
|
}
|
||||||
|
.home-sw-ic img{width:36px; height:36px; object-fit:contain; display:block}
|
||||||
|
.home-sw-nm{font-size:15px; font-weight:600; color:var(--swc)}
|
||||||
|
.home-sw-go{margin-left:auto; font-size:13px; color:var(--muted); flex:none}
|
||||||
|
.home-sw:hover .home-sw-go{color:var(--swc)}
|
||||||
|
.home-enter-alt{margin:12px 0 0}
|
||||||
.page-head h1{margin-bottom:4px}
|
.page-head h1{margin-bottom:4px}
|
||||||
.page-head .muted{font-size:14px; margin:0}
|
.page-head .muted{font-size:14px; margin:0}
|
||||||
|
|
||||||
@@ -386,6 +455,17 @@ main.wrap{padding-top:24px; padding-bottom:40px; min-height:60vh}
|
|||||||
.item-meta{display:flex; flex-wrap:wrap; align-items:center; gap:6px; font-size:13px; color:var(--muted)}
|
.item-meta{display:flex; flex-wrap:wrap; align-items:center; gap:6px; font-size:13px; color:var(--muted)}
|
||||||
.item-meta .who{color:#3a424d; font-weight:600}
|
.item-meta .who{color:#3a424d; font-weight:600}
|
||||||
|
|
||||||
|
/* 提交人凭 token 看自己未公开反馈时的提示条 */
|
||||||
|
.owner-note{
|
||||||
|
display:flex; flex-direction:column; gap:4px;
|
||||||
|
margin:0 0 16px; padding:12px 14px;
|
||||||
|
background:var(--warn-soft, #fff8e6);
|
||||||
|
border:1px solid #f0d9a8; border-left:3px solid #d99b1f;
|
||||||
|
border-radius:10px; font-size:13.5px; line-height:1.65;
|
||||||
|
color:#6b5313;
|
||||||
|
}
|
||||||
|
.owner-note b{font-size:14px; color:#5a4610}
|
||||||
|
|
||||||
/* ---------- 后台「待审核」列表 ----------
|
/* ---------- 后台「待审核」列表 ----------
|
||||||
* 列表式:一屏能看多条,右侧固定一个「通过并公开」。
|
* 列表式:一屏能看多条,右侧固定一个「通过并公开」。
|
||||||
* 详情/关闭说明留在管理页里填,列表页不塞表单。 */
|
* 详情/关闭说明留在管理页里填,列表页不塞表单。 */
|
||||||
|
|||||||
@@ -1,22 +1,44 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/**
|
||||||
* 问题列表
|
* 首页 / 当前软件的问题列表
|
||||||
|
*
|
||||||
|
* 两种形态(用户 2026-11-03 定的产品逻辑):
|
||||||
|
* ① **不带 sw**:首页 = 轻反馈系统的介绍页。
|
||||||
|
* 不再列「全部问题」—— 多个软件的反馈混在一起,对访客毫无意义:
|
||||||
|
* 他不是所有软件的使用者,看别人的反馈没价值。
|
||||||
|
* ② **带 sw**:当前软件的问题列表。
|
||||||
|
* 检索/筛选/分类全部只在本软件范围内,弱化其他软件。
|
||||||
*/
|
*/
|
||||||
require __DIR__ . '/lib/bootstrap.php';
|
require __DIR__ . '/lib/bootstrap.php';
|
||||||
|
|
||||||
$pdo = fb_db();
|
$pdo = fb_db();
|
||||||
|
|
||||||
/* ---- 软件切换:只配一个软件时自动落到它,不暴露软件选择器 ---- */
|
/* ---- 软件切换 ---- */
|
||||||
$swList = fb_softwares(true);
|
$swList = fb_softwares(true);
|
||||||
$swKey = isset($_GET['sw']) ? (string)$_GET['sw'] : '';
|
$swKey = isset($_GET['sw']) ? (string)$_GET['sw'] : '';
|
||||||
$sw = null;
|
$sw = null;
|
||||||
if ($swKey !== '') {
|
if ($swKey !== '') {
|
||||||
$sw = fb_software($swKey);
|
$sw = fb_software($swKey);
|
||||||
// 指定的软件不存在或已停用 → 退回默认,不报错(老链接要能打开)
|
// 指定的软件不存在或已停用 → 退回首页(不自动落到第一个软件)
|
||||||
if (!$sw || (int)$sw['enabled'] !== 1) { $sw = null; }
|
if (!$sw || (int)$sw['enabled'] !== 1) { $sw = null; }
|
||||||
}
|
}
|
||||||
if ($sw === null && $swList) { $sw = $swList[0]; }
|
|
||||||
$swId = $sw ? (int)$sw['id'] : 0;
|
/* ==================================================================
|
||||||
|
* ① 没有指定软件 → 首页介绍页
|
||||||
|
* ------------------------------------------------------------------
|
||||||
|
* 用户原话:「首页应该是对我们这个轻量级反馈系统的介绍,
|
||||||
|
* 说明它的特点是什么,然后告诉用户,如果现在想提交反馈,
|
||||||
|
* 可以点右上角的设置按钮,选择不同的系统,进去就可以了。」
|
||||||
|
*
|
||||||
|
* 🔴 不要在这里列问题。以前首页默认落到第一个软件并列出全部问题,
|
||||||
|
* 等于把 A 软件的反馈摊给 B 软件的用户看。
|
||||||
|
* ================================================================== */
|
||||||
|
if ($sw === null) {
|
||||||
|
fb_render_home($swList);
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
$swId = (int)$sw['id'];
|
||||||
|
|
||||||
$cats = fb_categories($swId);
|
$cats = fb_categories($swId);
|
||||||
$statuses = fb_status_keys();
|
$statuses = fb_status_keys();
|
||||||
@@ -194,3 +216,94 @@ fb_layout_head($sw ? fb_software_site_title($sw) : $headTitle, ['software' => $s
|
|||||||
|
|
||||||
<?php
|
<?php
|
||||||
fb_layout_foot();
|
fb_layout_foot();
|
||||||
|
|
||||||
|
/* ==================================================================
|
||||||
|
* 首页介绍页
|
||||||
|
* ------------------------------------------------------------------
|
||||||
|
* 用户 2026-11-03 定的产品逻辑:首页不讲"有哪些问题",只讲"这是什么"。
|
||||||
|
*
|
||||||
|
* 场景说明(决定了下面文案的取舍):
|
||||||
|
* 这个系统一般是给某个软件配一个反馈入口,把地址贴进那个软件的
|
||||||
|
* 「意见反馈」里。所以访客**总是从某个软件过来的**,他心里已经有一个
|
||||||
|
* 明确的目标软件;首页要做的是「介绍 + 指路」,不是「陈列别人的问题」。
|
||||||
|
* ================================================================== */
|
||||||
|
function fb_render_home(array $swList) {
|
||||||
|
$siteName = (string)fb_cfg('site_name');
|
||||||
|
$title = $siteName !== '' ? $siteName : '轻反馈';
|
||||||
|
|
||||||
|
fb_layout_head($title . ' · 轻量级反馈系统');
|
||||||
|
|
||||||
|
$n = count($swList);
|
||||||
|
?>
|
||||||
|
<div class="home">
|
||||||
|
<section class="home-hero">
|
||||||
|
<h1>轻量级反馈系统</h1>
|
||||||
|
<p class="home-lead">给每个软件一个独立的反馈入口:用户不用注册,填个昵称和邮箱就能提问题;
|
||||||
|
所有进展都会自动发邮件通知他。</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="home-feats">
|
||||||
|
<div class="home-feat">
|
||||||
|
<div class="home-feat-ic">📮</div>
|
||||||
|
<h3>不用注册</h3>
|
||||||
|
<p>填昵称和常用邮箱即可提交。后续每一条进展都会发邮件通知,不用反复回来刷。</p>
|
||||||
|
</div>
|
||||||
|
<div class="home-feat">
|
||||||
|
<div class="home-feat-ic">🔗</div>
|
||||||
|
<h3>私人追踪链接</h3>
|
||||||
|
<p>提交后会收到一条专属链接,凭它随时查看进度——哪怕这条反馈还没审核通过。</p>
|
||||||
|
</div>
|
||||||
|
<div class="home-feat">
|
||||||
|
<div class="home-feat-ic">🗂</div>
|
||||||
|
<h3>按软件分开</h3>
|
||||||
|
<p>每个软件一套独立的问题列表和分类,互不干扰。同一软件的其他人也能看到已解决的问题。</p>
|
||||||
|
</div>
|
||||||
|
<div class="home-feat">
|
||||||
|
<div class="home-feat-ic">💬</div>
|
||||||
|
<h3>公开讨论</h3>
|
||||||
|
<p>已公开的问题任何人都能查看和回复。提交前先搜一下,避免重复提同样的问题。</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section class="home-enter">
|
||||||
|
<h2>进入某个软件的反馈系统</h2>
|
||||||
|
<?php if ($n === 0): ?>
|
||||||
|
<p class="muted">管理员还没有配置软件。</p>
|
||||||
|
<?php else: ?>
|
||||||
|
<p class="home-enter-tip">
|
||||||
|
点击右上角的<span class="home-menu-demo" aria-hidden="true"><i></i><i></i><i></i></span>
|
||||||
|
<b>设置按钮</b>,从列表里选择您要反馈的软件即可。
|
||||||
|
</p>
|
||||||
|
<div class="home-sw-list">
|
||||||
|
<?php foreach ($swList as $s): ?>
|
||||||
|
<?php
|
||||||
|
list($c) = fb_software_colors($s);
|
||||||
|
if ($c === '') { $c = '#0057B8'; }
|
||||||
|
?>
|
||||||
|
<a class="home-sw" href="?sw=<?php echo e(urlencode((string)$s['slug'])); ?>"
|
||||||
|
style="--swc:<?php echo e($c); ?>">
|
||||||
|
<span class="home-sw-ic">
|
||||||
|
<?php
|
||||||
|
$ik = trim((string)(isset($s['icon_key']) ? $s['icon_key'] : ''));
|
||||||
|
$iu = $ik !== '' ? fb_icon_url($ik, 72) : '';
|
||||||
|
?>
|
||||||
|
<?php if ($iu !== ''): ?>
|
||||||
|
<img src="<?php echo e($iu); ?>" alt="" width="36" height="36">
|
||||||
|
<?php else: ?>
|
||||||
|
<span><?php echo e(fb_software_icon($s)); ?></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</span>
|
||||||
|
<span class="home-sw-nm"><?php echo e((string)$s['name']); ?></span>
|
||||||
|
<span class="home-sw-go">进入 →</span>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<p class="home-enter-alt muted small">
|
||||||
|
也可以直接点上方的软件卡片进入。
|
||||||
|
</p>
|
||||||
|
<?php endif; ?>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
fb_layout_foot();
|
||||||
|
}
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ if (!defined('FB_ROOT')) {
|
|||||||
define('FB_ROOT', dirname(__DIR__));
|
define('FB_ROOT', dirname(__DIR__));
|
||||||
// 静态资源版本号:改 assets/ 下的 css/js 后把这个数字 +1,
|
// 静态资源版本号:改 assets/ 下的 css/js 后把这个数字 +1,
|
||||||
// 让浏览器与 CDN 缓存失效。全站只此一处,别再往页面里写死数字。
|
// 让浏览器与 CDN 缓存失效。全站只此一处,别再往页面里写死数字。
|
||||||
if (!defined('FB_ASSET_V')) { define('FB_ASSET_V', 28); }
|
if (!defined('FB_ASSET_V')) { define('FB_ASSET_V', 30); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ------------------------------------------------------------------
|
/* ------------------------------------------------------------------
|
||||||
|
|||||||
+24
-2
@@ -94,17 +94,39 @@ CREATE TABLE IF NOT EXISTS threads (
|
|||||||
author_name TEXT NOT NULL,
|
author_name TEXT NOT NULL,
|
||||||
author_email TEXT NOT NULL,
|
author_email TEXT NOT NULL,
|
||||||
author_phone TEXT,
|
author_phone TEXT,
|
||||||
status TEXT NOT NULL DEFAULT 'open',
|
status TEXT NOT NULL DEFAULT 'hidden',
|
||||||
reply_count INTEGER NOT NULL DEFAULT 0,
|
reply_count INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at INTEGER NOT NULL,
|
created_at INTEGER NOT NULL,
|
||||||
updated_at INTEGER NOT NULL,
|
updated_at INTEGER NOT NULL,
|
||||||
ip_hash TEXT
|
ip_hash TEXT,
|
||||||
|
track_token TEXT NOT NULL DEFAULT ''
|
||||||
);
|
);
|
||||||
SQL
|
SQL
|
||||||
);
|
);
|
||||||
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_threads_updated ON threads(updated_at DESC)');
|
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_threads_updated ON threads(updated_at DESC)');
|
||||||
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_threads_cat ON threads(category)');
|
$pdo->exec('CREATE INDEX IF NOT EXISTS idx_threads_cat ON threads(category)');
|
||||||
|
|
||||||
|
/* ---- threads.track_token:提交人的「私人追踪钥匙」 ----
|
||||||
|
* 因为本站不做用户登录,没办法认人;但每个人都必须能跟踪自己提交的反馈,
|
||||||
|
* 哪怕是还没审核通过的。做法:提交时生成一串随机 token 只发到他的邮箱,
|
||||||
|
* 链接带 ?t=<token> 就能看 / 能回复,别人不知道这串东西就看不到。
|
||||||
|
*
|
||||||
|
* 🔴 不要用 id 代替:id 是自增的,任何人改个数字就能翻别人的私有反馈。
|
||||||
|
* token 必须是不可猜的随机值(这里用 32 位 hex = 128 位熵)。 */
|
||||||
|
$thCols = $pdo->query('PRAGMA table_info(threads)')->fetchAll();
|
||||||
|
$thNames = [];
|
||||||
|
foreach ($thCols as $c4) { if (isset($c4['name'])) { $thNames[(string)$c4['name']] = true; } }
|
||||||
|
if (!isset($thNames['track_token'])) {
|
||||||
|
$pdo->exec("ALTER TABLE threads ADD COLUMN track_token TEXT NOT NULL DEFAULT ''");
|
||||||
|
}
|
||||||
|
// 存量回填:给还没有 token 的帖子补一个,保证老数据的邮件链接也能用
|
||||||
|
$need = $pdo->query("SELECT id FROM threads WHERE track_token IS NULL OR track_token = ''")->fetchAll();
|
||||||
|
if ($need) {
|
||||||
|
$upd = $pdo->prepare('UPDATE threads SET track_token = ? WHERE id = ?');
|
||||||
|
foreach ($need as $r) { $upd->execute([bin2hex(random_bytes(16)), (int)$r['id']]); }
|
||||||
|
}
|
||||||
|
$pdo->exec('CREATE UNIQUE INDEX IF NOT EXISTS idx_threads_token ON threads(track_token)');
|
||||||
|
|
||||||
$pdo->exec(<<<'SQL'
|
$pdo->exec(<<<'SQL'
|
||||||
CREATE TABLE IF NOT EXISTS replies (
|
CREATE TABLE IF NOT EXISTS replies (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
|||||||
+8
-2
@@ -83,9 +83,15 @@ function fb_layout_head($title, array $opt = []) {
|
|||||||
</a>
|
</a>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
<div class="spacer"></div>
|
<div class="spacer"></div>
|
||||||
<a class="navlink hide-sm" href="./">全部问题</a>
|
<?php
|
||||||
|
/* 「问题列表」只在进入某个软件之后才有意义 ——
|
||||||
|
* 首页现在是介绍页,没有"全部问题"可回。 */
|
||||||
|
if ($curSw):
|
||||||
|
?>
|
||||||
|
<a class="navlink hide-sm" href="?sw=<?php echo e(urlencode((string)$curSw['slug'])); ?>">问题列表</a>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (fb_admin_logged_in()): ?><a class="navlink hide-sm" href="admin.php">管理后台</a><?php endif; ?>
|
<?php if (fb_admin_logged_in()): ?><a class="navlink hide-sm" href="admin.php">管理后台</a><?php endif; ?>
|
||||||
<a class="btn-mini hide-sm" href="new.php">我要反馈</a>
|
<a class="btn-mini hide-sm" href="new.php<?php echo $curSw ? '?sw=' . e(urlencode((string)$curSw['slug'])) : ''; ?>">我要反馈</a>
|
||||||
|
|
||||||
<?php if (count($swList) > 1): ?>
|
<?php if (count($swList) > 1): ?>
|
||||||
<?php /* 右上角三横线:点开就是一个小下拉菜单。
|
<?php /* 右上角三横线:点开就是一个小下拉菜单。
|
||||||
|
|||||||
+11
-2
@@ -445,7 +445,8 @@ if (!function_exists('fb_mail_layout')) {
|
|||||||
}
|
}
|
||||||
function fb_notify_html($thread, $reply, $isAdmin) {
|
function fb_notify_html($thread, $reply, $isAdmin) {
|
||||||
$base = fb_base_url();
|
$base = fb_base_url();
|
||||||
$url = $base . '/thread.php?id=' . (int)$thread['id'];
|
// 发给提问人的信,链接必须带 token,否则他看不了自己那条未公开的反馈
|
||||||
|
$url = fb_thread_link($thread, true);
|
||||||
$site = (string)fb_cfg('site_name');
|
$site = (string)fb_cfg('site_name');
|
||||||
$name = (string)$thread['author_name'];
|
$name = (string)$thread['author_name'];
|
||||||
$title = (string)$thread['title'];
|
$title = (string)$thread['title'];
|
||||||
@@ -770,8 +771,16 @@ if (!function_exists('fb_notify_log2')) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!function_exists('fb_notify_thread_url')) {
|
if (!function_exists('fb_notify_thread_url')) {
|
||||||
|
/**
|
||||||
|
* 邮件里给提问人的链接 —— **一律带 token**,不带 id 裸链。
|
||||||
|
*
|
||||||
|
* 为什么:邮件只发给提问人自己,而 token 只有他和我们(库里)知道。
|
||||||
|
* 带上它,即使这条反馈还没审核通过、甚至最终被关闭,他点进去也能看到
|
||||||
|
* 完整详情与进度。早期给的是 thread.php?id=N,未公开时点进去只看到
|
||||||
|
* 「不存在」或「等待审核」,等于邮件里的链接是废的。
|
||||||
|
*/
|
||||||
function fb_notify_thread_url(array $thread) {
|
function fb_notify_thread_url(array $thread) {
|
||||||
return fb_base_url() . '/thread.php?id=' . (int)$thread['id'];
|
return fb_thread_link($thread, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+77
-1
@@ -447,7 +447,11 @@ function fb_color_line($hex) {
|
|||||||
/** #rrggbb -> [r,g,b],非法返回 null */
|
/** #rrggbb -> [r,g,b],非法返回 null */
|
||||||
function fb_hex_to_rgb($hex) {
|
function fb_hex_to_rgb($hex) {
|
||||||
$h = ltrim((string)$hex, '#');
|
$h = ltrim((string)$hex, '#');
|
||||||
if (strlen($h) === 3) { $h = $h[0].$h[0].$h[1].$h[1].$h[2].$h[2]; }
|
// 三位简写只接受纯十六进制字符(#abc -> #aabbcc);
|
||||||
|
// 先做字符校验再展开,否则 "bad" 这种非色值会被当成 #bbaadd
|
||||||
|
if (strlen($h) === 3 && preg_match('/^[0-9a-fA-F]{3}$/', $h)) {
|
||||||
|
$h = $h[0].$h[0].$h[1].$h[1].$h[2].$h[2];
|
||||||
|
}
|
||||||
if (!preg_match('/^[0-9a-fA-F]{6}$/', $h)) { return null; }
|
if (!preg_match('/^[0-9a-fA-F]{6}$/', $h)) { return null; }
|
||||||
return [hexdec(substr($h, 0, 2)), hexdec(substr($h, 2, 2)), hexdec(substr($h, 4, 2))];
|
return [hexdec(substr($h, 0, 2)), hexdec(substr($h, 2, 2)), hexdec(substr($h, 4, 2))];
|
||||||
}
|
}
|
||||||
@@ -628,6 +632,78 @@ function fb_thread_public(array $thread) {
|
|||||||
return fb_is_admin();
|
return fb_is_admin();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 生成一个新的追踪 token(提交反馈时调用)。
|
||||||
|
* 32 位 hex = 128 位熵,猜不出来;这个值只存在自己库里、只发给提问人邮箱。
|
||||||
|
*/
|
||||||
|
function fb_track_new_token() {
|
||||||
|
return bin2hex(random_bytes(16));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 这个请求带来的 token 是否与帖子的匹配(常数时间比较,防时序探测) */
|
||||||
|
function fb_track_token_ok(array $thread, $given) {
|
||||||
|
$given = trim((string)$given);
|
||||||
|
if ($given === '') { return false; }
|
||||||
|
$real = isset($thread['track_token']) ? trim((string)$thread['track_token']) : '';
|
||||||
|
if ($real === '') { return false; }
|
||||||
|
return hash_equals($real, $given);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前访问者是否「有权跟踪」这条帖子 —— 即带对了 token。
|
||||||
|
* 审核后台的管理员也算有权(不然审核时看不了)。
|
||||||
|
*/
|
||||||
|
function fb_thread_trackable(array $thread, $given = null) {
|
||||||
|
if ($given === null) { $given = isset($_GET['t']) ? (string)$_GET['t'] : ''; }
|
||||||
|
if (fb_track_token_ok($thread, $given)) { return true; }
|
||||||
|
return fb_is_admin();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 当前访问者能否【查看】这条帖子。
|
||||||
|
*
|
||||||
|
* 公开的(visible=1)所有人可看;
|
||||||
|
* 未公开的只有两种人能看:审核后台管理员、拿着正确 token 的提交人。
|
||||||
|
* 这就是「不登录也能跟踪自己那条」的实现方式,不靠任何登录态。
|
||||||
|
*/
|
||||||
|
function fb_thread_viewable(array $thread, $given = null) {
|
||||||
|
if (fb_thread_public($thread)) { return true; }
|
||||||
|
return fb_thread_trackable($thread, $given);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 回「某个软件的问题列表」的链接。
|
||||||
|
*
|
||||||
|
* 首页现在是介绍页,不带 sw 的回首页等于把用户扔回介绍页 ——
|
||||||
|
* 从详情/提交页点「返回问题列表」应该回到**当前软件**的列表。
|
||||||
|
*/
|
||||||
|
function fb_software_list_url($sw) {
|
||||||
|
if (!is_array($sw) || empty($sw['slug'])) { return 'index.php'; }
|
||||||
|
return 'index.php?sw=' . urlencode((string)$sw['slug']);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 这条帖子的对外链接。
|
||||||
|
*
|
||||||
|
* 🔴 用 token 不用 id:
|
||||||
|
* - 邮件里发给提问人的链接一律带 ?t=<token>,他才能看未公开的那条;
|
||||||
|
* - 公开列表里给所有人看的链接照旧用 id(公开内容本来就谁都能看)。
|
||||||
|
* 早期所有链接都是 thread.php?id=N,导致「未审核就点不进去」——
|
||||||
|
* id 是自增的,用它当权限等于没有权限。
|
||||||
|
*
|
||||||
|
* @param array $thread 帖子行
|
||||||
|
* @param bool $personal true = 带 token 的私人链接;false = 公开链接
|
||||||
|
*/
|
||||||
|
function fb_thread_link(array $thread, $personal = false) {
|
||||||
|
$id = (int)(isset($thread['id']) ? $thread['id'] : 0);
|
||||||
|
$url = fb_base_url() . '/thread.php?id=' . $id;
|
||||||
|
if ($personal) {
|
||||||
|
$tok = isset($thread['track_token']) ? trim((string)$thread['track_token']) : '';
|
||||||
|
if ($tok !== '') { $url .= '&t=' . rawurlencode($tok); }
|
||||||
|
}
|
||||||
|
return $url;
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- 安装状态 ---------------- */
|
/* ---------------- 安装状态 ---------------- */
|
||||||
|
|
||||||
/** FeedLite 开源仓库地址(页脚版权链接的默认值,可在后台改) */
|
/** FeedLite 开源仓库地址(页脚版权链接的默认值,可在后台改) */
|
||||||
|
|||||||
@@ -8,7 +8,11 @@ $pdo = fb_db();
|
|||||||
$err = '';
|
$err = '';
|
||||||
$atts = []; // 校验失败时保留已上传的附件
|
$atts = []; // 校验失败时保留已上传的附件
|
||||||
|
|
||||||
/* ---- 软件:URL 带 sw 就用它,否则落到默认软件 ---- */
|
/* ---- 软件:URL 带 sw 就用它 ----
|
||||||
|
* 🔴 不再「没带 sw 就默认落到第一个软件」:
|
||||||
|
* 首页现在是介绍页,直接访问 new.php 没有软件上下文,
|
||||||
|
* 默默替他选一个软件会让反馈进错地方、发错通知邮件。
|
||||||
|
* 没有软件时给一句引导,让用户先选软件。 */
|
||||||
$swList = fb_softwares(true);
|
$swList = fb_softwares(true);
|
||||||
$sw = null;
|
$sw = null;
|
||||||
$swKey = isset($_GET['sw']) ? (string)$_GET['sw'] : '';
|
$swKey = isset($_GET['sw']) ? (string)$_GET['sw'] : '';
|
||||||
@@ -16,8 +20,32 @@ if ($swKey !== '') {
|
|||||||
$sw = fb_software($swKey);
|
$sw = fb_software($swKey);
|
||||||
if (!$sw || (int)$sw['enabled'] !== 1) { $sw = null; }
|
if (!$sw || (int)$sw['enabled'] !== 1) { $sw = null; }
|
||||||
}
|
}
|
||||||
if ($sw === null && $swList) { $sw = $swList[0]; }
|
if ($sw === null) {
|
||||||
$swId = $sw ? (int)$sw['id'] : 0;
|
fb_layout_head('请先选择软件', ['robots' => 'noindex,follow']);
|
||||||
|
?>
|
||||||
|
<div class="empty">
|
||||||
|
<h2 style="margin:0 0 12px">请先选择要给哪个软件提反馈</h2>
|
||||||
|
<p class="muted" style="margin:0 0 16px">不同软件的问题列表是分开的,请先选一个。</p>
|
||||||
|
<div class="home-sw-list" style="max-width:420px;margin:0 auto 18px">
|
||||||
|
<?php foreach ($swList as $s): ?>
|
||||||
|
<?php
|
||||||
|
list($c) = fb_software_colors($s);
|
||||||
|
if ($c === '') { $c = '#0057B8'; }
|
||||||
|
?>
|
||||||
|
<a class="home-sw" href="new.php?sw=<?php echo e(urlencode((string)$s['slug'])); ?>"
|
||||||
|
style="--swc:<?php echo e($c); ?>">
|
||||||
|
<span class="home-sw-nm"><?php echo e((string)$s['name']); ?></span>
|
||||||
|
<span class="home-sw-go">去反馈 →</span>
|
||||||
|
</a>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
</div>
|
||||||
|
<p><a class="btn" href="index.php">返回首页</a></p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
fb_layout_foot();
|
||||||
|
exit;
|
||||||
|
}
|
||||||
|
$swId = (int)$sw['id'];
|
||||||
|
|
||||||
$cats = fb_categories($swId);
|
$cats = fb_categories($swId);
|
||||||
$catKeys = [];
|
$catKeys = [];
|
||||||
@@ -97,7 +125,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
* 状态根本反映不出"还没公开"这个事实。 */
|
* 状态根本反映不出"还没公开"这个事实。 */
|
||||||
$visible = fb_moderation_on() ? 0 : 1;
|
$visible = fb_moderation_on() ? 0 : 1;
|
||||||
$initStatus = $visible === 1 ? 'open' : 'hidden';
|
$initStatus = $visible === 1 ? 'open' : 'hidden';
|
||||||
$ins = $pdo->prepare('INSERT INTO threads(title, body, category, category_id, software_id, author_name, author_email, author_phone, status, visible, reply_count, created_at, updated_at, ip_hash) VALUES(?,?,?,?,?,?,?,?,?,?,0,?,?,?)');
|
/* track_token:提交人的私人追踪钥匙,只发到他的邮箱。
|
||||||
|
* 有了它,即使这条还没审核通过,他也能凭链接看到进度。 */
|
||||||
|
$trackToken = fb_track_new_token();
|
||||||
|
$ins = $pdo->prepare('INSERT INTO threads(title, body, category, category_id, software_id, author_name, author_email, author_phone, status, visible, reply_count, created_at, updated_at, ip_hash, track_token) VALUES(?,?,?,?,?,?,?,?,?,?,0,?,?,?,?)');
|
||||||
$ins->execute([
|
$ins->execute([
|
||||||
$form['title'], $form['body'],
|
$form['title'], $form['body'],
|
||||||
$curCat ? (string)$curCat['slug'] : 'other', // 老列留个值,便于回退与检索
|
$curCat ? (string)$curCat['slug'] : 'other', // 老列留个值,便于回退与检索
|
||||||
@@ -105,7 +136,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$swId > 0 ? $swId : null,
|
$swId > 0 ? $swId : null,
|
||||||
$form['name'], $form['email'],
|
$form['name'], $form['email'],
|
||||||
$form['phone'] !== '' ? $form['phone'] : null,
|
$form['phone'] !== '' ? $form['phone'] : null,
|
||||||
$initStatus, $visible, $now, $now, fb_ip_hash(),
|
$initStatus, $visible, $now, $now, fb_ip_hash(), $trackToken,
|
||||||
]);
|
]);
|
||||||
$newId = (int)$pdo->lastInsertId();
|
$newId = (int)$pdo->lastInsertId();
|
||||||
fb_attach_save($pdo, $newId, null, $vList);
|
fb_attach_save($pdo, $newId, null, $vList);
|
||||||
@@ -138,7 +169,12 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
// 发信失败绝不能让用户提交失败 —— 反馈已经存库了
|
// 发信失败绝不能让用户提交失败 —— 反馈已经存库了
|
||||||
}
|
}
|
||||||
|
|
||||||
fb_redirect('thread.php?id=' . $newId . '&submitted=1');
|
/* 提交完成后直接带 token 跳到详情页:
|
||||||
|
* 这样用户当场就能看到自己刚提交的那条(哪怕还没审核),
|
||||||
|
* 而不是只看到一个「等待审核」的静态提示页。 */
|
||||||
|
fb_redirect('thread.php?id=' . $newId
|
||||||
|
. '&t=' . rawurlencode($trackToken)
|
||||||
|
. '&submitted=1');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -146,7 +182,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
fb_layout_head('我要反馈', ['robots' => 'noindex,follow', 'software' => $sw]);
|
fb_layout_head('我要反馈', ['robots' => 'noindex,follow', 'software' => $sw]);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="crumbs"><a href="index.php">← 返回问题列表</a></div>
|
<div class="crumbs"><a href="<?php echo e(fb_software_list_url($sw)); ?>">← 返回问题列表</a></div>
|
||||||
|
|
||||||
<section class="narrow">
|
<section class="narrow">
|
||||||
<h1>我要反馈</h1>
|
<h1>我要反馈</h1>
|
||||||
|
|||||||
+49
-12
@@ -11,16 +11,34 @@ if ($id <= 0) { fb_redirect('index.php'); }
|
|||||||
$st = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
|
$st = $pdo->prepare('SELECT * FROM threads WHERE id = ?');
|
||||||
$st->execute([$id]);
|
$st->execute([$id]);
|
||||||
$thread = $st->fetch();
|
$thread = $st->fetch();
|
||||||
|
|
||||||
|
/* 这条帖子所属的软件 —— 「返回问题列表」要回到它的列表,
|
||||||
|
* 而不是回到首页(首页现在是介绍页)。 */
|
||||||
|
$backUrl = fb_software_list_url(
|
||||||
|
$thread && !empty($thread['software_id']) ? fb_software((int)$thread['software_id']) : null
|
||||||
|
);
|
||||||
if (!$thread) {
|
if (!$thread) {
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
fb_layout_head('问题不存在');
|
fb_layout_head('问题不存在');
|
||||||
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
|
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="' . e($backUrl) . '">返回问题列表</a></p></div>';
|
||||||
fb_layout_foot();
|
fb_layout_foot();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 审核(先审后显):未通过审核的问题对访客不可见
|
/* 审核(先审后显)+ 提交人追踪:
|
||||||
if (!fb_thread_public($thread)) {
|
*
|
||||||
|
* 可见性三档 ——
|
||||||
|
* ① 已公开:所有人可看
|
||||||
|
* ② 未公开 + 带对 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');
|
$justSubmitted = (isset($_GET['submitted']) && (string)$_GET['submitted'] === '1');
|
||||||
if ($justSubmitted) {
|
if ($justSubmitted) {
|
||||||
fb_layout_head('提交成功', ['robots' => 'noindex,nofollow']);
|
fb_layout_head('提交成功', ['robots' => 'noindex,nofollow']);
|
||||||
@@ -29,15 +47,16 @@ if (!fb_thread_public($thread)) {
|
|||||||
. '<p>您的反馈已收到(编号 #' . (int)$thread['id'] . '),我们会先确认它是否为应用问题:</p>'
|
. '<p>您的反馈已收到(编号 #' . (int)$thread['id'] . '),我们会先确认它是否为应用问题:</p>'
|
||||||
. '<p>· 确认为问题 —— 审核通过后会公开显示,其他人也能查看与回复;<br>'
|
. '<p>· 确认为问题 —— 审核通过后会公开显示,其他人也能查看与回复;<br>'
|
||||||
. '· 确认不是问题 —— 我们会关闭它,并把结论与说明发送到您留下的邮箱。</p>'
|
. '· 确认不是问题 —— 我们会关闭它,并把结论与说明发送到您留下的邮箱。</p>'
|
||||||
. '<p class="muted">审核通常在 1~2 个工作日内完成,请留意邮箱。</p>'
|
. '<p class="muted">审核通常在 1~2 个工作日内完成。'
|
||||||
. '<p><a class="btn primary" href="index.php">返回问题列表</a></p>'
|
. '我们会发一封邮件给您,里面的链接可以随时查看这条反馈的处理进度。</p>'
|
||||||
|
. '<p><a class="btn primary" href="' . e($backUrl) . '">返回问题列表</a></p>'
|
||||||
. '</div>';
|
. '</div>';
|
||||||
fb_layout_foot();
|
fb_layout_foot();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
fb_layout_head('问题不存在');
|
fb_layout_head('问题不存在');
|
||||||
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
|
echo '<div class="empty"><p>该问题不存在或已被删除。</p><p><a class="btn primary" href="' . e($backUrl) . '">返回问题列表</a></p></div>';
|
||||||
fb_layout_foot();
|
fb_layout_foot();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -49,11 +68,12 @@ $form = ['name' => '', 'email' => '', 'body' => '', 'captcha' => ''];
|
|||||||
|
|
||||||
/* ---------------- 处理回复提交 ---------------- */
|
/* ---------------- 处理回复提交 ---------------- */
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
// 审核中(未公开)的问题不允许回复
|
/* 未公开的帖子,只有带对 token 的提交人(或管理员)能回复。
|
||||||
if (!fb_thread_public($thread)) {
|
* 别人连看都看不到,自然也不该能回。 */
|
||||||
|
if (!fb_thread_public($thread) && !$isOwnerView) {
|
||||||
http_response_code(403);
|
http_response_code(403);
|
||||||
fb_layout_head('暂不可回复');
|
fb_layout_head('暂不可回复');
|
||||||
echo '<div class="empty"><p>该问题还在审核中,暂时不能回复。</p><p><a class="btn primary" href="index.php">返回问题列表</a></p></div>';
|
echo '<div class="empty"><p>该问题还在审核中,暂时不能回复。</p><p><a class="btn primary" href="' . e($backUrl) . '">返回问题列表</a></p></div>';
|
||||||
fb_layout_foot();
|
fb_layout_foot();
|
||||||
exit;
|
exit;
|
||||||
}
|
}
|
||||||
@@ -131,7 +151,10 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
];
|
];
|
||||||
fb_notify_reply($pdo, $thread, $replyRow, $isAdmin);
|
fb_notify_reply($pdo, $thread, $replyRow, $isAdmin);
|
||||||
|
|
||||||
fb_redirect('thread.php?id=' . $id . '#r' . $replyId);
|
/* 回复后跳回去时要带上 token,否则提交人对未公开反馈的访问权会当场丢掉 */
|
||||||
|
fb_redirect('thread.php?id=' . $id
|
||||||
|
. ($isOwnerView ? '&t=' . rawurlencode($hasToken) : '')
|
||||||
|
. '#r' . $replyId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -153,7 +176,18 @@ $thSwForColor = !empty($thread['software_id']) ? fb_software((int)$thread['softw
|
|||||||
fb_layout_head(fb_excerpt($thread['title'], 50), ['software' => $thSwForColor]);
|
fb_layout_head(fb_excerpt($thread['title'], 50), ['software' => $thSwForColor]);
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="crumbs"><a href="index.php">← 返回问题列表</a></div>
|
<div class="crumbs"><a href="<?php echo e($backUrl); ?>">← 返回问题列表</a></div>
|
||||||
|
|
||||||
|
<?php if ($isOwnerView && !fb_thread_public($thread)): ?>
|
||||||
|
<?php /* 提交人凭 token 进来看到自己的未公开反馈时,要明确告诉他:
|
||||||
|
这条还没公开、别人看不到,但他可以一直用这个链接跟踪。
|
||||||
|
否则他会以为「既然能打开,是不是已经公开了」。 */ ?>
|
||||||
|
<div class="owner-note">
|
||||||
|
<b>这是您的私人跟踪链接</b>
|
||||||
|
<span>这条反馈还没通过审核,其他人看不到。请保留本页链接(或邮件里的链接),
|
||||||
|
审核结果与后续进展都能在这里查看。</span>
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
<article class="subject">
|
<article class="subject">
|
||||||
<div class="item-top">
|
<div class="item-top">
|
||||||
@@ -206,8 +240,11 @@ fb_layout_head(fb_excerpt($thread['title'], 50), ['software' => $thSwForColor]);
|
|||||||
<section class="reply-form">
|
<section class="reply-form">
|
||||||
<h2>我要回复</h2>
|
<h2>我要回复</h2>
|
||||||
<?php fb_flash_box($err, 'error'); ?>
|
<?php fb_flash_box($err, 'error'); ?>
|
||||||
<form method="post" action="thread.php?id=<?php echo (int)$id; ?>">
|
<?php /* 表单要带上 token:否则提交后跳转回不带 t 的地址,
|
||||||
|
提交人会立刻失去对自己未公开反馈的访问权,体验上就是「回复完就看不见了」。 */ ?>
|
||||||
|
<form method="post" action="thread.php?id=<?php echo (int)$id; ?><?php echo $isOwnerView ? '&t=' . e(rawurlencode($hasToken)) : ''; ?>">
|
||||||
<input type="hidden" name="id" value="<?php echo (int)$id; ?>">
|
<input type="hidden" name="id" value="<?php echo (int)$id; ?>">
|
||||||
|
<input type="hidden" name="t" value="<?php echo e($isOwnerView ? $hasToken : ''); ?>">
|
||||||
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
|
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
|
||||||
<input type="hidden" name="t0" value="<?php echo time(); ?>">
|
<input type="hidden" name="t0" value="<?php echo time(); ?>">
|
||||||
<div class="hp-field" aria-hidden="true">
|
<div class="hp-field" aria-hidden="true">
|
||||||
|
|||||||
Reference in New Issue
Block a user