NewHome/includes/layout.php

213 lines
7.6 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<?php
/**
* layout.php —— 页面公共输出组件
* 约定:每个页面在引入本文件前需定义全局 $P资源前缀根目录为 ''func/、admin/ 子目录为 '../')。
*/
require_once __DIR__ . '/db.php';
if (!isset($P)) {
$P = '';
}
/** HTML 转义 */
function he($s): string
{
return htmlspecialchars((string)$s, ENT_QUOTES, 'UTF-8');
}
/** 当前 logo 的站点相对路径(后台配置优先,空则默认) */
function site_logo_rel(): string
{
$logo = setting_get('site_logo', '');
return $logo !== '' ? $logo : 'assets/img/logo.svg';
}
/** 输出页面 <head>:标题/favicon/主题防闪烁/公共样式 */
function layout_head(string $title = ''): void
{
global $P;
$site = setting_get('site_name', '知识导航站');
$logo = site_logo_rel();
$fullTitle = ($title !== '' ? $title . ' - ' : '') . $site;
$prefix = $P;
echo '<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8">';
echo '<meta name="viewport" content="width=device-width, initial-scale=1.0">';
echo '<meta name="renderer" content="webkit">';
// 主题防闪烁:在任何样式加载前应用
echo '<script>(function(){var t;try{t=localStorage.getItem("hp-theme")}catch(e){}';
echo 'if(!t){t=(window.matchMedia&&window.matchMedia("(prefers-color-scheme: dark)").matches)?"dark":"light"}';
echo 'document.documentElement.setAttribute("data-theme",t);})();</script>';
echo '<title>' . he($fullTitle) . '</title>';
echo '<link rel="icon" href="' . he($prefix . $logo) . '">';
echo '<link rel="stylesheet" href="' . he($prefix) . 'assets/css/common.css">';
// 站长提示开关(后台可关):关闭时统一隐藏带 st-tip 标记的辅助文案
if ((setting_get('show_st_tips', '1')) !== '1') {
echo '<style>.st-tip{display:none!important}</style>';
}
echo '</head><body>' . "\n";
}
/** 昼夜切换浮动按钮 */
function layout_theme_fab(): void
{
echo '<button type="button" class="theme-fab js-theme-btn" title="切换昼夜模式" aria-label="切换昼夜模式">';
echo '<span class="js-theme-icon"></span></button>' . "\n";
}
/** 站内 logo 图片(内容区使用,同 favicon */
function layout_logo_img(string $cls = 'logo', string $alt = 'logo'): void
{
global $P;
echo '<img class="' . he($cls) . '" src="' . he($P . site_logo_rel()) . '" alt="' . he($alt) . '">';
}
/** 全站页脚 */
function layout_footer(): void
{
global $P;
$site = setting_get('site_name', '知识导航站');
$icp = setting_get('icp_no', '');
$gongan = setting_get('gongan_no', '');
$gonganLink = setting_get('gongan_link', '');
$copyright = setting_get('copyright', '');
$extra = setting_get('footer_text', '');
echo '<footer class="footer"><div class="wrap footer-inner">';
if ($icp !== '') {
echo '<div><a href="https://beian.miit.gov.cn/" target="_blank" rel="noopener noreferrer" style="color:inherit">' . he($icp) . '</a></div>';
}
if ($gongan !== '') {
if ($gonganLink !== '') {
echo '<div><a href="' . he($gonganLink) . '" target="_blank" rel="noopener noreferrer" style="color:inherit">' . he($gongan) . '</a></div>';
} else {
echo '<div>' . he($gongan) . '</div>';
}
}
if ($copyright !== '') {
echo '<div>' . he(str_replace('{site_name}', $site, $copyright)) . '</div>';
}
if ($extra !== '') {
echo '<div>' . he($extra) . '</div>';
}
echo '<div><a class="admin-link" href="' . he($P) . 'admin/login.php">后台管理</a></div>';
echo '</div></footer>' . "\n";
}
/** 读取首页展示的名言quota.txt 随机一行) */
function home_quote(): string
{
$file = DATA_DIR . '/quota.txt';
$lines = [];
if (is_file($file)) {
$raw = @file($file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
if (is_array($raw)) {
foreach ($raw as $ln) {
$ln = trim($ln);
if ($ln !== '' && strpos($ln, '#') !== 0) {
$lines[] = $ln;
}
}
}
}
if (!$lines) {
return '知识就是力量。';
}
return $lines[array_rand($lines)];
}
/** 图标值是否属于图片dataURL / http / 常见图片扩展 / 相对路径) */
function icon_is_image(string $raw): bool
{
$v = trim($raw);
if ($v === '') {
return false;
}
if (preg_match('/^data:image\//i', $v) || preg_match('#^https?://#i', $v)) {
return true;
}
if (preg_match('/\.(png|jpe?g|gif|svg|webp)(\?|#|$)/i', $v)) {
return true;
}
return $v[0] === '/' || strpos($v, './') === 0 || strpos($v, '../') === 0;
}
/** 渲染图标:图片返回 <img>,否则返回转义文本(供前台/后台预览复用) */
function site_icon(?string $raw, string $alt = 'icon'): string
{
$v = trim((string)$raw);
if ($v === '') {
return '';
}
if (icon_is_image($v)) {
return '<img class="icon-img" src="' . he($v) . '" alt="' . he($alt) . '" loading="lazy">';
}
return he($v);
}
/** 默认 CDN IPv4 段库每行CIDR + 空格 + 厂商名;# 开头为注释,由后台可覆盖) */
function default_cdn_ranges(): string
{
return implode("\n", [
'# 内置默认Cloudflare 官方 IPv4 段可在后台“CDN IP 段库”补充其它厂商)',
'103.21.244.0/22 Cloudflare',
'103.22.200.0/22 Cloudflare',
'103.31.4.0/22 Cloudflare',
'104.16.0.0/13 Cloudflare',
'104.24.0.0/14 Cloudflare',
'108.162.192.0/18 Cloudflare',
'131.0.72.0/22 Cloudflare',
'141.101.64.0/18 Cloudflare',
'162.158.0.0/15 Cloudflare',
'172.64.0.0/13 Cloudflare',
'173.245.48.0/20 Cloudflare',
'188.114.96.0/20 Cloudflare',
'190.93.240.0/20 Cloudflare',
'197.234.240.0/22 Cloudflare',
'198.41.128.0/17 Cloudflare',
]);
}
/** 当前生效的 CDN 段库文本(后台配置优先,为空回退默认内置列表) */
function cdn_ranges_text(): string
{
$v = setting_get('cdn_ranges', '');
return $v !== '' ? $v : default_cdn_ranges();
}
/** 默认搜索引擎列表(后台未配置时的兜底) */
function default_search_engines(): array
{
return [
['key' => 'bing', 'name' => 'Bing', 'icon' => 'B', 'url' => 'https://www.bing.com/search?q={kw}'],
['key' => 'baidu', 'name' => '百度', 'icon' => '度', 'url' => 'https://www.baidu.com/s?wd={kw}'],
['key' => 'github', 'name' => 'GitHub', 'icon' => 'G', 'url' => 'https://github.com/search?q={kw}'],
['key' => 'google', 'name' => 'Google', 'icon' => 'G', 'url' => 'https://www.google.com/search?q={kw}'],
];
}
/** 读取当前生效的搜索引擎列表(优先后台配置,无效或为空回退默认) */
function search_engines(): array
{
$raw = setting_get('search_engines', '');
if ($raw === '') {
return default_search_engines();
}
$arr = json_decode($raw, true);
if (!is_array($arr) || count($arr) === 0) {
return default_search_engines();
}
$out = [];
foreach ($arr as $i => $e) {
if (!is_array($e)) continue;
$name = trim((string)($e['name'] ?? ''));
$url = trim((string)($e['url'] ?? ''));
$icon = trim((string)($e['icon'] ?? ''));
if ($name === '' || $url === '' || strpos($url, '{kw}') === false) continue;
$key = trim((string)($e['key'] ?? ''));
if ($key === '') {
$key = 'e' . ((int)$i + 1);
}
$out[] = ['key' => $key, 'name' => $name, 'icon' => ($icon !== '' ? $icon : 'S'), 'url' => $url];
}
return $out !== [] ? $out : default_search_engines();
}