码桶
发现社区成员的开源项目
email.php12.4 KB
<?php
if (!defined('__TYPECHO_ROOT_DIR__')) exit;
require_once __DIR__ . '/phpmailer.php';
require_once __DIR__ . '/smtp.php';
class V3_Email
{
public static function send($comment)
{
try {
$options = Helper::options();
if ($options->v3_email_switch !== 'on') {
return;
}
if (!$options->v3_email_host || !$options->v3_email_port || !$options->v3_email_sendmail || !$options->v3_email_password) {
return;
}
$mail = new PHPMailer();
$mail->isSMTP();
$mail->SMTPAuth = true;
$mail->CharSet = 'UTF-8';
if ($options->v3_email_ssl && $options->v3_email_ssl !== 'none') {
$mail->SMTPSecure = $options->v3_email_ssl;
}
$mail->Host = $options->v3_email_host;
$mail->Port = intval($options->v3_email_port);
$mail->FromName = $options->v3_email_nickname ? $options->v3_email_nickname : $options->title;
$mail->Username = $options->v3_email_sendmail;
$mail->From = $options->v3_email_sendmail;
$mail->Password = $options->v3_email_password;
$mail->isHTML(true);
$text = self::sanitize($comment->text);
$img = $options->v3_email_pic ? $options->v3_email_pic : 'https://jihulab.com/ScarletDor/owo/-/raw/master/public/mail1.webp';
$copyright = '此邮件由系统自动发送,请勿直接回复<br>©' . date("Y") . ' - '
. '<a style="color:#999;text-decoration:none;" href="' . $options->siteUrl . '" target="_blank">' . $options->title . '</a>';
$permalinkNoHash = $comment->permalink;
$hashPos = strrpos($comment->permalink, '#');
if ($hashPos !== false) {
$permalinkNoHash = substr($comment->permalink, 0, $hashPos);
}
$linkHtml = '<a style="color:#ff5850;text-decoration:none;" href="' . $permalinkNoHash . '" target="_blank">《 ' . $comment->title . ' 》</a>';
$time = date('Y年n月j日 H:i', $comment->created);
$html_1 = self::template_blogger($img, $linkHtml, $copyright);
$html_2 = self::template_visitor($img, $linkHtml, $copyright);
$db = Typecho_Db::get();
if ($comment->authorId != $comment->ownerId) {
$authorMail = trim($options->v3_admin_email ?? '');
if (!$authorMail) {
$authorInfo = $db->fetchRow($db->select()->from('table.users')->where('uid = ?', 1));
$authorMail = isset($authorInfo['mail']) ? $authorInfo['mail'] : '';
}
if ($authorMail) {
$mail->Body = strtr($html_1, array(
'{avatar2}' => lb_avatar_url($comment->mail, 200),
'{author}' => htmlspecialchars($comment->author),
'{ip}' => $comment->ip,
'{mail}' => $comment->mail,
'{time}' => $time,
'{content}' => $text,
));
$mail->addAddress($authorMail);
$mail->Subject = '💬 ' . htmlspecialchars($comment->author) . ' 在 『' . $comment->title . '』 中发表了新评论';
$sendResult = $mail->send();
if (!$sendResult) {
error_log('[V3] 邮件发送失败(通知博主): ' . $mail->ErrorInfo);
}
$mail->clearAddresses();
}
}
if ($comment->parent > 0) {
$parentInfo = $db->fetchRow($db->select('mail', 'text', 'created')->from('table.comments')->where('coid = ?', $comment->parent));
if ($parentInfo) {
$parentMail = $parentInfo['mail'];
$parentText = self::sanitize($parentInfo['text']);
$parentCreated = $parentInfo['created'];
if ($parentMail && $parentMail != $comment->mail) {
$mail->Body = strtr($html_2, array(
'{avatar1}' => lb_avatar_url($parentMail, 200),
'{avatar2}' => lb_avatar_url($comment->mail, 200),
'{author}' => htmlspecialchars($comment->author),
'{ip}' => $comment->ip,
'{mail}' => $comment->mail,
'{time}' => $time,
'{oldtime}' => date('Y年n月j日 H:i', $parentCreated),
'{content}' => $text,
'{oldcontent}' => $parentText,
));
$mail->addAddress($parentMail);
$mail->Subject = '💬 ' . htmlspecialchars($comment->author) . ' 回复了您在 『' . $comment->title . '』 的评论';
$sendResult2 = $mail->send();
if (!$sendResult2) {
error_log('[V3] 邮件发送失败(通知访客): ' . $mail->ErrorInfo);
}
$mail->clearAddresses();
}
}
}
$mentionMails = self::getMentionMails($comment);
foreach ($mentionMails as $mentionMail) {
$mail->clearAddresses();
$mail->Body = strtr($html_2, array(
'{avatar1}' => lb_avatar_url($mentionMail, 200),
'{avatar2}' => lb_avatar_url($comment->mail, 200),
'{author}' => htmlspecialchars($comment->author),
'{ip}' => $comment->ip,
'{mail}' => $comment->mail,
'{time}' => $time,
'{oldtime}' => date('Y年n月j日 H:i', $comment->created),
'{content}' => $text,
'{oldcontent}' => '在评论中提到了您',
));
$mail->addAddress($mentionMail);
$mail->Subject = '📢 ' . htmlspecialchars($comment->author) . ' 在 『' . $comment->title . '』 的评论中 @了您';
$sendResult3 = $mail->send();
if (!$sendResult3) {
error_log('[V3] 邮件发送失败(@提及): ' . $mail->ErrorInfo);
}
}
} catch (\Throwable $e) {
if (defined('__TYPECHO_DEBUG__') && __TYPECHO_DEBUG__) {
error_log('[V3] 邮件发送异常: ' . $e->getMessage());
}
}
}
private static function getMentionMails($comment)
{
$text = $comment->text;
if (!preg_match_all('/@([\x{4e00}-\x{9fa5}a-zA-Z0-9_]+)/u', $text, $matches)) {
return array();
}
$brands = Helper::options()->v3_brands ?: '';
$brandNames = array();
foreach (explode("\n", $brands) as $line) {
$parts = explode('|', trim($line), 4);
if (!empty($parts[0])) {
$brandNames[] = trim($parts[0]);
}
}
$mentioned = array();
$db = Typecho_Db::get();
$currentMail = $comment->mail;
$parentMail = null;
if ($comment->parent > 0) {
$pi = $db->fetchRow($db->select('mail')->from('table.comments')->where('coid = ?', $comment->parent));
$parentMail = $pi ? $pi['mail'] : null;
}
foreach ($matches[1] as $name) {
if (in_array($name, $brandNames)) continue;
$row = $db->fetchRow($db->select('mail')->from('table.comments')
->where('cid = ?', $comment->cid)
->where('author = ?', $name)
->where('mail != ?', '')
->order('created', Typecho_Db::SORT_DESC)
->limit(1));
if ($row && !empty($row['mail'])
&& $row['mail'] !== $currentMail
&& $row['mail'] !== $parentMail) {
$mentioned[$row['mail']] = true;
}
}
return array_keys($mentioned);
}
private static function sanitize($text)
{
if (self::hasXss($text)) {
return '该回复疑似异常,已被系统拦截!';
}
return strip_tags($text, '<img>');
}
private static function hasXss($text)
{
$patterns = array('/on\w+\s*=/is', '/javascript:/is', '/eval/is', '/vbscript:/is');
foreach ($patterns as $p) {
if (preg_match($p, $text)) {
return true;
}
}
return false;
}
private static function template_blogger($img, $linkHtml, $copyright)
{
return '
<div style="width:550px;margin:0 auto;border-radius:8px;overflow:hidden;word-break:break-all;background:linear-gradient(117deg,#e2ffda,#f6ffff);">
<img src="' . $img . '" alt="您的邮件" style="width:100%;">
<div style="padding:1rem;position:relative;">
<p><b style="color:#ff5850;" title="IP:{ip} Mail:{mail}">{author} </b>在 ' . $linkHtml . ' 上给您留言啦:</p>
<div style="display:flex;margin-bottom:5px;">
<div style="width:auto;flex:none;">
<img src="{avatar2}" style="width:40px;height:40px;border-radius:5px;">
</div>
<div style="position:relative;margin-left:10px;">
<span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-right:8px solid;border-right-color:#f4f4f4;left:-7px;right:auto;top:12px;position:absolute;"></span>
<div style="background:linear-gradient(117deg,#dff6ff,#daffdf);padding:10px;border-radius:9px;margin-bottom:3px;">{content}</div>
</div>
</div>
<p style="color:#999;font-size:12px;">{time}</p>
<div style="text-align:center;color:#999;font-size:12px;line-height:1.5rem;">' . $copyright . '</div>
<div style="font-size:4rem;position:absolute;right:0;bottom:0;transform:rotate(-15deg);opacity:0.5;">💌</div>
</div>
</div>';
}
private static function template_visitor($img, $linkHtml, $copyright)
{
return '
<div style="width:550px;margin:0 auto;border-radius:8px;overflow:hidden;word-break:break-all;background:linear-gradient(117deg,#e2ffda,#f6ffff);">
<img src="' . $img . '" alt="您的邮件" style="width:100%;">
<div style="padding:1rem;position:relative;">
<p style="display:flex;justify-content:flex-end;">您在 ' . $linkHtml . ' 上的评论:</p>
<div style="display:flex;justify-content:flex-end;margin-bottom:5px;">
<div style="width:auto;flex:none;order:2;">
<img src="{avatar1}" style="width:40px;height:40px;border-radius:5px;">
</div>
<div style="position:relative;margin-right:10px;">
<span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-left:8px solid;border-left-color:#f4f4f4;right:-7px;left:auto;top:12px;position:absolute;"></span>
<div style="background:linear-gradient(117deg,#dff6ff,#daffdf);padding:10px;border-radius:9px;margin-bottom:3px;">{oldcontent}</div>
</div>
</div>
<p style="color:#999;font-size:12px;display:flex;justify-content:flex-end;">{oldtime}</p>
<p><b style="color:#ff5850;" title="IP:{ip} Mail:{mail}">{author} </b>给您回复到:</p>
<div style="display:flex;margin-bottom:5px;">
<div style="width:auto;flex:none;">
<img src="{avatar2}" style="width:40px;height:40px;border-radius:5px;">
</div>
<div style="position:relative;margin-left:10px;">
<span style="width:0;height:0;border-top:8px solid transparent;border-bottom:8px solid transparent;border-right:8px solid;border-right-color:#f4f4f4;left:-7px;right:auto;top:12px;position:absolute;"></span>
<div style="background:linear-gradient(117deg,#f3efff,#e7f1ff);padding:10px;border-radius:9px;margin-bottom:3px;">{content}</div>
</div>
</div>
<p style="color:#999;font-size:12px;">{time}</p>
<div style="text-align:center;color:#999;font-size:12px;line-height:1.5rem;">' . $copyright . '</div>
<div style="font-size:4rem;position:absolute;right:0;bottom:0;transform:rotate(-15deg);opacity:0.5;">💌</div>
</div>
</div>';
}
}