yoone-snow/yoone-snow.php

1081 lines
60 KiB
PHP

<?php
/*
Plugin Name: Yoone Snow
Description: 首页 canvas 雪花效果
Version: 1.9.0
Author: Yoone
*/
if (!defined('ABSPATH')) { exit; }
function yoone_snow_is_enabled() {
$mode = get_option('yoone_snow_display_routes_mode', 'home');
if ($mode === 'all') {
return true;
}
if ($mode === 'match') {
$routes = get_option('yoone_snow_display_routes', array());
if (is_string($routes)) {
$routes = array_filter(array_map('trim', explode("\n", $routes)));
}
if (!is_array($routes)) {
$routes = array();
}
$requestUri = isset($_SERVER['REQUEST_URI']) ? (string)$_SERVER['REQUEST_URI'] : '';
$path = parse_url($requestUri, PHP_URL_PATH);
$path = is_string($path) ? $path : '';
foreach ($routes as $rule) {
$r = trim((string)$rule);
if ($r === '') { continue; }
if (substr($r, -1) === '*') {
$prefix = substr($r, 0, -1);
if ($prefix === '' || strpos($path, $prefix) === 0) {
return true;
}
} else {
if ($path === $r) {
return true;
}
}
}
return false;
}
return function_exists('is_front_page') ? is_front_page() : false;
}
function yoone_snow_enqueue_assets() {
if (!yoone_snow_is_enabled()) { return; }
$style_handle = 'yoone-snow-style';
$style_src = plugins_url('css/snow.css', __FILE__);
$style_ver = @filemtime(plugin_dir_path(__FILE__) . 'css/snow.css');
wp_register_style($style_handle, $style_src, array(), $style_ver ? (string)$style_ver : '1.1.0', 'all');
wp_enqueue_style($style_handle);
// 注册形状渲染脚本 保证主脚本之前加载
$shape_index_handle = 'yoone-snow-shapes-index';
$ver_shapes_index = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/index.js');
wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), $ver_shapes_index ? (string)$ver_shapes_index : '1.1.0', true);
$shape_utils_handle = 'yoone-snow-shapes-utils';
$ver_shapes_utils = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/utils.js');
wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), $ver_shapes_utils ? (string)$ver_shapes_utils : '1.1.0', true);
$shape_dot_handle = 'yoone-snow-shapes-dot';
$ver_shapes_dot = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/dot.js');
wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), $ver_shapes_dot ? (string)$ver_shapes_dot : '1.1.0', true);
$shape_flake_handle = 'yoone-snow-shapes-flake';
$ver_shapes_flake = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/flake.js');
wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), $ver_shapes_flake ? (string)$ver_shapes_flake : '1.1.0', true);
$shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao';
$ver_shapes_yuanbao = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/yuanbao.js');
wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), $ver_shapes_yuanbao ? (string)$ver_shapes_yuanbao : '1.1.0', true);
$shape_coin_handle = 'yoone-snow-shapes-coin';
$ver_shapes_coin = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/coin.js');
wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), $ver_shapes_coin ? (string)$ver_shapes_coin : '1.1.0', true);
$shape_santa_handle = 'yoone-snow-shapes-santa-hat';
$ver_shapes_santa = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/santa_hat.js');
wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), $ver_shapes_santa ? (string)$ver_shapes_santa : '1.1.0', true);
$shape_cane_handle = 'yoone-snow-shapes-candy-cane';
$ver_shapes_cane = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/candy_cane.js');
wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), $ver_shapes_cane ? (string)$ver_shapes_cane : '1.1.0', true);
$shape_sock_handle = 'yoone-snow-shapes-christmas-sock';
$ver_shapes_sock = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_sock.js');
wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), $ver_shapes_sock ? (string)$ver_shapes_sock : '1.1.0', true);
$shape_tree_handle = 'yoone-snow-shapes-christmas-tree';
$ver_shapes_tree = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_tree.js');
wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), $ver_shapes_tree ? (string)$ver_shapes_tree : '1.1.0', true);
$shape_reindeer_handle = 'yoone-snow-shapes-reindeer';
$ver_shapes_reindeer = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/reindeer.js');
wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), $ver_shapes_reindeer ? (string)$ver_shapes_reindeer : '1.1.0', true);
$shape_berry_handle = 'yoone-snow-shapes-christmas-berry';
$ver_shapes_berry = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_berry.js');
wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), $ver_shapes_berry ? (string)$ver_shapes_berry : '1.1.0', true);
// 注册并加载主脚本 设置依赖确保顺序正确
$script_handle = 'yoone-snow-script';
$script_src = plugins_url('js/snow-canvas.js', __FILE__);
$script_ver = @filemtime(plugin_dir_path(__FILE__) . 'js/snow-canvas.js');
$deps = array($shape_index_handle);
$needs_utils = false;
$mixed_items_option = get_option('yoone_snow_mixed_items', array('dot','flake'));
if (is_string($mixed_items_option)) {
$mixed_items_option = array_filter(array_map('trim', explode(',', $mixed_items_option)));
}
$allowed_shapes = array('dot','flake','yuanbao','coin','santa_hat','candy_cane','christmas_sock','christmas_tree','reindeer','christmas_berry');
$mixed_items_sanitized = array_values(array_unique(array_intersect(is_array($mixed_items_option) ? $mixed_items_option : array('dot','flake'), $allowed_shapes)));
if (empty($mixed_items_sanitized)) { $mixed_items_sanitized = array('dot','flake'); }
$media_urls = array();
foreach ($mixed_items_sanitized as $key) {
if ($key === 'dot') { $deps[] = $shape_dot_handle; }
if ($key === 'flake') { $deps[] = $shape_flake_handle; }
if ($key === 'yuanbao') { $deps[] = $shape_yuanbao_handle; }
if ($key === 'coin') { $deps[] = $shape_coin_handle; }
if ($key === 'santa_hat') { $deps[] = $shape_santa_handle; $needs_utils = true; }
if ($key === 'candy_cane') { $deps[] = $shape_cane_handle; $needs_utils = true; }
if ($key === 'christmas_sock') { $deps[] = $shape_sock_handle; $needs_utils = true; }
if ($key === 'christmas_tree') { $deps[] = $shape_tree_handle; $needs_utils = true; }
if ($key === 'reindeer') { $deps[] = $shape_reindeer_handle; $needs_utils = true; }
if ($key === 'christmas_berry') { $deps[] = $shape_berry_handle; $needs_utils = true; }
}
if (!empty($media_urls)) { $needs_utils = true; }
if ($needs_utils) { $deps[] = $shape_utils_handle; }
$deps = array_values(array_unique($deps));
wp_register_script(
$script_handle,
$script_src,
$deps,
$script_ver ? (string)$script_ver : '1.1.0',
true
);
wp_enqueue_script($script_handle);
// 将后端设置传递到前端脚本 变量名称为 YooneSnowSettings
// 简化设置 仅保留复选框选择的形状集合
$mixed_items_option = get_option('yoone_snow_mixed_items', array('dot','flake'));
if (is_string($mixed_items_option)) {
$mixed_items_option = array_filter(array_map('trim', explode(',', $mixed_items_option)));
}
$allowed_shapes = array('dot','flake','yuanbao','coin','santa_hat','candy_cane','christmas_sock','christmas_tree','reindeer','christmas_berry');
$mixed_items_sanitized = array_values(array_unique(array_intersect($mixed_items_option, $allowed_shapes)));
if (empty($mixed_items_sanitized)) { $mixed_items_sanitized = array('dot','flake'); }
// 读取媒体形状集合 并映射为可用的 URL 列表
$media_ids = get_option('yoone_snow_media_items', array());
if (!is_array($media_ids)) { $media_ids = array(); }
$media_urls = array();
$media_weights_option = get_option('yoone_snow_media_weights', array());
if (!is_array($media_weights_option)) { $media_weights_option = array(); }
$media_weights_by_url = array();
foreach ($media_ids as $mid) {
$url = wp_get_attachment_url(intval($mid));
if ($url) {
$media_urls[] = $url;
$w = isset($media_weights_option[intval($mid)]) ? intval($media_weights_option[intval($mid)]) : 1;
if ($w < 0) { $w = 0; }
$media_weights_by_url[$url] = $w;
}
}
// 形状权重默认值 映射为形状键到非负整数权重
// 权重用于控制每次生成时的相对概率 权重越大被选中越频繁
// 权重为 0 表示可选但不参与随机生成
$default_weights = array(
'dot' => 1,
'flake' => 4,
'yuanbao' => 1,
'coin' => 1,
'santa_hat' => 1,
'candy_cane' => 1,
'christmas_sock' => 1,
'christmas_tree' => 1,
'reindeer' => 1,
'christmas_berry' => 1,
);
$saved_weights = get_option('yoone_snow_shape_weights', $default_weights);
if (!is_array($saved_weights)) { $saved_weights = array(); }
// 合并用户保存的权重与默认值 并规范为非负整数
$shape_weights = array();
foreach ($default_weights as $k => $v) {
$val = isset($saved_weights[$k]) ? intval($saved_weights[$k]) : intval($v);
if ($val < 0) { $val = 0; }
$shape_weights[$k] = $val;
}
// 读取尺寸组合设置 包含最小与最大半径 如未设置则回退到旧选项与默认值
$size_group = get_option('yoone_snow_size', array('min' => 1.0, 'max' => 3.0));
$radius_min_val = isset($size_group['min']) ? floatval($size_group['min']) : floatval(get_option('yoone_snow_radius_min', 1.0));
$radius_max_val = isset($size_group['max']) ? floatval($size_group['max']) : floatval(get_option('yoone_snow_radius_max', 3.0));
if ($radius_min_val < 0) { $radius_min_val = 0.0; }
if ($radius_max_val < $radius_min_val) { $radius_max_val = $radius_min_val; }
wp_localize_script($script_handle, 'YooneSnowSettings', array(
'selectedShapes' => $mixed_items_sanitized,
'mediaItems' => $media_urls,
'displayDurationSeconds' => intval(get_option('yoone_snow_home_duration', 0)),
'maxCount' => intval(get_option('yoone_snow_max_count', 0)),
'renderAccelerationEnabled' => (function(){ $v = get_option('yoone_snow_render_acceleration', 0); return intval($v) === 1; })(),
// 新增分屏最大数量设置 分别针对小中大屏 0 表示自动
'maxCountSmall' => (function(){
$grp = get_option('yoone_snow_max_count_breakpoints', array('small' => 0, 'medium' => 0, 'large' => 0));
$val = isset($grp['small']) ? intval($grp['small']) : 0;
if ($val < 0) { $val = 0; }
if ($val > 1000) { $val = 1000; }
return $val;
})(),
'maxCountMedium' => (function(){
$grp = get_option('yoone_snow_max_count_breakpoints', array('small' => 0, 'medium' => 0, 'large' => 0));
$val = isset($grp['medium']) ? intval($grp['medium']) : 0;
if ($val < 0) { $val = 0; }
if ($val > 1000) { $val = 1000; }
return $val;
})(),
'maxCountLarge' => (function(){
$grp = get_option('yoone_snow_max_count_breakpoints', array('small' => 0, 'medium' => 0, 'large' => 0));
$val = isset($grp['large']) ? intval($grp['large']) : 0;
if ($val < 0) { $val = 0; }
if ($val > 1000) { $val = 1000; }
return $val;
})(),
'radiusMin' => $radius_min_val,
'radiusMax' => $radius_max_val,
'driftMin' => floatval(get_option('yoone_snow_drift_min', 0.4)),
'driftMax' => floatval(get_option('yoone_snow_drift_max', 1.0)),
'swingMin' => floatval(get_option('yoone_snow_swing_min', 0.2)),
'swingMax' => floatval(get_option('yoone_snow_swing_max', 1.0)),
'assetsMap' => array(
'santa_hat' => plugins_url('assets/圣诞雪帽.svg', __FILE__),
'candy_cane' => plugins_url('assets/圣诞拐杖.svg', __FILE__),
'christmas_sock' => plugins_url('assets/圣诞袜子.svg', __FILE__),
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
),
'shapeWeights' => $shape_weights,
'mediaWeights' => $media_weights_by_url,
'emojiItems' => (function(){
// 读取已保存的 emoji 列表 返回字符串数组
$items = get_option('yoone_snow_emoji_items', array());
if (!is_array($items)) { $items = array(); }
$clean = array();
foreach ($items as $it) {
$s = trim((string)$it);
if ($s !== '') { $clean[] = $s; }
}
return $clean;
})(),
'emojiWeights' => (function(){
// 读取已保存的 emoji 权重 映射为字符到权重
$map = get_option('yoone_snow_emoji_weights', array());
if (!is_array($map)) { $map = array(); }
$clean = array();
foreach ($map as $k => $v) {
$key = trim((string)$k);
$num = intval($v);
if ($key !== '') {
if ($num < 0) { $num = 0; }
$clean[$key] = $num;
}
}
return $clean;
})(),
'textItems' => (function(){
$items = get_option('yoone_snow_text_items', array());
if (!is_array($items)) { $items = array(); }
$clean = array();
foreach ($items as $it) {
$s = trim((string)$it);
if ($s !== '') { $clean[] = $s; }
}
return $clean;
})(),
'textWeights' => (function(){
$map = get_option('yoone_snow_text_weights', array());
if (!is_array($map)) { $map = array(); }
$clean = array();
foreach ($map as $k => $v) {
$key = trim((string)$k);
$num = intval($v);
if ($key !== '') {
if ($num < 0) { $num = 0; }
$clean[$key] = $num;
}
}
return $clean;
})(),
));
}
// 在后台设置页面加载媒体库脚本和交互脚本 用于选择 SVG 或图片
function yoone_snow_admin_enqueue($hook) {
// 条件判断 仅在插件设置页面加载脚本 保持性能
if ($hook !== 'settings_page_yoone_snow') { return; }
// 加载媒体库脚本 以便使用 wp.media 选择器
wp_enqueue_media();
// 在后台也注册并加载 shapes 渲染脚本 以供预览复用
// 形状索引与工具脚本 提供全局渲染器与图像加载能力
$shape_index_handle = 'yoone-snow-shapes-index';
$ver_shapes_index = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/index.js');
wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), $ver_shapes_index ? (string)$ver_shapes_index : '1.1.0', true);
$shape_utils_handle = 'yoone-snow-shapes-utils';
$ver_shapes_utils = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/utils.js');
wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), $ver_shapes_utils ? (string)$ver_shapes_utils : '1.1.0', true);
// 各具体形状脚本 注册为依赖以保证顺序
$shape_dot_handle = 'yoone-snow-shapes-dot';
$ver_shapes_dot = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/dot.js');
wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), $ver_shapes_dot ? (string)$ver_shapes_dot : '1.1.0', true);
$shape_flake_handle = 'yoone-snow-shapes-flake';
$ver_shapes_flake = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/flake.js');
wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), $ver_shapes_flake ? (string)$ver_shapes_flake : '1.1.0', true);
$shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao';
$ver_shapes_yuanbao = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/yuanbao.js');
wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), $ver_shapes_yuanbao ? (string)$ver_shapes_yuanbao : '1.1.0', true);
$shape_coin_handle = 'yoone-snow-shapes-coin';
$ver_shapes_coin = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/coin.js');
wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), $ver_shapes_coin ? (string)$ver_shapes_coin : '1.1.0', true);
$shape_santa_handle = 'yoone-snow-shapes-santa-hat';
$ver_shapes_santa = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/santa_hat.js');
wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), $ver_shapes_santa ? (string)$ver_shapes_santa : '1.1.0', true);
$shape_cane_handle = 'yoone-snow-shapes-candy-cane';
$ver_shapes_cane = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/candy_cane.js');
wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), $ver_shapes_cane ? (string)$ver_shapes_cane : '1.1.0', true);
$shape_sock_handle = 'yoone-snow-shapes-christmas-sock';
$ver_shapes_sock = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_sock.js');
wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), $ver_shapes_sock ? (string)$ver_shapes_sock : '1.1.0', true);
$shape_tree_handle = 'yoone-snow-shapes-christmas-tree';
$ver_shapes_tree = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_tree.js');
wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), $ver_shapes_tree ? (string)$ver_shapes_tree : '1.1.0', true);
$shape_reindeer_handle = 'yoone-snow-shapes-reindeer';
$ver_shapes_reindeer = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/reindeer.js');
wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), $ver_shapes_reindeer ? (string)$ver_shapes_reindeer : '1.1.0', true);
$shape_berry_handle = 'yoone-snow-shapes-christmas-berry';
$ver_shapes_berry = @filemtime(plugin_dir_path(__FILE__) . 'js/shapes/christmas_berry.js');
wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), $ver_shapes_berry ? (string)$ver_shapes_berry : '1.1.0', true);
// 在后台本页入队 shapes 相关脚本 以便预览复用前端实现
wp_enqueue_script($shape_index_handle);
wp_enqueue_script($shape_utils_handle);
wp_enqueue_script($shape_dot_handle);
wp_enqueue_script($shape_flake_handle);
wp_enqueue_script($shape_yuanbao_handle);
wp_enqueue_script($shape_coin_handle);
wp_enqueue_script($shape_santa_handle);
wp_enqueue_script($shape_cane_handle);
wp_enqueue_script($shape_sock_handle);
wp_enqueue_script($shape_tree_handle);
wp_enqueue_script($shape_reindeer_handle);
wp_enqueue_script($shape_berry_handle);
// 为后台也提供 YooneSnowSettings 的 assetsMap 以满足形状脚本的资源需求
wp_localize_script($shape_utils_handle, 'YooneSnowSettings', array(
'assetsMap' => array(
'santa_hat' => plugins_url('assets/圣诞雪帽.svg', __FILE__),
'candy_cane' => plugins_url('assets/圣诞拐杖.svg', __FILE__),
'christmas_sock' => plugins_url('assets/圣诞袜子.svg', __FILE__),
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
)
));
// 注册并加载后台交互脚本 该脚本复用 shapes 渲染器进行预览
$admin_script_handle = 'yoone-snow-admin-media';
// 设置 admin 脚本依赖所有形状脚本 以保证渲染器已就绪
wp_register_script(
$admin_script_handle,
plugins_url('js/admin-media.js', __FILE__),
array(
$shape_index_handle,
$shape_utils_handle,
$shape_dot_handle,
$shape_flake_handle,
$shape_yuanbao_handle,
$shape_coin_handle,
$shape_santa_handle,
$shape_cane_handle,
$shape_sock_handle,
$shape_tree_handle,
$shape_reindeer_handle,
$shape_berry_handle
),
(@filemtime(plugin_dir_path(__FILE__) . 'js/admin-media.js') ? (string)@filemtime(plugin_dir_path(__FILE__) . 'js/admin-media.js') : '1.1.0'),
true
);
// 传递资源映射用于后台形状预览 保持与前端一致
wp_localize_script($admin_script_handle, 'YooneSnowAdmin', array(
'assetsMap' => array(
'santa_hat' => plugins_url('assets/圣诞雪帽.svg', __FILE__),
'candy_cane' => plugins_url('assets/圣诞拐杖.svg', __FILE__),
'christmas_sock' => plugins_url('assets/圣诞袜子.svg', __FILE__),
'christmas_tree' => plugins_url('assets/圣诞树.svg', __FILE__),
'reindeer' => plugins_url('assets/圣诞麋鹿.svg', __FILE__),
'christmas_berry' => plugins_url('assets/圣诞果.svg', __FILE__),
),
'shapeLabels' => array(
'dot' => esc_html__('Dot', 'yoone-snow'),
'flake' => esc_html__('Snowflake', 'yoone-snow'),
'yuanbao' => esc_html__('Yuanbao', 'yoone-snow'),
'coin' => esc_html__('Coin', 'yoone-snow'),
'santa_hat' => esc_html__('Santa Hat', 'yoone-snow'),
'candy_cane' => esc_html__('Candy Cane', 'yoone-snow'),
'christmas_sock' => esc_html__('Christmas Sock', 'yoone-snow'),
'christmas_tree' => esc_html__('Christmas Tree', 'yoone-snow'),
'reindeer' => esc_html__('Reindeer', 'yoone-snow'),
'christmas_berry' => esc_html__('Christmas Berry', 'yoone-snow'),
),
'i18n' => array(
'cancel' => esc_html__('Cancel', 'yoone-snow'),
'remove' => esc_html__('Remove', 'yoone-snow'),
'select_images_or_svg' => esc_html__('Select images or SVG', 'yoone-snow'),
'type' => esc_html__('Type', 'yoone-snow'),
'default' => esc_html__('Default', 'yoone-snow'),
'emoji' => esc_html__('Emoji', 'yoone-snow'),
'media' => esc_html__('Media', 'yoone-snow'),
'text' => esc_html__('Text', 'yoone-snow'),
'select_shape' => esc_html__('Select shape', 'yoone-snow'),
'select_emoji' => esc_html__('Select emoji', 'yoone-snow'),
'add_images' => esc_html__('Add Images', 'yoone-snow'),
'type_text' => esc_html__('Type text', 'yoone-snow'),
'type_emoji_or_alias' => esc_html__('Type emoji or alias', 'yoone-snow'),
'add_shapes_all_in_one' => esc_html__('Add shapes by type all in one list', 'yoone-snow'),
'shapes' => esc_html__('Shapes', 'yoone-snow')
)
));
wp_enqueue_script($admin_script_handle);
}
function yoone_snow_render_overlay() {
if (!yoone_snow_is_enabled()) { return; }
static $yoone_snow_rendered = false;
if ($yoone_snow_rendered) { return; }
$yoone_snow_rendered = true;
echo '<canvas id="effectiveAppsSnow" aria-hidden="true"></canvas>';
}
add_action('wp_enqueue_scripts', 'yoone_snow_enqueue_assets');
add_action('wp_body_open', 'yoone_snow_render_overlay');
add_action('wp_footer', 'yoone_snow_render_overlay', 100);
// 注册设置页面和设置项 用于选择雪花形状
function yoone_snow_register_settings() {
// 移除形状下拉选项 仅保留复选集合设置
// 注册 mixed 形状集合设置项 默认包含 dot 和 flake
register_setting('yoone_snow_options', 'yoone_snow_mixed_items', array(
'type' => 'array',
'sanitize_callback' => function($value) {
// 将输入统一为数组 并过滤到允许的集合
$allowed = array('dot','flake','yuanbao','coin','santa_hat','candy_cane','christmas_sock','christmas_tree','reindeer','christmas_berry');
if (is_string($value)) {
$value = array_filter(array_map('trim', explode(',', $value)));
}
if (!is_array($value)) { $value = array('dot','flake'); }
$filtered = array_values(array_unique(array_intersect($value, $allowed)));
return !empty($filtered) ? $filtered : array('dot','flake');
},
'default' => array('dot','flake'),
));
// 添加设置分区 标题为 Snow Settings
add_settings_section(
'yoone_snow_section',
esc_html__('Snow Settings', 'yoone-snow'),
function() {
echo '<p>' . esc_html__('Configure snow appearance', 'yoone-snow') . '</p>';
},
'yoone_snow'
);
// 移除下拉字段 保留复选框作为唯一选择入口
add_settings_field(
'yoone_snow_mixed_items',
esc_html__('Shapes', 'yoone-snow'),
function() {
// 读取默认形状列表 用于初始渲染卡片
$current_list = get_option('yoone_snow_mixed_items', array('dot','flake'));
if (is_string($current_list)) {
$current_list = array_filter(array_map('trim', explode(',', $current_list)));
}
if (!is_array($current_list)) { $current_list = array('dot','flake'); }
$options = array(
'dot' => esc_html__('Dot', 'yoone-snow'),
'flake' => esc_html__('Snowflake', 'yoone-snow'),
'yuanbao' => esc_html__('Yuanbao', 'yoone-snow'),
'coin' => esc_html__('Coin', 'yoone-snow'),
'santa_hat' => esc_html__('Santa Hat', 'yoone-snow'),
'candy_cane' => esc_html__('Candy Cane', 'yoone-snow'),
'christmas_sock' => esc_html__('Christmas Sock', 'yoone-snow'),
'christmas_tree' => esc_html__('Christmas Tree', 'yoone-snow'),
'reindeer' => esc_html__('Reindeer', 'yoone-snow'),
'christmas_berry' => esc_html__('Christmas Berry', 'yoone-snow'),
);
// 形状描述映射 用于在界面提示 使用国际化函数
$shape_descriptions = array(
'dot' => __('Basic dot shape simple and lightweight', 'yoone-snow'),
'flake' => __('Snowflake shape more decorative', 'yoone-snow'),
'yuanbao' => __('Yuanbao shape festive theme', 'yoone-snow'),
'coin' => __('Coin shape festive theme', 'yoone-snow'),
'santa_hat' => __('Santa hat shape seasonal theme', 'yoone-snow'),
'candy_cane' => __('Candy cane shape seasonal theme', 'yoone-snow'),
'christmas_sock' => __('Christmas sock shape seasonal theme', 'yoone-snow'),
'christmas_tree' => __('Christmas tree shape seasonal theme', 'yoone-snow'),
'reindeer' => __('Reindeer shape seasonal theme', 'yoone-snow'),
'christmas_berry' => __('Christmas berry shape seasonal theme', 'yoone-snow'),
);
// 读取已选择的媒体与 emoji 列表 用于统一卡片渲染
$current_media = get_option('yoone_snow_media_items', array());
if (!is_array($current_media)) { $current_media = array(); }
$current_emojis = get_option('yoone_snow_emoji_items', array());
if (!is_array($current_emojis)) { $current_emojis = array(); }
$current_texts = get_option('yoone_snow_text_items', array());
if (!is_array($current_texts)) { $current_texts = array(); }
// 读取当前权重值 用于在卡片中预填
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
$media_weights_current = get_option('yoone_snow_media_weights', array());
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); }
$text_weights_current = get_option('yoone_snow_text_weights', array());
if (!is_array($text_weights_current)) { $text_weights_current = array(); }
echo '<div id="yooneSnowShapeList" style="display:flex;flex-wrap:wrap;gap:12px;">';
// 渲染默认形状卡片
foreach ($current_list as $key) {
if (!isset($options[$key])) { continue; }
echo '<div class="yoone-snow-shape-item" data-shape-key="' . esc_attr($key) . '" title="' . esc_attr(isset($shape_descriptions[$key]) ? $shape_descriptions[$key] : '') . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div class="yoone-snow-shape-preview" style="width:32px;height:32px;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>';
echo '<span>' . esc_html($options[$key]) . '</span>';
$shapeWeightVal = isset($shape_weights_current[$key]) ? intval($shape_weights_current[$key]) : 1;
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($key) . ']" value="' . esc_attr($shapeWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_mixed_items[]" value="' . esc_attr($key) . '" />';
echo '<button type="button" class="button yoone-snow-cancel-shape" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
// 渲染 emoji 卡片
foreach ($current_emojis as $emoji_char) {
$label = trim((string)$emoji_char);
if ($label === '') { continue; }
echo '<div class="yoone-snow-emoji-item" data-emoji-char="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div style="font-size:28px;line-height:32px;margin-bottom:6px;width:32px;height:32px;display:flex;align-items:center;justify-content:center;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;">' . esc_html($label) . '</div>';
$emojiWeightVal = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($emojiWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_emoji_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-emoji" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
// 渲染媒体卡片
foreach ($current_media as $attachment_id) {
$url = wp_get_attachment_thumb_url($attachment_id);
if (!$url) { $url = wp_get_attachment_url($attachment_id); }
$safe_id = intval($attachment_id);
$safe_url = esc_url($url);
echo '<div class="yoone-snow-media-item" data-attachment-id="' . $safe_id . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<img src="' . $safe_url . '" alt="media" style="width:72px;height:72px;object-fit:contain;margin-bottom:6px;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;" />';
$mediaWeightVal = isset($media_weights_current[$safe_id]) ? intval($media_weights_current[$safe_id]) : 1;
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . $safe_id . ']" value="' . esc_attr($mediaWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_media_items[]" value="' . $safe_id . '" />';
echo '<button type="button" class="button yoone-snow-remove-media" style="margin-top:6px;">' . esc_html__('Remove', 'yoone-snow') . '</button>';
echo '</div>';
}
// 文本卡片
foreach ($current_texts as $text_item) {
$label = trim((string)$text_item);
if ($label === '') { continue; }
echo '<div class="yoone-snow-text-item" data-text-label="' . esc_attr($label) . '" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;min-width:96px;">';
echo '<div style="font-size:14px;line-height:18px;margin-bottom:6px;width:120px;min-height:32px;display:flex;align-items:center;justify-content:center;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;word-break:break-word;text-align:center;">' . esc_html($label) . '</div>';
$textWeightVal = isset($text_weights_current[$label]) ? intval($text_weights_current[$label]) : 1;
echo '<input type="number" min="0" name="yoone_snow_text_weights[' . esc_attr($label) . ']" value="' . esc_attr($textWeightVal) . '" style="width:120px;margin-top:6px;" />';
echo '<input type="hidden" name="yoone_snow_text_items[]" value="' . esc_attr($label) . '" />';
echo '<button type="button" class="button yoone-snow-remove-text" style="margin-top:6px;">' . esc_html__('Cancel', 'yoone-snow') . '</button>';
echo '</div>';
}
echo '<div id="yooneSnowAddUnified" class="yoone-snow-shape-item" style="border:1px solid #ddd;padding:8px;display:flex;flex-direction:column;align-items:center;gap:8px;min-width:96px;">';
echo '<div style="display:flex;gap:8px;align-items:center;">';
echo '<label>' . esc_html__('Type', 'yoone-snow') . ' <select id="yooneSnowAddTypeSelect" style="min-width:180px;">';
echo '<option value="default" selected>' . esc_html__('Default', 'yoone-snow') . '</option>';
echo '<option value="emoji">' . esc_html__('Emoji', 'yoone-snow') . '</option>';
echo '<option value="media">' . esc_html__('Media', 'yoone-snow') . '</option>';
echo '<option value="text">' . esc_html__('Text', 'yoone-snow') . '</option>';
echo '</select></label>';
echo '</div>';
echo '<div id="yooneSnowAddDefaultPane" style="display:flex;gap:8px;align-items:center;position:relative;">';
echo '<div id="yooneSnowAddShapeLabelPreview" style="width:32px;height:32px;display:none;align-items:center;justify-content:center;background-color:#808080;border-radius:4px;"></div>';
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">' . esc_html__('Select shape', 'yoone-snow') . '</option>';
foreach ($options as $key => $label) {
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
}
echo '</select>';
echo '<div id="yooneSnowShapeNativeOverlay" style="display:none;position:absolute;top:100%;left:0;width:280px;max-height:240px;overflow:auto;background:#fff;border:1px solid #ddd;border-radius:6px;box-shadow:0 4px 12px rgba(0,0,0,0.08);padding:6px;z-index:9999;">';
foreach ($options as $key => $label) {
echo '<button type="button" class="yoone-snow-shape-native-item" data-shape-key="' . esc_attr($key) . '" style="display:flex;align-items:center;width:100%;padding:6px;margin:0;background:none;border:none;border-radius:4px;cursor:pointer;text-align:left;">';
echo '<div class="yoone-snow-shape-native-preview" style="font-size:28px;line-height:32px;width:32px;height:32px;margin-right:8px;display:flex;align-items:center;justify-content:center;background-color:#e6e6e6;border:1px solid #ddd;border-radius:4px;"></div>';
echo '<span>' . esc_html($label) . '</span>';
echo '</button>';
}
echo '</div>';
echo '</div>';
echo '<div id="yooneSnowAddEmojiPane" style="display:none;gap:8px;align-items:center;">';
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">' . esc_html__('Select emoji', 'yoone-snow') . '</option></select>';
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="' . esc_attr__('Type emoji or alias', 'yoone-snow') . '" style="width:240px;" />';
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
echo '</div>';
echo '<div id="yooneSnowAddMediaPane" style="display:none;gap:8px;align-items:center;">';
echo '<button type="button" class="button button-primary" id="yooneSnowAddMediaUnified">' . esc_html__('Add Images', 'yoone-snow') . '</button>';
echo '</div>';
echo '<div id="yooneSnowAddTextPane" style="display:none;gap:8px;align-items:center;">';
echo '<input type="text" id="yooneSnowAddTextInput" placeholder="' . esc_attr__('Type text', 'yoone-snow') . '" style="width:240px;" />';
echo '</div>';
// 权重说明 提示用户权重影响概率且为非负整数
echo '<p class="description">' . esc_html__('Add shapes by type all in one list', 'yoone-snow') . '</p>';
echo '</div>';
echo '</div>';
echo '<p class="description">' . esc_html__('Weight controls relative probability Weight is a non negative integer Weight 0 disables a shape Probability equals shape weight divided by the sum of all shape weights Example dot 1 flake 4 flake has about four times the chance of dot', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 移除單獨 emoji 字段 渲染與交互已整合到 Shapes 字段
register_setting('yoone_snow_options', 'yoone_snow_shape_weights', array(
'type' => 'array',
'sanitize_callback' => function($value) {
$allowed = array('dot','flake','yuanbao','coin','santa_hat','candy_cane','christmas_sock','christmas_tree','reindeer','christmas_berry');
$defaults = array(
'dot' => 1,
'flake' => 4,
'yuanbao' => 1,
'coin' => 1,
'santa_hat' => 1,
'candy_cane' => 1,
'christmas_sock' => 1,
'christmas_tree' => 1,
'reindeer' => 1,
'christmas_berry' => 1,
);
if (!is_array($value)) { $value = array(); }
$clean = array();
foreach ($allowed as $key) {
$num = isset($value[$key]) ? intval($value[$key]) : (isset($defaults[$key]) ? intval($defaults[$key]) : 1);
if ($num < 0) { $num = 0; }
$clean[$key] = $num;
}
return $clean;
},
'default' => array(
'dot' => 1,
'flake' => 4,
'yuanbao' => 1,
'coin' => 1,
'santa_hat' => 1,
'candy_cane' => 1,
'christmas_sock' => 1,
'christmas_tree' => 1,
'reindeer' => 1,
'christmas_berry' => 1,
),
));
// 形状权重字段将移动到媒体形状字段之后 以满足界面顺序需求
// 注册媒体形状集合 设置项保存为附件 ID 数组
register_setting('yoone_snow_options', 'yoone_snow_media_items', array(
'type' => 'array',
'sanitize_callback' => function($value) {
// 将输入统一为整数 ID 数组 并过滤无效值
if (is_string($value)) {
$value = array_filter(array_map('trim', explode(',', $value)));
}
if (!is_array($value)) { $value = array(); }
$ids = array();
foreach ($value as $item) {
$id = intval($item);
if ($id > 0) { $ids[] = $id; }
}
return array_values(array_unique($ids));
},
'default' => array(),
));
// 移除單獨 Media 字段 渲染與交互已整合到 Shapes 字段
// 注册媒体权重设置项 将附件 ID 映射到权重数值 默认 1
register_setting('yoone_snow_options', 'yoone_snow_media_weights', array(
'type' => 'array',
'sanitize_callback' => function($value) {
// 将输入统一为附件 ID 到非负整数权重的映射
if (!is_array($value)) { $value = array(); }
$clean = array();
foreach ($value as $id => $num) {
$aid = intval($id);
$weight = intval($num);
if ($aid > 0) {
if ($weight < 0) { $weight = 0; }
$clean[$aid] = $weight;
}
}
return $clean;
},
'default' => array(),
));
// 移除單獨 Weights 字段 權重輸入已置於 Shapes 字段下方
// 注册 emoji 列表设置项 保存为字符串数组
register_setting('yoone_snow_options', 'yoone_snow_emoji_items', array(
'type' => 'array',
'sanitize_callback' => function($value) {
// 将输入统一为非空字符串数组 并去重
if (is_string($value)) {
$value = array_filter(array_map('trim', explode(',', $value)));
}
if (!is_array($value)) { $value = array(); }
$out = array();
foreach ($value as $item) {
$s = trim((string)$item);
if ($s !== '') { $out[] = $s; }
}
return array_values(array_unique($out));
},
'default' => array(),
));
// 注册 emoji 权重设置项 映射字符到非负整数权重
register_setting('yoone_snow_options', 'yoone_snow_emoji_weights', array(
'type' => 'array',
'sanitize_callback' => function($value) {
if (!is_array($value)) { $value = array(); }
$clean = array();
foreach ($value as $ch => $num) {
$key = trim((string)$ch);
$weight = intval($num);
if ($key !== '') {
if ($weight < 0) { $weight = 0; }
$clean[$key] = $weight;
}
}
return $clean;
},
'default' => array(),
));
register_setting('yoone_snow_options', 'yoone_snow_text_items', array(
'type' => 'array',
'sanitize_callback' => function($value) {
if (is_string($value)) {
$value = array_filter(array_map('trim', explode(',', $value)));
}
if (!is_array($value)) { $value = array(); }
$out = array();
foreach ($value as $item) {
$s = trim((string)$item);
if ($s !== '') { $out[] = $s; }
}
return array_values(array_unique($out));
},
'default' => array(),
));
register_setting('yoone_snow_options', 'yoone_snow_text_weights', array(
'type' => 'array',
'sanitize_callback' => function($value) {
if (!is_array($value)) { $value = array(); }
$clean = array();
foreach ($value as $ch => $num) {
$key = trim((string)$ch);
$weight = intval($num);
if ($key !== '') {
if ($weight < 0) { $weight = 0; }
$clean[$key] = $weight;
}
}
return $clean;
},
'default' => array(),
));
// 注册首页显示时长设置 项为整数秒 0 表示无限
register_setting('yoone_snow_options', 'yoone_snow_home_duration', array(
'type' => 'integer',
'sanitize_callback' => function($value) {
// 将输入转换为非负整数 单位为秒 0 表示无限
$num = intval($value);
if ($num < 0) { $num = 0; }
return $num;
},
'default' => 0,
));
// 添加首页显示时长字段 输入为数字最小值为 0
add_settings_field(
'yoone_snow_home_duration',
esc_html__('Display Duration Seconds', 'yoone-snow'),
function() {
// 读取当前设置值 并渲染数字输入框
$current = intval(get_option('yoone_snow_home_duration', 0));
echo '<input type="number" min="0" name="yoone_snow_home_duration" value="' . esc_attr($current) . '" style="width:120px;" />';
echo '<p class="description">' . esc_html__('Duration in seconds for snow on home 0 means infinite', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 注册目标在屏最大数量设置项 0 表示自动根据视口面积
register_setting('yoone_snow_options', 'yoone_snow_max_count', array(
'type' => 'integer',
'sanitize_callback' => function($value) {
$num = intval($value);
if ($num < 0) { $num = 0; }
// 上限保护 防止过大影响性能
if ($num > 1000) { $num = 1000; }
return $num;
},
'default' => 0,
));
// 添加输入字段 用于设置在屏最大数量 0 表示自动
add_settings_field(
'yoone_snow_max_count',
esc_html__('Max Snowflakes On Screen', 'yoone-snow'),
function() {
$current = intval(get_option('yoone_snow_max_count', 0));
echo '<input type="number" min="0" step="1" name="yoone_snow_max_count" value="' . esc_attr($current) . '" style="width:120px;" />';
echo '<p class="description">' . esc_html__('0 means auto based on viewport area upper bound 1000', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 渲染加速开关 设置是否跳过双门限并立即队列加载
register_setting('yoone_snow_options', 'yoone_snow_render_acceleration', array(
'type' => 'integer',
'sanitize_callback' => function($value) {
$num = intval($value);
return $num === 1 ? 1 : 0;
},
'default' => 0,
));
add_settings_field(
'yoone_snow_render_acceleration',
esc_html__('Render Acceleration', 'yoone-snow'),
function() {
$enabled = intval(get_option('yoone_snow_render_acceleration', 0)) === 1;
echo '<label><input type="checkbox" name="yoone_snow_render_acceleration" value="1" ' . checked($enabled, true, false) . ' /> ' . esc_html__('Enable eager asset warmup skip idle gating', 'yoone-snow') . '</label>';
echo '<p class="description">' . esc_html__('If enabled assets load immediately with sequential queue may increase early network usage', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 新增分屏最大数量设置 0 表示自动 根据屏幕大小采用不同默认计算
register_setting('yoone_snow_options', 'yoone_snow_max_count_breakpoints', array(
'type' => 'array',
'sanitize_callback' => function($value) {
if (!is_array($value)) { $value = array(); }
$s = isset($value['small']) ? intval($value['small']) : 0;
$m = isset($value['medium']) ? intval($value['medium']) : 0;
$l = isset($value['large']) ? intval($value['large']) : 0;
if ($s < 0) { $s = 0; } if ($m < 0) { $m = 0; } if ($l < 0) { $l = 0; }
if ($s > 1000) { $s = 1000; } if ($m > 1000) { $m = 1000; } if ($l > 1000) { $l = 1000; }
return array('small' => $s, 'medium' => $m, 'large' => $l);
},
'default' => array('small' => 0, 'medium' => 0, 'large' => 0),
));
add_settings_field(
'yoone_snow_max_count_breakpoints',
esc_html__('Max Count by Screen Size', 'yoone-snow'),
function() {
$grp = get_option('yoone_snow_max_count_breakpoints', array('small' => 0, 'medium' => 0, 'large' => 0));
$s = isset($grp['small']) ? intval($grp['small']) : 0;
$m = isset($grp['medium']) ? intval($grp['medium']) : 0;
$l = isset($grp['large']) ? intval($grp['large']) : 0;
echo '<label style="margin-right:12px;">' . esc_html__('Small', 'yoone-snow') . ' <input type="number" min="0" step="1" name="yoone_snow_max_count_breakpoints[small]" value="' . esc_attr($s) . '" style="width:120px;" /></label>';
echo '<label style="margin-right:12px;">' . esc_html__('Medium', 'yoone-snow') . ' <input type="number" min="0" step="1" name="yoone_snow_max_count_breakpoints[medium]" value="' . esc_attr($m) . '" style="width:120px;" /></label>';
echo '<label>' . esc_html__('Large', 'yoone-snow') . ' <input type="number" min="0" step="1" name="yoone_snow_max_count_breakpoints[large]" value="' . esc_attr($l) . '" style="width:120px;" /></label>';
echo '<p class="description">' . esc_html__('0 uses default auto by viewport small<=480 medium<=960 large>960', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 尺寸组合设置 使用单一选项 snow size 存储最小与最大半径
register_setting('yoone_snow_options', 'yoone_snow_size', array(
'type' => 'array',
'sanitize_callback' => function($value) {
// 将输入统一为包含 min 与 max 的数值数组 并保证非负与 max 不小于 min
if (!is_array($value)) { $value = array(); }
$min = isset($value['min']) ? floatval($value['min']) : 1.0;
$max = isset($value['max']) ? floatval($value['max']) : 3.0;
if ($min < 0) { $min = 0.0; }
if ($max < $min) { $max = $min; }
return array('min' => $min, 'max' => $max);
},
'default' => array('min' => 1.0, 'max' => 3.0),
));
add_settings_field(
'yoone_snow_size',
esc_html__('Snow Size', 'yoone-snow'),
function() {
// 渲染组合输入 使用同一选项保存最小与最大半径
$grp = get_option('yoone_snow_size', array('min' => 1.0, 'max' => 3.0));
$min = isset($grp['min']) ? floatval($grp['min']) : 1.0;
$max = isset($grp['max']) ? floatval($grp['max']) : 3.0;
echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_size[min]" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_size[max]" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">' . esc_html__('Random radius in [min max] single option', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 漂移速度与摆动幅度的随机范围设置 保持独立选项
register_setting('yoone_snow_options', 'yoone_snow_drift_min', array(
'type' => 'number',
'sanitize_callback' => function($value) {
$num = floatval($value);
if ($num < 0) { $num = 0; }
return $num;
},
'default' => 0.4,
));
register_setting('yoone_snow_options', 'yoone_snow_drift_max', array(
'type' => 'number',
'sanitize_callback' => function($value) {
$min = floatval(get_option('yoone_snow_drift_min', 0.4));
$num = floatval($value);
if ($num < $min) { $num = $min; }
return $num;
},
'default' => 1.0,
));
add_settings_field(
'yoone_snow_drift_range',
esc_html__('Drift Speed Random Range', 'yoone-snow'),
function() {
$min = floatval(get_option('yoone_snow_drift_min', 0.4));
$max = floatval(get_option('yoone_snow_drift_max', 1.0));
echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_drift_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_drift_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">' . esc_html__('Random vertical drift speed base in [min max]', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
register_setting('yoone_snow_options', 'yoone_snow_swing_min', array(
'type' => 'number',
'sanitize_callback' => function($value) {
$num = floatval($value);
if ($num < 0) { $num = 0; }
return $num;
},
'default' => 0.2,
));
register_setting('yoone_snow_options', 'yoone_snow_swing_max', array(
'type' => 'number',
'sanitize_callback' => function($value) {
$min = floatval(get_option('yoone_snow_swing_min', 0.2));
$num = floatval($value);
if ($num < $min) { $num = $min; }
return $num;
},
'default' => 1.0,
));
add_settings_field(
'yoone_snow_swing_range',
esc_html__('Swing Amplitude Random Range', 'yoone-snow'),
function() {
$min = floatval(get_option('yoone_snow_swing_min', 0.2));
$max = floatval(get_option('yoone_snow_swing_max', 1.0));
echo '<label style="margin-right:12px;">' . esc_html__('Min', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_swing_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
echo '<label>' . esc_html__('Max', 'yoone-snow') . ' <input type="number" min="0" step="0.1" name="yoone_snow_swing_max" value="' . esc_attr($max) . '" style="width:120px;" /></label>';
echo '<p class="description">' . esc_html__('Random horizontal swing amplitude base in [min max] before offset scale', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 路由显示模式设置
register_setting('yoone_snow_options', 'yoone_snow_display_routes_mode', array(
'type' => 'string',
'sanitize_callback' => function($value) {
$val = strtolower(trim((string)$value));
$allowed = array('home','all','match');
return in_array($val, $allowed, true) ? $val : 'home';
},
'default' => 'home',
));
add_settings_field(
'yoone_snow_display_routes_mode',
esc_html__('Display Routes Mode', 'yoone-snow'),
function() {
$current = get_option('yoone_snow_display_routes_mode', 'home');
echo '<label style="margin-right:12px;"><input type="radio" name="yoone_snow_display_routes_mode" value="home" ' . checked($current, 'home', false) . ' /> ' . esc_html__('Home', 'yoone-snow') . '</label>';
echo '<label style="margin-right:12px;"><input type="radio" name="yoone_snow_display_routes_mode" value="all" ' . checked($current, 'all', false) . ' /> ' . esc_html__('All Pages', 'yoone-snow') . '</label>';
echo '<label><input type="radio" name="yoone_snow_display_routes_mode" value="match" ' . checked($current, 'match', false) . ' /> ' . esc_html__('Match URL Path', 'yoone-snow') . '</label>';
echo '<p class="description">' . esc_html__('Default Home Only choose All Pages to enable globally choose Match URL Path to enable only for matched routes', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
// 路由匹配规则列表设置
register_setting('yoone_snow_options', 'yoone_snow_display_routes', array(
'type' => 'array',
'sanitize_callback' => function($value) {
if (is_string($value)) {
$value = preg_split('/\\r?\\n/', $value);
}
if (!is_array($value)) { $value = array(); }
$out = array();
foreach ($value as $item) {
$s = trim((string)$item);
if ($s !== '') { $out[] = $s; }
}
return array_values(array_unique($out));
},
'default' => array(),
));
add_settings_field(
'yoone_snow_display_routes',
esc_html__('Match Routes', 'yoone-snow'),
function() {
$current = get_option('yoone_snow_display_routes', array());
if (is_string($current)) {
$current = preg_split('/\\r?\\n/', $current);
}
if (!is_array($current)) { $current = array(); }
$text = implode("\n", array_map('strval', $current));
$placeholder = esc_attr__("Example /about\n/blog/*\n/shop", 'yoone-snow');
echo '<textarea name="yoone_snow_display_routes" rows="6" style="width:420px;" placeholder="' . $placeholder . '">' . esc_textarea($text) . '</textarea>';
echo '<p class="description">' . esc_html__('One rule per line compare against request path example exact path /about prefix match with trailing star /blog/*', 'yoone-snow') . '</p>';
},
'yoone_snow',
'yoone_snow_section'
);
}
// 添加设置页面到后台菜单 条目在设置菜单下
function yoone_snow_add_settings_page() {
add_options_page(
esc_html__('Yoone Snow', 'yoone-snow'),
esc_html__('Yoone Snow', 'yoone-snow'),
'manage_options',
'yoone_snow',
function() {
// 渲染设置页面 表单提交到 options.php
echo '<div class="wrap">';
echo '<h1>' . esc_html__('Yoone Snow', 'yoone-snow') . '</h1>';
echo '<form method="post" action="options.php">';
settings_fields('yoone_snow_options');
do_settings_sections('yoone_snow');
submit_button();
echo '</form>';
echo '</div>';
}
);
}
// 在 admin 初始化时注册设置 在 admin 菜单挂载页面
add_action('admin_init', 'yoone_snow_register_settings');
add_action('admin_menu', 'yoone_snow_add_settings_page');
add_action('admin_enqueue_scripts', 'yoone_snow_admin_enqueue');
// 在插件列表行添加 Settings 链接 指向设置页面
function yoone_snow_plugin_action_links($links) {
// 构造设置页面链接 使用 admin_url 保证后台路径正确
$settingsUrl = admin_url('options-general.php?page=yoone_snow');
$settingsLink = '<a href="' . esc_url($settingsUrl) . '">' . esc_html__('Settings', 'yoone-snow') . '</a>';
// 将设置链接插入到最前面 便于用户点击
array_unshift($links, $settingsLink);
return $links;
}
// 绑定到当前插件的 action links 钩子 使用 plugin_basename 计算插件标识
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'yoone_snow_plugin_action_links');
function yoone_snow_load_textdomain() {
load_plugin_textdomain('yoone-snow', false, dirname(plugin_basename(__FILE__)) . '/languages');
}
add_action('plugins_loaded', 'yoone_snow_load_textdomain');