页脚入口与安装向导;加固管理员口令存储
需求落实: 1. 页脚常驻「管理后台」链接,不用再手敲 admin.php。 2. 页脚「由 FeedLite 驱动」中的 FeedLite 改为超链接(指向开源仓库); 后台新增「页脚」设置组,可关闭整句(关闭后只留管理后台链接),也可改链接地址。 3. 管理员口令存储方式在后台如实展示:数据库 settings.admin.password_hash, 存 bcrypt 单向哈希(不可逆),明文不落盘。 4. 新增安装向导 install.php:data/ 下没有数据库时自动 302 引导; 体检环境 -> 填站点信息与口令 -> 生成 config.php -> 建库建表 -> 完成。 顺带修掉一个真实安全隐患: config.php 里的出厂占位口令 CHANGE_ME_admin 原先可被当作有效口令登录后台 (等于公开仓库里就写着线上后门口令)。现改为占位/空值一律拒绝, 未设口令时后台明确提示去安装向导,不再含糊报「口令不正确」。 实现要点: - 新增 fb_cfg_file():只读配置文件、不叠加 settings 表、绝不触达数据库。 「是否已安装」以数据库文件是否存在为判据,而 fb_cfg() 读 settings 会触发 fb_db() 就地建库 —— 那会把安装态自己造出来,所以判定必须走不碰库的通道。 - bootstrap 只在已安装时建库;未安装则 302 到 install.php(CLI 下报错退出)。 - fb_settings_flat() 在未安装时直接返回空,杜绝任何隐式建库。 - install.php 用文本级替换生成 config.php,保留全部注释与 OSS/SMTP 段落; 生成的 PHP 用 token_get_all(TOKEN_PARSE) 校验后才落盘。 - 页脚链接只放行 http/https,后台保存时校验(防 javascript: 伪协议)。 - style.css 页脚行改 flex,版本号 v6 -> v7。 验证: - php -l 全量通过;现有回归 25/25、附件专项 13/13 保持通过。 - 安装向导专项 60/60(含「明文口令未写入任何文件」「数据库字节里搜不到明文」)。 - 反向验证:撤销三处修复后对应断言确实失败(断言非装饰)。 - 几何检查:本机 15/15 + 页脚专项全过;线上真实 URL 15/15 + 页脚专项全过。 - 线上真机冒烟 22/22。
This commit is contained in:
+371
@@ -0,0 +1,371 @@
|
||||
<?php
|
||||
/**
|
||||
* FeedLite · 安装向导
|
||||
* ------------------------------------------------------------------
|
||||
* 什么时候会用到:`data/` 下还没有数据库文件(全新部署)时,任何页面都会把
|
||||
* 访问者送到这里。向导只做四件事:
|
||||
* ① 体检:PHP 版本、必需扩展、目录可写性;
|
||||
* ② 收信息:站点标题/描述、管理员口令、官方邮箱、站点根地址;
|
||||
* ③ 写配置:生成 config.php(在 config.example.php 基础上逐项替换,
|
||||
* 已有的 OSS / SMTP 段落原样保留,不覆盖你手工改过的内容);
|
||||
* ④ 建库建表 + 把口令以 bcrypt 哈希写进数据库 ——
|
||||
* **明文口令不落盘**,config.php 里始终保持出厂占位值。
|
||||
*
|
||||
* 装完之后:删除本文件与否都可以(已安装态下它会礼貌地拒绝重复安装),
|
||||
* 但建议删掉,少一个入口少一分风险。
|
||||
* ------------------------------------------------------------------
|
||||
* 安装向导必须能在「尚未安装」的状态下运行,所以这里自己引导,
|
||||
* 并显式声明跳过 bootstrap 的「未安装就跳 install」逻辑,否则会自己跳自己。
|
||||
*/
|
||||
define('FB_SKIP_INSTALL_REDIRECT', 1);
|
||||
require __DIR__ . '/lib/bootstrap.php';
|
||||
|
||||
$root = FB_ROOT;
|
||||
$cfgFile = $root . '/config.php';
|
||||
|
||||
/* ==================================================================
|
||||
* 已安装 → 拒绝重复安装
|
||||
* ================================================================== */
|
||||
$already = fb_installed();
|
||||
|
||||
/* ==================================================================
|
||||
* 环境体检
|
||||
* ================================================================== */
|
||||
function fb_install_checks() {
|
||||
$need = ['pdo_sqlite' => 'SQLite 数据库(PDO)', 'openssl' => 'SMTP / OSS 加密通信'];
|
||||
$opt = ['curl' => '访问 OSS(可选,没有会退回 PHP 流式请求)', 'mbstring' => '中文字数统计(可选)'];
|
||||
|
||||
$dirs = [
|
||||
FB_ROOT => '站点根目录(要写入 config.php)',
|
||||
FB_ROOT . '/data' => '数据库目录 data/',
|
||||
FB_ROOT . '/uploads' => '本机附件目录 uploads/(本机存储模式用)',
|
||||
];
|
||||
|
||||
$rows = [];
|
||||
$ok = true;
|
||||
|
||||
$verOk = version_compare(PHP_VERSION, '7.4.0', '>=');
|
||||
$rows[] = ['PHP 版本', PHP_VERSION, $verOk, '需要 7.4 及以上(推荐 8.0~8.3)', $verOk ? '' : '当前版本过低,请切换站点的 PHP 版本'];
|
||||
if (!$verOk) { $ok = false; }
|
||||
|
||||
foreach ($need as $ext => $why) {
|
||||
$has = extension_loaded($ext);
|
||||
if (!$has) { $ok = false; }
|
||||
$rows[] = ['PHP 扩展:' . $ext, $has ? '已启用' : '未启用', $has, $why, $has ? '' : ('请在 PHP 设置里开启 ' . $ext . ' 扩展')];
|
||||
}
|
||||
foreach ($opt as $ext => $why) {
|
||||
$has = extension_loaded($ext);
|
||||
$rows[] = ['PHP 扩展:' . $ext, $has ? '已启用' : '未启用(不影响安装)', true, $why, ''];
|
||||
}
|
||||
|
||||
foreach ($dirs as $d => $why) {
|
||||
if (!is_dir($d)) { @mkdir($d, 0755, true); }
|
||||
$w = is_dir($d) && is_writable($d);
|
||||
if (!$w) { $ok = false; }
|
||||
$shown = ($d === FB_ROOT) ? '(站点根)' : str_replace(FB_ROOT, '', $d);
|
||||
$rows[] = ['目录可写 ' . $shown, $w ? '可写' : '不可写', $w, $why, $w ? '' : ('请给 Web 用户写权限,例如 chown www:www ' . $d)];
|
||||
}
|
||||
|
||||
// config.php 若已存在(例如从模板复制过),必须可写才能就地更新
|
||||
if (is_file(FB_ROOT . '/config.php')) {
|
||||
$w = is_writable(FB_ROOT . '/config.php');
|
||||
if (!$w) { $ok = false; }
|
||||
$rows[] = ['config.php 可写', $w ? '可写' : '不可写', $w, '需要就地更新配置', $w ? '' : '请给该文件写权限,例如 chown www:www config.php'];
|
||||
} else {
|
||||
$rows[] = ['config.php', '尚不存在(将自动生成)', true, '由安装向导创建', ''];
|
||||
}
|
||||
|
||||
$ex = is_file(FB_ROOT . '/config.example.php');
|
||||
$rows[] = ['配置模板 config.example.php', $ex ? '存在' : '缺失', $ex, '生成 config.php 的底稿', $ex ? '' : '仓库文件不完整,请重新获取源码'];
|
||||
if (!$ex) { $ok = false; }
|
||||
|
||||
return [$ok, $rows];
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* 生成 config.php(在模板文本上逐项替换,保留注释与其它段落)
|
||||
* ================================================================== */
|
||||
function fb_install_write_config(array $v) {
|
||||
$example = FB_ROOT . '/config.example.php';
|
||||
$src = (string)@file_get_contents($example);
|
||||
if ($src === '') { return '读不到 config.example.php。'; }
|
||||
|
||||
/** 把顶层键(含引号形式)整项替换成新值;找不到就返回原串由调用方兜底 */
|
||||
$setTop = function ($key, $valueExpr, $txt) {
|
||||
$pat = "/^([ \t]*'" . preg_quote($key, '/') . "'[ \t]*=>[ \t]*)(.*?)(,[ \t]*)(\r?\n)/m";
|
||||
$rep = '$1' . $valueExpr . '$3$4';
|
||||
return preg_replace($pat, $rep, $txt, 1);
|
||||
};
|
||||
$phpStr = function ($s) { return "'" . str_replace(["\\", "'"], ["\\\\", "\\'"], (string)$s) . "'"; };
|
||||
|
||||
$dbPath = str_replace('\\', '/', FB_ROOT . '/data/feedback.db');
|
||||
|
||||
$src = $setTop('db_path', "'" . $dbPath . "'", $src);
|
||||
$src = $setTop('site_name', $phpStr($v['site_name']), $src);
|
||||
$src = $setTop('site_desc', $phpStr($v['site_desc']), $src);
|
||||
$src = $setTop('admin_email', $phpStr($v['admin_email']), $src);
|
||||
$src = $setTop('base_url', $phpStr($v['base_url']), $src);
|
||||
// 口令不写进文件:此处只把残留的旧明文重置为占位值(正常都是占位值,等价于不动)
|
||||
$src = $setTop('admin_password', "'CHANGE_ME_admin'", $src);
|
||||
$src = $setTop('api_token', $phpStr($v['api_token']), $src);
|
||||
$src = $setTop('ip_salt', $phpStr($v['ip_salt']), $src);
|
||||
|
||||
// 兜底校验:生成的文本必须是可解析的 PHP。
|
||||
// 注意必须带 TOKEN_PARSE —— 不加这一位 token_get_all 只做词法切分、不报语法错,等于没校验。
|
||||
$syntaxOk = true;
|
||||
if (defined('TOKEN_PARSE') && function_exists('token_get_all')) {
|
||||
try { @token_get_all($src, TOKEN_PARSE); }
|
||||
catch (Throwable $e) { $syntaxOk = false; }
|
||||
}
|
||||
if (!$syntaxOk) { return '生成的配置内容语法不正确,已放弃写入(请把 config.php 恢复为模板后再试)。'; }
|
||||
|
||||
$stamp = date('Y-m-d H:i:s');
|
||||
$header = "<?php\n"
|
||||
. "/**\n"
|
||||
. " * FeedLite · 轻反馈 · 配置文件\n"
|
||||
. " * ------------------------------------------------------------------\n"
|
||||
. " * 本文件由安装向导于 " . $stamp . " 生成,已按你填写的内容更新;\n"
|
||||
. " * 段落注释与 OSS / SMTP 等配置保持模板原样,可继续手工编辑。\n"
|
||||
. " * 注意:管理员口令【不在这里】——它以 bcrypt 哈希存在数据库 settings 表,\n"
|
||||
. " * 下行的 admin_password 只是工厂占位值(已被程序明确禁用,无法登录)。\n"
|
||||
. " */\n\n";
|
||||
$src = $header . preg_replace('/^<\?php\s*/', '', $src, 1);
|
||||
|
||||
$ok = @file_put_contents(FB_ROOT . '/config.php', $src);
|
||||
if ($ok === false) { return '写入 config.php 失败(目录或文件不可写)。'; }
|
||||
@chmod(FB_ROOT . '/config.php', 0640);
|
||||
return '';
|
||||
}
|
||||
|
||||
/** 随机串(口令 / 令牌 / 盐) */
|
||||
function fb_install_rand($n = 32) {
|
||||
try { return bin2hex(random_bytes($n)); } catch (Throwable $e) { return md5(uniqid('', true) . mt_rand()); }
|
||||
}
|
||||
|
||||
/* ==================================================================
|
||||
* 提交处理
|
||||
* ================================================================== */
|
||||
$err = '';
|
||||
$done = false;
|
||||
|
||||
if (isset($_POST['do_install'])) {
|
||||
list($envOk, ) = fb_install_checks();
|
||||
|
||||
$post = function ($k, $d = '') { return isset($_POST[$k]) ? trim((string)$_POST[$k]) : $d; };
|
||||
|
||||
$siteName = $post('site_name');
|
||||
$siteDesc = $post('site_desc');
|
||||
$admEmail = $post('admin_email');
|
||||
$baseUrl = rtrim($post('base_url'), '/');
|
||||
$pw = (string)(isset($_POST['admin_password']) ? $_POST['admin_password'] : '');
|
||||
$pw2 = (string)(isset($_POST['admin_password2']) ? $_POST['admin_password2'] : '');
|
||||
$setPw = !empty($_POST['set_password']);
|
||||
|
||||
if (!$envOk) {
|
||||
$err = '环境体检未通过,请先按提示处理好上面标红的问题。';
|
||||
} elseif (!fb_csrf_ok(isset($_POST['csrf']) ? (string)$_POST['csrf'] : '')) {
|
||||
$err = '会话已过期,请重新提交。';
|
||||
} elseif ($siteName === '' || fb_len($siteName) > 80) {
|
||||
$err = '站点标题请填写 1~80 个字符。';
|
||||
} elseif ($admEmail !== '' && !filter_var($admEmail, FILTER_VALIDATE_EMAIL)) {
|
||||
$err = '官方邮箱格式不正确(也可以留空)。';
|
||||
} elseif ($baseUrl !== '' && !preg_match('~^https?://~i', $baseUrl)) {
|
||||
$err = '站点根地址需要以 http:// 或 https:// 开头(留空则自动探测)。';
|
||||
} elseif ($setPw && fb_len($pw) < 8) {
|
||||
$err = '管理员口令至少 8 个字符。';
|
||||
} elseif ($setPw && $pw !== $pw2) {
|
||||
$err = '两次输入的口令不一致。';
|
||||
} else {
|
||||
// ① 先建库建表 —— 后面写设置才有地方写
|
||||
$pdo = fb_db();
|
||||
|
||||
// ② 写 config.php(口令不入文件)
|
||||
$cfgErr = fb_install_write_config([
|
||||
'site_name' => $siteName,
|
||||
'site_desc' => ($siteDesc !== '' ? $siteDesc : (string)fb_cfg('site_desc', '')),
|
||||
'admin_email' => $admEmail,
|
||||
'base_url' => $baseUrl,
|
||||
'api_token' => fb_install_rand(16),
|
||||
'ip_salt' => fb_install_rand(16),
|
||||
]);
|
||||
|
||||
if ($cfgErr === '') {
|
||||
fb_cfg_flush(); // 让后续取值看到刚落盘的新 config.php
|
||||
} else {
|
||||
// config.php 写不进去不算致命:settings 表里的值同样生效,先把系统跑起来并如实告知
|
||||
$err = '提示:' . $cfgErr . '(站内设置已写入数据库,系统可用;但建议修好后手工补齐 config.php)';
|
||||
}
|
||||
|
||||
// ③ 站点信息入库(优先生效,且不依赖 config.php 是否可写)
|
||||
fb_set_setting('site_name', $siteName);
|
||||
if ($siteDesc !== '') { fb_set_setting('site_desc', $siteDesc); }
|
||||
fb_set_setting('installed_at', time());
|
||||
|
||||
// ④ 口令:只写 bcrypt 哈希,绝不落盘明文
|
||||
$pwSet = false;
|
||||
if ($setPw && fb_len($pw) >= 8 && $pw === $pw2) {
|
||||
$pwSet = fb_admin_set_password($pw);
|
||||
}
|
||||
|
||||
$done = true;
|
||||
$GLOBALS['__fb_install_pw_set'] = $pwSet;
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="robots" content="noindex,nofollow">
|
||||
<title>安装 · 轻反馈 FeedLite</title>
|
||||
<link rel="icon" type="image/svg+xml" href="assets/favicon.svg">
|
||||
<link rel="icon" type="image/x-icon" href="assets/favicon.ico">
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="assets/apple-touch-icon.png">
|
||||
<link rel="stylesheet" href="assets/style.css?v=7">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<div class="wrap">
|
||||
<span class="brand-mini">
|
||||
<span class="logo-mark" role="img" aria-label="轻反馈图标"></span>
|
||||
<span class="brand-text">轻反馈 FeedLite</span>
|
||||
</span>
|
||||
<div class="spacer"></div>
|
||||
<span class="navlink hide-sm">安装向导</span>
|
||||
</div>
|
||||
</header>
|
||||
<main class="wrap">
|
||||
|
||||
<?php if ($already && !$done): ?>
|
||||
<section class="narrow">
|
||||
<h1>已经装好了</h1>
|
||||
<p class="muted">检测到数据库已存在,无需重复安装。</p>
|
||||
<p>
|
||||
<a class="btn primary" href="index.php">进入问题列表</a>
|
||||
<a class="btn" href="admin.php">管理后台</a>
|
||||
</p>
|
||||
<div class="card-form" style="margin-top:18px">
|
||||
<p class="muted small">如果你确实想重装(例如换了数据库位置),请先手动移走
|
||||
<code>data/feedback.db</code> 再刷新本页。本向导不会替你删除任何数据。</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<?php elseif ($done): ?>
|
||||
<?php $pwSet = !empty($GLOBALS['__fb_install_pw_set']); ?>
|
||||
<section class="narrow">
|
||||
<h1>安装完成 🎉</h1>
|
||||
<p class="muted">已生成 <code>config.php</code>、建好数据库表。下面是接下来要做的两件事:</p>
|
||||
|
||||
<div class="card-form" style="margin:14px 0">
|
||||
<div style="display:flex;flex-direction:column;gap:8px">
|
||||
<span><b>① 删除安装文件</b> —— 请把这个 <code>install.php</code> 从服务器上删掉(已安装态下它会拒绝重装,但删掉更干净)。</span>
|
||||
<span><b>② 设置口令</b> ——
|
||||
<?php if ($pwSet): ?>
|
||||
管理员口令已设成你刚才填的那个,并以 <b>bcrypt 哈希</b>存进数据库;<code>config.php</code> 里没有任何明文口令。
|
||||
<?php else: ?>
|
||||
你这次没设口令。<b>在设好之前后台无法登录</b>:请进入 <a href="admin.php">管理后台</a>,在「站点设置 → 登录口令」里设定(或重新运行本向导)。
|
||||
<?php endif; ?>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($err !== ''): ?><?php fb_flash_box($err, 'error'); ?><?php endif; ?>
|
||||
|
||||
<p>
|
||||
<a class="btn primary" href="index.php">进入问题列表</a>
|
||||
<a class="btn" href="admin.php">管理后台</a>
|
||||
</p>
|
||||
</section>
|
||||
|
||||
<?php else: ?>
|
||||
<?php list($envOk, $rows) = fb_install_checks(); ?>
|
||||
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1>安装向导</h1>
|
||||
<p class="muted">数据库目录 <code>data/</code> 下还没有数据,先完成这几步设置,装完即可使用。</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if (!$envOk): ?>
|
||||
<?php fb_flash_box('环境体检未通过:请先处理下方标红项,处理好后刷新本页。', 'error'); ?>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2 style="margin:22px 0 10px">一、环境体检</h2>
|
||||
<div class="card-form" style="margin:0 0 18px">
|
||||
<div style="display:flex;flex-direction:column;gap:6px">
|
||||
<?php foreach ($rows as $r): ?>
|
||||
<span>
|
||||
<?php echo $r[2] ? '✅' : '❌'; ?>
|
||||
<b><?php echo e($r[0]); ?></b>:<?php echo e($r[1]); ?>
|
||||
<?php if (!empty($r[3])): ?><i class="hint">(<?php echo e($r[3]); ?>)</i><?php endif; ?>
|
||||
<?php if (!empty($r[4])): ?><br><span class="muted small" style="margin-left:1.4em">↳ <?php echo e($r[4]); ?></span><?php endif; ?>
|
||||
</span>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 style="margin:22px 0 10px">二、填写站点信息</h2>
|
||||
<?php fb_flash_box($err, 'error'); ?>
|
||||
<form method="post" action="install.php" class="card-form">
|
||||
<input type="hidden" name="csrf" value="<?php echo e(fb_csrf_token()); ?>">
|
||||
|
||||
<label class="field"><span>站点标题 <b class="req">*</b></span>
|
||||
<input type="text" name="site_name" maxlength="80" required
|
||||
value="<?php echo e(isset($_POST['site_name']) ? (string)$_POST['site_name'] : (string)fb_cfg('site_name', '轻反馈')); ?>"
|
||||
placeholder="轻反馈">
|
||||
<i class="hint">显示在浏览器标签、页面顶栏与页脚。用来说明「本系统为哪个软件提供反馈服务」,如「XX 软件 · 问题反馈」。</i></label>
|
||||
|
||||
<label class="field"><span>站点描述</span>
|
||||
<textarea name="site_desc" rows="3" maxlength="300"
|
||||
placeholder="欢迎在这里反馈使用问题、提出功能建议。"><?php echo e(isset($_POST['site_desc']) ? (string)$_POST['site_desc'] : (string)fb_cfg('site_desc', '')); ?></textarea>
|
||||
<i class="hint">显示在首页标题下方,一句话说明这里可以反馈什么。</i></label>
|
||||
|
||||
<div class="grid2">
|
||||
<label class="field"><span>官方邮箱</span>
|
||||
<input type="email" name="admin_email" value="<?php echo e(isset($_POST['admin_email']) ? (string)$_POST['admin_email'] : (string)fb_cfg('admin_email', '')); ?>"
|
||||
placeholder="support@example.com">
|
||||
<i class="hint">对外展示用的联系邮箱,可留空。</i></label>
|
||||
<label class="field"><span>站点根地址</span>
|
||||
<input type="text" name="base_url" value="<?php echo e(isset($_POST['base_url']) ? (string)$_POST['base_url'] : (string)fb_cfg('base_url', '')); ?>"
|
||||
placeholder="https://你的域名/feedback">
|
||||
<i class="hint">用于邮件正文里的链接。留空则按访问域名自动探测。</i></label>
|
||||
</div>
|
||||
|
||||
<h2 style="margin:22px 0 10px">三、设置管理员口令</h2>
|
||||
<p class="muted small" style="margin:-4px 0 10px">
|
||||
口令用来登录管理后台、以及以「官方」身份回复。<b>它以 bcrypt 单向哈希写入数据库,明文不会保存在 config.php 或任何文件里。</b>
|
||||
留空则暂不设定,装完后可在后台补设(补设之前后台不可登录)。
|
||||
</p>
|
||||
<div class="grid2">
|
||||
<label class="field"><span>管理员口令(至少 8 位)</span>
|
||||
<input type="password" name="admin_password" autocomplete="new-password"></label>
|
||||
<label class="field"><span>再输入一次</span>
|
||||
<input type="password" name="admin_password2" autocomplete="new-password"></label>
|
||||
</div>
|
||||
<label class="check"><input type="checkbox" name="set_password" value="1" checked> 现在设定口令(推荐)</label>
|
||||
|
||||
<div class="form-actions">
|
||||
<button class="btn primary" type="submit" name="do_install" value="1"<?php echo $envOk ? '' : ' disabled'; ?>>开始安装</button>
|
||||
</div>
|
||||
<?php if (!$envOk): ?>
|
||||
<p class="muted small">环境体检未通过前无法提交,请先处理标红项。</p>
|
||||
<?php endif; ?>
|
||||
</form>
|
||||
|
||||
<div class="card-form" style="margin-top:18px">
|
||||
<p class="muted small">安装后再删掉本文件即可。<br>
|
||||
邮件通知(SMTP)与附件上传(阿里云 OSS)都在安装完成后、在后台「站点设置」里配置,这里不需要填。</p>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
</main>
|
||||
<footer class="foot">
|
||||
<div class="wrap">
|
||||
<p class="muted foot-line"><span>轻反馈 FeedLite</span><span class="dot">·</span><a href="<?php echo e(fb_credit_url_default()); ?>" target="_blank" rel="noopener">FeedLite</a></p>
|
||||
</div>
|
||||
</footer>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user