789 lines
39 KiB
PHP
789 lines
39 KiB
PHP
<?php
|
|
/*
|
|
Plugin Name: Yoone Snow
|
|
Description: 首页 canvas 雪花效果
|
|
Version: 1.7.0
|
|
Author: Yoone
|
|
*/
|
|
|
|
if (!defined('ABSPATH')) { exit; }
|
|
|
|
function yoone_snow_is_enabled() {
|
|
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__);
|
|
wp_register_style($style_handle, $style_src, array(), '1.1.0', 'all');
|
|
wp_enqueue_style($style_handle);
|
|
|
|
// 注册形状渲染脚本 保证主脚本之前加载
|
|
$shape_index_handle = 'yoone-snow-shapes-index';
|
|
wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), '1.1.0', true);
|
|
$shape_utils_handle = 'yoone-snow-shapes-utils';
|
|
wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_dot_handle = 'yoone-snow-shapes-dot';
|
|
wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_flake_handle = 'yoone-snow-shapes-flake';
|
|
wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao';
|
|
wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_coin_handle = 'yoone-snow-shapes-coin';
|
|
wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_santa_handle = 'yoone-snow-shapes-santa-hat';
|
|
wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_cane_handle = 'yoone-snow-shapes-candy-cane';
|
|
wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_sock_handle = 'yoone-snow-shapes-christmas-sock';
|
|
wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_tree_handle = 'yoone-snow-shapes-christmas-tree';
|
|
wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_reindeer_handle = 'yoone-snow-shapes-reindeer';
|
|
wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_berry_handle = 'yoone-snow-shapes-christmas-berry';
|
|
wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
|
|
// 注册并加载主脚本 设置依赖确保顺序正确
|
|
$script_handle = 'yoone-snow-script';
|
|
$script_src = plugins_url('js/snow-canvas.js', __FILE__);
|
|
wp_register_script(
|
|
$script_handle,
|
|
$script_src,
|
|
array(
|
|
$shape_index_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
|
|
),
|
|
'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;
|
|
}
|
|
}
|
|
$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)),
|
|
'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;
|
|
})(),
|
|
));
|
|
}
|
|
|
|
// 在后台设置页面加载媒体库脚本和交互脚本 用于选择 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';
|
|
wp_register_script($shape_index_handle, plugins_url('js/shapes/index.js', __FILE__), array(), '1.1.0', true);
|
|
$shape_utils_handle = 'yoone-snow-shapes-utils';
|
|
wp_register_script($shape_utils_handle, plugins_url('js/shapes/utils.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
// 各具体形状脚本 注册为依赖以保证顺序
|
|
$shape_dot_handle = 'yoone-snow-shapes-dot';
|
|
wp_register_script($shape_dot_handle, plugins_url('js/shapes/dot.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_flake_handle = 'yoone-snow-shapes-flake';
|
|
wp_register_script($shape_flake_handle, plugins_url('js/shapes/flake.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_yuanbao_handle = 'yoone-snow-shapes-yuanbao';
|
|
wp_register_script($shape_yuanbao_handle, plugins_url('js/shapes/yuanbao.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_coin_handle = 'yoone-snow-shapes-coin';
|
|
wp_register_script($shape_coin_handle, plugins_url('js/shapes/coin.js', __FILE__), array($shape_index_handle), '1.1.0', true);
|
|
$shape_santa_handle = 'yoone-snow-shapes-santa-hat';
|
|
wp_register_script($shape_santa_handle, plugins_url('js/shapes/santa_hat.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_cane_handle = 'yoone-snow-shapes-candy-cane';
|
|
wp_register_script($shape_cane_handle, plugins_url('js/shapes/candy_cane.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_sock_handle = 'yoone-snow-shapes-christmas-sock';
|
|
wp_register_script($shape_sock_handle, plugins_url('js/shapes/christmas_sock.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_tree_handle = 'yoone-snow-shapes-christmas-tree';
|
|
wp_register_script($shape_tree_handle, plugins_url('js/shapes/christmas_tree.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_reindeer_handle = 'yoone-snow-shapes-reindeer';
|
|
wp_register_script($shape_reindeer_handle, plugins_url('js/shapes/reindeer.js', __FILE__), array($shape_utils_handle), '1.1.0', true);
|
|
$shape_berry_handle = 'yoone-snow-shapes-christmas-berry';
|
|
wp_register_script($shape_berry_handle, plugins_url('js/shapes/christmas_berry.js', __FILE__), array($shape_utils_handle), '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';
|
|
wp_register_script($admin_script_handle, plugins_url('js/admin-media.js', __FILE__), array($shape_index_handle, $shape_utils_handle), '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 '<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',
|
|
'Snow Settings',
|
|
function() {
|
|
// 输出分区描述 使用英文标点保证兼容
|
|
echo '<p>Configure snow appearance</p>';
|
|
},
|
|
'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',
|
|
);
|
|
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) . '" 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;"></div>';
|
|
echo '<span>' . esc_html($options[$key]) . '</span>';
|
|
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;">Cancel</button>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
|
|
echo '<select id="yooneSnowAddShapeSelect" style="min-width:240px;"><option value="">Select shape</option>';
|
|
foreach ($options as $key => $label) {
|
|
echo '<option value="' . esc_attr($key) . '">' . esc_html($label) . '</option>';
|
|
}
|
|
echo '</select>';
|
|
echo '<button type="button" class="button" id="yooneSnowAddShapeBtn">Add Shape</button>';
|
|
echo '</div>';
|
|
echo '<p class="description">Choose shapes to render</p>';
|
|
},
|
|
'yoone_snow',
|
|
'yoone_snow_section'
|
|
);
|
|
|
|
// 添加 emoji 形狀選擇區域 放置在 Media Shapes 之前
|
|
add_settings_field(
|
|
'yoone_snow_emoji_items',
|
|
'Emoji Shapes',
|
|
function() {
|
|
// 渲染當前 emoji 列表與添加搜索框
|
|
$current_emojis = get_option('yoone_snow_emoji_items', array());
|
|
if (!is_array($current_emojis)) { $current_emojis = array(); }
|
|
echo '<div id="yooneSnowEmojiList" style="display:flex;flex-wrap:wrap;gap:12px;align-items:flex-start;">';
|
|
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;">';
|
|
echo '<div style="font-size:28px;line-height:32px;">' . esc_html($label) . '</div>';
|
|
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;">Cancel</button>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
echo '<div style="margin-top:8px;display:flex;gap:8px;align-items:center;">';
|
|
echo '<select id="yooneSnowEmojiSelect" style="min-width:240px;"><option value="">Select emoji</option></select>';
|
|
echo '<input type="text" id="yooneSnowAddEmojiInput" placeholder="Type emoji or alias" style="width:240px;" />';
|
|
echo '<button type="button" class="button" id="yooneSnowAddEmoji">Add Emoji</button>';
|
|
echo '</div>';
|
|
echo '<div id="yooneSnowEmojiSuggest" style="margin-top:6px;display:flex;flex-wrap:wrap;gap:6px;"></div>';
|
|
echo '<p class="description">Search by alias or paste emoji character then add</p>';
|
|
},
|
|
'yoone_snow',
|
|
'yoone_snow_section'
|
|
);
|
|
|
|
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(),
|
|
));
|
|
|
|
// 添加媒体形状选择字段 支持从媒体库选择图片或 SVG
|
|
add_settings_field(
|
|
'yoone_snow_media_items',
|
|
'Media Shapes',
|
|
function() {
|
|
// 读取当前已选择的附件 ID 列表 并渲染缩略图列表与添加按钮
|
|
$current_media = get_option('yoone_snow_media_items', array());
|
|
if (!is_array($current_media)) { $current_media = array(); }
|
|
echo '<div id="yooneSnowMediaList" style="display:flex;flex-wrap:wrap;gap:12px;">';
|
|
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;">';
|
|
echo '<img src="' . $safe_url . '" alt="media" style="width:72px;height:72px;object-fit:contain;" />';
|
|
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;">Remove</button>';
|
|
echo '</div>';
|
|
}
|
|
echo '</div>';
|
|
echo '<p><button type="button" class="button button-primary" id="yooneSnowAddMedia">Add Images</button></p>';
|
|
echo '<p class="description">Choose images or SVG from media library to render</p>';
|
|
},
|
|
'yoone_snow',
|
|
'yoone_snow_section'
|
|
);
|
|
|
|
// 注册媒体权重设置项 将附件 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(),
|
|
));
|
|
|
|
// 在媒体形状字段之后渲染权重字段 包含形状权重与媒体权重
|
|
add_settings_field(
|
|
'yoone_snow_weights_combined',
|
|
'Weights',
|
|
function() {
|
|
// 渲染形状权重 输入仅显示已勾选的形状 默认 1
|
|
$selected_shapes = get_option('yoone_snow_mixed_items', array('dot','flake'));
|
|
if (is_string($selected_shapes)) {
|
|
$selected_shapes = array_filter(array_map('trim', explode(',', $selected_shapes)));
|
|
}
|
|
if (!is_array($selected_shapes)) { $selected_shapes = array('dot','flake'); }
|
|
$shape_labels = 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' => 'Emoji',
|
|
);
|
|
$shape_weights_current = get_option('yoone_snow_shape_weights', array());
|
|
if (!is_array($shape_weights_current)) { $shape_weights_current = array(); }
|
|
echo '<div id="yooneSnowWeightsContainer" style="margin-top:8px;">';
|
|
echo '<div id="yooneSnowShapeWeights" style="display:flex;flex-wrap:wrap;gap:12px;margin-bottom:12px;">';
|
|
foreach ($selected_shapes as $sk) {
|
|
if (!isset($shape_labels[$sk])) { continue; }
|
|
$val = isset($shape_weights_current[$sk]) ? intval($shape_weights_current[$sk]) : 1;
|
|
echo '<label data-shape-key="' . esc_attr($sk) . '" style="display:flex;flex-direction:column;min-width:160px;">';
|
|
echo '<span>' . esc_html($shape_labels[$sk]) . '</span>';
|
|
echo '<input type="number" min="0" name="yoone_snow_shape_weights[' . esc_attr($sk) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
|
|
echo '</label>';
|
|
}
|
|
echo '</div>';
|
|
// 渲染媒体权重 按当前媒体列表生成 输入名称为附件 ID 映射 默认 1
|
|
$current_media = get_option('yoone_snow_media_items', array());
|
|
if (!is_array($current_media)) { $current_media = array(); }
|
|
$media_weights_current = get_option('yoone_snow_media_weights', array());
|
|
if (!is_array($media_weights_current)) { $media_weights_current = array(); }
|
|
echo '<div id="yooneSnowMediaWeights" style="display:flex;flex-wrap:wrap;gap:12px;">';
|
|
foreach ($current_media as $attachment_id) {
|
|
$aid = intval($attachment_id);
|
|
$label = 'Media ' . $aid;
|
|
$val = isset($media_weights_current[$aid]) ? intval($media_weights_current[$aid]) : 1;
|
|
echo '<label data-attachment-id="' . esc_attr($aid) . '" style="display:flex;flex-direction:column;min-width:160px;">';
|
|
echo '<span>' . esc_html($label) . '</span>';
|
|
echo '<input type="number" min="0" name="yoone_snow_media_weights[' . esc_attr($aid) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
|
|
echo '</label>';
|
|
}
|
|
echo '</div>';
|
|
// 渲染 emoji 权重 输入为字符键 默认 1
|
|
$emoji_items_current = get_option('yoone_snow_emoji_items', array());
|
|
if (!is_array($emoji_items_current)) { $emoji_items_current = array(); }
|
|
$emoji_weights_current = get_option('yoone_snow_emoji_weights', array());
|
|
if (!is_array($emoji_weights_current)) { $emoji_weights_current = array(); }
|
|
echo '<div id="yooneSnowEmojiWeights" style="display:flex;flex-wrap:wrap;gap:12px;margin-top:12px;">';
|
|
foreach ($emoji_items_current as $emojiChar) {
|
|
$label = trim((string)$emojiChar);
|
|
if ($label === '') { continue; }
|
|
$val = isset($emoji_weights_current[$label]) ? intval($emoji_weights_current[$label]) : 1;
|
|
echo '<label data-emoji-char="' . esc_attr($label) . '" style="display:flex;flex-direction:column;min-width:160px;">';
|
|
echo '<span>' . esc_html($label) . '</span>';
|
|
echo '<input type="number" min="0" name="yoone_snow_emoji_weights[' . esc_attr($label) . ']" value="' . esc_attr($val) . '" style="width:120px;" />';
|
|
echo '</label>';
|
|
}
|
|
echo '</div>';
|
|
echo '<p class="description">Higher value increases selection probability 0 disables default is 1</p>';
|
|
echo '</div>';
|
|
// 绑定交互脚本 通过已有 admin 脚本实现动态刷新
|
|
},
|
|
'yoone_snow',
|
|
'yoone_snow_section'
|
|
);
|
|
|
|
// 注册 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(),
|
|
));
|
|
|
|
// 注册首页显示时长设置 项为整数秒 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',
|
|
'Home Display Duration Seconds',
|
|
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">Duration in seconds for snow on home 0 means infinite</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',
|
|
'Max Snowflakes On Screen',
|
|
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">0 means auto based on viewport area upper bound 1000</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',
|
|
'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 '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_size[min]" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
|
|
echo '<label>Max <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">Random radius in [min max] single option</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',
|
|
'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 '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_drift_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
|
|
echo '<label>Max <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">Random vertical drift speed base in [min max]</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',
|
|
'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 '<label style="margin-right:12px;">Min <input type="number" min="0" step="0.1" name="yoone_snow_swing_min" value="' . esc_attr($min) . '" style="width:120px;" /></label>';
|
|
echo '<label>Max <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">Random horizontal swing amplitude base in [min max] before offset scale</p>';
|
|
},
|
|
'yoone_snow',
|
|
'yoone_snow_section'
|
|
);
|
|
}
|
|
|
|
// 添加设置页面到后台菜单 条目在设置菜单下
|
|
function yoone_snow_add_settings_page() {
|
|
add_options_page(
|
|
'Yoone Snow',
|
|
'Yoone Snow',
|
|
'manage_options',
|
|
'yoone_snow',
|
|
function() {
|
|
// 渲染设置页面 表单提交到 options.php
|
|
echo '<div class="wrap">';
|
|
echo '<h1>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) . '">Settings</a>';
|
|
// 将设置链接插入到最前面 便于用户点击
|
|
array_unshift($links, $settingsLink);
|
|
return $links;
|
|
}
|
|
|
|
// 绑定到当前插件的 action links 钩子 使用 plugin_basename 计算插件标识
|
|
add_filter('plugin_action_links_' . plugin_basename(__FILE__), 'yoone_snow_plugin_action_links');
|