Files
wordpress-kratos-yang/inc/avatar-styles/glass.php
T
laoyang 3fcaf25c58 3.2.36 新增「自动头像」:按邮箱本地生成,14 套 CC0/MIT 风格可选 #5
为没有头像的访客按邮箱确定性地生成一张 SVG 头像,替代 Gravatar
那个灰底"神秘人"(cn.gravatar.com 在国内已超时,不勾加速时头像
干脆是空白)。全程纯 PHP,零外网请求、零落盘。

后台「主题设置」新增「自动头像」复选框 + 风格列表:勾上之后才
出现 14 套风格,点一下即时换 16 格预览,保存后全站生效。

实现要点(都是踩出来的):
- PRNG 必须与 DiceBear 9.x 逐位一致:xorshift + hashSeed 全程跑在
  JS 的 int32 语义里,PHP 64 位整数要逐步 i32() 归一;
  charCodeAt 按 UTF-16 码元取值,emoji 必须真做代理对拆分。
- pickComponent 有简繁两版:只有 glass/rings/shapes/thumbs 额外取
  三次整数(rotation/offsetX/offsetY)。给简版也补三次 ⇒ PRNG 多走
  3N 步 ⇒ 31 套里 27 套全错。编译期算 pc_has_ints 才治得住。
- 自带人类调色板:官方默认配色几乎全单值,30 个邮箱只出 2 种配色,
  那才是"默认头像丑"的真因,光换画风解决不了。
- 只收录 CC0/MIT(13 CC0 + 1 MIT),CC BY 那 13 套要求署名,一律不收——
  主题装给用户用,用户不会维护署名,收进来等于转嫁合规义务。

接入 get_avatar 的两个关键修正:
- 必须先看原 src 是不是 Gravatar 家族域名再决定是否替换。原先无条件
  替换会把媒体库头像、外链头像、别的插件处理过的 data: 头像一起盖掉。
- 必须删 srcset。只改 src 留着 srcset,浏览器优先用 srcset,
  生成的图根本不会被加载(现象是"代码明明换了,页面还是老样子"),
  且不能重建 <img> 标签(会丢掉评论头像 CSS 依赖的 class)。

后台三处必须这么做:
- body class 由 PHP 输出(admin_body_class),否则每次进设置页/保存后
  重载都会先显示风格列表再藏起来,闪一下。
- 事件监听 radio 的 change,不能监听 label 的 click —— label 会把
  click 转发给关联 input,冒泡回来再触发一次 ⇒ 同一风格发两次请求
  (实测 hits 2→4)。
- 首屏已渲染的 16 格写进 JS 缓存,否则点一圈回到默认风格会白请求一次。

不依赖任何可选扩展:不用 mbstring(未安装时 mb_substr 是 Fatal error,
整站白屏)、不用 gd/imagick。UTF-8 切分/截断/大写全部自己实现。
PHP 7.0 语法,PHP 8.5 下 E_ALL 无任何 warning。

验证:
- 14 套 × 6 种子 = 84 个用例与 DiceBear 官方 Node 输出逐字节一致,
  PHP 8.4 / 8.5 / 无 mbstring(php -n)三种环境均 84/0。
- 静态守卫 55/0;反向验证 17 项注入全部被捕获。
- 后台面板浏览器验证 26/0(显隐、几何、点选、缓存、连点、零报错)。
- 包内内容契约 24/0;包校验反向验证 6 项注入全部被捕获。
- 单次渲染稳态 0.047~0.105ms,峰值内存 2~4MB。
2026-10-25 18:38:11 +08:00

83 lines
6.4 KiB
PHP

<?php
return array(
'name' => 'glass',
'meta' => array(
'title' => 'Glass',
'creator' => 'DiceBear',
'source' => 'https://www.dicebear.com',
'license_name' => 'CC0 1.0',
'license_url' => 'https://creativecommons.org/publicdomain/zero/1.0/',
),
'view_box' => '0 0 100 100',
'clip_default' => true,
'has_pre_create' => false,
'has_post_create' => false,
'is_initials' => false,
'attr_pairs' => array(
array('viewBox', '0 0 100 100'),
array('fill', 'none'),
array('shape-rendering', 'auto'),
),
'bg_colors' => array('eb4747', 'eb6247', 'eb7e47', 'eb9947', 'ebb447', 'ebd047', 'ebeb47', 'd0eb47', 'b4eb47', '99eb47', '7eeb47', '62eb47', '47eb47', '47eb62', '47eb7e', '47eb99', '47ebb4', '47ebd0', '47ebeb', '47d0eb', '47b4eb', '4799eb', '477eeb', '4762eb', '4747eb', '6247eb', '7e47eb', '9947eb', 'b447eb', 'd047eb', 'eb47eb', 'eb47d0', 'eb4799', 'eb477e', 'eb4762'),
'bg_type' => array('solid'),
'bg_rotation' => array('0', '360'),
'body_template' => '<g style="mix-blend-mode:screen" opacity="0.6" filter="url(#dicebearGlass-a)"><g transform="translate(-25 -25)">@@k:shape2@@</g></g><g style="mix-blend-mode:screen" opacity="0.6" filter="url(#dicebearGlass-b)"><g transform="translate(-25 -25)">@@k:shape1@@</g></g><defs><filter id="dicebearGlass-a" x="-57" y="-57" width="214" height="214" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_25_7"/></filter><filter id="dicebearGlass-b" x="-57" y="-57" width="214" height="214" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB"><feFlood flood-opacity="0" result="BackgroundImageFix"/><feBlend in="SourceGraphic" in2="BackgroundImageFix" result="shape"/><feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_25_7"/></filter></defs>',
'pick_order' => array('shape2', 'shape1'),
'bool_order' => array(),
'color_order' => array(
),
'pc_has_ints' => true,
'pc_meta' => array(
array(
'group' => 'shape2',
'values_opt' => 'shape2',
'width' => 150,
'height' => 150,
'rotation' => array('opt' => 'shape2Rotation', 'fallback' => array('0')),
'offsetX' => array('opt' => 'shape2OffsetX', 'fallback' => array('0')),
'offsetY' => array('opt' => 'shape2OffsetY', 'fallback' => array('0')),
),
array(
'group' => 'shape1',
'values_opt' => 'shape1',
'width' => 150,
'height' => 150,
'rotation' => array('opt' => 'shape1Rotation', 'fallback' => array('0')),
'offsetX' => array('opt' => 'shape1OffsetX', 'fallback' => array('0')),
'offsetY' => array('opt' => 'shape1OffsetY', 'fallback' => array('0')),
),
),
'defaults' => array(
'backgroundColor' => array('eb4747', 'eb6247', 'eb7e47', 'eb9947', 'ebb447', 'ebd047', 'ebeb47', 'd0eb47', 'b4eb47', '99eb47', '7eeb47', '62eb47', '47eb47', '47eb62', '47eb7e', '47eb99', '47ebb4', '47ebd0', '47ebeb', '47d0eb', '47b4eb', '4799eb', '477eeb', '4762eb', '4747eb', '6247eb', '7e47eb', '9947eb', 'b447eb', 'd047eb', 'eb47eb', 'eb47d0', 'eb4799', 'eb477e', 'eb4762'),
'shape1' => array('g', 'r', 'a', 'd', 'i', 'e', 'n', 't'),
'shape1OffsetX' => array('-80', '80'),
'shape1OffsetY' => array('-80', '80'),
'shape1Rotation' => array('-180', '180'),
'shape2' => array('g', 'r', 'a', 'd', 'i', 'e', 'n', 't'),
'shape2OffsetX' => array('-80', '80'),
'shape2OffsetY' => array('-80', '80'),
'shape2Rotation' => array('-160', '160'),
),
'groups' => array(
'shape2' => array(
'g' => '<path d="M101.6 51.2a20 20 0 0 0-14.7-14.5q-3.9-1-8.5-1-10 0-17 4.7A30 30 0 0 0 50.9 54q-3.6 9-3.6 21.6 0 12.8 3.5 21.8 3.4 9 10.3 14a29 29 0 0 0 17.2 4.8q9 0 15-2.6 6-2.7 9-7.5t3-11.4l6.8.7H78.7v-28h64.5v20q0 20-8.4 34.1a56 56 0 0 1-23.2 21.6Q97 150.7 78 150.7q-21.3 0-37.3-9t-25-25.8q-9-16.5-9-39.7a87 87 0 0 1 5.4-32 67 67 0 0 1 38-38q12.9-5 27.6-5 12.9 0 24 3.7 11.1 3.6 19.7 10.4a50 50 0 0 1 20.2 36z" fill="white"/>',
'r' => '<path d="M17.6 148.7V3.2h62.8q16.2 0 28.3 6 12 5.8 18.9 16.9a50 50 0 0 1 6.7 26.6q0 15.6-7 26.3A43 43 0 0 1 108 95.2q-12.5 5.5-29 5.5H41.3V70H71q7 0 11.8-1.7a15 15 0 0 0 7.6-5.6 17 17 0 0 0 2.7-10q0-6.3-2.7-10.2a15 15 0 0 0-7.6-5.8q-4.9-2-11.8-2H57v114zm85.2-66.8 36.4 66.8h-43L60.9 81.9z" fill="white"/>',
'a' => '<path d="M42.6 148.7H0L48 3.2h54l48 145.5h-42.7L75.5 43.3h-1.1zm-8-57.4h80.1v29.5h-80z" fill="white"/>',
'd' => '<path d="M67 148.7H11V3.2h55.4q22.5 0 38.8 8.8a60 60 0 0 1 25.3 25q9 16.2 9 39 0 22.6-9 39a61 61 0 0 1-25 25 81 81 0 0 1-38.5 8.7m-16.5-33.5h15a44 44 0 0 0 18.5-3.5q7.6-3.5 11.6-12t4-23.8q0-15-4-23.7a24 24 0 0 0-12-12 47 47 0 0 0-19.2-3.5H50.5z" fill="white"/>',
'i' => '<path d="M94.8 3.2v145.5H55.3V3.2z" fill="white"/>',
'e' => '<path d="M22.2 148.7V3.2H127V35H61.7v25h59.9v32h-60v25h65.1v31.8z" fill="white"/>',
'n' => '<path d="M138 3.2v145.5h-33L52.5 72.3h-1v76.4H12.2V3.2h33.5l51.7 76.2h1.2V3.2z" fill="white"/>',
't' => '<path d="M11.7 35V3.2h126.5V35H94.4v113.7h-39V35z" fill="white"/>',
),
'shape1' => array(
'g' => '<path d="M101.6 51.1a20 20 0 0 0-14.7-14.5q-3.9-1-8.5-1.1-10 0-17 4.8A30 30 0 0 0 50.9 54q-3.6 9-3.6 21.6 0 12.8 3.5 21.8 3.4 9 10.3 14a29 29 0 0 0 17.2 4.8q9 0 15-2.6 6-2.7 9-7.5t3-11.4l6.8.7H78.7v-28h64.5v20q0 20-8.4 34.1a56 56 0 0 1-23.2 21.6Q97 150.6 78 150.6q-21.3 0-37.3-9t-25-25.8-9-39.7a87 87 0 0 1 5.4-32 67 67 0 0 1 38-38Q63 1 77.6 1q12.9 0 24 3.7 11.1 3.6 19.7 10.4a50 50 0 0 1 20.2 36z" fill="white"/>',
'r' => '<path d="M17.6 148.6V3h62.8q16.2 0 28.3 6 12 5.8 18.9 16.9a50 50 0 0 1 6.7 26.5q0 15.7-7 26.4A43 43 0 0 1 108 95q-12.5 5.5-29 5.5H41.3V69.9H71q7 0 11.8-1.7a15 15 0 0 0 7.6-5.6 17 17 0 0 0 2.7-10q0-6.3-2.7-10.2a15 15 0 0 0-7.6-5.8q-4.9-2-11.8-2H57v114zm85.2-66.8 36.4 66.8h-43L60.9 81.8z" fill="white"/>',
'a' => '<path d="M42.6 148.6H0L48 3h54l48 145.5h-42.7L75.5 43.2h-1.1zm-8-57.4h80.1v29.5h-80z" fill="white"/>',
'd' => '<path d="M67 148.6H11V3h55.4q22.5 0 38.8 8.7a60 60 0 0 1 25.3 25q9 16.4 9 39 0 22.8-9 39.1a61 61 0 0 1-25 25 81 81 0 0 1-38.5 8.7M50.5 115h15a44 44 0 0 0 18.5-3.4q7.6-3.6 11.6-12t4-23.8q0-15-4-23.7a24 24 0 0 0-12-12 47 47 0 0 0-19.2-3.5H50.5z" fill="white"/>',
'i' => '<path d="M94.8 3.1v145.5H55.3V3z" fill="white"/>',
'e' => '<path d="M22.2 148.6V3H127v32H61.7v25h59.9v31.8h-60v25h65.1v31.9z" fill="white"/>',
'n' => '<path d="M138 3.1v145.5h-33L52.5 72h-1v76.5H12.2V3h33.5l51.7 76.1h1.2v-76z" fill="white"/>',
't' => '<path d="M11.7 35V3h126.5v32H94.4v113.7h-39V34.9z" fill="white"/>',
),
),
);