Files
plugins/CommentNotifier/Plugin.php
chorblack e75f275ef4
Some checks failed
定时更新GitHub源插件 / 自动更新GitHub插件 (push) Has been cancelled
Initial commit
2026-03-07 11:19:25 +08:00

456 lines
17 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
namespace TypechoPlugin\CommentNotifier;
use Typecho\Plugin\PluginInterface;
use Typecho\Widget\Helper\Form;
use Typecho\Widget\Helper\Form\Element\Text;
use Typecho\Widget\Helper\Form\Element\Checkbox;
use Typecho\Widget\Helper\Form\Element\Radio;
use Typecho\Widget\Helper\Layout;
use Widget\Options;
use Widget\Base\Comments;
use Typecho\Db;
use Typecho\Date;
use Utils\Helper;
/**
* typecho 评论通过时发送邮件提醒,要求typecho1.2.0及以上,项目地址<a href="https://github.com/jrotty/CommentNotifier" target="_blank">https://github.com/jrotty/CommentNotifier</a>
* @package CommentNotifier
* @author 泽泽社长
* @version 1.2.9
* @link http://blog.zezeshe.com
*/
require dirname(__FILE__) . '/PHPMailer/PHPMailer.php';
require dirname(__FILE__) . '/PHPMailer/SMTP.php';
require dirname(__FILE__) . '/PHPMailer/Exception.php';
use PHPMailer\PHPMailer\PHPMailer;
use PHPMailer\PHPMailer\Exception;
class Plugin implements PluginInterface
{
/** @var string 控制菜单链接 */
public static $panel = 'CommentNotifier/console.php';
/**
* 激活插件方法,如果激活失败,直接抛出异常
*
* @access public
* @return string
*/
public static function activate()
{
\Typecho\Plugin::factory('Widget_Feedback')->finishComment = __CLASS__ . '::resendMail'; // 前台提交评论完成接口
\Typecho\Plugin::factory('Widget_Comments_Edit')->finishComment = __CLASS__ . '::resendMail'; // 后台操作评论完成接口
\Typecho\Plugin::factory('Widget_Comments_Edit')->mark = __CLASS__ . '::mark'; // 后台标记评论状态完成接口
\Typecho\Plugin::factory('Widget_Service')->refinishComment = __CLASS__ . '::refinishComment';//异步接口
Helper::addPanel(1, self::$panel, '评论邮件提醒', '评论邮件提醒控制台', 'administrator');
return _t('请配置邮箱SMTP选项!');
}
/**
* 禁用插件方法,如果禁用失败,直接抛出异常
*
* @static
* @access public
* @return void
*/
public static function deactivate()
{
Helper::removePanel(1, self::$panel);
}
/**
* 获取插件配置面板
*
* @access public
* @param Form $form 配置面板
* @return void
*/
public static function config(Form $form)
{
// 记录log
$log = new Checkbox('log', ['log' => _t('记录日志')], 'log', _t('记录日志'), _t('启用后将当前目录生成一个log.txt 注:目录需有写入权限'));
$form->addInput($log);
$yibu = new Radio('yibu', array('0' => _t('不启用'), '1' => _t('启用'),), '0', _t('异步提交'), _t('注意如你博客使用ajax提交评论请不要开启此项否则可能导致邮件无法发送'));
$form->addInput($yibu);
$layout = new Layout();
$layout->html(_t('<h3>邮件服务配置:</h3>'));
$form->addItem($layout);
// SMTP服务地址
$STMPHost = new Text('STMPHost', NULL, 'smtp.qq.com', _t('SMTP服务器地址'), _t('如:smtp.163.com,smtp.gmail.com,smtp.exmail.qq.com,smtp.sohu.com,smtp.sina.com'));
$form->addInput($STMPHost->addRule('required', _t('SMTP服务器地址必填!')));
// SMTP用户名
$SMTPUserName = new Text('SMTPUserName', NULL, NULL, _t('SMTP登录用户'), _t('SMTP登录用户名一般为邮箱地址'));
$form->addInput($SMTPUserName->addRule('required', _t('SMTP登录用户必填!')));
// 发件邮箱
$from = new Text('from', NULL, NULL, _t('SMTP邮箱地址'), _t('请填写用于发送邮件的邮箱一般与SMTP登录用户名一致'));
$form->addInput($from->addRule('required', _t('发件邮箱必填!')));
// SMTP密码
$description = _t('一般为邮箱登录密码, 有特殊如: QQ邮箱有独立的SMTP密码. 可参考: ');
$description .= '<a href="https://service.mail.qq.com/cgi-bin/help?subtype=1&&no=1001256&&id=28" target="_blank">QQ邮箱</a> ';
$description .= '<a href="https://mailhelp.aliyun.com/freemail/detail.vm?knoId=6521875" target="_blank">阿里邮箱</a> ';
$description .= '<a href="https://support.office.com/zh-cn/article/outlook-com-%E7%9A%84-pop%E3%80%81imap-%E5%92%8C-smtp-%E8%AE%BE%E7%BD%AE-d088b986-291d-42b8-9564-9c414e2aa040?ui=zh-CN&rs=zh-CN&ad=CN" target="_blank">Outlook邮箱</a> ';
$description .= '<a href="http://help.sina.com.cn/comquestiondetail/view/160/" target="_blank">新浪邮箱</a> ';
$SMTPPassword = new Text('SMTPPassword', NULL, NULL, _t('SMTP登录密码'), $description);
$form->addInput($SMTPPassword->addRule('required', _t('SMTP登录密码必填!')));
// 服务器安全模式
$SMTPSecure = new Radio('SMTPSecure', array('' => _t('无安全加密'), 'ssl' => _t('SSL加密'), 'tls' => _t('TLS加密')), '', _t('SMTP加密模式'));
$form->addInput($SMTPSecure);
// SMTP server port
$SMTPPort = new Text('SMTPPort', NULL, '25', _t('SMTP服务端口'), _t('默认25 SSL为465 TLS为587'));
$form->addInput($SMTPPort);
$layout = new Layout();
$layout->html(_t('<h3>邮件信息配置:</h3>'));
$form->addItem($layout);
// 发件人姓名
$fromName = new Text('fromName', NULL, NULL, _t('发件人姓名'), _t('发件人姓名'));
$form->addInput($fromName->addRule('required', _t('发件人姓名必填!')));
// 收件邮箱
$adminfrom = new Text('adminfrom', NULL, NULL, _t('站长收件邮箱'), _t('遇到待审核评论或文章作者邮箱为空时,评论提醒会发送到此邮箱地址!'));
$form->addInput($adminfrom->addRule('required', _t('收件邮箱必填!')));
// 模板
$template = new Text('template', NULL, 'default', _t('邮件模板选择'), _t('该项请不要在插件设置里填写,请到邮件模板列表页面选择模板启动!'));
$template->setAttribute('class', 'hidden');
$form->addInput($template);
}
/**
* 个人用户的配置面板
*
* @access public
* @param Form $form
* @return void
*/
public static function personalConfig(Form $form)
{
}
/**
* 插件实现方法
*
* @access public
* @return void
*/
public static function render()
{
}
/**
* @param $comment
* @return array
* @throws Typecho_Db_Exception
* 获取上级评论人
*/
public static function getParent($comment): array
{
$recipients = [];
$parent = Helper::widgetById('comments', $comment->parent);
$recipients = [
'name' => $parent->author,
'mail' => $parent->mail,
];
return $recipients;
}
/**
* @param $comment
* @return array
* @throws Typecho_Db_Exception
* 获取文章作者邮箱
*/
public static function getAuthor($comment): array
{
$CommentNotifier = Options::alloc()->plugin('CommentNotifier');
$recipients = [];
$db = Db::get();
$ae = $db->fetchRow($db->select()->from('table.users')->where('table.users.uid=?', $comment->ownerId));
if (empty($ae['mail'])) {
$ae['screenName'] = $CommentNotifier->fromName;
$ae['mail'] = $CommentNotifier->adminfrom;
}
$recipients = [
'name' => $ae['screenName'],
'mail' => $ae['mail'],
];
// 查询
return $recipients;
}
/**
* @param $comment
* @param Widget_Comments_Edit $edit
* @param $status
* @throws Typecho_Db_Exception
* @throws Typecho_Plugin_Exception
* 在后台标记评论状态时的回调
*/
public static function mark($comment, $edit, $status)
{
$recipients = [];
$CommentNotifier = Options::alloc()->plugin('CommentNotifier');
$from = $CommentNotifier->adminfrom; // 站长邮箱
// 在后台标记评论状态为[approved 审核通过]时, 发信给上级评论人或作者
if ($status == 'approved') {
$type = 0;
// 如果有上级
if ($edit->parent > 0) {
$recipients[] = self::getParent($edit);//获取上级评论信息
$type = 1;
} else {
$recipients[] = self::getAuthor($edit);//获取作者信息
}
// 如果自己回复自己的评论, 不做任何操作
if ($recipients[0]['mail'] == $edit->mail) {
return;
}
// 如果上级是博主, 不做任何操作
if ($recipients[0]['mail'] == $from) {
return;
}
//邮箱为空时就不发邮件
if (empty($recipients[0]['mail'])) {
return;
}
self::sendMail($edit, $recipients, $type);
}
}
/**
* @param Widget_Comments_Edit|Widget_Feedback $comment
* @throws Typecho_Db_Exception
* @throws Typecho_Plugin_Exception
* 评论/回复时的回调
*/
public static function refinishComment($comment)
{
$CommentNotifier = Options::alloc()->plugin('CommentNotifier');
$from = $CommentNotifier->adminfrom; // 站长邮箱
$fromName = $CommentNotifier->fromName; // 发件人
$recipients = [];
// 审核通过
if ($comment->status == 'approved') {
$type = 0;//0为无父级评论
// 不需要发信给博主
if ($comment->authorId != $comment->ownerId && $comment->mail != $from) {
$recipients[] = self::getAuthor($comment);//收到新评论后发送给文章作者
}
// 如果有上级
if ($comment->parent) {
$type = 1;//1为有父级评论
// 查询上级评论人
$parent = self::getParent($comment);//获取上级评论者邮箱
// 如果上级是博主和自己回复自己, 不需要发信
if ($parent['mail'] != $from && $parent['mail'] != $comment->mail) {
$recipients[] = $parent;
}
}
self::sendMail($comment, $recipients, $type);
} else {
// 如果所有评论必须经过审核, 通知博主审核评论
$recipients[] = ['name' => $fromName, 'mail' => $from];
self::sendMail($comment, $recipients, 2);//2为待审核评论
}
}
/**
* @param Widget_Comments_Edit|Widget_Feedback $comment
* @param array $recipients
* @param $type
*/
private static function sendMail($comment, array $recipients, $type)
{
if (empty($recipients)) return; // 没有收信人
try {
// 获取系统配置选项
$options = Options::alloc();
// 获取插件配置
$CommentNotifier = $options->plugin('CommentNotifier');
$from = $CommentNotifier->from; // 发件邮箱
$fromName = $CommentNotifier->fromName; // 发件人
// Server settings
$mail = new PHPMailer(false);
$mail->CharSet = PHPMailer::CHARSET_UTF8;
$mail->Encoding = PHPMailer::ENCODING_BASE64;
$mail->isSMTP();
$mail->Host = $CommentNotifier->STMPHost; // SMTP 服务地址
$mail->SMTPAuth = true; // 开启认证
$mail->Username = $CommentNotifier->SMTPUserName; // SMTP 用户名
$mail->Password = $CommentNotifier->SMTPPassword; // SMTP 密码
$mail->SMTPSecure = $CommentNotifier->SMTPSecure; // SMTP 加密类型 'ssl' or 'tls'.
$mail->Port = $CommentNotifier->SMTPPort; // SMTP 端口
$mail->setFrom($from, $fromName);
foreach ($recipients as $recipient) {
$mail->addAddress($recipient['mail'], $recipient['name']); // 发件人
}
if ($type == 1) {
$mail->Subject = '你在[' . $comment->title . ']的评论有了新的回复';
} elseif ($type == 2) {
$mail->Subject = '文章《' . $comment->title . '》有条待审评论';
} else {
$mail->Subject = '你的《' . $comment->title . '》文章有了新的评论';
}
$mail->isHTML(); // 邮件为HTML格式
// 邮件内容
$content = self::mailBody($comment, $options, $type);
$mail->Body = $content;
$mail->send();
// 记录日志
if ($CommentNotifier->log) {
$at = date('Y-m-d H:i:s');
if ($mail->isError()) {
$data = $at . ' ' . $mail->ErrorInfo; // 记录发信失败的日志
} else { // 记录发信成功的日志
$recipientNames = $recipientMails = '';
foreach ($recipients as $recipient) {
$recipientNames .= $recipient['name'] . ', ';
$recipientMails .= $recipient['mail'] . ', ';
}
$data = PHP_EOL . $at . ' 发送成功! ';
$data .= ' 发件人:' . $fromName;
$data .= ' 发件邮箱:' . $from;
$data .= ' 接收人:' . $recipientNames;
$data .= ' 接收邮箱:' . $recipientMails . PHP_EOL;
}
$fileName = dirname(__FILE__) . '/log.txt';
file_put_contents($fileName, $data, FILE_APPEND);
}
} catch (Exception $e) {
$fileName = dirname(__FILE__) . '/log.txt';
$str = "\nerror time: " . date('Y-m-d H:i:s') . "\n";
file_put_contents($fileName, $str, FILE_APPEND);
file_put_contents($fileName, $e, FILE_APPEND);
}
}
/**
* @param $comment
* @param $options
* @param $type
* @return string
* 很朴素的邮件风格
*/
private static function mailBody($comment, $options, $type): string
{
$commentAt = new Date($comment->created);
$commentAt = $commentAt->format('Y-m-d H:i:s');
$commentText = htmlspecialchars($comment->text);
$html = 'owner';
if ($type == 1) {
$html = 'guest';
} elseif ($type == 2) {
$html = 'notice';
}
$Pmail = '';
$Pname = '';
$Ptext = '';
if ($comment->parent) {
$parent = Helper::widgetById('comments', $comment->parent);
$Pmail = $parent->mail;
$Pname = $parent->author;
$Ptext = $parent->text;
}
$content = self::getTemplate($html);
$template = Options::alloc()->plugin('CommentNotifier')->template;
$search = array(
'{title}',//文章标题
'{time}',//评论发出时间
'{commentText}',//评论内容
'{author}',//评论人昵称
'{mail}',//评论者邮箱
'{permalink}',//评论楼层链接
'{siteUrl}',//网站地址
'{siteTitle}',//网站标题
'{Pname}',//父级评论昵称
'{Ptext}',//父级评论内容
'{Pmail}',//父级评论邮箱
'{url}',//当前模板文件夹路径
);
$replace = array(
$comment->title,
$commentAt,
$commentText,
$comment->author,
$comment->mail,
$comment->permalink,
$options->siteUrl,
$options->title,
$Pname,
$Ptext,
$Pmail,
Options::alloc()->pluginUrl . '/CommentNotifier/template/' . $template,
);
return str_replace($search, $replace, $content);
}
/**
* 获取评论模板
*
* @param template owner 为博主 guest 为访客
* @return false|string
*/
private static function getTemplate($template = 'owner')
{
$template .= '.html';
$templateDir = self::configStr('template', 'default');
$filePath = dirname(__FILE__) . '/template/' . $templateDir . '/' . $template;
if (!file_exists($filePath)) {//如果模板文件缺失就调用根目录下的default文件夹中用于垫底的模板
$filePath = dirname(__FILE__) . 'template/default/' . $template;
}
return file_get_contents($filePath);
}
public static function resendMail($comment)
{
if(Options::alloc()->plugin('CommentNotifier')->yibu==1){
Helper::requestService('refinishComment', $comment);
}else{
self::refinishComment($comment);
}
}
/**
* 从 Widget_Options 对象获取 Typecho 选项值(文本型)
* @param string $key 选项 Key
* @param mixed $default 默认值
* @param string $method 测空值方法
* @return string
*/
public static function configStr(string $key, $default = '', string $method = 'empty'): string
{
$value = Helper::options()->plugin('CommentNotifier')->$key;
if ($method === 'empty') {
return empty($value) ? $default : $value;
} else {
return call_user_func($method, $value) ? $default : $value;
}
}
}