日程表单的日历本下拉只列可写 custom 本(#4)
用户原话(2026-10-26):「网页端,添加日程,如果日历本是只读的,例如别人
共享给我只读,或者系统自带日历、观影、纪念日等,就不要出现在日历本选框中,
只出现那些可写的。」
三条判据缺一不可(只做一条都修不好):
① canWrite() —— 挡「别人共享给我且只读」。
实测:只有这一类能被 canWrite 拦住。
② !isReadonlyKind() —— 挡系统只读本(农历/节假日/传统节日/二十四节气)。
它们的 owner_id 就是我 ⇒ canWrite() 恒真,光靠 ① 一本也拦不住。
③ kind === 'custom' —— 挡观影/旅行的派生本。
这两本**确实可写**(往里加一条 = 建一条观影/旅程记录),
但不是"放日程的地方",放在新建日程下拉里只会让人建出别的东西。
- app/Calendar.php:新增 writableForEvent() / filterWritableForEvent(),
作为**唯一口径**(日历页、首页卡片、API 都走它),
避免 v82 那种"两个入口的下拉内容可以不一样"的老毛病。
判据用 READONLY_KINDS 常量而不是 kind 白名单:新增系统本只要登记就自动生效。
- templates/calendar/index.php、index.php:改用上面的口径。
- templates/calendar/_add_modal.php:清掉恒假的 disabled 与「(自动生成)」
死代码(前端已过滤,留着只会误导);保留 data-kind 给探针当锚点;
按钮色点与文案改成**服务端就带出来**(无 JS 时也看得见颜色和本名)。
验证:
- 浏览器探针 28/0:含自建夹具(只读共享 / 可写共享 / 自己的)三类,
与 PHP 侧独立重算的期望集逐项比对,真保存一次,
以及**绕过前端直接 POST** —— 只读共享回「这本日历你没有写权限。」且库无新增;
观影/旅行回 ok:true 且真的落 events 表(不进 calendar_events)。
- 反向验证 6/6 全抓住:退回 canWrite / 只看 kind / 不管 custom / 全滤空 /
只读共享漏进下拉 / 拆掉 event_add 的 canWrite 闸门。
最后一条注入时**只有 ⑪ 变红**,证明它测的是服务端拦截而非前端下拉。
- 全量流水线(--all)退出码 0,已部署并确认线上 4 个文件 php -l 全通过。
工单:#4
This commit is contained in:
@@ -203,6 +203,70 @@ final class Calendar
|
||||
return ($cal['perm'] ?? '') === 'write';
|
||||
}
|
||||
|
||||
/**
|
||||
* 「日程表单里可以选来放的日历本」= 我会看到的本 里,**真正能往里放日程**的那些。
|
||||
*
|
||||
* 🔴🔴 v95(用户 2026-10-26):「网页端,添加日程,如果日历本是只读的,
|
||||
* 例如别人共享给我只读,或者系统自带日历、观影、纪念日等,
|
||||
* 就不要出现在日历本选框中,只出现那些可写的。」
|
||||
*
|
||||
* ⚠️ 为什么**不能**只写 `canWrite()` —— 这是本轮最容易写错的地方,
|
||||
* 我一开始也以为 canWrite 就够了,实测(`_v95_dump_mine.py`)打脸:
|
||||
*
|
||||
* kind perm canWrite isReadonlyKind
|
||||
* movie owner **Y** n ← 派生本,canWrite 放行!
|
||||
* trip owner **Y** n ← 同上
|
||||
* lunar owner **Y** **Y** ← 系统本,perm 是 owner 所以 canWrite 也放行!
|
||||
* holiday owner **Y** **Y**
|
||||
* custom/read read n n ← 只有"只读共享"这一类能被 canWrite 拦住
|
||||
*
|
||||
* ⇒ 三条判据缺一不可:
|
||||
* ① `canWrite()` —— 拦掉「别人共享给我且只读」
|
||||
* ② `!isReadonlyKind()` —— 拦掉系统只读本(农历/节假日/传统节日/节气)。
|
||||
* 它们的 `owner_id` 就是我,所以 ① 恒真 —— 光靠 ① 一本也拦不住。
|
||||
* ③ `kind === 'custom'` —— 拦掉观影/旅行派生本。
|
||||
* 这两本**确实可写**(往里加一条 = 建一条观影/旅程记录,见
|
||||
* `event_add` 里那两段分支),但它们不是"放日程的地方",
|
||||
* 放在"新建日程"的下拉里只会让人建出别的东西 —— 用户点名要去掉。
|
||||
*
|
||||
* ⚠️ 别图省事写成 kind 白名单(`in_array($kind, [...])`):系统日历本每加一本
|
||||
* 就要改一次、漏改就是"这本漏网"。② 用的是 `READONLY_KINDS` 常量,
|
||||
* 新增系统本时只要登记在那里就自动生效。
|
||||
*
|
||||
* ⚠️ 这个函数是**唯一口径**:`calendar/index.php`、`index.php`(首页卡片)、
|
||||
* API 都该走它。v82 那次首页自己写了一份 `array_filter(canWrite)`,
|
||||
* 结果两个入口的下拉内容可以不一样 —— 一处改了另一处忘改。
|
||||
*/
|
||||
public static function writableForEvent(int $familyId, int $personId): array
|
||||
{
|
||||
return self::filterWritableForEvent(self::mine($familyId, $personId), $personId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上面那个的「已有一份 `mine()` 结果」版本 —— 给**手上已经有 `$cals`** 的调用方用
|
||||
* (比如 `calendar/index.php` 的模板:那里拿到的是 `$cals`,没有 `$familyId`)。
|
||||
*
|
||||
* ⚠️ 判据必须**只有这一份**。把过滤逻辑在模板里再抄一遍,
|
||||
* 就是 v82 那个"两个入口的下拉内容可以不一样"的老毛病。
|
||||
*/
|
||||
public static function filterWritableForEvent(array $cals, int $personId): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($cals as $c) {
|
||||
if (!self::canWrite($c, $personId)) {
|
||||
continue; /* ① 只读共享 */
|
||||
}
|
||||
if (self::isReadonlyKind((string)($c['kind'] ?? ''))) {
|
||||
continue; /* ② 系统只读本 */
|
||||
}
|
||||
if ((string)($c['kind'] ?? '') !== 'custom') {
|
||||
continue; /* ③ 观影/旅行派生本 */
|
||||
}
|
||||
$out[] = $c;
|
||||
}
|
||||
return $out;
|
||||
}
|
||||
|
||||
// ---------------- 我的日程显示偏好(v24 用户 2026-10-03 第 4、5 条)----------------
|
||||
|
||||
/** 三种视图(用户第 4 条:设置里可选默认) */
|
||||
|
||||
Reference in New Issue
Block a user