fix(avatar): 注册用户保留原头像;人脸风格肤色只留黄种人区间
#8 邮箱能在本站用户表查到的,一律用他自己的头像,不再替换 · 新增 yang_avatar_is_registered_email():get_user_by('email') 查表, 邮箱先 trim + strtolower 规范化(导入用户/插件写库/老数据都有大小写混用) · 加请求内 static $memo 缓存:评论列表一页 20 条、同一个人常有 2~3 条, 不缓存就是同一个邮箱反复打库(实测 20 次 → 1 次) · 新增 yang_avatar_keep_member() 读开关,运行时默认 '1' · 过滤器在拼 data: URI 之前拦截,命中即原样 return $avatar · 后台新增「注册用户保留原头像」复选框 · 关于「注册用户自己也没有真 Gravatar」:WP 的 get_avatar() 从不检查图上 是否存在,只拼 URL 交给浏览器;要分辨必须向 gravatar.com 发请求, 而本功能坚持零外网调用(发布脚本会断言)。因此做成开关、默认保留, 这类用户保持灰人像、不猜。 #9 肤色从 7 档收窄到 4 档,只留最浅到偏黄 · 移除 d9a066(偏深)、c68642(深棕)、8d5524(最深) · 安全性依据:Prng::pick() 用 integer(0, count-1) 取索引,只消耗一次 next(),与区间跨度无关 ⇒ 缩短数组不改变 PRNG 消耗次数,发型/衣着/ 表情/背景抽到的结果一点不变,只有肤色跟着变 (已用 _v39_isolate.php 逐字节证明:7 色与 4 色渲染除肤色外完全相同) 顺带修掉一个老 bug:baseColor 错当肤色 · bottts 的 baseColor 是机器人外壳色(19 种彩色),映射成肤色后外壳被 涂成肉色 e8b98f / f1c27d。从 $map 里去掉 'baseColor' => 'skinColor' · 「base」这名字像是底色,但各家风格语义并不统一,不能按名字猜 开关位置(踩过的坑) · 新开关必须排在「头像风格」之前。风格列表是 18 张带缩略图的卡片、 实测高约 2100px,排在它后面的控件会落到 y≈2327px,而设置页视口 约 1000px —— 用户不滚三屏根本看不到。3.2.36 就是栽在这里。 挪到之前后实测 y=216px、首屏可见。 验证 _v39_probe.php 5 / 0 肤色收窄 + bottts 修复 _v39_isolate.php 50 / 0 「只改肤色、其它不变」逐字节隔离证明 _v39_member.php 19 / 0 注册用户判据(含缓存次数精确断言) _v39_falsify.py 8 / 8 上述判据反向 _guard337_layout.py 25 / 0 布局守卫(新增开关位置三项) _guard337_layout_falsify.py 5 / 5 _guard336_avatar.py 55 / 0 源码守卫 _guard336_falsify.py 17 / 17 _geo337_avatar_reach.py 34 / 0 真实几何 + 显隐交互 _smoke18.py 18 / 0 18 套风格全量冒烟 _verify4_prod.php 80 / 0 逐字节对官方(对配色无感,见 docblock) _pkgcheck339.py 52 / 0 包内内容契约 _pkgcheck339_falsify.py 20 / 20 包内断言反向 回归 88/0、56/0、77/0、13/0;93 个 php -l 全通过 PHP 8.4.25 与 8.5.12 严格错误级别均 0 警告 Refs #8 #9
This commit is contained in:
+119
-13
@@ -471,9 +471,10 @@ class Yang_Avatar_Prng
|
||||
/**
|
||||
* 收录的风格清单
|
||||
*
|
||||
* 只收 CC0 1.0 与 MIT。CC BY 4.0 那些(adventurer / avataaars / bottts /
|
||||
* croodles / micah / toon-head 等)虽然更好看,但要求保留署名 ——
|
||||
* 主题是给用户装的,用户不会去维护署名,等于把合规义务转嫁给用户。
|
||||
* 收录的 18 套全部可自由使用与商用,且都不要求署名(许可证明细见文件头)。
|
||||
* CC BY 4.0 那 13 套(adventurer / big-ears / croodles / micah /
|
||||
* toon-head 等)虽然更好看,但要求保留署名 —— 主题是给用户装的,
|
||||
* 用户不会去维护署名,等于把合规义务转嫁给用户。
|
||||
*
|
||||
* 'label' 用中文,因为后台是给中文站长看的。
|
||||
* 'size' 是模板文件体积,用于后台提示「这套会占多少」。
|
||||
@@ -644,9 +645,13 @@ function yang_avatar_palette()
|
||||
'f8961e', 'f9c74f', '4d908e', '577590', 'f94144', 'f3722c',
|
||||
'a8dadc', '457b9d',
|
||||
),
|
||||
// 肤色:从浅到深,覆盖常见人种
|
||||
// 肤色:只留偏黄/偏浅的区间,不出现深棕与黑色
|
||||
// ⚠️ 缩短这个数组是**安全的**:Prng::pick() 用
|
||||
// integer(0, count-1) 取索引,消耗的随机数与长度无关,
|
||||
// 所以其它组件(发型、衣着、表情)抽到的结果一点不变,
|
||||
// 只有肤色这一项跟着变。别在这里加逻辑,改长度就够了。
|
||||
'skinColor' => array(
|
||||
'f2d3b8', 'e8b98f', 'd9a066', 'c68642', '8d5524', 'ffdbac', 'f1c27d',
|
||||
'ffdbac', 'f2d3b8', 'f1c27d', 'e8b98f',
|
||||
),
|
||||
// 发色
|
||||
'hairColor' => array(
|
||||
@@ -678,10 +683,13 @@ function yang_avatar_style_palette($style)
|
||||
? $style['defaults'] : array();
|
||||
|
||||
// 字段名 → 调色板类别
|
||||
// ⚠️ 这里只放**真的是皮肤**的字段。`baseColor` 不算 ——
|
||||
// 它在 bottts 里是机器人的外壳色(19 种彩色),实测把它映射成
|
||||
// 肤色后,机器人外壳会被涂成肉色 e8b98f / f1c27d,非常难看。
|
||||
// 「base」这个名字像是底色,但各家风格语义并不统一,别按名字猜。
|
||||
$map = array(
|
||||
'backgroundColor' => 'backgroundColor',
|
||||
'skinColor' => 'skinColor',
|
||||
'baseColor' => 'skinColor',
|
||||
'hairColor' => 'hairColor',
|
||||
'topColor' => 'hairColor',
|
||||
'beardColor' => 'hairColor',
|
||||
@@ -768,14 +776,16 @@ function yang_avatar_data_uri($seed, $style = '', $size = 96)
|
||||
* 响应状态码)都要发一次外网请求,而 gravatar.com 在国内全部超时,
|
||||
* 这条路根本走不通。
|
||||
*
|
||||
* 所以这里退一步,用「src 指向谁」做判据:
|
||||
* 所以这里退一步,用「src 指向谁」+「邮箱属不属于本站用户」做判据:
|
||||
* · src 是 data: → 说明别的插件/主题已经给过头像了,放行;
|
||||
* · src 主机属于 Gravatar 家族 → 替换成生成图;
|
||||
* · src 主机属于 Gravatar 家族,**且**这个邮箱不是本站注册用户的
|
||||
* → 替换成生成图;
|
||||
* · 邮箱属于本站注册用户 → 原样放行(用他自己的头像);
|
||||
* · 其它(媒体库图片、外链头像)→ 放行。
|
||||
*
|
||||
* 代价:如果站长开了「Gravatar 加速」镜像、并且访客确实有 Gravatar
|
||||
* 头像,这里也会替换掉。这是有意的取舍 —— 相比"偶尔盖掉一个真实
|
||||
* 头像","多数访客是一张破图/灰头像"更伤。选项说明里写清了这一点。
|
||||
* 代价:如果站长开了「Gravatar 加速」镜像、并且匿名访客确实有 Gravatar
|
||||
* 头像,这里仍会替换掉。这是有意的取舍 —— 相比"偶尔盖掉一个匿名访客
|
||||
* 的真实头像","多数访客是一张破图/灰头像"更伤。选项说明里写清了这一点。
|
||||
*
|
||||
* ── 为什么不再重新拼整个 <img> ────────────────────────────────────
|
||||
* 早先的实现直接用 $args 里的 class / extra_attr 重新拼一个标签。
|
||||
@@ -827,6 +837,22 @@ function yang_avatar_filter($avatar, $id_or_email, $size, $default, $alt, $args
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
/* ── 本站注册用户:原样返回,一个字都不改 ──────────────────────
|
||||
用户诉求:邮箱能在用户表里查到,说明这个人是本站的人,用他自己的
|
||||
头像;查不到的(匿名评论者)才给他生成一张。
|
||||
|
||||
这一步必须放在**拼 data: URI 之前**,否则白白算一遍 SVG 才丢弃。
|
||||
|
||||
⚠️ 这里有个服务端无法回避的限制:WP 的 get_avatar() 从不检查
|
||||
Gravatar 上到底有没有这张图,它只拼 URL 交给浏览器。所以
|
||||
「注册用户自己也没上传头像、Gravatar 上其实是默认灰人像」
|
||||
这种情况,**不联网就分辨不出来**。本功能坚持零外网调用
|
||||
(发布检查会断言不含 curl/wp_remote_get),因此这里选择
|
||||
「只要是注册用户就不动」—— 宁可留一张灰人像,也不去猜。 */
|
||||
if (yang_avatar_keep_member() && yang_avatar_is_registered_email($email)) {
|
||||
return $avatar;
|
||||
}
|
||||
|
||||
// 尺寸取实际要显示的那个,别用 96 蒙一把
|
||||
$px = (int) $size;
|
||||
if ($px <= 0) {
|
||||
@@ -959,6 +985,83 @@ function yang_avatar_email_of($id_or_email)
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个邮箱是不是本站**注册用户**的邮箱
|
||||
*
|
||||
* ── 为什么要有这一层 ──────────────────────────────────────────────
|
||||
* 早先的实现只判断「src 是不是 Gravatar 家族」,只要是就换成本地生成的
|
||||
* 头像。结果是**连本站注册用户自己的头像也被盖掉** —— 站长看后台、
|
||||
* 作者看文章列表,全是同一套卡通脸,很别扭。
|
||||
*
|
||||
* 用户的判据是:邮箱能在用户表里查到 ⇒ 这个人属于本站,用他自己的;
|
||||
* 查不到(匿名评论者)⇒ 才给他生成一张。
|
||||
*
|
||||
* ── 查表方式 ────────────────────────────────────────────────────
|
||||
* 优先 get_user_by('email'):WP 内部会把结果缓存到 object cache,
|
||||
* 同一请求内多次调用同一邮箱不会重复打库。评论列表一页 20 条也只
|
||||
* 打有限几次。
|
||||
*
|
||||
* ⚠️ 邮箱比较必须**先规范化**(trim + 小写)。WP 建用户时邮箱理论上
|
||||
* 已小写,但导入用户、插件写库、老数据都可能出现大小写混用;
|
||||
* 不规范化会漏判,表现成"明明是注册用户却还是被换了头像"。
|
||||
*
|
||||
* @param string $email
|
||||
* @return bool
|
||||
*/
|
||||
function yang_avatar_is_registered_email($email)
|
||||
{
|
||||
$email = trim((string) $email);
|
||||
if ($email === '') {
|
||||
return false;
|
||||
}
|
||||
// 大小写不敏感:WP 的 user_email 列在大多数排序规则下也是不敏感的,
|
||||
// 但函数调用前先规范化,避免依赖数据库排序规则
|
||||
$email = strtolower($email);
|
||||
|
||||
/* 请求内缓存
|
||||
────────────────────────────────────────────────────────────
|
||||
评论列表一页 20 条,同一个人常有 2~3 条;不缓存就是同一个邮箱
|
||||
反复打库。WP 的 get_user_by 内部虽有 object cache,但那是**按
|
||||
user ID 缓存**的,第一次仍要走一次邮箱查询。
|
||||
这里再压一层,保证「同一请求内同一邮箱只查一次」。
|
||||
静态变量在请求结束即销毁,不会跨请求污染。 */
|
||||
static $memo = array();
|
||||
if (array_key_exists($email, $memo)) {
|
||||
return $memo[$email];
|
||||
}
|
||||
|
||||
$hit = false;
|
||||
if (function_exists('get_user_by')) {
|
||||
$u = get_user_by('email', $email);
|
||||
if ($u && isset($u->ID) && (int) $u->ID > 0) {
|
||||
$hit = true;
|
||||
}
|
||||
}
|
||||
$memo[$email] = $hit;
|
||||
return $hit;
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否「只有匿名访客才换头像」
|
||||
*
|
||||
* 开关存库;默认 '1'(开)—— 这正是用户提需求时的原话:
|
||||
* 「邮箱在我们博客里面属于这个博客的用户的,就用他自己的头像」。
|
||||
*
|
||||
* 关掉这个开关等于回到 3.2.38 的行为:所有 Gravatar 家族的 src 都换。
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
function yang_avatar_keep_member()
|
||||
{
|
||||
if (!yang_avatar_active()) {
|
||||
return false;
|
||||
}
|
||||
if (function_exists('kratos_option')) {
|
||||
return kratos_option('a_auto_keep_member', '1') !== '0';
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/* ══════════════════════════════════════════════════════════════════
|
||||
* 渲染器本体
|
||||
* ══════════════════════════════════════════════════════════════════ */
|
||||
@@ -1822,8 +1925,11 @@ function yang_avatar_admin_assets($hook)
|
||||
.of-avatar-preview-cell figcaption{font-size:10px;color:#646970;margin-top:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
|
||||
.of-avatar-preview-note{margin:10px 0 0;font-size:12px;color:#646970}
|
||||
/* 开关没打开时把风格那一行藏起来 —— 用 body class 而不是 JS 内联样式,
|
||||
这样保存后页面重载也不会闪一下 */
|
||||
body.yang-avatar-off #section-a_auto_style{display:none}
|
||||
这样保存后页面重载也不会闪一下。
|
||||
⚠️ 新增的「注册用户保留原头像」也必须一起藏:它同样只有在
|
||||
自动头像启用后才有意义,否则是一个悬空选项。 */
|
||||
body.yang-avatar-off #section-a_auto_style,
|
||||
body.yang-avatar-off #section-a_auto_keep_member{display:none}
|
||||
';
|
||||
wp_register_style('yang-avatar-admin', false, array(), null);
|
||||
wp_enqueue_style('yang-avatar-admin');
|
||||
|
||||
@@ -286,6 +286,20 @@ function kratos_options()
|
||||
'type' => 'checkbox',
|
||||
);
|
||||
|
||||
/* ⚠️ 这个开关必须排在「头像风格」**之前**。
|
||||
风格列表是 18 张带缩略图的卡片,实测高约 2100px;
|
||||
排在它后面的控件实际落到 y≈2327px,而设置页视口约 1000px
|
||||
—— 用户不滚三屏根本看不到。3.2.36 就是栽在「塞在长列表末尾」
|
||||
这个坑里,别再犯。 */
|
||||
$options[] = array(
|
||||
'name' => __('注册用户保留原头像', 'kratos'),
|
||||
'desc' => __('<b>建议开启。</b>开启后只给「邮箱在用户表里查不到」的匿名访客生成头像;本站注册用户一律用他自己的头像,不会被替换。<br>关掉则所有 Gravatar 头像都会被换掉 —— 站内作者、管理员也会变成卡通脸。<br><b>说明:</b>注册用户若自己也没上传过头像,Gravatar 上就是一张默认灰人像。要分辨这种情况必须向 gravatar.com 发请求,而本功能坚持不访问任何外部服务,所以这类用户会保持灰人像,不会自动生成。', 'kratos'),
|
||||
'std' => '1',
|
||||
'id' => 'a_auto_keep_member',
|
||||
'type' => 'checkbox',
|
||||
'requires' => 'a_auto_enable',
|
||||
);
|
||||
|
||||
$options[] = array(
|
||||
'name' => __('头像风格', 'kratos'),
|
||||
'desc' => __('勾选上面的「启用自动头像」后,这里才会出现风格列表。所有风格都可自由使用与商用,且不要求署名。点一下即时预览,保存后生效。', 'kratos'),
|
||||
|
||||
Reference in New Issue
Block a user