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)), '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__), ) )); 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 ''; } 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 '
' . esc_html__('Configure snow appearance', 'yoone-snow') . '
'; }, 'yoone_snow' ); // 移除下拉字段 保留复选框作为唯一选择入口 add_settings_field( 'yoone_snow_mixed_items', 'Shapes', 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' => 'Dot', 'flake' => 'Snowflake', 'yuanbao' => 'Yuanbao', 'coin' => 'Coin', 'santa_hat' => 'Santa Hat', 'candy_cane' => 'Candy Cane', 'christmas_sock' => 'Christmas Sock', 'christmas_tree' => 'Christmas Tree', 'reindeer' => 'Reindeer', 'christmas_berry' => 'Christmas Berry', ); // 读取已选择的媒体与 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 'Add shapes by type all in one list
'; echo 'Duration in seconds for snow on home 0 means infinite
'; }, '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', 'Max Snowflakes On Screen', function() { $current = intval(get_option('yoone_snow_max_count', 0)); echo ''; echo '0 means auto based on viewport area upper bound 1000
'; }, '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', 'Snow Size', 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 ''; echo ''; echo 'Random radius in [min max] single option
'; }, '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', 'Drift Speed Random Range', function() { $min = floatval(get_option('yoone_snow_drift_min', 0.4)); $max = floatval(get_option('yoone_snow_drift_max', 1.0)); echo ''; echo ''; echo 'Random vertical drift speed base in [min max]
'; }, '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', 'Swing Amplitude Random Range', function() { $min = floatval(get_option('yoone_snow_swing_min', 0.2)); $max = floatval(get_option('yoone_snow_swing_max', 1.0)); echo ''; echo ''; echo 'Random horizontal swing amplitude base in [min max] before offset scale
'; }, '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 '