diff --git a/app/Calendar.php b/app/Calendar.php index 983db7b..44b1aa2 100644 --- a/app/Calendar.php +++ b/app/Calendar.php @@ -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 条:设置里可选默认) */ diff --git a/index.php b/index.php index 7727f39..9352eb7 100644 --- a/index.php +++ b/index.php @@ -976,12 +976,13 @@ switch ($r) { 不补的话首页这张弹框的默认本可能落到**观影本**上, 用户直接点"加上"就建出一条观影记录(v24 第 21 批真事故)。 */ Calendar::ensurePersonalBook($familyId, $me); - $agCals = Calendar::mine($familyId, $me); + /* 🔴 v95(用户 2026-10-26):口径统一走 `writableForEvent()`。 + ⚠️ 原来这里是首页自己写的 `array_filter(canWrite)`,与日程页 + "看起来一样、实则不是同一份代码" —— 改一处忘另一处就漂移。 + canWrite 拦不住观影/旅行派生本,也拦不住系统只读本 + (它们的 perm 是 owner),详见 `Calendar::writableForEvent()`。 */ $homeAddModals['agenda'] = [ - 'writable' => array_values(array_filter( - $agCals, - static fn($c) => Calendar::canWrite($c, $me) - )), + 'writable' => Calendar::writableForEvent($familyId, $me), ]; } elseif ($hcKey === 'contacts' && !isset($homeAddModals['contacts'])) { /* 🔴 v82(用户 2026-10-23):「名册,也没有新增成员这个按钮」—— diff --git a/templates/calendar/_add_modal.php b/templates/calendar/_add_modal.php index 46e3dd0..b4a5c21 100644 --- a/templates/calendar/_add_modal.php +++ b/templates/calendar/_add_modal.php @@ -178,21 +178,27 @@ $fbAddModalAttr = ($calBack === 'home');