https://github.com/jrotty/CommentNotifier * @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('

邮件服务配置:

')); $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 .= 'QQ邮箱 '; $description .= '阿里邮箱 '; $description .= 'Outlook邮箱 '; $description .= '新浪邮箱 '; $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('

邮件信息配置:

')); $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; } } }