first commit

This commit is contained in:
root
2026-03-21 17:04:18 +08:00
commit 3c38481573
617 changed files with 65539 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
<?php
/**
* PHPMailer Exception class.
* PHP Version 5.5.
*
* @see https://github.com/PHPMailer/PHPMailer/ The PHPMailer GitHub project
*
* @author Marcus Bointon (Synchro/coolbru) <phpmailer@synchromedia.co.uk>
* @author Jim Jagielski (jimjag) <jimjag@gmail.com>
* @author Andy Prevost (codeworxtech) <codeworxtech@users.sourceforge.net>
* @author Brent R. Matzelle (original founder)
* @copyright 2012 - 2020 Marcus Bointon
* @copyright 2010 - 2012 Jim Jagielski
* @copyright 2004 - 2009 Andy Prevost
* @license https://www.gnu.org/licenses/old-licenses/lgpl-2.1.html GNU Lesser General Public License
* @note This program is distributed in the hope that it will be useful - WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE.
*/
namespace PHPMailer\PHPMailer;
/**
* PHPMailer exception handler.
*
* @author Marcus Bointon <phpmailer@synchromedia.co.uk>
*/
class Exception extends \Exception
{
/**
* Prettify error message output.
*
* @return string
*/
public function errorMessage()
{
return '<strong>' . htmlspecialchars($this->getMessage(), ENT_COMPAT | ENT_HTML401) . "</strong><br />\n";
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,214 @@
<?php
/**
* Harmony Hues主题
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-7-6 18:00:04
*/
if (! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
?>
<?php
/* 判断是否是手机 */
function isMobile()
{
if (isset($_SERVER['HTTP_X_WAP_PROFILE'])) {
return true;
}
if (isset($_SERVER['HTTP_VIA'])) {
return stristr($_SERVER['HTTP_VIA'], 'wap') ? true : false;
}
if (isset($_SERVER['HTTP_USER_AGENT'])) {
$clientkeywords = array('nokia', 'sony', 'ericsson', 'mot', 'samsung', 'htc', 'sgh', 'lg', 'sharp', 'sie-', 'philips', 'panasonic', 'alcatel', 'lenovo', 'iphone', 'ipod', 'blackberry', 'meizu', 'android', 'netfront', 'symbian', 'ucweb', 'windowsce', 'palm', 'operamini', 'operamobi', 'openwave', 'nexusone', 'cldc', 'midp', 'wap', 'mobile');
if (preg_match('/(' . implode('|', $clientkeywords) . ')/i', strtolower($_SERVER['HTTP_USER_AGENT']))) {
return true;
}
}
if (isset($_SERVER['HTTP_ACCEPT'])) {
if ((strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') !== false) && (strpos($_SERVER['HTTP_ACCEPT'], 'text/html') === false || (strpos($_SERVER['HTTP_ACCEPT'], 'vnd.wap.wml') < strpos($_SERVER['HTTP_ACCEPT'], 'text/html')))) {
return true;
}
}
return false;
}
/** 获取浏览器信息 */
function getBrowser($agent)
{
$outputer = false;
if (preg_match('/MSIE\s([^\s|;]+)/i', $agent, $regs)) {
$outputer = 'IE';
} elseif (preg_match('/FireFox\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('Firefox/', $regs[0]);
$FireFox_vern = explode('.', $str1[1]);
$outputer = '火狐' . $FireFox_vern[0];
} elseif (preg_match('/Maxthon([\d]*)\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('Maxthon/', $agent);
$Maxthon_vern = explode('.', $str1[1]);
$outputer = '傲游' . $Maxthon_vern[0];
} elseif (preg_match('#SE 2([a-zA-Z0-9.]+)#i', $agent, $regs)) {
$outputer = '搜狗';
} elseif (preg_match('#360([a-zA-Z0-9.]+)#i', $agent, $regs)) {
$outputer = '360';
} elseif (preg_match('/Edge([\d]*)\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('Edge/', $regs[0]);
$Edge_vern = explode('.', $str1[1]);
$outputer = 'Edge' . $Edge_vern[0];
} elseif (preg_match('/EdgiOS([\d]*)\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('EdgiOS/', $regs[0]);
$outputer = 'Edge';
} elseif (preg_match('/UC/i', $agent)) {
$str1 = explode('rowser/', $agent);
$UCBrowser_vern = explode('.', $str1[1]);
$outputer = 'UC' . $UCBrowser_vern[0];
} elseif (preg_match('/OPR/i', $agent)) {
$str1 = explode('OPR/', $agent);
$opr_vern = explode('.', $str1[1]);
$outputer = '欧朋 ' . $opr_vern[0];
} elseif (preg_match('/MicroMesseng/i', $agent, $regs)) {
$outputer = '微信';
} elseif (preg_match('/WeiBo/i', $agent, $regs)) {
$outputer = '微博';
} elseif (preg_match('/QQ/i', $agent, $regs) || preg_match('/QQBrowser\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('rowser/', $agent);
$QQ_vern = explode('.', $str1[1]);
$outputer = 'QQ ' . $QQ_vern[0];
} elseif (preg_match('/MQBHD/i', $agent, $regs)) {
$str1 = explode('MQBHD/', $agent);
$QQ_vern = explode('.', $str1[1]);
$outputer = 'QQ ' . $QQ_vern[0];
} elseif (preg_match('/BIDU/i', $agent, $regs)) {
$outputer = '百度';
} elseif (preg_match('/LBBROWSER/i', $agent, $regs)) {
$outputer = '猎豹';
} elseif (preg_match('/TheWorld/i', $agent, $regs)) {
$outputer = '世界之窗';
} elseif (preg_match('/XiaoMi/i', $agent, $regs)) {
$outputer = '小米';
} elseif (preg_match('/UBrowser/i', $agent, $regs)) {
$str1 = explode('rowser/', $agent);
$UCBrowser_vern = explode('.', $str1[1]);
$outputer = 'UC' . $UCBrowser_vern[0];
} elseif (preg_match('/mailapp/i', $agent, $regs)) {
$outputer = 'email';
} elseif (preg_match('/2345Explorer/i', $agent, $regs)) {
$outputer = '2345';
} elseif (preg_match('/Sleipnir/i', $agent, $regs)) {
$outputer = '神马';
} elseif (preg_match('/YaBrowser/i', $agent, $regs)) {
$outputer = 'Yandex';
} elseif (preg_match('/Opera[\s|\/]([^\s]+)/i', $agent, $regs)) {
$outputer = 'Opera';
} elseif (preg_match('/MZBrowser/i', $agent, $regs)) {
$outputer = '魅族';
} elseif (preg_match('/VivoBrowser/i', $agent, $regs)) {
$outputer = 'vivo';
} elseif (preg_match('/Quark/i', $agent, $regs)) {
$outputer = '夸克';
} elseif (preg_match('/mixia/i', $agent, $regs)) {
$outputer = '米侠';
} elseif (preg_match('/fusion/i', $agent, $regs)) {
$outputer = '客户端';
} elseif (preg_match('/CoolMarket/i', $agent, $regs)) {
$outputer = '基安';
} elseif (preg_match('/Thunder/i', $agent, $regs)) {
$outputer = '迅雷';
} elseif (preg_match('/Chrome([\d]*)\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('Chrome/', $agent);
$chrome_vern = explode('.', $str1[1]);
$outputer = 'Chrome' . $chrome_vern[0];
} elseif (preg_match('/safari\/([^\s]+)/i', $agent, $regs)) {
$str1 = explode('Version/', $agent);
$safari_vern = @explode('.', $str1[1]);
$outputer = 'Safari' . $safari_vern[0];
} else {
return false;
}
return $outputer;
}
/** 获取操作系统信息 */
function getOs($agent)
{
$os = false;
if (preg_match('/win/i', $agent)) {
if (preg_match('/nt 6.0/i', $agent)) {
$os = 'WIN Vista';
} elseif (preg_match('/nt 6.1/i', $agent)) {
$os = 'WIN7';
} elseif (preg_match('/nt 6.2/i', $agent)) {
$os = 'WIN8';
} elseif (preg_match('/nt 6.3/i', $agent)) {
$os = 'WIN8.1';
} elseif (preg_match('/nt 5.1/i', $agent)) {
$os = 'WINXP';
} elseif (preg_match('/nt 10.0/i', $agent)) {
$os = 'WIN10';
} else {
$os = 'WIN';
}
} elseif (preg_match('/android/i', $agent)) {
if (preg_match('/android 9/i', $agent)) {
$os = '安卓P';
} elseif (preg_match('/android 8/i', $agent)) {
$os = '安卓O';
} elseif (preg_match('/android 7/i', $agent)) {
$os = '安卓N';
} elseif (preg_match('/android 6/i', $agent)) {
$os = '安卓M';
} elseif (preg_match('/android 5/i', $agent)) {
$os = '安卓L';
} else {
$os = '安卓';
}
} elseif (preg_match('/ubuntu/i', $agent)) {
$os = 'Linux';
} elseif (preg_match('/linux/i', $agent)) {
$os = 'Linux';
} elseif (preg_match('/iPhone/i', $agent)) {
$os = 'iPhone';
} elseif (preg_match('/iPad/i', $agent)) {
$os = 'iPad';
} elseif (preg_match('/mac/i', $agent)) {
$os = 'OSX';
} elseif (preg_match('/cros/i', $agent)) {
$os = 'chrome os';
} else {
return false;
}
return $os;
}
/* 加强评论拦截功能 */
Typecho_Plugin::factory('Widget_Feedback')->comment = array('Intercept', 'message');
class Intercept
{
public static function message($comment)
{
// 判断用户输入是否大于字符
if (Helper::options()->TextLimit && strlen($comment['text']) > Helper::options()->TextLimit) {
$comment['status'] = 'waiting';
} else {
// 判断评论内容是否包含敏感词
if (Helper::options()->SensitiveWords) {
if (checkSensitiveWords(Helper::options()->SensitiveWords, $comment['text'])) {
$comment['status'] = 'waiting';
}
}
// 判断评论是否至少包含一个中文
if (Helper::options()->LimitOneChinese === 'on') {
if (preg_match("/[\x{4e00}-\x{9fa5}]/u", $comment['text']) == 0) {
$comment['status'] = 'waiting';
}
}
}
Typecho_Cookie::delete('__typecho_remember_text');
return $comment;
}
}
?>

View File

@@ -0,0 +1,312 @@
<?php
/**
* Harmony Hues主题
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-12-21 17:55:16
*/
if (! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
use Utils\Helper;
?>
<?php
// 添加文章标题锚点
function createAnchor($obj)
{
global $catalog;
global $catalog_count;
$catalog = array();
$catalog_count = 0;
$obj = preg_replace_callback('/<h([1-4])(.*?)>(.*?)<\/h\1>/i', function ($obj) {
global $catalog;
global $catalog_count;
$catalog_count++;
$catalog[] = array('text' => trim(strip_tags($obj[3])), 'depth' => $obj[1], 'count' => $catalog_count);
return '<h' . $obj[1] . $obj[2] . ' id="cl-' . $catalog_count . '">' . $obj[3] . '</h' . $obj[1] . '>';
}, $obj);
return $obj;
}
// 灯箱
function replaceImgSrc($content)
{
$pattern = '/<img(.*?)src="(.*?)"(.*?)title="(.*?)"(.*?)>/i';
$replacement = '<img data-original="$2" $3title="$4"$5 src="' . getLazyload(false) . '" show-img class="lazy" />';
return preg_replace($pattern, $replacement, $content);
}
// 提示短代码
function ContentHint($content)
{
$patterns = array(
'/\[(success)\]\s*(.*?)\s*\[\s*\/\1\s*\]/s',
'/\[(info)\]\s*(.*?)\s*\[\s*\/\1\s*\]/s',
'/\[(warning)\]\s*(.*?)\s*\[\s*\/\1\s*\]/s',
'/\[(danger)\]\s*(.*?)\s*\[\s*\/\1\s*\]/s',
);
$replacements = array(
'success' => '<div class="hint-content hint-success p-2"><i class="iconfont icon-success"></i><div>%s</div></div>',
'info' => '<div class="hint-content hint-info p-2"><i class="iconfont icon-info"></i><div>%s</div></div>',
'warning' => '<div class="hint-content hint-warning p-2"><i class="iconfont icon-warning"></i><div>%s</div></div>',
'danger' => '<div class="hint-content hint-danger p-2"><i class="iconfont icon-danger"></i><div>%s</div></div>',
);
$callback = function ($matches) use ($replacements) {
$type = $matches[1]; // 获取匹配的类型 (success, info, warning, danger)
// 去除开头的 <br> 标签
$text = preg_replace('/^(<br\s*\/?>)+/i', '', $matches[2]);
$text = Markdown::convert($text); // 转换 Markdown
return sprintf($replacements[$type], $text); // 替换内容
};
foreach ($patterns as $pattern) {
$content = preg_replace_callback($pattern, $callback, $content);
}
return $content;
}
// 视频短代码
function ContentVideo($content)
{
$pattern = '/\[player\s+url="([^"]*)"(?:\s+pic="([^"]*)")?\s+\/\]/';
$content = preg_replace_callback($pattern, function ($matches) {
$videoSrc = @strip_tags($matches[1]); // 获取 url 的值
$posterSrc = @strip_tags($matches[2]); // 获取 pic 的值
if (empty($posterSrc)) {
$posterSrc = getAssets('assets/images/thumb/' . rand(1, 20) . '.webp', false);
}
return '<video class="video-content" src="' . $videoSrc . '" poster="' . $posterSrc . '" title="视频" controls></video>';
}, $content);
return $content;
}
// 链接短代码
function extractToLinks($content)
{
$pattern = '/\[tolink\s+title="([^"]*)"\s+url="([^"]*)"(?:\s+favicon="([^"]*)")?\s+\/\]/';
$content = preg_replace_callback($pattern, function ($matches) {
$tohtml = '<div class="to-links-content">';
if (count($matches) >= 2) {
$name = @strip_tags($matches[1]);
$url = @strip_tags($matches[2]);
$favicon = @strip_tags($matches[3]);
$tohtml .= '<a data-links href="' . $url . '" class="d-flex align-items-center p-2 to-links-item short-code-card"
target="_blank" title="' . ($name ?: ' 短代码链接') . '">';
if (! empty($favicon)) {
$tohtml .= '<img src="' . getLazyload(false) . '" data-original="' . $favicon . '" class="lazy"
alt="' . $name . '" />';
}
$tohtml .= '<div class="to-links-text">';
$tohtml .= '<span>' . $name . '</span>';
$tohtml .= '<span class="to-links-url"><i class="iconfont icon-lianjie mr-1"></i>' . $url . '</span>';
$tohtml .= '</div></a>';
} else {
$tohtml .= 'ToLinks短代码格式不正确请检查名称、URL是否填写。';
}
$tohtml .= '</div>';
return $tohtml;
}, $content);
return $content;
}
// 过滤a标签链接
function ContentLink($content)
{
if (empty(Helper::options()->isGoLink)) {
return $content;
}
// 正则表达式匹配所有的<a>标签及其属性
$pattern = '/<a\s+([^>]*?)>/i';
// 替换回调函数
$content = preg_replace_callback($pattern, function ($matches) {
$attributes = $matches[1];
// 检查是否包含需要排除的属性
if (preg_match('/\b(data-fancybox|data-cloud|data-test2)\b/', $attributes)) {
// 包含需要排除的属性,不处理
return '<a ' . $attributes . '>';
}
// 获取href属性
if (preg_match('/\bhref=["\']([^"\']*)["\']/i', $attributes, $hrefMatches)) {
$originalHref = $hrefMatches[1];
// 构造新的href
$newHref = getGoLink($originalHref);
// 替换href属性
$newAttributes = preg_replace('/\bhref=["\'][^"\']*["\']/i', 'href="' . $newHref . '"', $attributes);
// 检查是否已存在rel或target属性
if (strpos($attributes, 'rel=') === false) {
$newAttributes .= ' rel="nofollow"';
}
if (strpos($attributes, 'target=') === false) {
$newAttributes .= ' target="_blank"';
}
return '<a ' . $newAttributes . '>';
}
// 没有href属性保持不变
return '<a ' . $attributes . '>';
}, $content);
return $content;
}
// 过滤网盘下载
function ContentCloud($content)
{
$pattern = '/\[cloud\s+type="([^"]*)"\s+title="([^"]*)"\s+url="([^"]*)"\s+password="([^"]*)"\s+\/\]/';
$cloudList = array(
'default' => '默认网盘',
'baidu' => '百度网盘',
'quark' => '夸克网盘',
'aliyun' => '阿里云网盘',
'lanzou' => '蓝奏云网盘',
'360' => '360网盘',
'weiyun' => '腾讯微云',
'ctfile' => '城通网盘',
'github' => 'GitHub仓库',
);
$content = preg_replace_callback($pattern, function ($matches) use ($cloudList) {
// 提取属性值
$type = @strip_tags($matches[1]); // 获取 type 的值
$title = @strip_tags($matches[2]); // 获取 title 的值
$url = @strip_tags($matches[3]); // 获取 url 的值
$password = @strip_tags($matches[4]); // 获取 password 的值
// 根据 type 获取网盘名称
$name = isset($cloudList[$type]) ? $cloudList[$type] : '未知网盘';
$password = $password ? $password : '无';
$icon = '<div data-svg="' . $type . 'cloud" data-viewbox="0 0 1024 1024" data-class="cloud-icon"></div>';
$cloudHtml = '<div class="cloud-download-box p-2 short-code-card d-flex align-items-center">';
$cloudHtml .= '<div class="cloud-download-icon">' . $icon . '</div>';
$cloudHtml .= '<div class="cloud-download-info">';
$cloudHtml .= '<div class="cloud-download-title"><a data-cloud href="' . $url . '" target="_blank"
title="' . $title . '">' . $title . '</a></div>';
$cloudHtml .= '<div class="cloud-download-password">提取码: ' . $password . '</div>';
$cloudHtml .= '<div class="cloud-download-btn d-flex justify-content-between align-items-center">
<span>来源:' . $name . '</span><a class="px-2 py-1" data-cloud href="' . $url . '" target="_blank"
title="' . $title . '"><i class="iconfont icon-xiazai"></i></a>
</div>';
$cloudHtml .= '</div>';
$cloudHtml .= '</div>';
return $cloudHtml;
}, $content);
return $content;
}
// 过滤pre代码标签
function ContentCode($content)
{
// 正则表达式匹配
$pattern = '#<pre([^>]*)>(.*?)</pre>#si';
// 替换的 HTML 结构
$replacement = '
<div class="pre-container mb-3">
<div class="py-1 px-2 d-flex justify-content-between align-items-center pre-header">
<div class="pre-icon"></div>
<button class="pre-copy">复制</button>
</div>
<pre$1>$2</pre>
</div>
';
$content = preg_replace($pattern, $replacement, $content);
return $content;
}
// 过滤fold折叠框
function ContentFold($content)
{
$pattern = '#\[fold\s+title="([^"]*)"\s+type="(open|close)"\s*\](.*?)\[/fold\]#si';
$content = preg_replace_callback($pattern, function ($matches) {
$title = $matches[1];
$type = $matches[2];
$contentText = Markdown::convert($matches[3]);
$openAttr = ($type === 'open') ? 'open' : '';
return '
<details class="fold-container mb-3" ' . $openAttr . '>
<summary class="fold-header py-2 px-3">
' . $title . '
</summary>
<div class="fold-content py-2 px-3">
' . $contentText . '
</div>
</details>
';
}, $content);
return $content;
}
// 过滤多余的html标签
function ContentHtml($content)
{
// 使用一个正则表达式同时匹配并删除空段落和仅包含两个换行符的段落
$content = preg_replace('#<p></p>|<br><br></p>#si', '', $content);
return $content;
}
// 运行所以函数
function parseContens($content)
{
// 添加文章标题锚点
$content = createAnchor($content);
// 添加图片懒加载
$content = replaceImgSrc($content);
// 提示短代码
$content = ContentHint($content);
// 视频短代码
$content = ContentVideo($content);
// 链接短代码
$content = extractToLinks($content);
// 网盘下载短代码
$content = ContentCloud($content);
// 过滤a标签链接添加golink
$content = ContentLink($content);
// 表情包
$content = formatEmoji($content);
// 过滤pre代码标签
$content = ContentCode($content);
// 过滤fold折叠框
$content = ContentFold($content);
// 过滤多余的html标签
$content = ContentHtml($content);
return $content;
}
?>

View File

@@ -0,0 +1,27 @@
<?php
/**
* Harmony Hues主题
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-7-6 18:00:04
*/
if (! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
?>
<?php
/* 公用函数 */
require_once 'content-parse.php'; // 文章内容解析
require_once 'function.php'; // 主题函数
/* 主题函数 */
require_once 'comment-config.php'; // 主题评论拦截
require_once 'editor-config.php'; //后台文章编辑器按钮添加+文章自定义字段
require_once 'theme-config.php'; //主题设置
require_once 'theme-email.php'; // 邮件通知
/* 页面加载计时 */
startCountTime();
?>

View File

@@ -0,0 +1,92 @@
<?php
/**
* Harmony Hues主题
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-7-6 18:00:04
*/
if (! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
?>
<?php
//后台编辑器添加功能
function themeFields($layout)
{
$showSidebar = new Typecho_Widget_Helper_Form_Element_Radio(
'showSidebar',
array(
'0' => _t('单栏'),
'1' => _t('双栏')
),
'1',
_t('侧边栏显示'),
_t('选择当前文章页是否显示侧边栏,默认显示侧边栏,也推荐显示')
);
$layout->addItem($showSidebar);
$keywords = new Typecho_Widget_Helper_Form_Element_Text(
'keywords',
NULL,
NULL,
'SEO关键词非常重要',
'介绍用于设置当前页SEO关键词 <br />
注意:多个关键词使用英文逗号进行隔开 <br />
例如Typecho,Typecho主题,Typecho模板 <br />
其他:如果不填写此项,则默认取文章标签'
);
$layout->addItem($keywords);
$description = new Typecho_Widget_Helper_Form_Element_Textarea(
'description',
NULL,
NULL,
'SEO描述语非常重要',
'介绍用于设置当前页SEO描述语 <br />
注意SEO描述语不应当过长也不应当过少 <br />
其他:如果不填写此项,则默认截取文章片段'
);
$layout->addItem($description);
$abstract = new Typecho_Widget_Helper_Form_Element_Textarea(
'abstract',
NULL,
NULL,
'自定义摘要(非必填)',
'填写时:将会显示填写的摘要 <br>
不填写时:默认取文章里的内容'
);
$layout->addItem($abstract);
$thumb = new Typecho_Widget_Helper_Form_Element_Textarea(
'thumb',
NULL,
NULL,
'自定义缩略图(非必填)',
'填写时:将会显示填写的文章缩略图 <br>
不填写时:<br>
1、若文章有图片则取文章内图片 <br>
2、若文章无图片并且外观设置里未填写·自定义缩略图·选项则取模板自带图片 <br>
3、若文章无图片并且外观设置里填写了·自定义缩略图·选项则取自定义缩略图图片'
);
$layout->addItem($thumb);
}
/* 编辑器添加按钮及功能 */
Typecho_Plugin::factory('admin/write-post.php')->bottom = array('Editor', 'edit');
Typecho_Plugin::factory('admin/write-page.php')->bottom = array('Editor', 'edit');
class Editor
{
public static function edit()
{
echo '
<link rel="stylesheet" href="' . getAssets('assets/typecho/editor/css/editor.min.css?v=' . getVersion(), false) . '"
type="text/css" media="all">';
echo '<script src="' . getAssets('assets/typecho/editor/js/editor.min.js?v=' . getVersion(), false) . '"></script>';
}
}
?>

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,66 @@
<?php
/**
* Harmony Hues主题 - 主题设置备份
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-7-6 18:00:04
*/
if ( ! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
define('THEME_URL', str_replace('//usr', '/usr', str_replace(Helper::options()->siteUrl, Helper::options()->rootUrl.'/', Helper::options()->themeUrl)));
$str1 = explode('/themes/', (THEME_URL.'/'));
$str2 = explode('/', $str1[1]);
define('THEME_NAME', $str2[0]);
$name = THEME_NAME;
$db = Typecho_Db::get();
$sjdq = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:'.$name));
$ysj = $sjdq['value'];
if (isset($_POST['type'])) {
switch ($_POST['type']) {
case '备份模板':
if ($db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:'.$name.'bf'))) {
$update = $db->update('table.options')->rows(array('value' => $ysj))->where('name = ?', 'theme:'.$name.'bf');
$db->query($update);
echo '<script>let flag = confirm("备份更新成功!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
} else {
if ($ysj) {
$insert = $db->insert('table.options')->rows(array('name' => 'theme:'.$name.'bf', 'user' => '0', 'value' => $ysj));
$db->query($insert);
echo '<script>let flag = confirm("备份成功!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
}
}
break;
case '还原备份':
if ($db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:'.$name.'bf'))) {
$sjdub = $db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:'.$name.'bf'));
$bsj = $sjdub['value'];
$update = $db->update('table.options')->rows(array('value' => $bsj))->where('name = ?', 'theme:'.$name);
$db->query($update);
echo '<script>let flag = confirm("恢复成功!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
} else {
echo '<script>let flag = confirm("未备份过数据,无法恢复!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
}
break;
case '删除备份':
if ($db->fetchRow($db->select()->from('table.options')->where('name = ?', 'theme:'.$name.'bf'))) {
$delete = $db->delete('table.options')->where('name = ?', 'theme:'.$name.'bf');
$db->query($delete);
echo '<script>let flag = confirm("删除成功!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
} else {
echo '<script>let flag = confirm("没有备份内容,无法删除!"); if (flag || !flag) window.location.href = "'.Helper::options()->adminUrl('options-theme.php').'";</script>';
}
break;
}
}
echo '<form class="d-flex harmonyhues-backup mb-3" action="?'.$name.'bf" method="post">
<input type="submit" name="type" value="备份模板" />
<input type="submit" name="type" value="还原备份" />
<input type="submit" name="type" value="删除备份" />
</form>';

View File

@@ -0,0 +1,527 @@
<?php
/**
* Harmony Hues主题
*
* @author 星语社长
* @link https://biibii.cn
* @update 2024-7-6 18:00:04
*/
if ( ! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
?>
<?php
// 主题外观设置
function themeConfig($form) {
// 导入备份
require_once 'theme-backup.php';
?>
<link rel="stylesheet" href="<?php getAssets('assets/lib/bootstrap4/bootstrap.min.css')?>" type="text/css" media="all">
<link rel="stylesheet" href="<?php getAssets('assets/css/style.min.css')?>" type="text/css" media="all">
<link rel="stylesheet" href="<?php getAssets('assets/typecho/config/css/config.min.css')?>">
<div class="harmonyhues-config card mt-2 mb-4 p-2">
<?php
//---------------------------- 基础设置 ----------------------------//
// 网站favicon图标
$favicon = new Typecho_Widget_Helper_Form_Element_Text(
'favicon',
NULL,
'/usr/themes/HarmonyHues/assets/images/favicon.webp',
'favicon地址必填',
'介绍一般为http://www.yourblog.com/image.png,支持 https:// 或 //,留空则不设置favicon'
);
$form->addInput($favicon);
$favicon->setAttribute('class', 'setting-content my-3');
// 网站logo
$logoUrl = new Typecho_Widget_Helper_Form_Element_Text(
'logoUrl',
NULL,
'/usr/themes/HarmonyHues/assets/images/logo.webp',
'站点LOGO地址必填',
'介绍在这里填入一个图片URL地址, 以在网站标题前加上一个LOGO'
);
$form->addInput($logoUrl);
$logoUrl->setAttribute('class', 'setting-content my-3');
// 网站个人简介
$blogmeabout = new Typecho_Widget_Helper_Form_Element_Text(
'blogmeabout',
NULL,
'欢迎来到我的typecho博客。',
'个人简介(非必填)',
'介绍在这里填入你的个人简介例如欢迎来到我的typecho博客。'
);
$form->addInput($blogmeabout);
$blogmeabout->setAttribute('class', 'setting-content my-3');
// ---------------------------- 主题设置 ----------------------------//
// 侧边栏博主信息社交
$socialInfo = new Typecho_Widget_Helper_Form_Element_Textarea(
'socialInfo',
NULL,
NULL,
'个人社交(非必填)',
'介绍在这里填入你的JSON格式社交图标www.iconfont.cn和URLJSON格式模板{"name":"bilibili","icon":"icon-logo-github","link":"https://bilibili.com/xxxx"}'
);
$form->addInput($socialInfo);
$socialInfo->setAttribute('class', 'setting-content my-3');
// 微信赞赏二维码
$wechatQrCode = new Typecho_Widget_Helper_Form_Element_Text(
'wechatQrCode',
NULL,
'/usr/themes/HarmonyHues/assets/images/wechatQr.webp',
'微信二维码(必填)',
'介绍在这里填入你的微信收款二维码URL地址'
);
$form->addInput($wechatQrCode);
$wechatQrCode->setAttribute('class', 'setting-content my-3');
// 支付宝赞赏二维码
$alipayQrCode = new Typecho_Widget_Helper_Form_Element_Text(
'alipayQrCode',
NULL,
'/usr/themes/HarmonyHues/assets/images/alipayQr.webp',
'支付宝二维码(必填)',
'介绍在这里填入你的支付宝收款二维码URL地址'
);
$form->addInput($alipayQrCode);
$alipayQrCode->setAttribute('class', 'setting-content my-3');
// 导航栏样式
$navStyle = new Typecho_Widget_Helper_Form_Element_Select(
'navStyle',
array(
'default' => '默认导航栏',
'mini' => 'MINI导航栏',
),
'default',
'导航栏样式(非必填)',
'介绍默认导航栏宽度为100%MINI导航栏为container居中。'
);
$form->addInput($navStyle->multiMode());
$navStyle->setAttribute('class', 'setting-content my-3');
// 自定义懒加载IMG
$lazyload = new Typecho_Widget_Helper_Form_Element_Text(
'lazyload',
NULL,
NULL,
'自定义懒加载图片(非必填)',
'介绍在这里填入一个图片URL地址, 以在图片加载失败时显示'
);
$form->addInput($lazyload);
$lazyload->setAttribute('class', 'setting-content my-3');
// SVG签名
$svgName = new Typecho_Widget_Helper_Form_Element_Textarea(
'svgName',
NULL,
NULL,
'SVG名字签名非必填',
'介绍在这里填入一个SVG代码,SVG生成地址https://danmarshall.github.io/google-font-to-svg-path/,注意SVG的width和height属性需要自行调整'
);
$form->addInput($svgName);
$svgName->setAttribute('class', 'setting-content my-3');
// 自定义导航栏
$navbarInfo = new Typecho_Widget_Helper_Form_Element_Textarea(
'navbarInfo',
NULL,
'{"name":"自定义","link":"/","target":"_self","sub":[{"name":"子菜单名称","link":"https://www.xxx.com/xxxx.html","target":"_self"}]}',
'导航栏菜单配置(非必填)',
'介绍:支持自定义菜单,不支持三级目录优先分类和独立页面里面找没有才匹配link<br/>
在这里填入JSON格式模板{name":"菜单名称","link":"https://www.xxx.com/xxxx.html","target":"_self","sub":[{"name":"子菜单名称","link":"https://www.xxx.com/xxxx.html","target":"_self"}]}'
);
$form->addInput($navbarInfo);
$navbarInfo->setAttribute('class', 'setting-content my-3');
// 文章置顶
$sticky = new Typecho_Widget_Helper_Form_Element_Text(
'sticky',
NULL,
NULL,
'文章置顶(非必填)',
'介绍置顶的文章cid按照排序输入, 请以半角逗号或空格分隔'
);
$form->addInput($sticky);
$sticky->setAttribute('class', 'setting-content my-3');
// 是否开启goLink外链
$isGoLink = new Typecho_Widget_Helper_Form_Element_Select(
'isGoLink',
array(
'0' => '关闭GoLink外链',
'1' => '开启GoLink外链',
),
'0',
'GoLink外链页面非必填',
'介绍开启后文章页面的外链会自动跳转到GoLink页面'
);
$form->addInput($isGoLink->multiMode());
$isGoLink->setAttribute('class', 'setting-content my-3');
// 头像源
$Gravatars = new Typecho_Widget_Helper_Form_Element_Select(
'Gravatars',
array(
'gravatar.helingqi.com/wavatar' => '禾令奇(默认)',
'www.gravatar.com/avatar' => 'gravatar的www源',
'cn.gravatar.com/avatar' => 'gravatar的cn源',
'secure.gravatar.com/avatar' => 'gravatar的secure源',
'sdn.geekzu.org/avatar' => '极客族',
'cdn.v2ex.com/gravatar' => 'v2ex源',
'dn-qiniu-avatar.qbox.me/avatar' => '七牛源[不建议]',
'gravatar.loli.net/avatar' => 'loli.net源',
),
'gravatar.helingqi.com/wavatar',
'选择头像源(非必填)',
'介绍:不同的源响应速度不同,头像也不同'
);
$Gravatars->setAttribute('class', 'setting-content my-3');
$form->addInput($Gravatars->multiMode());
// CSP策略
$isCSP = new Typecho_Widget_Helper_Form_Element_Select(
'isCSP',
array(
'0' => '关闭CSP',
'1' => '开启CSP',
),
'0',
'CSP策略非必填',
'介绍CSP策略可以防止XSS攻击但是会降低页面加载速度如果页面加载速度变慢请关闭CSP'
);
$form->addInput($isCSP->multiMode());
$isCSP->setAttribute('class', 'setting-content my-3');
// CSP内容
$contentCSP = new Typecho_Widget_Helper_Form_Element_Textarea(
'contentCSP',
NULL,
NULL,
'CSP内容非必填',
'介绍在这里填入CSP内容例如script-src \'self\' \'unsafe-inline\' \'unsafe-eval\' https://cdn.jsdelivr.net/; style-src \'self\' \'unsafe-inline\' https://cdn.jsdelivr.net/; img-src \'self\' data:; font-src \'self\' https://cdn.jsdelivr.net/; connect-src \'self\' https://cdn.jsdelivr.net/;'
);
$form->addInput($contentCSP);
$contentCSP->setAttribute('class', 'setting-content my-3');
// 侧边栏设置
$sidebarBlock = new Typecho_Widget_Helper_Form_Element_Checkbox(
'sidebarBlock',
array(
'ShowAboutMe' => '博主信息',
'ShowSidebarYiyan' => '一言',
'ShowSidebarWeather' => '天气',
'ShowSidebarComments' => '最新评论',
'ShowHotPosts' => '热门文章',
'ShowBlogSignage' => '博客路牌',
'ShowDevilEyes' => '恶魔之眼',
),
array('ShowAboutMe', 'ShowSidebarYiyan', 'ShowHotPosts'),
'侧边栏显示(非必填)',
'介绍:侧边栏模块,默认显示博主信息和最新评论'
);
$form->addInput($sidebarBlock->multiMode());
$sidebarBlock->setAttribute('class', 'setting-content my-3');
// 侧边栏-博客路牌
$blogSignageText = new Typecho_Widget_Helper_Form_Element_Text(
'blogSignageText',
NULL,
'我在贵州很想你',
'侧边栏-博客路牌(非必填)',
'介绍:在这里填入你的博客路牌内容,例如:我在贵州很想你'
);
$form->addInput($blogSignageText);
$blogSignageText->setAttribute('class', 'setting-content my-3');
// 天气城市设置
$weatherCity = new Typecho_Widget_Helper_Form_Element_Text(
'weatherCity',
NULL,
'Beijing',
'天气城市(非必填)',
'介绍填写用于查询天气的城市名称例如北京或Beijing'
);
$form->addInput($weatherCity);
$weatherCity->setAttribute('class', 'setting-content my-3');
// 天气 API Key
$weatherApiKey = new Typecho_Widget_Helper_Form_Element_Text(
'weatherApiKey',
NULL,
NULL,
'天气 API Key非必填',
'介绍:在此填写 OpenWeatherMap 或其他天气接口的 API Key'
);
$form->addInput($weatherApiKey);
$weatherApiKey->setAttribute('class', 'setting-content my-3');
// 首页内容显示
$indexBlock = new Typecho_Widget_Helper_Form_Element_Checkbox(
'indexBlock',
array(
'ShowTravelling' => '顶部-开往友链接力',
'ShowSwiper' => '首页-轮播幻灯片',
'ShowHello' => '底部-Hello信息',
'ShowTimeJourney' => '底部-时间之旅',
'ShowLinks' => '友情链接(未开发)',
),
array('ShowHello'),
'首页内容显示(非必填)',
'介绍首页模块默认显示Hello信息'
);
$form->addInput($indexBlock->multiMode());
$indexBlock->setAttribute('class', 'setting-content my-3');
// 首页顶部-轮播幻灯片
$swiperText = new Typecho_Widget_Helper_Form_Element_Textarea(
'swiperText',
NULL,
'HarmonyHues主题|欢迎使用HarmonyHues主题|2月29日 今日标题|https://www.biibii.cn/usr/themes/HarmonyHues/assets/images/themeImg.webp|https://www.biibii.cn/harmonyhues.html',
'首页顶部-轮播幻灯片内容(非必填)',
'介绍:在这里填入你的轮播幻灯片内容,格式:标题|描述|标签|图片URL或者MP4格式|链接URL用|号隔开内容没有的用NULL内容一行一个建议最多5个例如HarmonyHues主题|欢迎使用HarmonyHues主题|2月28日 今日标题|https://www.biibii.cn/usr/themes/HarmonyHues/assets/images/themeImg.webp|https://www.biibii.cn/harmonyhues.html'
);
$form->addInput($swiperText);
$swiperText->setAttribute('class', 'setting-content my-3');
// 首页底部-Hello文字
$helloText = new Typecho_Widget_Helper_Form_Element_Text(
'helloText',
NULL,
'Welcome to BIIBII.CN',
'首页底部-Hello内容非必填',
'介绍在这里填入你的Hello内容,例如Welcome to BIIBII.CN'
);
$form->addInput($helloText);
$helloText->setAttribute('class', 'setting-content my-3');
// 首页底部-时间之旅内容
$timeJourneyText = new Typecho_Widget_Helper_Form_Element_Text(
'timeJourneyText',
NULL,
'2025-03-26|天|星际旅行|本站服务器燃料剩余{remainingPercentage}%',
'首页底部-时间之旅内容(非必填)',
'介绍:在这里填入你的时间之旅内容,格式:时间|单位|标题|{剩余百分比remainingPercentage|已过去百分比progressPercentage},例如2025-03-26|天|星际旅行|本站服务器燃料剩余{remainingPercentage}%'
);
$form->addInput($timeJourneyText);
$timeJourneyText->setAttribute('class', 'setting-content my-3');
// 网站icon
$iconfont = new Typecho_Widget_Helper_Form_Element_Text(
'iconfont',
NULL,
'//at.alicdn.com/t/c/font_4612620_nzptu6bs4cb.css',
'iconfont图标非必填',
'介绍在这里填入你自定义的iconfont图标外链前往iconfont图标https://www.iconfont.cn<br/>
例如://at.alicdn.com/t/c/font_4612620_nzptu6bs4cb.css'
);
$form->addInput($iconfont);
$iconfont->setAttribute('class', 'setting-content my-3');
// 评论敏感词
$SensitiveWords = new Typecho_Widget_Helper_Form_Element_Textarea(
'SensitiveWords',
NULL,
'你妈死了|傻逼|操你妈|我是你爹',
'评论敏感词(非必填)',
'介绍:用于设置评论敏感词汇,如果用户评论包含这些词汇,则将会把评论置为审核状态 <br />
例如:你妈死了|你妈炸了|我是你爹(使用|分隔开)'
);
$SensitiveWords->setAttribute('class', 'setting-content my-3');
$form->addInput($SensitiveWords);
// 评论限制开关
$LimitOneChinese = new Typecho_Widget_Helper_Form_Element_Select(
'LimitOneChinese',
array('off' => '关闭(默认)', 'on' => '开启'),
'off',
'是否开启评论至少包含一个中文(非必填)',
'介绍:开启后如果评论内容未包含一个中文,则将会把评论置为审核状态 <br />
其他:用于屏蔽国外机器人刷的全英文垃圾广告信息'
);
$LimitOneChinese->setAttribute('class', 'setting-content my-3');
$form->addInput($LimitOneChinese->multiMode());
// 评论限制
$TextLimit = new Typecho_Widget_Helper_Form_Element_Text(
'TextLimit',
NULL,
NULL,
'限制用户评论最大字符(非必填)',
'介绍:如果用户评论的内容超出字符限制,则将会把评论置为审核状态 <br />
其他:请输入数字格式,不填写则不限制'
);
$form->addInput($TextLimit->multiMode());
$TextLimit->setAttribute('class', 'setting-content my-3');
// 评论邮件通知
$commentMail = new Typecho_Widget_Helper_Form_Element_Select(
'commentMail',
array('off' => '关闭(默认)', 'on' => '开启'),
'off',
'是否开启评论邮件通知(非必填)',
'介绍:开启后评论内容将会进行邮箱通知 <br />
注意:此项需要您完整无错的填写下方的邮箱设置!! <br />
其他下方例子以QQ邮箱为例推荐使用QQ邮箱'
);
$commentMail->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMail->multiMode());
$commentMailHost = new Typecho_Widget_Helper_Form_Element_Text(
'commentMailHost',
NULL,
NULL,
'邮箱服务器地址',
'例如smtp.qq.com'
);
$commentMailHost->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMailHost->multiMode());
$commentSMTPSecure = new Typecho_Widget_Helper_Form_Element_Select(
'commentSMTPSecure',
array('ssl' => 'ssl默认', 'tsl' => 'tsl'),
'ssl',
'加密方式',
'介绍:用于选择登录鉴权加密方式'
);
$commentSMTPSecure->setAttribute('class', 'setting-content my-3');
$form->addInput($commentSMTPSecure->multiMode());
$commentMailPort = new Typecho_Widget_Helper_Form_Element_Text(
'commentMailPort',
NULL,
NULL,
'邮箱服务器端口号',
'例如465'
);
$commentMailPort->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMailPort->multiMode());
$commentMailFromName = new Typecho_Widget_Helper_Form_Element_Text(
'commentMailFromName',
NULL,
NULL,
'发件人昵称',
'例如:蔡徐坤博客评论回复通知'
);
$commentMailFromName->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMailFromName->multiMode());
$commentMailAccount = new Typecho_Widget_Helper_Form_Element_Text(
'commentMailAccount',
NULL,
NULL,
'发件人邮箱',
'例如123456@qq.com'
);
$commentMailAccount->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMailAccount->multiMode());
$commentMailPassword = new Typecho_Widget_Helper_Form_Element_Text(
'commentMailPassword',
NULL,
NULL,
'邮箱授权码',
'介绍:这里填写的是邮箱生成的授权码 <br>
获取方式以QQ邮箱为例<br>
QQ邮箱 > 设置 > 账户 > IMAP/SMTP服务 > 开启 <br>
其他:这个可以百度一下开启教程,有图文教程'
);
$commentMailPassword->setAttribute('class', 'setting-content my-3');
$form->addInput($commentMailPassword->multiMode());
// 底部设置
$icp = new Typecho_Widget_Helper_Form_Element_Text(
'icp',
NULL,
NULL,
'备案HTML内容非必填',
'介绍在这里填入你的网站备案HTML内容 注意底部footer内容会覆盖此内容只适合默认的底部内容'
);
$form->addInput($icp);
$icp->setAttribute('class', 'setting-content my-3');
// 网站建站日期
$sitedate = new Typecho_Widget_Helper_Form_Element_Text(
'sitedate',
NULL,
'2024-08-06',
'网站建站日期(必填)',
'介绍:在这里填入你的网站建站日期, 例如2024-05-20'
);
$form->addInput($sitedate);
$sitedate->setAttribute('class', 'setting-content my-3');
// 底部footer内容
$footerContent = new Typecho_Widget_Helper_Form_Element_Textarea(
'footerContent',
NULL,
NULL,
'底部footer内容非必填',
'介绍在这里填入底部footer HTML内容注意此处填写会覆盖主题默认底部footer内容'
);
$form->addInput($footerContent);
$footerContent->setAttribute('class', 'setting-content my-3');
// 网站统计代码(非必填)
$zztj = new Typecho_Widget_Helper_Form_Element_Textarea(
'zztj',
NULL,
NULL,
'网站统计代码(非必填)',
'介绍在这里填入你的网站统计代码这个可以到百度统计或者cnzz上申请。'
);
$form->addInput($zztj);
$zztj->setAttribute('class', 'setting-content my-3');
// 自定义CSS
$customStyle = new Typecho_Widget_Helper_Form_Element_Textarea(
'customStyle',
null,
null,
'自定义CSS非必填',
'介绍:不需要添加 &lt;style&gt; 标签'
);
$form->addInput($customStyle);
$customStyle->setAttribute('class', 'setting-content my-3');
// 自定义JS
$customScript = new Typecho_Widget_Helper_Form_Element_Textarea(
'customScript',
null,
null,
'自定义JS非必填',
'介绍:不需要添加 &lt;script&gt; 标签'
);
$form->addInput($customScript);
$customScript->setAttribute('class', 'setting-content my-3');
// 自定义增加head里内容非必填
$customHeadEnd = new Typecho_Widget_Helper_Form_Element_Textarea(
'customHeadEnd',
NULL,
NULL,
'自定义增加&lt;head&gt;&lt;/head&gt;里内容(非必填)',
'介绍:此处用于在&lt;head&gt;&lt;/head&gt;标签里增加自定义内容可以填写引入第三方css、js等等'
);
$customHeadEnd->setAttribute('class', 'setting-content my-3');
$form->addInput($customHeadEnd);
// 自定义增加body里内容非必填
$customBodyEnd = new Typecho_Widget_Helper_Form_Element_Textarea(
'customBodyEnd',
NULL,
NULL,
'自定义&lt;body&gt;&lt;/body&gt;末尾位置内容(非必填)',
'介绍:此处用于填写在&lt;body&gt;&lt;/body&gt;标签末尾位置的内容例如可以填写引入第三方js脚本等等'
);
$customBodyEnd->setAttribute('class', 'setting-content my-3');
$form->addInput($customBodyEnd);
}
?>

View File

@@ -0,0 +1,293 @@
<?php
/**
* Harmony Hues主题 - 邮件通知
*
* @author 星语社长
* @link https://biibii.cn
* @update 2025-2-25 23:16:37
*/
if (! defined('__TYPECHO_ROOT_DIR__')) {
exit;
}
// 引入PHPMailer组件
use PHPMailer\PHPMailer\Exception;
use PHPMailer\PHPMailer\PHPMailer;
require 'PHPMailer/PHPMailer.php';
require 'PHPMailer/SMTP.php';
require 'PHPMailer/Exception.php';
$options = Helper::options();
// 检查邮件配置是否完整
if (
$options->commentMail === 'on' &&
$options->commentMailHost &&
$options->commentMailPort &&
$options->commentMailFromName &&
$options->commentMailAccount &&
$options->commentMailPassword &&
$options->commentSMTPSecure
) {
// 注册评论完成时的回调
Typecho_Plugin::factory('Widget_Feedback')->finishComment = array('Email', 'send');
}
class Email
{
/**
* 发送评论通知的主入口
*
* @param Widget_Feedback $comment 评论对象
*/
public static function send($comment)
{
try {
$params = array(
'title' => htmlspecialchars($comment->title),
'postlink' => preg_replace('/\/comment-page-\d+#comment-\d+/', '', $comment->permalink),
'permalink' => htmlspecialchars($comment->permalink),
'author' => htmlspecialchars($comment->author),
'text' => self::processCommentText($comment->text),
'mail' => $comment->mail,
);
// 作者评论处理(博主回复)
if ($comment->authorId == $comment->ownerId) {
self::handleAuthorComment($comment, $params);
} else {
self::handleGuestComment($comment, $params);
}
} catch (Exception $e) {
error_log('[Email Plugin] Error: ' . $e->getMessage());
}
}
/**
* 初始化邮件发送器
* 新增SMTP主机连通性检测
*/
private static function initMailer()
{
try {
$options = Helper::options();
$host = $options->commentMailHost;
$port = $options->commentMailPort;
$timeout = 3;
$connection = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (! $connection) {
throw new Exception("SMTP主机不可达: {$host}:{$port} - {$errstr} ({$errno})");
}
fclose($connection);
$mail = new PHPMailer(true);
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->CharSet = 'UTF-8';
$mail->SMTPSecure = $options->commentSMTPSecure;
$mail->Host = $host;
$mail->Port = $port;
$mail->FromName = $options->commentMailFromName;
$mail->Username = $options->commentMailAccount;
$mail->From = $options->commentMailAccount;
$mail->Password = $options->commentMailPassword;
$mail->isHTML(true);
return $mail;
} catch (Exception $e) {
error_log('[Email Plugin] SMTP初始化失败: ' . $e->getMessage());
throw $e;
}
}
/**
* 处理评论内容
* - 转换图片标签为响应式展示
* - 处理表情符号
*
* @param string $content 原始评论内容
* @return string 处理后的HTML内容
*/
private static function processCommentText($content)
{
$content = preg_replace_callback(
'/!\[(.*?)\]\((.*?)\)/',
function ($matches) {
$alt = htmlspecialchars($matches[1]);
$siteUrl = Helper::options()->siteUrl;
$src = rtrim($siteUrl, '/') . '/' . ltrim($matches[2], '/');
return '<img style="display:block;width:auto;height:20rem;" src="' . $src . '" alt="' . $alt . '" />';
},
$content
);
if (function_exists('formatEmoji')) {
$content = formatEmoji($content, false);
}
return $content;
}
/**
* 构建邮件HTML正文
* 新增原评论内容展示区域
*
* @param string $title 邮件标题
* @param string $subtitle 邮件副标题
* @param string $content 当前评论内容
* @param string $originalContent 原评论内容(新增)
* @param string $permalink 评论链接
* @return string 完整的HTML邮件正文
*/
private static function buildEmailBody($title, $subtitle, $content, $originalContent, $permalink)
{
$options = Helper::options();
$siteUrl = rtrim($options->siteUrl, '/');
$favicon = $options->favicon;
if ($favicon && ! parse_url($favicon, PHP_URL_SCHEME)) {
$favicon = $siteUrl . '/' . ltrim($favicon, '/');
}
$originalSection = '';
if (! empty($originalContent)) {
$originalSection = <<<HTML
<div style="margin:15px 0;padding: 10px;border-radius: 3px;border: 1px solid #ededed;border-left: 3px solid #ddd;">
<div style="margin-bottom:5px;color:#666;">您原评论的内容:</div>
<div style="padding: 10px;margin-top: 10px;background-image: radial-gradient(transparent 2px, #eee 1px);background-size: 4px 4px;border-radius: 3px;">
{$originalContent}
</div>
</div>
HTML;
}
return <<<HTML
<div style="margin:5px;border-radius:10px;font-size:14px;color:#333;max-width:100%;box-shadow:0 1px 5px rgba(0,0,0,.15);border:2px solid #fff;overflow:hidden;word-break:break-all;">
<div style="padding:10px;font-size:16px;background-color:#e2ebf0;">{$title}</div>
<div style="background-color: #fff;padding:20px;font-size: 14px;color: #555;">
<div style="margin-bottom:10px;">{$subtitle}</div>
<div style="padding:10px;margin-bottom:10px;background-image:radial-gradient(transparent 2px,#eee 1px);background-size:4px 4px;border-radius:3px;">{$content}</div>
{$originalSection}
<div style="margin-bottom:10px;"><a style="display: inline-block;color: #333;text-decoration: none;background: #f3f3f3;border-radius: 8px;margin-top: 10px;padding: 5px 20px;" href="{$permalink}" target="_blank">查看详情</a></div>
<hr style="margin:15px 0;border:1px dashed #eee;">
<div style="display:flex;color:#999;flex-wrap:wrap;flex-direction:row;justify-content:space-between;align-items:flex-end;">
<span>注:此邮件由系统自动发送,请勿直接回复。<br>若此邮件不是您请求的,请忽略并删除!</span>
<a href="{$siteUrl}" target="_blank"><img style="width: auto;max-width:35px;height:35px;" src="{$favicon}" /></a>
</div>
</div>
</div>
HTML;
}
/**
* 处理博主回复(作者评论)
* - 博主回复其他用户评论时触发
* - 新增原评论内容展示
*/
private static function handleAuthorComment($comment, $params)
{
if ($comment->parent == 0) {
return;
}
$parentComment = self::getParentComment($comment->parent);
if (! $parentComment || $parentComment['mail'] == $comment->mail) {
return;
}
$originalText = self::processCommentText($parentComment['text']);
$mail = self::initMailer();
$mail->addAddress($parentComment['mail']);
$mail->Subject = "您在[{$params['title']}]的评论有了新的回复!";
$mail->Body = self::buildEmailBody(
$mail->Subject,
"{$params['author']}在《<a style=\"color:#0077FF;text-decoration:none;\" href=\"{$params['postlink']}\" target=\"_blank\">{$params['title']}</a>》上回复了您:",
$params['text'],
$originalText,
$params['permalink']
);
$mail->send();
}
/**
* 处理访客评论
* - 区分新评论和回复评论
*/
private static function handleGuestComment($comment, $params)
{
if ($comment->parent == 0) {
self::notifyAuthor($comment, $params);
} else {
self::notifyParent($comment, $params);
}
}
/**
* 通知文章作者有新评论
*/
private static function notifyAuthor($comment, $params)
{
$authorMail = self::getAuthorMail($comment->ownerId);
if (! $authorMail) {
return;
}
$mail = self::initMailer();
$mail->addAddress($authorMail);
$mail->Subject = "您的文章[{$params['title']}]收到一条新的评论!";
$mail->Body = self::buildEmailBody(
$mail->Subject,
"{$params['author']}在您的《<a style=\"color:#0077FF;text-decoration:none;\" href=\"{$params['postlink']}\" target=\"_blank\">{$params['title']}</a>》上发表评论:",
$params['text'],
'',
$params['permalink']
);
$mail->send();
}
/**
* 通知被回复的评论者
* - 新增原评论内容展示
*/
private static function notifyParent($comment, $params)
{
$parentComment = self::getParentComment($comment->parent);
if (! $parentComment || $parentComment['mail'] == $comment->mail) {
return;
}
$originalText = self::processCommentText($parentComment['text']);
$mail = self::initMailer();
$mail->addAddress($parentComment['mail']);
$mail->Subject = "您在[{$params['title']}]的评论有了新的回复!";
$mail->Body = self::buildEmailBody(
$mail->Subject,
"{$params['author']}在《<a style=\"color:#0077FF;text-decoration:none;\" href=\"{$params['postlink']}\" target=\"_blank\">{$params['title']}</a>》上回复了您:",
$params['text'],
$originalText,
$params['permalink']
);
$mail->send();
}
/**
* 获取父评论信息(增强版)
* 现在返回完整评论数据而不仅是邮箱
*
* @param int $parentId 父评论ID
* @return array|null 包含邮件和文本的数组
*/
private static function getParentComment($parentId)
{
$db = Typecho_Db::get();
return $db->fetchRow($db->select('mail', 'text')
->from('table.comments')
->where('coid = ?', $parentId));
}
/**
* 获取文章作者邮箱
*
* @param int $ownerId 用户ID
* @return string|null 邮箱地址
*/
private static function getAuthorMail($ownerId)
{
$db = Typecho_Db::get();
$result = $db->fetchRow($db->select('mail')
->from('table.users')
->where('uid = ?', $ownerId));
return $result['mail'] ?? null;
}
}