Files
wordpress-kratos-yang/inc/theme-mourning.php
T

379 lines
16 KiB
PHP
Raw Normal View History

<?php
/**
* 纪念日自动哀悼
*
* ── 依据 ─────────────────────────────────────────────────────
* 以下日期分三类,可信度不同,代码里也分开对待:
*
* A. 法定纪念日(全国人大常委会决定设立,每年国家举行纪念活动)
* · 09-03 中国人民抗日战争胜利纪念日
* 2014-02-27 十二届全国人大常委会第七次会议决定设立
* · 09-30 烈士纪念日
* 2014-08-31 十二届全国人大常委会第十次会议决定设立
* · 12-13 南京大屠杀死难者国家公祭日
* 2014-02-27 同一会议决定设立
* 依据:全国人大网《铭记历史坐标 凝聚前行力量》
* http://www.npc.gov.cn/c2/c30834/202510/t20251020_448426.html
* http://npc.gov.cn/c2/c12435/c12487/201905/t20190521_220992.html
*
* B. 民间广泛采用(非法定,但主流网站普遍黑白)
* · 05-12 汶川地震纪念(2008-05-12 14:28)
* · 09-18 九一八事变纪念(1931-09-18)
* 这两个日期黑白是网络惯例,但不是法定「哀悼日」,所以默认**不勾选**,
* 由用户自己决定。硬编码成默认开启会替用户做价值判断,不合适。
*
* C. 历史全国哀悼日(一次性事件,非每年)
* · 2008-05-19~21 汶川地震(国务院决定)
* · 2010-04-21 玉树地震(国务院决定)
* · 2010-08-15 舟曲泥石流(国务院决定)
* · 2020-04-04 新冠疫情牺牲烈士和逝世同胞(国务院决定)
* 这些是**特定年份的特定日期**,不是周年循环。所以代码里不把它们
* 当成"每年重复",而是保留为可自定义的具体日期,避免明年 4 月 4 日
* 又自动变黑白(那是错的)。
*
* ── 设计取舍 ──────────────────────────────────────────────────
* 1. **按站点时区判断**,不按服务器时区。用 current_time('md'),
* 它走 WordPress 设置的时区。否则服务器在 UTC,北京时间 9 月 18 日
* 早上 8 点前还算 17 日,哀悼会晚 8 小时生效。
*
* 2. **只判断月日,不判断年份**。纪念日是周年性质,每年的同一天都要生效。
*
* 3. **兼容旧的 g_rip 开关**。老版本是纯手动「一直黑白」。
* 若用户勾着旧的 g_rip,仍然一直黑白;新逻辑是叠加在上面的。
* 不能因为升级就让原本黑白中的站点突然恢复彩色。
*
* @author Yongquan Yang <yangyq.net>
* @license MIT License
*/
if (!defined('ABSPATH')) {
exit;
}
/**
* 内置纪念日清单
*
* 返回 array( 键 => array(月份, 日, 名称, 说明, 分类, 是否默认勾选) )
*
* 键名用 `m-d` 形式(补零),与 current_time('m') / current_time('d') 直接可比。
*/
function yang_mourning_presets()
{
return array(
// ── A 类:法定纪念日 ──
'09-03' => array(
'name' => __('中国人民抗日战争胜利纪念日', 'kratos'),
'note' => __('1945 年 9 月 2 日日本签署投降书,9 月 3 日定为胜利纪念日', 'kratos'),
'group' => 'legal',
'default' => 0,
),
'09-30' => array(
'name' => __('烈士纪念日', 'kratos'),
'note' => __('向人民英雄敬献花篮仪式在天安门广场举行', 'kratos'),
'group' => 'legal',
'default' => 0,
),
'12-13' => array(
'name' => __('南京大屠杀死难者国家公祭日', 'kratos'),
'note' => __('1937 年 12 月 13 日南京沦陷,30 万同胞遇难', 'kratos'),
'group' => 'legal',
'default' => 0,
),
// ── B 类:民间惯例,默认不勾 ──
'05-12' => array(
'name' => __('汶川地震纪念日', 'kratos'),
'note' => __('2008 年 5 月 12 日 14 时 28 分,汶川发生 8.0 级地震', 'kratos'),
'group' => 'custom',
'default' => 0,
),
'09-18' => array(
'name' => __('九一八事变纪念日', 'kratos'),
'note' => __('1931 年 9 月 18 日,日本关东军发动九一八事变', 'kratos'),
'group' => 'custom',
'default' => 0,
),
'07-07' => array(
'name' => __('七七事变纪念日', 'kratos'),
'note' => __('1937 年 7 月 7 日,卢沟桥事变,全民族抗战爆发', 'kratos'),
'group' => 'custom',
'default' => 0,
),
'12-12' => array(
'name' => __('西安事变纪念日', 'kratos'),
'note' => __('1936 年 12 月 12 日,张学良、杨虎城发动兵谏', 'kratos'),
'group' => 'custom',
'default' => 0,
),
'08-15' => array(
'name' => __('日本宣布无条件投降纪念日', 'kratos'),
'note' => __('1945 年 8 月 15 日,日本天皇宣布接受波茨坦公告', 'kratos'),
'group' => 'custom',
'default' => 0,
),
'04-04' => array(
'name' => __('清明节', 'kratos'),
'note' => __('节气日期每年浮动,这里固定为 4 月 4 日,仅作近似', 'kratos'),
'group' => 'custom',
'default' => 0,
),
);
}
/**
* 读取用户勾选的纪念日
*
* 老站点升级上来时这个选项还不存在,此时返回 presets 里 default=1 的那些。
* 注意**不能**简单用 `kratos_option('g_rip_days', 默认数组)`:kratos_option
* 对未设置的键返回默认值,但如果用户曾经保存过空数组(全部取消勾选),
* 存进去的就是空数组,此时必须尊重「用户就是要关掉」,不能再回退到默认。
* 所以这里用「option 数组里有没有这个键」来判断,而不是判断值是否为空。
*/
function yang_mourning_days()
{
$presets = yang_mourning_presets();
$opts = get_option('kratos');
$has = is_array($opts) && array_key_exists('g_rip_days', $opts);
if (!$has) {
// 从未保存过:用内置默认
$out = array();
foreach ($presets as $md => $info) {
if (!empty($info['default'])) {
$out[] = $md;
}
}
return $out;
}
$saved = $opts['g_rip_days'];
if (!is_array($saved)) {
return array();
}
// 只保留在 presets 里存在、且值为真的日期(防止脏数据)
$out = array();
foreach ($saved as $md => $v) {
if ($v && isset($presets[$md])) {
$out[] = $md;
}
}
return $out;
}
/**
* 今天是否应当哀悼
*/
function yang_is_mourning_today()
{
// 旧的手动开关:开了就一直黑白(保持向后兼容,不能让升级把黑白站点变回彩色)
if (kratos_option('g_rip', false)) {
return true;
}
if (!kratos_option('g_rip_auto', false)) {
return false;
}
// 走站点时区,不是服务器时区
$md = current_time('m') . '-' . current_time('d');
$days = yang_mourning_days();
// 自定义附加日期(用户自己填的,如特定年份的哀悼日)
if (kratos_option('g_rip_extra', '')) {
$extra = kratos_option('g_rip_extra', '');
foreach (preg_split('/[\s,,、;;]+/u', (string) $extra) as $one) {
$one = trim($one);
if ($one === '') {
continue;
}
// 支持 MM-DD 与 YYYY-MM-DD 两种写法
if (preg_match('/^\d{2}-\d{2}$/', $one)) {
$days[] = $one;
} elseif (preg_match('/^(\d{4})-(\d{2})-(\d{2})$/', $one, $m)) {
// 带年份的只在当年生效,不是周年循环
if ((int) $m[1] === (int) current_time('Y')) {
$days[] = $m[2] . '-' . $m[3];
}
}
}
}
return in_array($md, $days, true);
}
/**
* 输出黑白样式
*
* 几个细节:
* 1. `html { filter: grayscale(1) }` 会给整页建一个合成层,fixed 定位的元素
* 在某些旧版 Chrome 上会错位。所以额外加 `background-attachment: fixed`
* 的补丁样式不写;这里改用给 body 和 .k-header 分别处理更稳。
* —— 实际上实测最稳的是给 html 加 filter 并用 transform: translateZ(0)
* 在 body 上兜住,避免 iOS Safari 的渲染错位。这里保留 html 方案,
* 因为它在用户当前主题结构下工作正常(老功能的写法),只是补一条
* iOS 的兼容声明。
* 2. 不能只挂 is_home():详情页也应该黑白。老代码限定首页,那是 bug。
* 3. 只在真正哀悼的日子输出,避免给所有页面增加一条无用样式。
*/
function yang_mourning_style()
{
if (is_admin() || !yang_is_mourning_today()) {
return;
}
echo '<style id="yang-mourning">'
. "\nhtml{filter:grayscale(100%);-webkit-filter:grayscale(100%);}"
. "\nhtml body{-webkit-transform:translateZ(0);}"
// 打印时不灰度,否则打印出来是灰的
. "\n@media print{html{filter:none!important;-webkit-filter:none!important;}}"
. "\n</style>\n";
}
add_action('wp_head', 'yang_mourning_style', 1);
/**
* 后台显示今天的哀悼状态,便于确认配置是否生效
*/
function yang_mourning_status_text()
{
if (kratos_option('g_rip', false)) {
return __('当前为手动常开模式:站点一直黑白,与日期无关。', 'kratos');
}
if (!kratos_option('g_rip_auto', false)) {
return __('自动哀悼未开启。', 'kratos');
}
$days = yang_mourning_days();
if (empty($days)) {
return __('自动哀悼已开启,但一个日期都没勾选,因此不会生效。', 'kratos');
}
sort($days);
$today = current_time('m') . '-' . current_time('d');
$is = in_array($today, $days, true);
return sprintf(
/* translators: 1: 今天的月日, 2: 是否处于哀悼, 3: 勾选的日期列表 */
__('今天是 %1$s,%2$s。已启用:%3$s', 'kratos'),
$today,
$is ? __('正在哀悼,站点已黑白', 'kratos') : __('不在哀悼日', 'kratos'),
implode('、', $days)
);
}
/**
* 哀悼期间在页面顶部显示一条提示,链接到说明页
*
* ── 为什么必须放在顶部 ────────────────────────────────────────
* 用户的原话是「别人都不知道咋回事,为啥变黑白」。只把入口藏在页脚,
* 绝大多数人根本不会看到 —— 他们看到一片灰就关掉了。
* 所以提示条要出现在**首屏就能看到**的位置。
*
* ── 为什么不做成弹窗 ──────────────────────────────────────────
* 弹窗会挡住内容,纪念日当天每个访客每次访问都被弹一次,是骚扰。
* 一条可以关闭的细横条才是合适的强度,且关闭状态记在 sessionStorage
* (用 session 而不是 localStorage:当天关闭后,第二天不应该还记着)。
*/
function yang_mourning_notice_bar()
{
// 只在真正哀悼时显示,且不在说明页自身显示(那页自己会说明)
if (is_admin() || !yang_is_mourning_today()) {
return;
}
if (yang_mourning_page_is_current()) {
return;
}
$url = yang_mourning_page_url();
$name = yang_mourning_today_name();
$text = $name !== ''
? sprintf(__('今天是%s,本站以黑白致哀。', 'kratos'), $name)
: __('今天是纪念日,本站以黑白致哀。', 'kratos');
?>
<div id="yang-mourning-bar" class="yang-mourning-bar" role="region" aria-label="<?php esc_attr_e('哀悼提示', 'kratos'); ?>">
<span class="ymb-dot" aria-hidden="true"></span>
<span class="ymb-text"><?php echo esc_html($text); ?></span>
<a class="ymb-link" href="<?php echo esc_url($url); ?>"><?php _e('了解详情', 'kratos'); ?></a>
<button type="button" class="ymb-close" aria-label="<?php esc_attr_e('关闭提示', 'kratos'); ?>">&times;</button>
</div>
<style>
.yang-mourning-bar{position:relative;z-index:9990;display:flex;align-items:center;justify-content:center;flex-wrap:wrap;gap:10px;padding:9px 46px 9px 18px;background:#2b2f36;color:#e8eaed;font-size:13.5px;line-height:1.6;font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"PingFang SC","Microsoft YaHei",sans-serif;letter-spacing:.2px}
.yang-mourning-bar .ymb-dot{width:6px;height:6px;border-radius:50%;background:#b0b7c0;flex:0 0 auto}
.yang-mourning-bar .ymb-text{opacity:.92}
.yang-mourning-bar .ymb-link{color:#fff;text-decoration:none;border-bottom:1px solid rgba(255,255,255,.42);padding-bottom:1px;transition:border-color .18s ease}
.yang-mourning-bar .ymb-link:hover{border-bottom-color:#fff}
.yang-mourning-bar .ymb-close{position:absolute;right:12px;top:50%;transform:translateY(-50%);background:none;border:0;color:#9aa3ad;font-size:19px;line-height:1;cursor:pointer;padding:2px 7px;border-radius:4px}
.yang-mourning-bar .ymb-close:hover{color:#fff;background:rgba(255,255,255,.1)}
.yang-mourning-bar.is-hidden{display:none}
@media (max-width:560px){
.yang-mourning-bar{font-size:12.5px;padding:8px 42px 8px 12px;gap:7px;justify-content:flex-start}
}
</style>
<script>
(function () {
var bar = document.getElementById('yang-mourning-bar');
if (!bar) return;
// 当天关闭过就不再显示。用 sessionStorage 而非 localStorage:
// 明天是另一天,不该还记着昨天的关闭操作。
var KEY = 'yangMournBarClosed:' + <?php echo wp_json_encode(current_time('Ymd')); ?>;
try { if (sessionStorage.getItem(KEY) === '1') { bar.className += ' is-hidden'; return; } } catch (e) {}
var btn = bar.querySelector('.ymb-close');
if (btn) {
btn.addEventListener('click', function () {
bar.className += ' is-hidden';
try { sessionStorage.setItem(KEY, '1'); } catch (e) {}
});
}
})();
</script>
<?php
}
add_action('wp_body_open', 'yang_mourning_notice_bar', 5);
/**
* 页脚常驻入口
*
* 提示条只在哀悼日出现,但说明页应该随时可达 ——
* 有人可能事后才知道,想去看看是怎么回事。
*/
function yang_mourning_footer_link()
{
ob_start();
?>
<p class="yang-mourning-footer">
<a href="<?php echo esc_url(yang_mourning_page_url()); ?>"><?php _e('纪念日哀悼说明', 'kratos'); ?></a>
</p>
<style>
.yang-mourning-footer{margin:6px 0 0}
.yang-mourning-footer a{color:inherit;opacity:.75;text-decoration:none;border-bottom:1px dotted currentColor;font-size:13px;transition:opacity .18s ease}
.yang-mourning-footer a:hover{opacity:1}
</style>
<?php
echo ob_get_clean();
}
add_action('wp_footer', 'yang_mourning_footer_link', 20);
/**
* 当前请求是不是说明页自己
*/
function yang_mourning_page_is_current()
{
$path = trim((string) parse_url($_SERVER['REQUEST_URI'] ?? '', PHP_URL_PATH), '/');
$home_path = trim((string) parse_url(home_url('/'), PHP_URL_PATH), '/');
if ($home_path !== '' && strpos($path, $home_path) === 0) {
$path = trim(substr($path, strlen($home_path)), '/');
}
return ($path === yang_mourning_page_slug());
}
/**
* 今天哀悼的纪念日名称(用于提示条文案)
*
* 取第一个命中的,取不到返回空串。
*/
function yang_mourning_today_name()
{
$md = current_time('m') . '-' . current_time('d');
$presets = yang_mourning_presets();
if (isset($presets[$md]) && in_array($md, yang_mourning_days(), true)) {
return $presets[$md]['name'];
}
return '';
}