「个人简介」挂件:头像/昵称/简介/背景四项可单独填 + 修大图撑爆 #21
一、头像 / 昵称 / 简介原来**只读主题设置**,挂件表单里没有字段
· 用户点开侧栏「个人简介」只能改背景图,头像和那句话得跑去
「主题设置 → 站长信息」改全站共用的那份。
· 现在四项都是挂件实例字段:avatar / nickname / introduce / background。
· 取值顺序:实例 → 主题设置 → 内置兜底,**留空即回落**,
老站点升级后显示一点不变。
· pick() 判空统一用「trim 后是不是空串」,不用 empty()
(!empty('0') 是 false,昵称就叫「0」会被当没填)。
· 简介用 sanitize_textarea_field(保留换行),URL 用 esc_url_raw
(不做 HTML 实体化,避免与 esc_attr 双重转义)。
二、后台 form() 补齐五个字段
· 头像 / 背景各带「选择图片」按钮(调媒体库),背景另有「清除」。
· 输入框显示用户填的原始值,缩略图显示前台最终会用的那张,刻意分开。
· 资源加载统一到 yang_widget_admin_assets(),不再每个挂件各挂一次。
三、大图撑爆挂件的真因:CSS 选择器依赖三层祖先
· 老规则是 `.k-main .sidebar .w-about .wrapper img`,而后台
「外观 → 小工具」预览里没有 `.k-main .sidebar` 这层祖先,
规则一条都不生效 ⇒ 头像按原图 2136×2136 铺开、背景塌成 0。
前台看着正常是因为前台恰好有那层祖先,问题被完全掩盖。
· 改为只管 `.w-about` 内部元素,并补 max-width/max-height 双保险。
· 老规则必须**删掉**而不是并存:它是 (0,3,1),新规则是 (0,2,1),
并存时老规则在前台永远赢,新规则只对后台生效。
· 保留 `.k-main .sidebar .w-about { padding:0 }`:通用规则
`.k-main .sidebar .widget` 是 (0,3,0),少了这条会多出内边距、整体高 57px。
· 头像保持 inline(display:block 会让它脱离 text-align:center 的居中)。
· 顺带:object-fit 从 fill 改 cover(超宽图不再压扁);
背景 url() 补引号(文件名带空格时不再整条声明作废)。
验证
· 行为测试 67 条全通过;反向验证 13 组全部真红。
· 浏览器几何 76 条全通过(前台/后台 × 四种极端原图)。
· 修复前 CSS 跑同一套判据:38 条报红,精确复现用户症状
(后台挂件高 2217、头像 2136×2136、横向滚动 2146)。
· 线上真实页面 + 新版 CSS:16 条全通过。
This commit is contained in:
+92
-13
@@ -1777,43 +1777,122 @@ button:focus {
|
|||||||
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.k-main .sidebar > div:last-child {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ══════════════════════════════════════════════════════════════════════
|
||||||
|
* 「个人简介」挂件(.w-about):尺寸自持(3.2.51)
|
||||||
|
*
|
||||||
|
* ── 原来为什么会被大图撑爆 ──────────────────────────────────────────
|
||||||
|
* 这一组规则原来全部写成三段祖先选择器:
|
||||||
|
*
|
||||||
|
* .k-main .sidebar .w-about .wrapper img { width:100px; height:100px }
|
||||||
|
* .k-main .sidebar .w-about .background { height:120px }
|
||||||
|
*
|
||||||
|
* `.k-main` → `.sidebar` → `.w-about` 这三段是**前台文章页/首页的布局**。
|
||||||
|
* 挂件一旦出现在没有这层祖先的地方:
|
||||||
|
*
|
||||||
|
* · WordPress 后台「外观 → 小工具」的挂件预览
|
||||||
|
* · 区块小工具(Widgets Block Editor)的预览面板
|
||||||
|
* · 侧栏被别的主题/插件改写结构时
|
||||||
|
*
|
||||||
|
* 这些规则**一条都不生效**,头像 <img> 没有任何宽高约束,直接按原图
|
||||||
|
* 尺寸铺开;`.background` 也会塌成 0 高。
|
||||||
|
*
|
||||||
|
* 线上实测(avatar.jpg 原图 2136×2136):
|
||||||
|
* 有祖先 → 挂件 397×285, 头像 100×100 ✅
|
||||||
|
* 无祖先 → 挂件 1400×2217,头像 2136×2136 ❌ 撑爆,横向滚动 2156px
|
||||||
|
*
|
||||||
|
* ── 现在的做法 ──────────────────────────────────────────────────────
|
||||||
|
* 只依赖**挂件自己的类名** `.w-about`,不再依赖外层布局。这样无论挂件
|
||||||
|
* 被丢进什么容器,头像都是固定小圆、背景都是固定高。
|
||||||
|
*
|
||||||
|
* 🔴 必须把老的三段选择器**删掉**,不是"再补一条新的"。
|
||||||
|
* 实测过:`.k-main .sidebar .w-about .wrapper img` 优先级 (0,3,1),
|
||||||
|
* 而 `.w-about .wrapper img` 只有 (0,2,1)。两条并存时**老规则在前台永远赢**,
|
||||||
|
* 于是新规则只在后台生效 —— 前台改新规则等于白改,而且会让人误以为
|
||||||
|
* "改了没反应"。我把头像故意改成 200px 反证过:像素完全不变,
|
||||||
|
* 因为前台根本用的是老规则。单一真相来源才不会有这个坑。
|
||||||
|
*
|
||||||
|
* 🔴 为什么不用 !important:那会让用户/子主题以后想改头像尺寸时
|
||||||
|
* 完全改不动。
|
||||||
|
* ══════════════════════════════════════════════════════════════════════ */
|
||||||
|
|
||||||
|
/* 背景条:高度不依赖祖先 */
|
||||||
|
.w-about .background {
|
||||||
|
height: 120px;
|
||||||
|
background-position: center center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* 挂件自身去掉内边距。
|
||||||
|
*
|
||||||
|
* 原来写的是 `.k-main .sidebar .w-about, .k-main .sidebar .w-ad { padding:0 }`
|
||||||
|
* —— 依赖祖先,所以后台预览里会吃 `.widget` 的 15px 内边距,跟前台不一致。
|
||||||
|
* 下面这组把它拆成两条:
|
||||||
|
* ① `.w-about` / `.w-ad`(无祖先)—— 覆盖后台/其它容器里的 `.widget` 内边距;
|
||||||
|
* ② `.k-main .sidebar .w-about` / `.w-ad`(前台)—— 覆盖主题自己的
|
||||||
|
* `.k-main .sidebar .widget { padding: 15px 30px }`。
|
||||||
|
*
|
||||||
|
* 🔴 ② 这条**不能省**:它的对手 `.k-main .sidebar .widget` 优先级 (0,3,0),
|
||||||
|
* 而 ① 只有 (0,1,0),光靠 ① 在前台会被压回去 —— 实测挂件会多出
|
||||||
|
* 15px/30px 内边距、整体高 57px(284→341),头像和文字全部错位。
|
||||||
|
* 两处的**值必须一致**(都是 0),前后台才不会出现分歧。 */
|
||||||
|
.w-about,
|
||||||
|
.w-ad {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-about,
|
.k-main .sidebar .w-about,
|
||||||
.k-main .sidebar .w-ad {
|
.k-main .sidebar .w-ad {
|
||||||
padding: 0;
|
padding: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-main .sidebar > div:last-child {
|
/* 头像:固定尺寸 + 裁切,永远不可能超出自己的盒子。
|
||||||
margin: 0;
|
*
|
||||||
}
|
* object-fit:cover 是关键 —— 原图 2136×2136、1×1、4000×100、100×4000
|
||||||
|
* 都能被忠实裁进 100×100 的圆里,不会变形,也不会把盒子撑开。
|
||||||
.k-main .sidebar .w-about .background {
|
* 在没有祖先约束的环境里尤其重要:那里没有别的规则兜底。
|
||||||
height: 120px;
|
* (原来是 fill,非正方形头像会被拉伸变形,这次一并修掉。)
|
||||||
}
|
*
|
||||||
|
* 🔴 不要写 display:block —— 实测会带来两处前台外观变化:
|
||||||
.k-main .sidebar .w-about .wrapper img {
|
* ① 头像从「行内被 text-align:center 居中」变成「块级独占一行」,
|
||||||
|
* 父级 .wrapper 高度从 50px 变 100px;
|
||||||
|
* ② x 坐标从 1096.66(居中)变成 948.33(左对齐)。
|
||||||
|
* 两处都是肉眼可见的位移。保持 inline(默认)才能原样继承 text-center。
|
||||||
|
*
|
||||||
|
* 🔴 margin 保持 `-50px 0 0`(水平 0)而不是 `auto`:水平居中本来就由
|
||||||
|
* 父级 text-align:center 完成,写 auto 在 inline 元素上也不生效。 */
|
||||||
|
.w-about .wrapper img {
|
||||||
margin: -50px 0 0;
|
margin: -50px 0 0;
|
||||||
width: 100px;
|
width: 100px;
|
||||||
height: 100px;
|
height: 100px;
|
||||||
|
max-width: 100px;
|
||||||
|
max-height: 100px;
|
||||||
|
object-fit: cover;
|
||||||
border: rgba(255, 255, 255, 0.4) 4px solid;
|
border: rgba(255, 255, 255, 0.4) 4px solid;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-about .textwidget {
|
/* 文字区:同样不依赖祖先 */
|
||||||
|
.w-about .textwidget {
|
||||||
padding: 5px 20px 20px;
|
padding: 5px 20px 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-about .textwidget p {
|
.w-about .textwidget p {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
cursor: default;
|
cursor: default;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-about .textwidget .username {
|
.w-about .textwidget .username {
|
||||||
font-size: 17px;
|
font-size: 17px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.k-main .sidebar .w-about .textwidget .about {
|
.w-about .textwidget .about {
|
||||||
font-size: 15px;
|
font-size: 15px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Vendored
+1
-1
File diff suppressed because one or more lines are too long
+267
-31
@@ -35,6 +35,83 @@ function widgets_init()
|
|||||||
}
|
}
|
||||||
add_action('widgets_init', 'widgets_init');
|
add_action('widgets_init', 'widgets_init');
|
||||||
|
|
||||||
|
/* ==========================================================================
|
||||||
|
* 后台挂件表单的公共样式与脚本(3.2.51)
|
||||||
|
*
|
||||||
|
* ── 为什么不再各个挂件自己挂一份 ────────────────────────────────────
|
||||||
|
* 原来 `widget_about` / `widget_ad` 各自在构造函数里 add_action 一个
|
||||||
|
* `admin_enqueue_scripts`,动作里 enqueue 媒体库 + widget.min.js。
|
||||||
|
* 这套写法有两个毛病:
|
||||||
|
*
|
||||||
|
* ① 两个挂件都启用时,同一份资源被 enqueue 两次(id 相同,WP 会去重,
|
||||||
|
* 但每次都会多跑一次 wp_enqueue_media,白白初始化一遍媒体框);
|
||||||
|
* ② 更要紧的:媒体框的 JS 只在**挂件实例展开**用得上,可 WP 的旧版
|
||||||
|
* 挂件页会把所有实例的 form() 一次性渲染进页面 —— 资源没到就等于
|
||||||
|
* 「选择图片」按钮点了没反应。
|
||||||
|
*
|
||||||
|
* 现在统一挂在一个函数里,只在挂件相关页面加载。
|
||||||
|
*
|
||||||
|
* 🔴 缩略图预览这部分样式用 wp_add_inline_style 而不是去改
|
||||||
|
* assets/css/widget.min.css:那个文件**没有源文件**(仓库里只有压缩产物),
|
||||||
|
* 为了加几条规则去手改一行压缩 CSS,下次别人再改就无从维护。
|
||||||
|
* ====================================================================== */
|
||||||
|
|
||||||
|
if (!function_exists('yang_widget_admin_assets')):
|
||||||
|
|
||||||
|
function yang_widget_admin_assets($hook)
|
||||||
|
{
|
||||||
|
// 只在挂件页 / 自定义器加载。别的后台页面不需要媒体框。
|
||||||
|
if (!in_array($hook, array('widgets.php', 'customize.php'), true)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
wp_enqueue_media();
|
||||||
|
wp_enqueue_script('widget_scripts', ASSET_PATH . '/assets/js/widget.min.js', array('jquery'), THEME_VERSION, true);
|
||||||
|
wp_enqueue_style('widget_css', ASSET_PATH . '/assets/css/widget.min.css', array(), THEME_VERSION);
|
||||||
|
|
||||||
|
$css = '
|
||||||
|
/* 挂件表单里的图片缩略图。
|
||||||
|
没有它的话,用户只能看到一串 URL,根本不知道那张图长什么样 ——
|
||||||
|
「背景图在哪改」这个问题有一半是"看不见"造成的。 */
|
||||||
|
.yang-media-preview{display:block;margin:8px 0 0;line-height:0}
|
||||||
|
.yang-media-preview img{display:block;width:64px;height:64px;object-fit:cover;border:1px solid #dcdcde;border-radius:4px;background:#f6f7f7}
|
||||||
|
.yang-media-preview-wide img{width:100%;max-width:260px;height:auto;aspect-ratio:16/9}
|
||||||
|
/* 「清除」按钮跟「选择图片」贴在一起,别让它单独换行成一个大按钮 */
|
||||||
|
.media-widget-control .yang-media-clear{margin-left:6px}
|
||||||
|
';
|
||||||
|
wp_register_style('yang-widget-admin', false, array('widget_css'), THEME_VERSION);
|
||||||
|
wp_enqueue_style('yang-widget-admin');
|
||||||
|
wp_add_inline_style('yang-widget-admin', $css);
|
||||||
|
|
||||||
|
/* 「清除」按钮:把输入框清空,并顺手把缩略图去掉。
|
||||||
|
*
|
||||||
|
* 🔴 为什么必须同时清缩略图:只清 input 的话,图片还挂在那儿,
|
||||||
|
* 用户会以为没清掉,再点一次又点回原来的图。
|
||||||
|
* 🔴 为什么用 change 事件触发:主题的 widget.min.js 靠
|
||||||
|
* `.change()` 让 WP 把表单标记为"已修改",否则直接
|
||||||
|
* 改 .val() 不会点亮「保存」按钮,用户点保存时改动丢失。 */
|
||||||
|
$js = <<<'JS'
|
||||||
|
(function(){
|
||||||
|
if (typeof jQuery === 'undefined') { return; }
|
||||||
|
jQuery(function($){
|
||||||
|
$(document).on('click', '.yang-media-clear', function(e){
|
||||||
|
e.preventDefault();
|
||||||
|
var id = $(this).attr('data-target');
|
||||||
|
if (!id) { return; }
|
||||||
|
var $input = $('#' + id);
|
||||||
|
$input.val('').trigger('change');
|
||||||
|
$input.closest('p').find('.yang-media-preview').remove();
|
||||||
|
$(this).remove();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
JS;
|
||||||
|
wp_add_inline_script('widget_scripts', $js);
|
||||||
|
}
|
||||||
|
|
||||||
|
endif;
|
||||||
|
add_action('admin_enqueue_scripts', 'yang_widget_admin_assets');
|
||||||
|
|
||||||
/* ==========================================================================
|
/* ==========================================================================
|
||||||
* 挂件标题与外框(3.2.50)
|
* 挂件标题与外框(3.2.50)
|
||||||
*
|
*
|
||||||
@@ -362,8 +439,6 @@ class widget_ad extends WP_Widget
|
|||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
add_action('admin_enqueue_scripts', array($this, 'scripts'));
|
|
||||||
|
|
||||||
$widget_ops = array(
|
$widget_ops = array(
|
||||||
'name' => __('图片广告', 'kratos'),
|
'name' => __('图片广告', 'kratos'),
|
||||||
'description' => __('显示自定义图片广告的工具', 'kratos'),
|
'description' => __('显示自定义图片广告的工具', 'kratos'),
|
||||||
@@ -372,14 +447,6 @@ class widget_ad extends WP_Widget
|
|||||||
parent::__construct(false, false, $widget_ops);
|
parent::__construct(false, false, $widget_ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scripts()
|
|
||||||
{
|
|
||||||
wp_enqueue_script('media-upload');
|
|
||||||
wp_enqueue_media();
|
|
||||||
wp_enqueue_script('widget_scripts', ASSET_PATH . '/assets/js/widget.min.js', array('jquery'));
|
|
||||||
wp_enqueue_style('widget_css', ASSET_PATH . '/assets/css/widget.min.css', array());
|
|
||||||
}
|
|
||||||
|
|
||||||
public function widget($args, $instance)
|
public function widget($args, $instance)
|
||||||
{
|
{
|
||||||
$subtitle = !empty($instance['subtitle']) ? $instance['subtitle'] : __('广告', 'kratos');
|
$subtitle = !empty($instance['subtitle']) ? $instance['subtitle'] : __('广告', 'kratos');
|
||||||
@@ -451,8 +518,6 @@ class widget_about extends WP_Widget
|
|||||||
{
|
{
|
||||||
public function __construct()
|
public function __construct()
|
||||||
{
|
{
|
||||||
add_action('admin_enqueue_scripts', array($this, 'scripts'));
|
|
||||||
|
|
||||||
$widget_ops = array(
|
$widget_ops = array(
|
||||||
'name' => __('个人简介', 'kratos'),
|
'name' => __('个人简介', 'kratos'),
|
||||||
'description' => __('可跳转后台的个人简介展示工具', 'kratos'),
|
'description' => __('可跳转后台的个人简介展示工具', 'kratos'),
|
||||||
@@ -461,20 +526,76 @@ class widget_about extends WP_Widget
|
|||||||
parent::__construct(false, false, $widget_ops);
|
parent::__construct(false, false, $widget_ops);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function scripts()
|
/**
|
||||||
|
* 取挂件实例里的某个值;空则返回回退值。
|
||||||
|
*
|
||||||
|
* 🔴 为什么不用 `!empty()` 判定:
|
||||||
|
* ① 用户在后台把字段清空后,update() 存进去的是空字符串 '',
|
||||||
|
* `!empty('')` 为 false —— 这一条两边一致,没问题;
|
||||||
|
* ② 但 `!empty('0')` 也是 **false**!昵称就叫「0」、简介只写「0」
|
||||||
|
* 的站点会莫名其妙被回落到主题设置。判空只能判「去掉空白后是空串」。
|
||||||
|
*
|
||||||
|
* 3.2.51 起 widget_about 的四个字段都走这里,取值顺序统一为:
|
||||||
|
* 实例自己的值 → $fallback(主题设置 / 主题自带默认图)
|
||||||
|
*
|
||||||
|
* @param array $instance 挂件实例
|
||||||
|
* @param string $key 字段名
|
||||||
|
* @param string $fallback 实例里没填时用的值
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function pick($instance, $key, $fallback)
|
||||||
{
|
{
|
||||||
wp_enqueue_script('media-upload');
|
$v = isset($instance[$key]) ? $instance[$key] : '';
|
||||||
wp_enqueue_media();
|
if (!is_string($v)) {
|
||||||
wp_enqueue_script('widget_scripts', ASSET_PATH . '/assets/js/widget.min.js', array('jquery'));
|
return $fallback;
|
||||||
wp_enqueue_style('widget_css', ASSET_PATH . '/assets/css/widget.min.css', array());
|
}
|
||||||
|
|
||||||
|
$v = trim($v);
|
||||||
|
|
||||||
|
return ($v === '') ? $fallback : $v;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function widget($args, $instance)
|
public function widget($args, $instance)
|
||||||
{
|
{
|
||||||
$introduce = kratos_option('a_about', __('保持饥渴的专注,追求最佳的品质', 'kratos'));
|
/* ══════════════════════════════════════════════════════════════
|
||||||
$username = kratos_option('a_nickname', __('Kratos', 'kratos'));
|
* 3.2.51:这三项原来**只读主题设置**,挂件表单里压根没有对应字段。
|
||||||
$avatar = yang_option_image('a_gravatar');
|
*
|
||||||
$background = !empty($instance['background']) ? $instance['background'] : ASSET_PATH . '/assets/img/about-background.png';
|
* 老代码长这样(拿掉注释后的实质):
|
||||||
|
* $introduce = kratos_option('a_about', ...);
|
||||||
|
* $username = kratos_option('a_nickname', ...);
|
||||||
|
* $avatar = yang_option_image('a_gravatar');
|
||||||
|
*
|
||||||
|
* 于是用户点开侧栏「个人简介」,表单里只有「背景图片」一个字段,
|
||||||
|
* 头像 / 昵称 / 简介**无处可填**,只能跑去「主题设置 → 站长信息」
|
||||||
|
* 改那份全站共用的。同一侧栏想放两个人的简介就不可能。
|
||||||
|
*
|
||||||
|
* 取值顺序(实例优先,留空回落):
|
||||||
|
* ① 挂件实例自己填的
|
||||||
|
* ② 主题设置「站长信息」—— **必须保留**:线上站点正用着
|
||||||
|
* 「老杨 / 博士,非博学之士…」那一套,改成"空了就空着"
|
||||||
|
* 会让所有老站点升级后侧栏突然变空白。
|
||||||
|
* ③ 代码里的兜底文案
|
||||||
|
* ══════════════════════════════════════════════════════════════ */
|
||||||
|
$introduce = $this->pick(
|
||||||
|
$instance,
|
||||||
|
'introduce',
|
||||||
|
kratos_option('a_about', __('保持饥渴的专注,追求最佳的品质', 'kratos'))
|
||||||
|
);
|
||||||
|
$username = $this->pick(
|
||||||
|
$instance,
|
||||||
|
'nickname',
|
||||||
|
kratos_option('a_nickname', __('Kratos', 'kratos'))
|
||||||
|
);
|
||||||
|
$avatar = $this->pick(
|
||||||
|
$instance,
|
||||||
|
'avatar',
|
||||||
|
yang_option_image('a_gravatar')
|
||||||
|
);
|
||||||
|
$background = $this->pick(
|
||||||
|
$instance,
|
||||||
|
'background',
|
||||||
|
ASSET_PATH . '/assets/img/about-background.png'
|
||||||
|
);
|
||||||
|
|
||||||
/* 🔴 原来写的是 `background:url(' . $background . ') no-repeat ...`
|
/* 🔴 原来写的是 `background:url(' . $background . ') no-repeat ...`
|
||||||
* —— url() 里的地址**没有引号**。只要路径含空格、括号、单引号,
|
* —— url() 里的地址**没有引号**。只要路径含空格、括号、单引号,
|
||||||
@@ -492,9 +613,9 @@ class widget_about extends WP_Widget
|
|||||||
* 统一转义一次。这样 `&` 只会被编成 `&`(正确),
|
* 统一转义一次。这样 `&` 只会被编成 `&`(正确),
|
||||||
* 浏览器解析回属性值后仍是原始的 `&`。
|
* 浏览器解析回属性值后仍是原始的 `&`。
|
||||||
*
|
*
|
||||||
* 🔴 $introduce 来自主题选项 textarea,$username 来自 text,
|
* 🔴 $introduce / $username / $avatar 现在都可能来自**后台表单**
|
||||||
* 原来是裸 echo。个人简介里写个 < 就把后面的结构吃掉了。
|
* (用户自己填的),原来是裸 echo。个人简介里写个 < 就把后面的
|
||||||
* 简介保留换行(textarea 的自然语义),其余全部转义。 */
|
* 结构吃掉了。简介保留换行(textarea 的自然语义),其余全部转义。 */
|
||||||
$bg = 'background-image:url("' . esc_url_raw($background) . '");'
|
$bg = 'background-image:url("' . esc_url_raw($background) . '");'
|
||||||
. 'background-repeat:no-repeat;'
|
. 'background-repeat:no-repeat;'
|
||||||
. 'background-position:center center;'
|
. 'background-position:center center;'
|
||||||
@@ -517,31 +638,146 @@ class widget_about extends WP_Widget
|
|||||||
echo '</div>';
|
echo '</div>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 保存实例。
|
||||||
|
*
|
||||||
|
* 🔴 不能用 `!empty($new_instance[...])` 判"用户填了没有":
|
||||||
|
* `!empty('0')` 是 **false** ⇒ 昵称就叫「0」、简介只写「0」的站点
|
||||||
|
* 保存后会变成空串、前台回落主题设置,而且**后台看起来像没保存成功**。
|
||||||
|
* 测试 `_about_test.php` 的「昵称『0』能存下来」抓到的就是这个。
|
||||||
|
* 统一用「转成字符串、trim 后是不是空串」判断,与 pick() 一致。
|
||||||
|
*/
|
||||||
public function update($new_instance, $old_instance)
|
public function update($new_instance, $old_instance)
|
||||||
{
|
{
|
||||||
$instance = array();
|
$instance = array();
|
||||||
|
|
||||||
$instance['title'] = (!empty($new_instance['title'])) ? sanitize_text_field($new_instance['title']) : '';
|
$instance['title'] = $this->clean_text($new_instance, 'title');
|
||||||
$instance['background'] = (!empty($new_instance['background'])) ? esc_url_raw($new_instance['background']) : '';
|
$instance['background'] = $this->clean_url($new_instance, 'background');
|
||||||
|
|
||||||
|
/* 3.2.51 新增三项。
|
||||||
|
*
|
||||||
|
* 🔴 简介是 textarea,**不能**用 sanitize_text_field:
|
||||||
|
* 那个函数会去掉所有换行,用户分段的简介会挤成一整段。
|
||||||
|
* 要用 sanitize_textarea_field(WP 4.7+ 才有)。
|
||||||
|
* 主题没声明最低 WP 版本,所以对更老的版本做个兜底 ——
|
||||||
|
* 兜底只做「去标签 + 规整换行」,不追求完全等价。
|
||||||
|
*
|
||||||
|
* 🔴 头像/背景是 URL,用 esc_url_raw(只清洗协议与字符),
|
||||||
|
* 不要在这里 esc_url —— 存进库的值必须是可以直接用的原地址。 */
|
||||||
|
$instance['avatar'] = $this->clean_url($new_instance, 'avatar');
|
||||||
|
$instance['nickname'] = $this->clean_text($new_instance, 'nickname');
|
||||||
|
$instance['introduce'] = $this->clean_textarea($new_instance, 'introduce');
|
||||||
|
|
||||||
return $instance;
|
return $instance;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 取一个新提交的字段;没提交或只有空白 → 空串。
|
||||||
|
*
|
||||||
|
* @param array $src 新提交的实例数组
|
||||||
|
* @param string $key 字段名
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
private function submitted($src, $key)
|
||||||
|
{
|
||||||
|
if (!isset($src[$key])) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
$v = $src[$key];
|
||||||
|
if (!is_string($v)) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clean_text($src, $key)
|
||||||
|
{
|
||||||
|
$v = $this->submitted($src, $key);
|
||||||
|
|
||||||
|
return $v === '' ? '' : sanitize_text_field($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clean_url($src, $key)
|
||||||
|
{
|
||||||
|
$v = $this->submitted($src, $key);
|
||||||
|
|
||||||
|
return $v === '' ? '' : esc_url_raw($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function clean_textarea($src, $key)
|
||||||
|
{
|
||||||
|
$v = $this->submitted($src, $key);
|
||||||
|
if ($v === '') {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('sanitize_textarea_field')) {
|
||||||
|
return sanitize_textarea_field($v);
|
||||||
|
}
|
||||||
|
|
||||||
|
return trim(preg_replace('/\r\n|\r/', "\n", wp_strip_all_tags($v)));
|
||||||
|
}
|
||||||
|
|
||||||
public function form($instance)
|
public function form($instance)
|
||||||
{
|
{
|
||||||
$title = !empty($instance['title']) ? $instance['title'] : '';
|
$title = !empty($instance['title']) ? $instance['title'] : '';
|
||||||
$background = !empty($instance['background']) ? $instance['background'] : ASSET_PATH . '/assets/img/about-background.png';
|
|
||||||
|
/* 输入框里显示**用户自己填的原始值**(没填就是空),
|
||||||
|
* 预览图显示**前台实际会用的那张**(含回落)。
|
||||||
|
*
|
||||||
|
* 两者刻意分开:
|
||||||
|
* · 输入框若直接显示回落值,用户会以为"我已经填了",
|
||||||
|
* 保存后才发现其实存的是主题设置那份;
|
||||||
|
* · 预览图画的是最终效果,用户一眼能看出现在用的是哪张。 */
|
||||||
|
$avatarRaw = isset($instance['avatar']) ? trim((string) $instance['avatar']) : '';
|
||||||
|
$bgRaw = isset($instance['background']) ? trim((string) $instance['background']) : '';
|
||||||
|
$nicknameRaw = isset($instance['nickname']) ? trim((string) $instance['nickname']) : '';
|
||||||
|
$introduceRaw = isset($instance['introduce']) ? (string) $instance['introduce'] : '';
|
||||||
|
|
||||||
|
$avatarShown = $this->pick($instance, 'avatar', yang_option_image('a_gravatar'));
|
||||||
|
$bgShown = $this->pick($instance, 'background', ASSET_PATH . '/assets/img/about-background.png');
|
||||||
?>
|
?>
|
||||||
<div class="media-widget-control">
|
<div class="media-widget-control">
|
||||||
<p>
|
<p>
|
||||||
<label for="<?php echo $this->get_field_id('title'); ?>"><?php _e('标题:', 'kratos');?></label>
|
<label for="<?php echo esc_attr($this->get_field_id('title')); ?>"><?php _e('标题:', 'kratos');?></label>
|
||||||
<input class="widefat" id="<?php echo $this->get_field_id('title'); ?>" name="<?php echo $this->get_field_name('title'); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('title')); ?>" name="<?php echo esc_attr($this->get_field_name('title')); ?>" type="text" value="<?php echo esc_attr($title); ?>">
|
||||||
<span class="description"><?php _e('留空则不显示标题。', 'kratos');?></span>
|
<span class="description"><?php _e('留空则不显示标题。', 'kratos');?></span>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
<label for="<?php echo $this->get_field_id('background'); ?>"><?php _e('背景图片:', 'kratos');?></label>
|
<label for="<?php echo esc_attr($this->get_field_id('avatar')); ?>"><?php _e('个人头像:', 'kratos');?></label>
|
||||||
<input class="widefat" id="<?php echo $this->get_field_id('background'); ?>" name="<?php echo $this->get_field_name('background'); ?>" type="text" value="<?php echo esc_attr($background); ?>">
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('avatar')); ?>" name="<?php echo esc_attr($this->get_field_name('avatar')); ?>" type="text" value="<?php echo esc_attr($avatarRaw); ?>">
|
||||||
|
<button type="button" class="button-update-media upload_avatar"><?php _e('选择图片', 'kratos');?></button>
|
||||||
|
<span class="description"><?php _e('留空则使用「主题设置 → 站长信息 → 个人头像」。', 'kratos');?></span>
|
||||||
|
<?php if ($avatarShown !== '') : ?>
|
||||||
|
<span class="yang-media-preview"><img src="<?php echo esc_url($avatarShown); ?>" alt=""></span>
|
||||||
|
<?php endif; ?>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('nickname')); ?>"><?php _e('昵称:', 'kratos');?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('nickname')); ?>" name="<?php echo esc_attr($this->get_field_name('nickname')); ?>" type="text" value="<?php echo esc_attr($nicknameRaw); ?>" placeholder="<?php echo esc_attr(kratos_option('a_nickname', __('Kratos', 'kratos'))); ?>">
|
||||||
|
<span class="description"><?php _e('留空则使用「主题设置 → 站长信息 → 个人昵称」。', 'kratos');?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('introduce')); ?>"><?php _e('个人简介:', 'kratos');?></label>
|
||||||
|
<textarea class="widefat" rows="4" id="<?php echo esc_attr($this->get_field_id('introduce')); ?>" name="<?php echo esc_attr($this->get_field_name('introduce')); ?>" placeholder="<?php echo esc_attr(kratos_option('a_about', __('保持饥渴的专注,追求最佳的品质', 'kratos'))); ?>"><?php echo esc_textarea($introduceRaw); ?></textarea>
|
||||||
|
<span class="description"><?php _e('支持换行。留空则使用「主题设置 → 站长信息 → 个人简介」。', 'kratos');?></span>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
<label for="<?php echo esc_attr($this->get_field_id('background')); ?>"><?php _e('背景图片:', 'kratos');?></label>
|
||||||
|
<input class="widefat" id="<?php echo esc_attr($this->get_field_id('background')); ?>" name="<?php echo esc_attr($this->get_field_name('background')); ?>" type="text" value="<?php echo esc_attr($bgRaw); ?>">
|
||||||
<button type="button" class="button-update-media upload_background"><?php _e('选择图片', 'kratos');?></button>
|
<button type="button" class="button-update-media upload_background"><?php _e('选择图片', 'kratos');?></button>
|
||||||
|
<?php if ($bgRaw !== '') : ?>
|
||||||
|
<button type="button" class="button yang-media-clear" data-target="<?php echo esc_attr($this->get_field_id('background')); ?>"><?php _e('清除', 'kratos');?></button>
|
||||||
|
<?php endif; ?>
|
||||||
|
<span class="description"><?php _e('留空则使用主题自带的默认背景图。', 'kratos');?></span>
|
||||||
|
<?php if ($bgShown !== '') : ?>
|
||||||
|
<span class="yang-media-preview yang-media-preview-wide"><img src="<?php echo esc_url($bgShown); ?>" alt=""></span>
|
||||||
|
<?php endif; ?>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<?php
|
<?php
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Theme Name: Kratos_Yang
|
Theme Name: Kratos_Yang
|
||||||
Text Domain: kratos_Yang
|
Text Domain: kratos_Yang
|
||||||
Version: 3.2.50
|
Version: 3.2.51
|
||||||
Requires PHP: 7.0
|
Requires PHP: 7.0
|
||||||
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
Description: 这是一个Kratos主题的分支版本,用来增加一些定制化功能。
|
||||||
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
Tags: 博客, 双栏, 响应式, 自定义背景, 自定义颜色, 自定义图标, 自定义菜单, 特色图像, 收录优化, 邮件中心, 主题选项, 小工具, 文章置顶, 无障碍友好
|
||||||
|
|||||||
Reference in New Issue
Block a user