为没有头像的访客按邮箱确定性地生成一张 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。
133 lines
5.4 KiB
PHP
133 lines
5.4 KiB
PHP
<?php
|
|
return array(
|
|
'name' => 'identicon',
|
|
'meta' => array(
|
|
'title' => 'Identicon',
|
|
'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 5 5',
|
|
'clip_default' => true,
|
|
'has_pre_create' => false,
|
|
'has_post_create' => false,
|
|
'is_initials' => false,
|
|
'attr_pairs' => array(
|
|
array('viewBox', '0 0 5 5'),
|
|
array('fill', 'none'),
|
|
array('shape-rendering', 'crispEdges'),
|
|
),
|
|
'bg_colors' => array(),
|
|
'bg_type' => array('solid'),
|
|
'bg_rotation' => array('0', '360'),
|
|
'body_template' => '@@k:row1@@@@k:row2@@@@k:row3@@@@k:row4@@@@k:row5@@',
|
|
'pick_order' => array('row1', 'row2', 'row3', 'row4', 'row5'),
|
|
'bool_order' => array(),
|
|
'color_order' => array(
|
|
'row' => 'rowColor',
|
|
),
|
|
'pc_has_ints' => false,
|
|
'pc_meta' => array(
|
|
array(
|
|
'group' => 'row1',
|
|
'values_opt' => 'row1',
|
|
'width' => 0,
|
|
'height' => 0,
|
|
'rotation' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetX' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetY' => array('opt' => null, 'fallback' => array('0')),
|
|
),
|
|
array(
|
|
'group' => 'row2',
|
|
'values_opt' => 'row2',
|
|
'width' => 0,
|
|
'height' => 0,
|
|
'rotation' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetX' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetY' => array('opt' => null, 'fallback' => array('0')),
|
|
),
|
|
array(
|
|
'group' => 'row3',
|
|
'values_opt' => 'row3',
|
|
'width' => 0,
|
|
'height' => 0,
|
|
'rotation' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetX' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetY' => array('opt' => null, 'fallback' => array('0')),
|
|
),
|
|
array(
|
|
'group' => 'row4',
|
|
'values_opt' => 'row4',
|
|
'width' => 0,
|
|
'height' => 0,
|
|
'rotation' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetX' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetY' => array('opt' => null, 'fallback' => array('0')),
|
|
),
|
|
array(
|
|
'group' => 'row5',
|
|
'values_opt' => 'row5',
|
|
'width' => 0,
|
|
'height' => 0,
|
|
'rotation' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetX' => array('opt' => null, 'fallback' => array('0')),
|
|
'offsetY' => array('opt' => null, 'fallback' => array('0')),
|
|
),
|
|
),
|
|
'defaults' => array(
|
|
'row1' => array('xooox', 'xxoxx', 'xoxox', 'oxxxo', 'xxxxx', 'oxoxo', 'ooxoo'),
|
|
'row2' => array('xooox', 'xxoxx', 'xoxox', 'oxxxo', 'xxxxx', 'oxoxo', 'ooxoo'),
|
|
'row3' => array('xooox', 'xxoxx', 'xoxox', 'oxxxo', 'xxxxx', 'oxoxo', 'ooxoo'),
|
|
'row4' => array('xooox', 'xxoxx', 'xoxox', 'oxxxo', 'xxxxx', 'oxoxo', 'ooxoo'),
|
|
'row5' => array('xooox', 'xxoxx', 'xoxox', 'oxxxo', 'xxxxx', 'oxoxo', 'ooxoo'),
|
|
'rowColor' => array('e53935', 'ffb300', '1e88e5', '546e7a', '6d4c41', '00acc1', 'f4511e', '5e35b1', '43a047', '757575', '3949ab', '039be5', '7cb342', 'c0ca33', 'fb8c00', 'd81b60', '8e24aa', '00897b', 'fdd835'),
|
|
),
|
|
'groups' => array(
|
|
'row1' => array(
|
|
'xooox' => '<path d="M1 0H0v1h1V0ZM5 0H4v1h1V0Z" fill="@@c:row@@"/>',
|
|
'xxoxx' => '<path d="M2 0H0v1h2V0ZM5 0H3v1h2V0Z" fill="@@c:row@@"/>',
|
|
'xoxox' => '<path d="M0 0h1v1H0V0ZM4 0h1v1H4V0ZM3 0H2v1h1V0Z" fill="@@c:row@@"/>',
|
|
'oxxxo' => '<path fill="@@c:row@@" d="M1 0h3v1H1z"/>',
|
|
'xxxxx' => '<path fill="@@c:row@@" d="M0 0h5v1H0z"/>',
|
|
'oxoxo' => '<path d="M2 0H1v1h1V0ZM4 0H3v1h1V0Z" fill="@@c:row@@"/>',
|
|
'ooxoo' => '<path fill="@@c:row@@" d="M2 0h1v1H2z"/>',
|
|
),
|
|
'row2' => array(
|
|
'xooox' => '<path d="M1 1H0v1h1V1ZM5 1H4v1h1V1Z" fill="@@c:row@@"/>',
|
|
'xxoxx' => '<path d="M2 1H0v1h2V1ZM5 1H3v1h2V1Z" fill="@@c:row@@"/>',
|
|
'xoxox' => '<path d="M0 1h1v1H0V1ZM4 1h1v1H4V1ZM3 1H2v1h1V1Z" fill="@@c:row@@"/>',
|
|
'oxxxo' => '<path fill="@@c:row@@" d="M1 1h3v1H1z"/>',
|
|
'xxxxx' => '<path fill="@@c:row@@" d="M0 1h5v1H0z"/>',
|
|
'oxoxo' => '<path d="M2 1H1v1h1V1ZM4 1H3v1h1V1Z" fill="@@c:row@@"/>',
|
|
'ooxoo' => '<path fill="@@c:row@@" d="M2 1h1v1H2z"/>',
|
|
),
|
|
'row3' => array(
|
|
'xooox' => '<path d="M1 2H0v1h1V2ZM5 2H4v1h1V2Z" fill="@@c:row@@"/>',
|
|
'xxoxx' => '<path d="M2 2H0v1h2V2ZM5 2H3v1h2V2Z" fill="@@c:row@@"/>',
|
|
'xoxox' => '<path d="M0 2h1v1H0V2ZM4 2h1v1H4V2ZM3 2H2v1h1V2Z" fill="@@c:row@@"/>',
|
|
'oxxxo' => '<path fill="@@c:row@@" d="M1 2h3v1H1z"/>',
|
|
'xxxxx' => '<path fill="@@c:row@@" d="M0 2h5v1H0z"/>',
|
|
'oxoxo' => '<path d="M2 2H1v1h1V2ZM4 2H3v1h1V2Z" fill="@@c:row@@"/>',
|
|
'ooxoo' => '<path fill="@@c:row@@" d="M2 2h1v1H2z"/>',
|
|
),
|
|
'row4' => array(
|
|
'xooox' => '<path d="M1 3H0v1h1V3ZM5 3H4v1h1V3Z" fill="@@c:row@@"/>',
|
|
'xxoxx' => '<path d="M2 3H0v1h2V3ZM5 3H3v1h2V3Z" fill="@@c:row@@"/>',
|
|
'xoxox' => '<path d="M0 3h1v1H0V3ZM4 3h1v1H4V3ZM3 3H2v1h1V3Z" fill="@@c:row@@"/>',
|
|
'oxxxo' => '<path fill="@@c:row@@" d="M1 3h3v1H1z"/>',
|
|
'xxxxx' => '<path fill="@@c:row@@" d="M0 3h5v1H0z"/>',
|
|
'oxoxo' => '<path d="M2 3H1v1h1V3ZM4 3H3v1h1V3Z" fill="@@c:row@@"/>',
|
|
'ooxoo' => '<path fill="@@c:row@@" d="M2 3h1v1H2z"/>',
|
|
),
|
|
'row5' => array(
|
|
'xooox' => '<path d="M1 4H0v1h1V4ZM5 4H4v1h1V4Z" fill="@@c:row@@"/>',
|
|
'xxoxx' => '<path d="M2 4H0v1h2V4ZM5 4H3v1h2V4Z" fill="@@c:row@@"/>',
|
|
'xoxox' => '<path d="M0 4h1v1H0V4ZM4 4h1v1H4V4ZM3 4H2v1h1V4Z" fill="@@c:row@@"/>',
|
|
'oxxxo' => '<path fill="@@c:row@@" d="M1 4h3v1H1z"/>',
|
|
'xxxxx' => '<path fill="@@c:row@@" d="M0 4h5v1H0z"/>',
|
|
'oxoxo' => '<path d="M2 4H1v1h1V4ZM4 4H3v1h1V4Z" fill="@@c:row@@"/>',
|
|
'ooxoo' => '<path fill="@@c:row@@" d="M2 4h1v1H2z"/>',
|
|
),
|
|
),
|
|
); |